在日常代码的调试中,经常遇到段错误(segfault),调试非常费劲,除了单元测试和基本测试外,有些时候是在在线环境下,没有基本开发和测试工具,这就需要调试的技能。今天就来介绍一下利用core dump文件和gdb做应用程序调试和追踪的方法。
1. core dump 文件
在linux下当应用程序发生异常中止退出或者发生崩溃的时候,linux内核会将应用程序在这段运行期间的内存状态等相关信息转存到磁盘,以供系统故障排查或者调试。这个转存的文件叫core dump文件。core dump文件中会记录程序当时的内存调用、堆栈引用、进程和线程调用等信息,可以帮助开发人员和维护人员了解异常发生当时的环境参数和信息,所以core dump对故障排查和bug调试具有重大的意义。在Linux下,我们需要配置相关参数让系统生成core dump 文件,相关指令如下:
ulimit -c unlimited
运行 ulimit -c 命令设置生成core dump文件的最大值,系统默认为0,unlimited参数设置为生成文件大小无限制。
echo "/home/root/log/core_%e_%p_%t" > /proc/sys/kernel/core_pattern
设置core dump文件的生成位置,本次指向系统的 /home/root/log/ 目录下。
运行gcc或g++编译器进行代码,需要携带 -g 参数告诉编译器在编译程序时加入调试信息。
2. GDB调试命令
标准的GDB具有以下调试命令:
break:设置断点,右面可以跟<函数名>,<行号>, <文件名:行号>,<文件名:函数名>,<*地址>
run(或r):运行,如果设置断点,执行到断点处暂停运行
backtrace(或bt):查看各级函数调用及参数
print(或p):打印表达式的值,通过表达式可以修改变量的值或者调用函数,格式:print 变量名
next(或n):执行下一条语句,如果一行代码上有多条语句,每次仅顺序执行其中的一条语句
step(或s):进入函数提内执行下一步
delete(或d):删除断点,格式为:delete <断点编号>
clear:删除断点,格式为:clear <函数名> 或clear <行号> 或clear <文件名:行号> 或clear <文件名:函数名>
info breakpoints:显示断点信息
disable:禁用断点,具体命令如下:
disable:禁用所有断点
disable <断点编号>:禁用指定断点。
disable display <显示编号>:禁用 display 命令定义的自动显示
disable mem <内存显示>:禁用 mem 命令定义的内存区域
enable:启用断点,具体命令如下:
enable <断点编号>
enable once <断点编号>:使指定的断点只启用一次
enable delete <断点编号>
enable display <显示编号>
enable mem <内存显示>
set variable:改变变量的值,格式为:set variable <变量=表达式>
info(或i): locals查看当前栈帧局部变量的值
list(或l): 列出源代码,接着上次的位置往下列,每次列10行list 行号列出从第几行开始的源代码list 函数名列出某个函数的源代码
start:开始执行程序,停在main函数第一行语句前面等待命令
finish :连续运行到当前函数返回为止,然后停下来等待命令
quit(或q): 退出gdb调试环境set var修改变量的值
3.GDB调试技巧
3.1 attach 进程
要调试已经启动的进程,或是调试陷入死循环而无法返回控制台的进程时,可以使用 attach 命令。格式:attach ,执行这一命令可以 attach 到进程 ID 为 pid 的进程上。
attach 之后就能使用普通的 gdb 命令。
gdb 和进程分离时使用 detach 命令,调试的进程就从 gdb 的控制下释放出来。进程被 detach 后继续运行。
进程信息可以用 info proc 命令显示。
守护者进程在启动好子进程后,会自动关闭主进程,如果没有设定监控模式的话,gdb 会提示断开与进程的链接。所以必须设定监控对象,设置命令为 set follow-fork-mode child/parent。
3.2 条件断点
break <断点> if <条件>,这条命令将测试给定的条件,如果为真则暂停运行。
如果断点已经存在,condition <断点编号> <条件> 命令给断点添加触发条件,condition <断点编号> 命令删除指定编号断点的触发条件。
4.示例调试过程
示例代码:
#include <stdio.h>
int func(void)
{
int p = 0;
int q = 0;
for(p = 0; p < 5; p++){
q++;
printf("p = %d\n", p);
printf("q = %d\n", q);run
}
return 0;
}
int main(void)
{
int i = 1;
i = i + 1;
printf("func= %s, line= %d, i= %d\n", __func__, __LINE__, i);
func();
i++;
printf("func= %s, line= %d, i= %d\n", __func__, __LINE__, i);
return 0;
}
编译命令:
gcc test.c -g -o test
调试过程如下:
上述是一个很简单的示例,关于gdb的用法可以在实际的工程调试中运行探索!