使用 Core Dump 调试 C++

8/19/2024 C++

Core Dump 是指当操作系统中的进程收到某些信号而终止运行时,操作系统将此时进程地址空间的内容以及有关进程状态的其他信息写出的一个磁盘文件。 。这个文件包含了程序崩溃时的详细快照,内容包括程序运行时的内存布局、变量值、处理器寄存器的状态、堆栈跟踪等关键信息。

程序崩溃常因非法内存访问(如空指针)、除零错误、堆栈溢出等引起。操作系统会发送信号(如SIGSEGV、SIGFPE)给程序。若程序未捕获或未正确处理这些信号,系统则终止程序并生成Core Dump文件,以记录崩溃时的状态供调试。

# 启用

查看状态(MacOS)

sysctl kern.coredump
kern.coredump: 1
1
2

查看大小限制(MacOS/Linux)

ulimit -c 
0
1
2

启用 (MacOS),linux 下无需启用

sudo sysctl -w kern.coredump=1 
kern.coredump: 0 -> 1
1
2

设置大小并查看 (MacOS/Linux)

ulimit -c unlimited
1

仅对当前 shell 生效

ulimit -c 
unlimited
1
2

修改 core 文件生成规则

  • %P 进程pid
  • %N 进程名 (MacOS)
  • %e 进程名 (Linux)
  • %s 最后一个信号(Linux)
  • %t 时间戳(Linux)

MacOS

# 生成到固定目录
sudo sysctl kern.corefile=/cores/core.%N.%P
# 生成到程序运行目录
sudo sysctl kern.corefile=core.%N.%P
1
2
3
4

Linux

echo "/cores/core.%e.%P.%t" > /proc/sys/kernel/core_pattern
1

# 段错误案例

#include <iostream>

int main(int argc, char* argv[])
{
    char* a = nullptr;
    std::cout << a[100];
}
1
2
3
4
5
6
7

为当前 shell 配置 core dump 大小

ulimit -c unlimited
1

# 使用 LLDB 调试(MacOS)

设置 core dump 生成路径为程序运行目录

bash-3.2$ sudo sysctl kern.corefile=core.%N.%P
kern.corefile: core.%N.%P -> core.%N.%P
1
2

使用 g++ 编译程序

bash-3.2$ g++ -g main.cpp
1

执行可执行文件 a.out,可见 core dump 已经正常生成

[1]    9712 segmentation fault (core dumped)  ./a.out
1

这个文件的体积竟然有 4.2G

bash-3.2$ ls -lsh core.a.out.9712
8795752 -r--------  1 alex  staff   4.2G Aug 19 12:36 core.a.out.7968
1
2

使用 LLDB 调试

lldb -c core.a.out.9712 -e a.out
1

输入 bt 或 thread backtrace 来查看导致崩溃的堆栈跟踪。这将显示程序崩溃时的函数调用栈。

(lldb) target create "./a.out" --core "core.a.out.9712"
Core file 'core.a.out.9712' (arm64) was loaded.
(lldb) bt
* thread #1, stop reason = ESR_EC_DABORT_EL0 (fault address: 0x64)
  * frame #0: 0x0000000102623234 a.out`main(argc=1, argv=0x000000016d7df4e0) at main.cpp:6:18
    frame #1: 0x00000001917ee0e0 dyld`start + 2360
(lldb) thread backtrace
* thread #1, stop reason = ESR_EC_DABORT_EL0 (fault address: 0x64)
  * frame #0: 0x0000000102623234 a.out`main(argc=1, argv=0x000000016d7df4e0) at main.cpp:6:18
    frame #1: 0x00000001917ee0e0 dyld`start + 2360
(lldb) q
1
2
3
4
5
6
7
8
9
10
11

ESR_EC_DABORT_EL0 是 arm 特有的寄存器错误,比较不直观,但是通过frame#0 基本上也能定位到错误的位置。

# GDB 调试 core dump(Linux)

使用 g++ 编译程序

g++ -g main.cpp
1

执行 a.out 并生成 core dump,Linux 下 core 文件默认生成在程序运行目录,无需重新设置

Segmentation fault (core dumped)
1

core 文件就只有 500K 了

244K -rw-------  1 root root 532K Aug 19 13:24 core.57907
1

将 core 文件载入 GDB 调试 (可能要先安装 debuginfo)

gdb a.out core.57907
1

无需任何输入,自动会跳转到程序报错的地方

GNU gdb (Ubuntu 12.1-0ubuntu1~22.04.2) 12.1
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.
--Type <RET> for more, q to quit, c to continue without paging--

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from a.out...
[New LWP 57907]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Core was generated by `./a.out'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x0000561719d8c1ac in main (argc=1, argv=0x7fffa5a38cc8) at main.cpp:6
6           std::cout << a[100];
(gdb) list
1       #include <iostream>
2       
3       int main(int argc, char* argv[])
4       {
5           char* a = nullptr;
6           std::cout << a[100];
7       }
8       
(gdb) 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

输出比 LLDB 更直观,很明显的可以看到是第六行触发了段错误。

Last Updated: 8/19/2024, 6:21:16 AM