百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

GDB调试的高级技巧(gdb调试命令的使用及总结)

myzbx 2025-07-27 22:30 12 浏览

GDB是我们平时调试c/c++程序的利器, 查起复杂的bug问题,比打印大法要好得多,但是也不得不说, gdb在默认情况下用起来并不是很好用,最近学习到几个高级点的技巧,分享下:

一 美化打印

先上个例子:

#include <stdio.h>

typedef struct {
 int i ;
 int j;
 char * str;
 int array[10];
} test_type_t;

int main (int argc, char * argv[])
{
  test_type_t t = {1,2,"test a str" ,{1,2,3,4,5,6,7,8,9,10}};
  printf("i:%d,j:%d\n", t.i,t.j);
  return 0;
}

gdb调试:

(gdb) n
14   printf("i:%d,j:%d\n", t.i,t.j);
(gdb) p t
$1 = {i = 1, j = 2, str = 0x400710 "test a str", array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}}
(gdb) set print pretty
(gdb) p t
$2 = {
  i = 1, 
  j = 2, 
  str = 0x400710 "test a str", 
  array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
}
(gdb) 

set print pretty 通过这个命令可以美化gdb的打印,特别是复杂的数据结构,比较推荐. set print array-indexes on 打印数组的下标

这些命令如果记不住怎么办, 类似linux启动,会执行些初始化脚本,gdb也一样,脚本名为: .gdbinit将刚才的两个命令加入进去,就不用每次执行这个命令调试.

二 动态打印

一般情况下,我们用gdb调试的时候,先在产生问题的代码的调用逻辑链上设置端点,调试,有时候为了更方便调试,我们会在这些关键地方加上打印语句. 有时候加的打印代码不够全,来回要反复几次. 每次加好打印代码后,需要编译程序,来回折腾几次,确实比较麻烦.

GDB提供动态打印功能,可以在不修改代码情况下,动态插入打印语句,说的比较抽象,举例说明:

  2 #include <stdio.h>
  3 
  4 typedef struct {
  5  int i ;
  6  int j;
  7  char * str;
  8  int array[10];
  9 } test_type_t;
 10 
 11 int main (int argc, char * argv[])
 12 {
 13   test_type_t t = {1,2,"test a str" ,{1,2,3,4,5,6,7,8,9,10}};
 14   printf("i:%d,j:%d\n", t.i,t.j);
 15 
 16   for (int k = 0; k <= 10; k++ ) {
 17       t.array[k] += 10;
 18   }
 19   printf("index 0 :%d" ,t.array[0]);
 20   return 0;
 21 }


上面位置k <= 10; 这一行越界,调试下:

(gdb) dprintf 17,"k:%d,array[k]:%d\n",k,t.array[k]
Dprintf 2 at 0x40067f: file 1.c, line 17.
(gdb) r
Starting program: /test/a.out 

Breakpoint 1, main (argc=1, argv=0x7fffffffe648) at 1.c:13
13   test_type_t t = {1,2,"test a str" ,{1,2,3,4,5,6,7,8,9,10}};
Missing separate debuginfos, use: debuginfo-install glibc-2.17-196.el7_4.2.x86_64 libgcc-4.8.5-16.el7_4.2.x86_64 libstdc++-4.8.5-16.el7_4.2.x86_64
(gdb) c
Continuing.
i:1,j:2
k:0,array[k]:1
k:1,array[k]:2
k:2,array[k]:3
k:3,array[k]:4
k:4,array[k]:5
k:5,array[k]:6
k:6,array[k]:7
k:7,array[k]:8
k:8,array[k]:9
k:9,array[k]:10
k:10,array[k]:0
index 0 :11[Inferior 1 (process 150109) exited normally]
(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x00000000004005fc in main(int, char**) at 1.c:13
 breakpoint already hit 1 time
2       dprintf        keep y   0x000000000040067f in main(int, char**) at 1.c:17
 breakpoint already hit 11 times
        printf "k:%d,array[k]:%d\n",k,t.array[k]

我们通过dprintf 17,"k:%d,array[k]:%d\n",k,t.array[k] 在17行动态插入打印语句,然后执行的时候会直接打印出来,17 为行号,后面的语法类似printf.

动态的语句实际是一个断点(通过info b 可以看到),只不过这个断点会自动的恢复,这种动态语句是否可以继续在上面设置端点的,答案是不行的,通过next执行的时候也执行不到这个语句,只是运行到这里面的时候,可以自动打印变量.

三 断点信息的保存和加载

调试大型程序的时候,往往会设置很多断点,如果需要多次调试,来回敲这些断点信息,也是很烦的,可以通过save breakpoints filename的方法,将断点设置信息保存到filename文件中,GDB启动后,在通过source filename方式加载:

(gdb) save breakpoints break.bp
Saved to file 'break.bp'.
(gdb) quit
[root@localhost test]# ls
1.c  a.out  break.bp
[root@localhost test]# cat break.bp 
break main
dprintf  /test/1.c:17,"k:%d,array[k]:%d\n",k,t.array[k]

加载:

[root@localhost test]# gdb ./a.out 
Reading symbols from /test/a.out...done.
(gdb) source break.bp
Breakpoint 1 at 0x4005fc: file 1.c, line 13.
Dprintf 2 at 0x40067f: file 1.c, line 17.
(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x00000000004005fc in main(int, char**) at 1.c:13
2       dprintf        keep y   0x000000000040067f in main(int, char**) at 1.c:17
        printf "k:%d,array[k]:%d\n",k,t.array[k]

是不是很方便那.

四 宏打印

如下示例代码:

 1 #include <stdio.h>
  2 
  3 #define MAX(x,y) (x)>=(y)?(x):(y)
  4 
  5 int main(int argc , char * argv[])
  6 {
  7     char * p = (char*)0x12345;
  8     printf("max(3,5):%d\n",MAX(3,5));
  9     printf("print a test str\n");
 10     *p = 1;
 11     printf("end\n");
 12 }

如果通过-g选项编译是无法查看宏定义的,可以通过-g3 编译,就可以轻松查看宏定义和宏展开了,如下:

[root@localhost test]# gdb ./2
Reading symbols from /test/2...done.
(gdb) b main
Breakpoint 1 at 0x40064c: file 2.c, line 7.
(gdb) r
Starting program: /test/./2 

Breakpoint 1, main (argc=1, argv=0x7fffffffe648) at 2.c:7
7     char * p = (char*)0x12345;
Missing separate debuginfos, use: debuginfo-install glibc-2.17-196.el7_4.2.x86_64 libgcc-4.8.5-16.el7_4.2.x86_64 libstdc++-4.8.5-16.el7_4.2.x86_64
(gdb) n
8     printf("max(3,5):%d\n",MAX(3,5));
(gdb) p MAX(3,5)
$1 = 5
(gdb) macro expand MAX(3,5)
expands to: (3)>=(5)?(3):(5)

可以直接通过p命令打印宏,也可以进行macro expand MAX(3,5)宏展开,调试宏的时候比较有用.

五 gdb调试界面

gdb的调试一般是通过n执行下一条命令,然后通过l查看当前的代码,不够直观,gdb其实也可以类似IDE那样,显示一行行代码执行,简单的直接输入layout 切换布局:

再输入n的时候,箭头自动下移一行.

更棒的是可以同时显示源码和汇编,输入layout split

六 Debug汇编

有时候,比较麻烦的bug,可以需要反编译为汇编(disassemble /m main),然后调试, 还是如上的例子程序,调试如下,汇编模式常需要看寄存器的值,输入layout regs可以同时展示汇编和寄存器的值:

这个模式下好处,可以通过n命令执行一条条看汇编指令.

我还是更喜欢用以下的方式调试:

gdb) disassemble /m main
Dump of assembler code for function main(int, char**):
6 {
   0x000000000040063d <+0>: push   %rbp
   0x000000000040063e <+1>: mov    %rsp,%rbp
   0x0000000000400641 <+4>: sub    $0x20,%rsp
   0x0000000000400645 <+8>: mov    %edi,-0x14(%rbp)
   0x0000000000400648 <+11>: mov    %rsi,-0x20(%rbp)

7     char * p = (char*)0x12345;
=> 0x000000000040064c <+15>: movq   $0x12345,-0x8(%rbp)

8     printf("max(3,5):%d\n",MAX(3,5));
   0x0000000000400654 <+23>: mov    $0x5,%esi
   0x0000000000400659 <+28>: mov    $0x400720,%edi
   0x000000000040065e <+33>: mov    $0x0,%eax
   0x0000000000400663 <+38>: callq  0x400510 <printf@plt>

9     printf("print a test str\n");
   0x0000000000400668 <+43>: mov    $0x40072d,%edi
   0x000000000040066d <+48>: callq  0x400530 <puts@plt>

10     *p = 1;
   0x0000000000400672 <+53>: mov    -0x8(%rbp),%rax
   0x0000000000400676 <+57>: movb   $0x1,(%rax)

11     printf("end\n");
   0x0000000000400679 <+60>: mov    $0x40073e,%edi
   0x000000000040067e <+65>: callq  0x400530 <puts@plt>

12 }
   0x0000000000400683 <+70>: mov    $0x0,%eax
   0x0000000000400688 <+75>: leaveq 
   0x0000000000400689 <+76>: retq   

End of assembler dump.
(gdb) ni
8     printf("max(3,5):%d\n",MAX(3,5));
(gdb) ni
0x0000000000400659 8     printf("max(3,5):%d\n",MAX(3,5));
(gdb) ni
0x000000000040065e 8     printf("max(3,5):%d\n",MAX(3,5));
(gdb) ni
0x0000000000400663 8     printf("max(3,5):%d\n",MAX(3,5));
(gdb) ni
max(3,5):5
9     printf("print a test str\n");
(gdb) ni
0x000000000040066d 9     printf("print a test str\n");
(gdb) ni
print a test str
10     *p = 1;
(gdb) info register

通过disassemble /m main 将源码和汇编一起显示,ni 执行下一条汇编指令. info register来显示汇编信息,core的位置的指令为: 0x0000000000400676 <+57>: movb $0x1,(%rax) 将1赋值给rax寄存器保存的地址, 我们可以看下rax寄存器,p $rax 直接打印,然后通过x /x 0x12345 来测试下这个地址是否合法,这个地址不是栈的地址,也不是我们申请的堆地址,直接写,就引起的段错误.

七 诗词欣赏

# 滚滚长江东逝水,浪花淘尽英雄。

出自明代[杨慎] 

滚滚长江东逝水,浪花淘尽英雄。
是非成败转头空。青山依旧在,几度夕阳红。
白发渔樵江渚上,惯看秋月春风。
一壶浊酒喜相逢。古今多少事,都付笑谈中。

相关推荐

OPPO Find X9手机曝料:6.6英寸屏幕、天玑9500芯片

IT之家8月27日消息,科技媒体xpertpick今天(8月27日)发布博文,报道称OPPO计划于今年10月推出FindX9系列旗舰手机,其中包括FindX9和...

OPPO Find X9系列搭载影像新硬件,支持Ultra级画质和色彩还原

IT之家8月27日消息,OPPOFindX9系列手机发布时间逐渐临近,目前官方已开启新机的前瞻预热。OPPOFind系列产品负责人周意保今日发文解释了厂商为什么现在都喜欢跨界合作这一...

我回来了!聊聊屏幕对续航的影响_屏幕耗电吗

时隔一周终于回国,让大家久等了本来上周日就能到家,结果在旧金山转机的时候把护照弄丢了…幸好后来被一位黑人大姐找到了,才能顺利回国,感谢勤劳朴实的美利坚人民。出差途中笔记本的续航是很重要的,刚好联想的产...

J人福音、P人救星,Lumix Flow如何重塑专业视频拍摄工作流

“等一下,刚才那个中景拍了没有?”“A机位的素材是哪一场的?”“完了,我忘了记哪一条是最好的了!”“今晚加个班,先把能用的素材挑出来……”作为经常一个人拍视频的内容创作者,这种崩溃称得上习以为常。如果...

realme史上最窄边框和下巴 realme GT Neo3正式发布

中关村在线消息:今天下午14点,realme召开真我GTNeo3发布会。realmeGTNeo3搭载6.7英寸2412×1080OLED直屏,其支持120Hz刷新率,360Hz触控采样率,智能...

用酒精擦屏幕,对屏幕的伤害有多大?

天府新青年你触手可及的朋友圈附录:1.不是所有电脑的屏幕都不能用酒精来擦,通常来说只有镜面屏屏幕才有涂层,这种不能用酒精擦;而雾面屏用的是另外一种抗反射技术,这种一般擦了没事。镜面屏和雾面屏特别好认...

windows11截屏快捷键是哪个?windows11快捷键设置大全

windows11键盘快捷方式就是键盘快捷方式就是按键或按键组合,可提供一种替代方式来执行通常使用鼠标执行的操作。下面就来分享下windows11截屏快捷键是哪个和windows11快捷键设置大全。一...

三星Galaxy S25 Slim配置曝光 6.7英寸屏幕搭配2亿像素主摄

【CNMO科技新闻】三星GalaxyS25系列将于北京时间1月23日正式发布,CNMO注意到有博主爆出了即将亮相GalaxyS25Slim的配置信息。据悉,GalaxyS25Slim将配备一...

两种手机屏幕到底有什么不一样?哪种手机屏幕更好?

一般来说,我们的手机屏幕只分为两种OLED和LCD,LCD是大火的一种手机屏幕,是千元机以及高端机的标配,OLED算是后起之秀,是近几年才渐渐兴起的一种类型的手机屏幕,那么这两种手机屏幕到底有什么不一...

有强芯才好用 这三款高性价比旗舰芯热机最低仅需1799元

在选购手机时,相信大家肯定都会把性能作为考虑的重点之一。而如果希望拥有出色的性能表现,一颗旗舰处理器是必不可少的。今天我就为大家汇总了几款采用旗舰处理器的底价新机,感兴趣的朋友千万不要错过。moto...

一文搞定FastDFS的搭建和使用_fastdfs怎么样

1.FastDFS概述FastDFS是一个开源的轻量级分布式文件系统,它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的问题。特别适合以文...

涨姿势!超级计算机用啥文件系统呢?

2015-10-1705:58:00作者:赵为民在计算机中,文件系统(filesystem)是一个非常重要的组件,你可以将他看做是操作系统的子系统,其实质就是一种软件的组件,通过文件系统我们可以...

Window as a VM:Chrome OS 现可窗口化运行其它 Linux 分支

这世上纵然有多种办法可以在Chromebook上安装运行ChromeOS和其它Linux分支多系统,但如果无需重启通过引导切换,确实是个很酷的改进。Google布道师Francois...

Win10新预览版19577开始推送:新图标+多项新功能

今日凌晨,微软正式向Windows10Insider快速通道用户推送了全新版本Windows10——Windows10InsiderPreviewBuild19577。19577版本是...

微软Windows升级密钥(例如家庭版升级为企业版)

下面的密钥,是微软官方提供的,仅能用于Windows10系统版本的升级,比如从家庭版升级为专业版、专业版升级为企业版等。升级密钥不能用于激活系统,激活需要KMS或者数字权利,由于涉及到版权问题,在此不...