(gdb) set logging file test.log # 设置 log 文件名,
(gdb) set logging enabled on # 打开 log
(gdb) info b # 实际操作,显示断点
(gdb) set logging enabled off # 关闭 log
(gdb) quit # 退出 gdb 调试
log 文件示例
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000001200 in main at test.c:20
i locals
i args
2 breakpoint keep y 0x000000000000117b in binary_search at test.c:5
stop only if target == 5
i locals
i args
代码调试一般有两种方法:printf 和 debug。
print 要比用 debugger 设下断点更为简单粗暴,有时甚至会更有用。不过 debugger 对比于 print 有三个优点:
无需重新编译
可以在调试时改变变量
debugger 可以实现 print 做不到的复杂操作
通常我们只有在程序出问题才会启动 gdb,开始调试工作,调试完毕后退出。不过,让 gdb 一直开着未尝不是更好的做法。每个 gdb 老司机都懂得,gdb 在 r 的时候会加载当前程序的最新版本。也即是说,就算不退出 gdb,每次运行的也会是当前最新的版本。不退出当前调试会话有两个好处:
调试上下文可以得到保留。不用每次运行都重新设一轮断点。
一旦 core dump 了,可以显示 core dump 的位置,无需带着 core 重新启动一次。
在开发 C/C++ 项目,一般的工作流程:一个窗口开着编辑器,编译也在这个窗口执行;另一个窗口开着 gdb,这个窗口同时也用来运行程序。一旦要调试了(或者,又 segment fault 了),随手就可以开始干活。
当然了,劳作一天之后,总需要关电脑回家。这时候只能退出 gdb。不想明天一早再把断点设上一遍?gdb 提供了保留断点的功能。输入 save br .gdb_bp,gdb 会把本次会话的断点存在. gdb_bp 中。明天早上一回来,启动 gdb 的时候,加上 - x .gdb_bp
,让 gdb 把. gdb_bp 当做命令文件逐条重新执行,一切又回到昨晚。
.gdb_bp
文件的内容如下
break /home/tyustli/code/c_project/test.c:5
break /home/tyustli/code/c_project/test.c:6
break /home/tyustli/code/c_project/test.c:7
实际就是将 gdb 命令保存下来,加载的时候在将命令自动添加到调试中。
二分法代码如下
#include
int binary_search(int *ary, unsigned int ceiling, int target)
{
unsigned int floor = 0;
while (ceiling> floor)
{
unsigned int pivot = (ceiling + floor) / 2;
if (ary[pivot] < target)
floor = pivot + 1;
else if (ary[pivot] > target)
ceiling = pivot - 1; /* 正确的代码应该为: ceiling = pivot */
else
return pivot;
}
return -1;
}
int main(int argc, char *argv)
{
int a[] = {1, 2, 4, 5, 6};
printf("%d\r\n", binary_search(a, 5, 7)); /* -1 */
printf("%d\r\n", binary_search(a, 5, 6)); /* 4 */
printf("%d\r\n", binary_search(a, 5, 5)); /* 期望 3,实际运行结果是 - 1 */
return 0;
}
编译并运行 gdb 调试
gcc -g test.c
gdb a.out
打算调试下 binary_search(a, 5, 5)
这个组合。若如果用 print 大法,就在 binary_search 中插入几个 print,运行后扫一眼,看看 target=5 的时候运行流是怎样的。
debugger 大法看似会复杂一点,如果在 binary_search 中插断点,那么前两次调用只能连按 c 跳过。其实没那么复杂,gdb 允许用户设置条件断点。你可以这么设置:
b binary_search if target == 5
现在就只有第三次调用会触发断点。
问题看上去跟 floor 和 ceiling 值的变化有关。要想观察它们的值,可以 p floor 和 p ceiling。不过有个简单的方法,你可以对它们设置 watch 断点:wa floor if target == 5。当 floor 的值变化时,就会触发断点。
对于我们的示例程序来说,靠脑补也能算出这两个值的变化,专门设置断点似乎小题大做。不过在调试真正的程序时,watch 断点非常实用,尤其当你对相关代码不熟悉时。使用 watch 断点可以更好地帮助你理解程序流程,有时甚至会有意外惊喜。另外结合 debugger 运行时修改值的能力,你可以在值变化的下一刻设置目标值,观察走不同路径会不会出现类似的问题。如果有需要的话,还可以给某个内存地址设断点:wa *0x7fffffffda40。
除了 watch 之外,gdb 还有一类 catch 断点,可以用来捕获异常 / 系统调用 / 信号。因为用途不大(我从没实际用过),就不介绍了,感兴趣的话在 gdb 里面 help catch 看看。
参考 GDB command 命令
参考 GDB 自定义命令
几个 GDB 内建变量
$argc 自定义命令的参数个数
$arg0 自定义命令的第一个参数
$arg1 自定义命令的第二个参数
$arg2 自定义命令的第三个参数
$argN 自定义命令的第 N+1 个参数
举个有实际意义的例子。由于源代码的改变,我们需要更新断点的位置。通常的做法是删掉原来的断点,并新设一个。让我们现学现用,用宏把这两步合成一步:
# gdb_macro
define mv
if $argc == 2
delete $arg0
# 注意新创建的断点编号和被删除断点的编号不同
break $arg1
else
print "输入参数数目不对,help mv 以获得用法"
end
end
# (gdb) help mv 会输出以下帮助文档
document mv
Move breakpoint.
Usage: mv old_breakpoint_num new_breakpoint
Example:
(gdb) mv 1 binary_search -- move breakpoint 1 to `b binary_search`
end
# vi:set ft=gdb ts=4 sw=4 et
将上述命令放在 .gdbinit
文件中,启动调试。
使用 mv
命令之前,查看断点
info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000001200 in main at test.c:20
i locals
i args
2 breakpoint keep y 0x000000000000117b in binary_search at test.c:5
stop only if target == 5
i locals
i args
Breakpoint 3 at 0x117b: file test.c, line 5.
使用 mv
mv 1 5
Num Type Disp Enb Address What
2 breakpoint keep y 0x000000000000117b in binary_search at test.c:5
stop only if target == 5
i locals
i args
3 breakpoint keep y 0x000000000000117b in binary_search at test.c:5
设想如下的情景:某个项目出现内存泄露的迹象。事先分配好的内存池用着用着就满了,一再地吞噬系统的内存。内存管理是自己实现的,所以无法用 valgrind 来分析。鉴于内存管理部分代码最近几个版本都没有改动过,猜测是业务逻辑代码里面有谁借了内存又不还。现在你需要把它揪出来。一个办法是给内存的分配和释放加上日志,再编译,然后重新运行程序,谋求复现内存泄露的场景。不过更快的办法是,敲上这一段代码:
假设分配内存的接口是 my_malloc(size_t size),释放内存的接口是 my_free(char *p)
b my_malloc
comm
printf "my_malloc %lu\n", size
end
b my_free
comm
printf "my_free 0x%x\n", p
end
c 源码如下
void * my_malloc(size_t size)
{
return malloc(size);
}
void my_free(void *p)
{
free(p);
}
int main(int argc, char *argv)
{
void *p1 = my_malloc(6);
void *p2 = my_malloc(7);
void *p3 = my_malloc(8);
void *p4 = my_malloc(9);
my_free(p1);
my_free(p2);
my_free(p3);
return 0;
}
启动调试之后将 log 文件保存到 test.log
中
最终和 malloc 和 free 相关的日志如下
Breakpoint 3, my_malloc (size=6) at test.c:23
my_malloc 6
0x0000555555555247 in main (argc=1, argv=0x7fffffffe0a8 "D\343\377\377\377\177") at test.c:33
Value returned is $2 = (void *) 0x5555555592a0
Continuing.
Breakpoint 3, my_malloc (size=7) at test.c:23
my_malloc 7
0x0000555555555255 in main (argc=1, argv=0x7fffffffe0a8 "D\343\377\377\377\177") at test.c:34
Value returned is $3 = (void *) 0x5555555592c0
Continuing.
Breakpoint 3, my_malloc (size=8) at test.c:23
my_malloc 8
0x0000555555555263 in main (argc=1, argv=0x7fffffffe0a8 "D\343\377\377\377\177") at test.c:35
Value returned is $4 = (void *) 0x5555555592e0
Continuing.
Breakpoint 3, my_malloc (size=9) at test.c:23
my_malloc 9
0x0000555555555271 in main (argc=1, argv=0x7fffffffe0a8 "D\343\377\377\377\177") at test.c:36
Value returned is $5 = (void *) 0x555555559300
Continuing.
Breakpoint 4, my_free (p=0x5555555592a0) at test.c:28
my_free 0x555592a0
Continuing.
Breakpoint 4, my_free (p=0x5555555592c0) at test.c:28
my_free 0x555592c0
Continuing.
Breakpoint 4, my_free (p=0x5555555592e0) at test.c:28
my_free 0x555592e0
Continuing.
.gdbinit
文件layout src
b main
b binary_search if target == 5
# 断点 1 触发执行的命令
command 1
i locals
i args
end
# 断点 2 触发执行的命令
comm 2
i locals
i args
end
# 自定义一个 print-tyustli 命令
define print-tyustli
echo hello, world\n
end
# 自定义命令 print-tyustli 的帮助文档
document print-tyustli
usage: print-list LIST NODE_TYPE NEXT_FIELD [COUNT]
打印 tyustli
data: 2023-09-27
author: tyustli
end
# gdb_macro
define mv
if $argc == 2
delete $arg0
# 注意新创建的断点编号和被删除断点的编号不同
break $arg1
else
print "输入参数数目不对,help mv以获得用法"
end
end
# (gdb) help mv 会输出以下帮助文档
document mv
Move breakpoint.
Usage: mv old_breakpoint_num new_breakpoint
Example:
(gdb) mv 1 binary_search -- move breakpoint 1 to `b binary_search`
end
# vi:set ft=gdb ts=4 sw=4 et
b my_malloc
comm
printf "my_malloc %lu\n", size
finish # 查看函数 malloc 返回的指针
end
b my_free
comm
printf "my_free 0x%x\n", p
end
https://segmentfault.com/a/1190000005367875