gdb调试技巧

如何生成core文件

系统ulimit -c大于0,一般设为unlimited,进程接收到异常信号后,默认是生成core文件,如果/proc/sys/kernel/core_pattern为core,则文件生成在进程的getcwd工作目录,如果/proc/sys/kernel/core_uses_pid为1,则core文件以pid结尾。

对于正在运行的进程,如果不想让进程退出,可以用gcore pid生成该进程的core文件。

对于代码中有对异常信号进行处理的情况,则接收到异常信号时,进入自定义的信号处理函数中,不再生成core文件,此时如果还想生成core文件,需要在处理函数末尾做如下处理:

1
2
signal(signo, SIG_DFL); // 默认生成core文件
kill(getpid(), signo); // signo为当前正在处理的信号类型

如果系统没有设置为unlimited,可以在程序中进行修改,这样只对本进程有效,修改完成后,可以在/proc/pid/limits查看。

1
2
3
4
5
6
7
#include <sys/resource.h>

struct rlimit rlim;
rlim.rlim_cur = RLIM_INFINITY;
rlim.rlim_max = RLIM_INFINITY;
setrlimit(RLIMIT_CORE, &rlim);
// [RLIMIT_CORE] = {"Max core file size", "bytes"}, 单位为bytes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ cat /proc/121897/limits
Limit Soft Limit Hard Limit Units
Max cpu time unlimited unlimited seconds
Max file size unlimited unlimited bytes
Max data size unlimited unlimited bytes
Max stack size 8388608 unlimited bytes
Max core file size 209715200 unlimited bytes
Max resident set unlimited unlimited bytes
Max processes 14952 14952 processes
Max open files 1048576 1048576 files
Max locked memory 65536 65536 bytes
Max address space unlimited unlimited bytes
Max file locks unlimited unlimited locks
Max pending signals 14952 14952 signals
Max msgqueue size 819200 819200 bytes
Max nice priority 0 0
Max realtime priority 0 0
Max realtime timeout unlimited unlimited us

如何利用core文件进行调试

首先需要有debug信息的二进制文件,也就是加-g编译出来的文件,gdb+二进制文件+core文件即可开启调试了,如果没有debug信息的二进制文件,那么函数名、行号、文件名等信息是看不到的,只能看到地址信息。

开启debug后,发现libc库相关的函数显示为??

此时是有些库需要安装一下,其实打开gdb时是有提示信息的

安装完成后,就正常了

使用 info threads 命令可以查看所有线程的信息,包括线程ID和当前所在的函数名等.
一般调试过程:thread apply all bt,可以显示所有线程的堆栈信息,通过t tid切换线程,再bt full打印该切换线程的堆栈信息,f id切换到指定的函数,p打印相关变量的值等。

1
2
3
4
5
6
# 查看vec中某个元素的值的地址
p &(vec._M_impl._M_start)[4]
# 查看信息更方便
set p pretty on
# 设置打印数量
set print elements 1000

以下是 GDB 常用的一些命令:

  • run: 启动程序并开始调试
  • break: 设置断点,可以指定文件名和行号或函数名
  • delete: 删除断点,可以指定断点编号或者 * 删除所有断点
  • info breakpoints: 查看当前设置的所有断点
  • next: 单步执行一行代码,不会进入函数内部
  • step: 单步执行一行代码,会进入函数内部
  • finish: 执行到当前函数返回为止
  • continue: 恢复程序执行
  • print: 打印变量的值,可以使用表达式,如 print i+1
  • backtrace: 查看当前函数调用堆栈
  • up: 进入当前堆栈帧的上一层
  • down: 进入当前堆栈帧的下一层
  • frame: 切换到指定的堆栈帧
  • list: 查看当前代码的上下文
  • info locals: 查看当前堆栈帧的本地变量
  • info args: 查看当前堆栈帧的函数参数
  • set variable: 修改变量的值,可以使用表达式,如 set variable i=10
  • display: 在每次停下来时打印指定变量的值
  • undisplay: 取消显示指定变量的值
  • attach: 附加到一个正在运行的进程上
  • detach: 从一个正在调试的进程中分离出来
  • kill: 终止正在调试的进程
  • info threads: 查看当前程序中的所有线程
  • thread: 切换到指定的线程
  • set scheduler-locking: 开启或关闭调度器锁定,可以使用 set scheduler-locking off 命令来禁用调度锁定,使 GDB 可以同时调试多个线程。
  • set detach-on-fork: 指定子进程在 fork 后是跟随调试还是脱离调试,在使用这个选项时,GDB 会自动启用 set follow-fork-mode child 选项,以便在子进程创建后能够自动跟踪子进程。如果想要禁用 detach-on-fork 选项,可以使用 unset detach-on-fork 命令。
  • set follow-fork-mode: 指定子进程在 fork 后的行为,如child 当子进程开始执行时,GDB会自动跟踪子进程的执行。

如何对运行中的进程进行调试

gdb -p pid或者gdb attach pid即可开启调试,有些注意的命令。

1
2
3
show scheduler-locking //显示线程的scheduler-locking状态
set scheduler-locking on //调试加锁当前线程,停止所有其他线程
handle SIGPIPE nostop print //pipe信号不要终端当前线程

如何打印线程的异常堆栈

在没办法获取到core文件的情况下,能够获取到堆栈数据也是一种调试的办法。

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <unwind.h>
#ifdef LIBUNWIND
#define UNW_LOCAL_ONLY
#include <libunwind.h>
#endif

#ifdef LIBUNWIND
#define MAX_STACK_LAYERS 20
#define MAX_FRAME_LEN 20
string DumpBacktrace()
{
string ret;
void *buffer[MAX_STACK_LAYERS];
int num = unw_backtrace(buffer, MAX_STACK_LAYERS);
if (num > 0)
{
char *btBuf = new char[num * MAX_FRAME_LEN];
memset(btBuf, 0, num * MAX_FRAME_LEN);
for (int i = 0; i < num; ++i)
{
if (i != 0)
{
strcat(btBuf, " ");
}
snprintf(btBuf + strlen(btBuf), 100, "0x%x", buffer[i]);
}
ret = btBuf;
delete[] btBuf;
}
return ret;
}
#else
struct SBacktraceState
{
void **current;
void **end;
};

_Unwind_Reason_Code UnwindCallback(struct _Unwind_Context *context, void *arg)
{
SBacktraceState *state = static_cast<SBacktraceState *>(arg);
void *pc = (void *)_Unwind_GetIP(context);
if (pc)
{
if (state->current == state->end)
{
return _URC_END_OF_STACK;
}
else
{
*state->current++ = pc;
}
}
return _URC_NO_REASON;
}

string DumpBacktrace()
{
string ret;
void *array[MAX_STACK_LAYERS];
size_t num = 0;
SBacktraceState state = {array, array + MAX_STACK_LAYERS};
_Unwind_Backtrace(UnwindCallback, &state);
num = state.current - array;
if (num > 0)
{
char *btBuf = new char[num * MAX_FRAME_LEN];
memset(btBuf, 0, num * MAX_FRAME_LEN);
for (int i = 0; i < num; ++i)
{
if (i != 0)
{
strcat(btBuf, " ");
}
snprintf(btBuf + strlen(btBuf), 100, "0x%x", array[i]);
}
ret = btBuf;
delete[] btBuf;
}
return ret;
}
#endif

获取到堆栈后,使用addr2line去调试。

nephen wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!
坚持原创技术分享,您的支持将鼓励我继续创作!