目录

K8S中开启收集pod的core

当程序崩溃(例如,由于段错误)时,操作系统可以生成核心转储文件。这个文件包含了崩溃时刻程序的内存映像、寄存器状态、栈信息等。

使用GDB载入执行文件和核心转储文件进行调试

gdb ./program_binary core

运行这个命令后,GDB会用核心转储文件初始化调试会话,这允许你检查程序崩溃时的状态。例如,你可以执行以下命令来查看程序崩溃时的栈跟踪:

(gdb) bt

bt命令(backtrace的缩写)会显示导致程序崩溃的函数调用栈。你还可以使用各种GDB命令来查看程序崩溃时的变量值、寄存器状态以及其他各种有用的信息。这些信息对于理解崩溃的原因和确定出现错误的代码位置非常有帮助。

在处理 coredump 文件时,除了使用 bt (backtrace) 命令之外,还可以使用其他工具和方法来获取更详细的信息。这些方法包括查看局部变量、检查寄存器状态、分析内存泄漏等。下面介绍几种常用的方法以及相应的测试用例。

默认情况下,程序运行崩溃导致 core dump,是不会生成 core 文件的,因为系统的 RLIMIT_CORE(核心文件大小)资源限制,默认情况下设置为 0。

以下命令可在用户进程触发信号时启用 core dump 生成,并使用合理的名称将核心文件位置设置为 /tmp/

ulimit -c unlimited
echo 1 > /proc/sys/kernel/core_uses_pid
echo "/tmp/core-%e-%s-%u-%g-%p-%t" > /proc/sys/kernel/core_pattern

  • • %e:导致 core dump 的程序的可执行文件名。
  • • %s:导致 core dump 的信号编号。
  • • %u:导致 core dump 的程序的实际用户 ID。
  • • %g:导致 core dump 的程序的实际组 ID。
  • • %p:导致 core dump 的程序的进程 ID。
  • • %t:core dump 发生时的时间戳(自 epoch 时间以来的秒数)。

因此,/tmp/core-%e-%s-%u-%g-%p-%t 会生成包含如下信息的 core 文件:

/tmp/core-<executable>-<signal>-<uid>-<gid>-<pid>-<timestamp>

gdb 分析 core dump

info signals 命令查看信号情况:

从 info signals 的输出中可以看出,SIGSEGV(Segmentation fault)信号是设置为在程序接收到该信号时停止执行并打印信息的。也就说,可以人为地使用 kill -11 发送了 SIGSEGV 信号来终止程序并生成 core dump。

对于coredump文件,除了bt外还有其他方法获取更加细节的信息嘛?请给出对应的方法以及测试用例

方法一:使用 info locals 查看局部变量

可以通过 info locals 命令查看函数内所有局部变量的值。

示例:

(gdb) core corefile
(gdb) bt
(gdb) frame 0
(gdb) info locals

方法二:使用 info registers 查看寄存器状态

可以通过 info registers 命令查看 CPU 寄存器的当前状态。

示例:

(gdb) core corefile
(gdb) bt
(gdb) info registers

在输入 bt 命令查看堆栈情况时,有出现了两个变量,分别是 req 和 rem。使用过nanosleep 函数的小伙伴可能会很眼熟这两个变量,因为这个两个变量是 nanosleep 函数的形参,原型是 int nanosleep(const struct timespec *req, struct timespec *rem)。

用 print 命令打印出两个变量的地址:

使用 info registers 命令查看寄存器状态,检查程序在崩溃时的上下文:

从寄存器状态来看,没有明显的错误迹象,函数的栈帧空间没什么问题,形参的位置和值也没什么问题,所有值看起来都在正常范围内。

方法三:使用 list 查看源代码

可以通过 list 命令查看当前函数的源代码。

示例:

(gdb) core corefile
(gdb) bt
(gdb) frame 0
(gdb) list

方法四:使用 valgrind 检查内存泄漏

可以在程序运行时使用 valgrind 来检查内存泄漏问题,从而帮助在 coredump 分析时获得更多信息。

示例:

valgrind --leak-check=full ./your_program

方法五:使用 objdump 分析二进制文件

可以使用 objdump 命令对二进制文件进行反汇编,以查看更底层的信息。

示例:

objdump -d your_program > disassembly.txt

测试用例

假设有一个简单的 C++ 程序,会导致段错误并生成 coredump 文件:

示例代码 (segfault.cpp):

#include <iostream>

void cause_segfault() {
    int *p = nullptr;
    *p = 42; // 这里会导致段错误
}

int main() {
    cause_segfault();
    return 0;
}

编译并运行程序以生成 coredump:

g++ -g -o segfault segfault.cpp
ulimit -c unlimited
./segfault

使用 GDB 分析 coredump:

gdb ./segfault core
(gdb) bt
(gdb) frame 0
(gdb) info locals
(gdb) info registers
(gdb) list

使用 Valgrind 检查内存泄漏:

valgrind --leak-check=full ./segfault

使用 Objdump 反汇编:

objdump -d segfault > disassembly.txt

通过以上方法,可以从多个方面获取 coredump 文件的详细信息,有助于更全面地分析程序崩溃的原因。

bt 时候有的地方显示?号,没有显示出函数名字,什么原因呢

在使用 GDB 对 coredump 文件进行分析时,如果 backtrace (bt) 命令输出中有的地方显示问号(?),而没有显示出函数名字,这通常是因为调试符号(debug symbols)丢失或缺失。以下是一些常见原因及解决方法:

原因一:缺少调试符号

编译程序时没有包含调试符号,这使得 GDB 无法解析函数名。

解决方法:

在编译程序时添加 -g 选项,以包含调试符号。

示例:

g++ -g -o your_program your_program.cpp

原因二:库文件缺少调试符号

如果程序依赖的库文件缺少调试符号,GDB 也无法解析这些库中的函数名。

解决方法:

安装相应库的调试符号包。例如,在基于 Debian 的系统上,可以使用 apt-get 安装调试符号包:

sudo apt-get install libc6-dbg

原因三:优化过度

编译时过度优化(如使用 -O2 或 -O3)会导致函数内联等优化行为,可能影响 GDB 解析调试信息。

解决方法:

在调试时,尽量使用较低的优化级别,如 -O0 或 -O1。

示例:

g++ -g -O0 -o your_program your_program.cpp

原因四:地址空间布局随机化(ASLR)

地址空间布局随机化(ASLR)会导致程序每次运行时地址空间不同,影响调试。

解决方法:

可以临时禁用 ASLR 以进行调试:

sudo sysctl -w kernel.randomize_va_space=0

完成调试后,记得重新启用 ASLR:

sudo sysctl -w kernel.randomize_va_space=2

示例测试用例

示例代码 (example.cpp):

#include <iostream>

void cause_segfault() {
    int *p = nullptr;
    *p = 42; // 这里会导致段错误
}

int main() {
    cause_segfault();
    return 0;
}

编译并运行程序以生成 coredump:

g++ -g -O0 -o example example.cpp
ulimit -c unlimited
./example

使用 GDB 分析 coredump:

gdb ./example core
(gdb) bt

完整示例输出分析:

编译并运行程序:

g++ -g -O0 -o example example.cpp
ulimit -c unlimited
./example

生成的 coredump 文件分析:

gdb ./example core
(gdb) bt
#0  0x000000000040113d in cause_segfault () at example.cpp:5
#1  0x000000000040114d in main () at example.cpp:10

在上述步骤中,正确编译并包含调试符号后,bt 命令将能够正确显示函数名和文件行号。如果仍然遇到问号,检查是否所有依赖库都包含调试符号,并确保没有过度优化代码。