如何生成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
2signal(signo, SIG_DFL); // 默认生成core文件
kill(getpid(), signo); // signo为当前正在处理的信号类型
如果系统没有设置为unlimited,可以在程序中进行修改,这样只对本进程有效,修改完成后,可以在/proc/pid/limits查看。1
2
3
4
5
6
7
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 | $ cat /proc/121897/limits |
如何利用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 | show scheduler-locking //显示线程的scheduler-locking状态 |
如何打印线程的异常堆栈
在没办法获取到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
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;
}
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;
}
获取到堆栈后,使用addr2line去调试。