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时是有提示信息的

安装完成后,就正常了

一般调试过程: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 -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
欢迎您扫一扫上面的微信公众号,订阅我的博客!
坚持原创技术分享,您的支持将鼓励我继续创作!