GDB常见用法

1. 前言

工作中时不时会用到 GDB 来定位软件 bug,编写此文档只是为了方便查阅相关用法,并不会详述相关细节。因此本文档更像是一本书的目录而不是一整本书。

本文档所述仅供参考,请以 gdb help 查询到的用法为准。

2. 为何要使用 GDB?

当软件运行异常时,需要我们能快速定位软件 bug 并进行修复,GDB 这一工具正是为此而生。比一步步地加日志更高效!

3. 如何使用 GDB?

请自行安装 GDB,安装完成之后可自行通过下列命令查阅 GDB 的用法:

$ gdb
(gdb) help

也可自行通过GDB 官网查阅相关信息。

4. 常用命令一览表

命令缩写功能
helph查看帮助文档
infoi查看正在调试的程序的相关信息
breakb新建断点
runr开始调试
quitq终止调试
continuec继续运行直到断点或者程序结束
nextn单步调试:按行执行代码,不进入函数内部
steps单步调试:按语句执行代码,会进入函数内部
finishfin正常运行当前函数直至结束
returnret直接结束当前函数并返回
listl查看代码
backtracebt查看调用栈
where作用同 backtrace ,查看调用栈
attach调试正在运行的程序
framef查看调用栈当前帧信息或者选择指定帧
printp查看变量值
x查看内存地址中的值
display自行选择 print 或者 x 命令来查看表达式的值
set设置变量值或更改 GDB 环境设置
show查看 GDB 环境设置
deleted删除断点或者其他 GDB 对象
clear删除指定位置处的所有断点
disable禁用断点
enable启用断点
enable启用断点
save保存断点或其他 GDB 信息到文件中
source加载文件中保存的 GDB 信息
watch设置监控点,当监控对象的值被改变时中断程序
rwatch设置监控点,当监控对象的值被读取时中断程序
awatch设置监控点,当监控对象的值被改变或被读取时中断程序
handle设置信号处理模式
disassemble查看汇编代码

5. 如何在可执行文件中加入调试信息?

GCC 默认生成 release 版本,生成的可执行文件不包含调试信息。为了使用 GDB 能直接调试可执行文件,需要在编译时使用 GCC 的-g 选项告知 GCC 在可执行文件中加入调试信息。

$ gcc main.c -o main -g

6. 如何启动调试?

6.1. 如何启动新的进程来调试?

$ gdb 可执行文件名
(gdb) break main
(gdb) run

6.2. 如何在调试时给程序传递命令行参数?

方法一,在启动 GDB 命令时加入命令行参数:

$ gdb --args 可执行文件名 [参数1] [参数2] ...
(gdb) break main
(gdb) run

方法二,在启动 GDB 命令之后加入命令行参数:

$ gdb 可执行文件名
(gdb) set args [参数1] [参数2] ...
(gdb) break main
(gdb) run

6.3. 如何调试正在运行的进程?

先使用 ps 命令查询进程 ID,在启动 GDB 使用 attach 到进程 ID (pid)命令来进行调试:

$ ps -ef | grep 可执行文件名 | grep -v grep | awk '{print $2}'
$ gdb
(gdb) attach pid
(gdb) run

7. 怎么调试不带调试信息的可执行文件?

当可执行文件或者动态库文件为 release 版本时,文件中不带符号表信息,需要手动加载符号表信息。

7.1. 场景一:启动 GDB 时指定符号表文件

$ gdb --symbol=符号表文件名或者debug版本可执行文件名 --exec=release版本可执行文件名
(gdb) break main
(gdb) run

7.2. 场景二:启动 GDB 后加载符号表文件

$ ps -ef | grep 可执行文件名 | grep -v grep | awk '{print $2}'
$ gdb
(gdb) file 符号表文件名
(gdb) attach 进程ID
(gdb) run

7.3. 场景三:启动 GDB 后加载动态库符号表文件

$ gdb
(gdb) info sharedlibrary
(gdb) add-symbol-file 符号表文件名 符号表地址(上一条命令查询到的对应动态库文件的起始地址)
(gdb) run

8. 如何终止调试?

场景一,进程已中断,直接使用 quit 命令退出即可。

场景二,进程正在运行,按 Ctrl+C 中断进程,然后使用 quit 命令退出即可。

9. 如何管理断点?

使用断点可以让进程在我们期望的地方中断,以便进行调试。

9.1. 如何新建断点?

场景一,在指定源文件的指定行新建断点,例:

(gdb) break main.c:123

场景二,在进入函数处新建断点,例:

(gdb) break main

场景三,在指定源文件指定函数指定标签处新建断点,例:

(gdb) break factorial.c:fact:the_top
(gdb) break -source factorial.c -function fact -label the_top

场景四,新建条件断点,例:

(gdb) break main if argc > 1
(gdb) break myfunc if i % (j + 3) != 0
(gdb) break test.c:34 if (x & y) == 1
(gdb) break test.c:180 if (p_str == NULL && i < 0)

场景五,在调用栈中新建断点,用于使进程在调用栈指定帧处中断,例:

(gdb) backtrace
(gdb) frame 6
(gdb) break

9.2. 如何运行直至遇到断点?

场景一,启动了 GDB,未启动进程,使用 run 命令。

场景二,进程已中断,运行到下一个断点处使用 continue 命令。

(gdb) run
(gdb) continue

9.3. 如何查看现有断点?

使用 info breakpoints 命令或者缩写命令查看断点:

(gdb) info breakpoints
(gdb) i b

9.4. 如何删除断点?

场景一,删除指定编号断点,例:

(gdb) info breakpoints
(gdb) delete 1

场景二,删除所有断点,例:

(gdb) delete

9.5. 如何禁用和启用断点?

禁用断点使用 disable 命令,可禁用指定编号的单个(多个)断点、禁用指定编号范围内的多个断点、禁用所有断点,例:

(gdb) disable 1
(gdb) disable 1 2
(gdb) disable 4-10
(gdb) disable

启用断点使用 enable 命令,可启用指定编号的单个断点、启用指定编号范围内的多个断点、启用所有断点、启用指定断点后只命中指定次数、启用指定断点后只命中一次、启用指定断点后只命中一次且命中后自动删除断点,例:

(gdb) enable 1
(gdb) enable 4-10
(gdb) enable
(gdb) enable 1 5
(gdb) enable 1 once
(gdb) enable 1 delete

9.6. 如何保存和加载断点?

保存断点至文件使用 save breakpoints 命令,例:

(gdb) save breakpoints gdb.cfg

从文件加载断点使用 source 命令,例:

(gdb) source gdb.cfg

10. 如何调试?

10.1. 如何中断正在运行的进程?

GDB 调试时,若进程正在运行,可按 Ctrl+C 中断进程。

10.2. 如何进入函数?

使用 step 命令,可一次执行多步,默认执行一步,例:

(gdb) step
(gdb) step 5

10.3. 如何逐行调试?

使用 next 命令,不进入函数执行,可一次执行多步,默认执行一步,例:

(gdb) next
(gdb) next 5

10.4. 如何从当前函数返回?

场景一,执行函数到正常退出函数,使用 finish 命令,例:

(gdb) finish

场景二,立即结束执行当前函数并返回,可指定返回值,使用 return 命令,例:

(gdb) return 10

10.5. 如何查看变量值?

可通过 print/p 命令来查看变量值,使用:p[/输出类型] [变量名/表达式]。输出类型如下所示:

/x 以十六进制的形式打印出整数。

/d 以有符号、十进制的形式打印出整数。

/u 以无符号、十进制的形式打印出整数。

/o 以八进制的形式打印出整数。

/t 以二进制的形式打印出整数。

/f 以浮点数的形式打印变量或表达式的值。

/c 以字符形式打印变量或表达式的值。

场景一,打印普通变量的值:

(gdb) p var1
(gdb) p/x var2
(gdb) p/d (short)(0xfec0)

场景二,打印结构体变量或结构体指针变量指定成员的值:

(gdb) p var1.member1
(gdb) p/d var2->member2

场景三,打印结构体变量或者结构体指针变量所有成员的值:

(gdb) p var1
(gdb) p *var2

场景四,打印最近一次打印过的变量的值:

(gdb) p

场景五,查看内存地址中的值,可使用 x 命令,通过 help 命令自行查询其用法,例:

(gdb) help x
(gdb) x/10xb 0xffffaa7fb790

10.6. 如何修改变量值?

可通过 info locals 查看临时变量列表,通过 set 命令设置变量值(若变量名与 set 子命令名冲突可加上 var 关键字),如:

(gdb) info locals
(gdb) set x = 2
(gdb) set var x = 4

10.7. 如何显示源代码?

可通过 list 命令查看源代码,自行通过 help list 命令查看其详细用法,例:

(gdb) help list
(gdb) list
(gdb) list -
(gdb) list 100, 200
(gdb) list 100,
(gdb) list , 200
(gdb) list func
(gdb) list main.c:50

10.8. 如何查看调用栈?

可通过 backtrace 或者 where 命令查看调用栈信息,可通过 frame 命令查看当前帧或者切换到指定帧,然后可以通过 print 命令查看当前帧的临时变量,例:

(gdb) backtrace
(gdb) where
(gdb) frame
(gdb) frame 6
(gdb) print x

10.9. 如何调试多线程程序?

场景一,显示当前可调式的所有线程:

(gdb) info threads

场景二,切换当前调试的线程为指定 ID 的线程:

(gdb) thread 6

场景三,让新建断点在所有线程中生效:

(gdb) break test.c:10 thread all

场景四,让指定线程执行 GDB 命令,thread apply ID1 ID2 command,例:

(gdb) thread apply 1 2 3 step

场景五,设置现场调度模式,set scheduler-locking off|on|step,在使用 step 或者 continue 命令调试当前被调试线程的时候,其他线程也是同时执行的。off 不锁定任何线程,也就是所有线程都执行,这是默认值。 on 只有当前被调试程序会执行。 step 在单步的时候,除了 next 过一个函数的情况以外,只有当前线程会执行。例:

(gdb) set scheduler-locking off

场景六,如何查看锁的持有者。可通过 print/t 命令来查看,例如 _owner = 12345 说明当前锁的持有者的 pid 为 12345,例:

(gdb) p *(pthread_mutex_t*)[锁地址]

11. 如何进行远程调试?

在目标开发板上安装好 gdbserver 后,使用命令启动新的程序或者调试正在运行的程序:

$ gdbserver 192.168.1.34:54321 test
$ gdbserver 192.168.1.34:54321 --attach pid

在 PC 端:

$ gdb
(gdb) target remote 192.168.1.34:54321

12. 如何分析 coredump 文件?

为了让程序在异常退出时可生成 coredump 文件,在终端上执行命令:

$ ulimit -c unlimited

使用 GDB 分析 coredump 文件,使用 gdb [exec file] [core file] 命令行格式打开 gdb 和生成的 core 文件,然后即可像正常调试一样查看对应信息:

$ gdb ./a.out core
(gdb) backtrace
(gdb) frame 6
(gdb) list
(gdb) info locals
(gdb) print x
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值