GDB调试的高级技巧(gdb调试工具的使用)
nanshan 2025-07-06 15:20 4 浏览 0 评论
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 来测试下这个地址是否合法,这个地址不是栈的地址,也不是我们申请的堆地址,直接写,就引起的段错误.
七 诗词欣赏
# 滚滚长江东逝水,浪花淘尽英雄。
出自明代[杨慎]
滚滚长江东逝水,浪花淘尽英雄。
是非成败转头空。青山依旧在,几度夕阳红。
白发渔樵江渚上,惯看秋月春风。
一壶浊酒喜相逢。古今多少事,都付笑谈中。
相关推荐
- 爆肝 30 天!从 JVM 调优到百万级 QPS,我的 Java 性能飞升全记录(2)
-
前言:从崩溃边缘到百万级QPS的逆袭凌晨3点的办公室,监控大屏突然飙红,QPS从5万断崖式下跌到800,CPU满载报警,GC时间突破3秒大关——这是我们的电商大促系统在压测中遭...
- 如何彻底清除服务器上的恶意软件与后门 ?
-
当服务器遭受入侵后,清除恶意软件和后门是恢复系统安全性的关键步骤。如果清除不彻底,攻击者可能通过隐藏后门程序再次发动攻击。以下是一个系统化的操作指南,帮助您彻底清除服务器上的恶意软件和后门,同时加强服...
- Docker 部署高性能抖音 TikTok数据爬取工具,支持无水印视频下载
-
一、项目简介此项目基于PyWebIO、FastAPI和HTTPX,是一个高效的异步数据爬取工具,专注于抖音/TikTok平台的数据提取。通过Web端界面,用户可以在线批量解析并下载无水印的视频或...
- 我如何将Unix时间转换为可读的值?
-
高频处理时间问题在处理时间值时,程序中的一种常见方法是将其转换为线性刻度表示。无法将"2005年1月17日下午5:37"这样的日期存储为变量,并期望能够进行任何操作。因此,在合格的程序...
- 用shell进行ASCII字符转换与URL编码技巧
-
如何将ASCII字符转换为十进制(或十六进制)值并进行相反的转换?如何进行URL编码和URL解码?如果你在编写脚本时已知八进制或十六进制值,你可以使用printf命令实现:#POSIXprintf...
- Linux远程shell登录出现bash-4.2#问题
-
出现以上问题的原因是/root目录下丢失了.bashrc和.bash_profile文件/etc/skel/.bash_profile和/etc/skel/.bashrc的文件复制到/root下即可命...
- 三部门:推进算力互联互通 推动国家枢纽节点和需求地之间400G/800G 高带宽全光连接
-
每经AI快讯,1月6日,国家发展改革委等三部门印发《国家数据基础设施建设指引》。其中提出,加强新兴网络技术创新应用,优化网络计费方式,降低东西部数据传输成本,促进东部中高时延业务向西部转移。推进算力互...
- 三部门:推动国家枢纽节点和需求地之间400G/800G高带宽全光连接
-
国家发展改革委、国家数据局、工业和信息化部等印发《国家数据基础设施建设指引》的通知。其中提到,加强新兴网络技术创新应用,优化网络计费方式,降低东西部数据传输成本,促进东部中高时延业务向西部转移。推进算...
- 高带宽低延迟如何开启?实际效果如何?
-
在上次的《实测AMD平台玩游戏用什么频率的内存更好?》中通过测试已经得知,AMDCPU的最佳频率是6000,具体该如何选择,如何设置能提升游戏帧数,往下看小白新手也能看明白。内存选择6000频率内存...
- 排列五第22237期规律预测走势图分享
-
二定头尾:03458,X,X,035890XX00XX30XX50XX80XX93XX03XX33XX53XX83XX94XX04XX34XX54XX84XX95...
- 格式化字符串漏洞及利用_萌新食用
-
前言格式化字符串漏洞具有任意地址读,任意地址写。printfprintf--一个参数:情况1当参数只有1个字符串的话(含有%?),//?即i,x,s等等<br>第一个...
- Linux配置ip地址的两种方法(linux配置ip详细步骤)
-
Linux配置ip地址的两种方法,实验环境为centos7.6方法1:nmcli工具配置(centos7以下版本不支持该方法)第一步,通过nmcliconnection查看网卡名称[root@lo...
- 排列五9月30日第22263期最新规律走势预测讲解
-
二定头尾:034589,X,X,0125670XX00XX10XX20XX50XX60XX73XX03XX13XX23XX53XX63XX74XX04XX14XX2...
- GDB调试的高级技巧(gdb调试工具的使用)
-
GDB是我们平时调试c/c++程序的利器,查起复杂的bug问题,比打印大法要好得多,但是也不得不说,gdb在默认情况下用起来并不是很好用,最近学习到几个高级点的技巧,分享下:一美化打印先上个例子...
- 给NAS测评打个样:QNAP TS-251D双盘位NAS全面测评体验
-
这两年随着大家网络条件越来越好,视频、电影资源越来越丰富。以及智能手机的普及拍照也更加方便,大家对于存储的需求也越来越高。除了传统的优盘、移动硬盘之外现在私有云方面也有了更多的选择。那么日常私有云选购...
你 发表评论:
欢迎- 一周热门
-
-
极空间如何无损移机,新Z4 Pro又有哪些升级?极空间Z4 Pro深度体验
-
UOS服务器操作系统防火墙设置(uos20关闭防火墙)
-
如何修复用户配置文件服务在 WINDOWS 上登录失败的问题
-
手机如何设置与显示准确时间的详细指南
-
如何在安装前及安装后修改黑群晖的Mac地址和Sn系列号
-
日本海上自卫队的军衔制度(日本海上自卫队的军衔制度是什么)
-
爱折腾的特斯拉车主必看!手把手教你TESLAMATE的备份和恢复
-
10个免费文件中转服务站,分享文件简单方便,你知道几个?
-
FANUC 0i-TF数据备份方法(fanuc系统备份教程)
-
NAS:DS video/DS file/DS photo等群晖移动端APP远程访问的教程
-
- 最近发表
- 标签列表
-
- linux 查询端口号 (58)
- docker映射容器目录到宿主机 (66)
- 杀端口 (60)
- yum更换阿里源 (62)
- internet explorer 增强的安全配置已启用 (65)
- linux自动挂载 (56)
- 禁用selinux (55)
- sysv-rc-conf (69)
- ubuntu防火墙状态查看 (64)
- windows server 2022激活密钥 (56)
- 无法与服务器建立安全连接是什么意思 (74)
- 443/80端口被占用怎么解决 (56)
- ping无法访问目标主机怎么解决 (58)
- fdatasync (59)
- 405 not allowed (56)
- 免备案虚拟主机zxhost (55)
- linux根据pid查看进程 (60)
- dhcp工具 (62)
- mysql 1045 (57)
- 宝塔远程工具 (56)
- ssh服务器拒绝了密码 请再试一次 (56)
- ubuntu卸载docker (56)
- linux查看nginx状态 (63)
- tomcat 乱码 (76)
- 2008r2激活序列号 (65)