使用 GDB 调试程序时,在合适的时机暂停程序的运行是最基本且必须的操作。这里提到的合适时机暂停包括控制程序在哪行暂停、在什么条件下暂停、在收到什么信号时暂停等。

GDB 提供的暂停程序运行的方式包括:断点 (BreakPoint)、观察点 (WatchPoint)、捕捉点 (CatchPoint)、信号 (Signals)、线程停止 (Thread Stops)。

Vim教程网(https://vimjc.com)下面具体介绍下几种暂停程序运行的命令和方法。

一、断点设置与查看

断点是调试程序最常用的方式。在 GDB 内部,可以使用 break 命令 (缩写形式 b) 来设置断点。break 命令的主要使用方式包括:

(1) break
表示程序执行当下一条指令时由 GDB 暂停程序

(2) break +offset
表示当程序执行到当前行后面的 offset 行时由 GDB 暂停程序

(3) break -offset
表示当程序执行到当前行前面的 offset 行时由 GDB 暂停程序

(4) break <linenum>
表示当程序执行到当前文件的第 linenum 行时由 GDB 暂停程序

(5) break filename:linenum
表示当程序执行到源文件名为 filename 的第 linenum 行处由 GDB 暂停程序

(6) break foo
表示当程序执行到函数名为 foo 时的函数时由 GDB 暂停程序 (C++ 使用 namespace::class_name::foo 格式指定函数名

(7) break filename:foo
表示当程序执行到源文件名为 filenamefoo 函数入口处由 GDB 暂停程序

(8) break *address
表示当程序执行到内存地址为 address 处由 GDB 暂停程序

(9) break ... if <condition>
... 可以是上述的参数,if condition 表示当且仅当 condition 条件成立时才会由 GDB 在对应位置暂停程序

GDB断点

GDB 会为每个生效的断点分配一个内部的断点编号,info 命令 (缩写形式 i) 可用来查看已设置的断点信息。

info break (缩写形式 i b) 可查看本次调试会话所有已设置的断点,info b n 可查看断点编号为 n 的断点信息。

二、观察点设置与查看

观察点一般用来观察/监视某个变量、表达式、内存地址的变化状态。例如,可以在一个变量被修改时设置观察点,那么当该变量在任何时候被程序执行写入操作时就会被 gdb 暂停执行。

可以将观察点当做一种特殊的断点看待,其实现一般依靠 CPU 硬件断点支持。gdb 观察点只能在程序启动后设置,所以一般是先设置断点,待断点触发后再设置需要的观察点。

gdb 设置观察点的命令和形式主要由以下几种:

(1) watch var
表示在变量 var 设置一个写观察点,当该变量被写时,由 GDB 暂停程序

(2) rwatch var
表示在变量 var 设置一个读观察点,当该变量被读时,由 GDB 暂停程序

(3) awatch var
表示在变量 var 设置一个读写观察点,当该变量被读或被写时,由 GDB 暂停程序

三、捕捉点设置与查看

捕捉点主要用于补捉程序运行时产生的一些事件 (如:C++异常、加载动态链接库等) 并由 GDB 暂停程序执行。

GDB 设置捕捉点的格式为:catch <event>。有效的 event 包括:

(1) throw
表示当程序运行抛出一个 C++ 异常时由 GDB 暂停程序

(2) catch
表示当程序运行捕捉到的异常时由 GDB 暂停程序

(3) exec
表示当程序运行调用系统调用 exec 时由 GDB 暂停程序

(4) fork
表示当程序运行调用系统调用 fork 时由 GDB 暂停程序

(5) load
表示当程序运行载入动态链接库时由 GDB 暂停程序

(6) unload
表示当程序运行卸载动态链接库时由 GDB 暂停程序

此外,可使用 tcatch <event> 命令设置一次临时性的捕捉点 (temp catch),当程序因为该捕捉点而被 GDB 暂停后,该捕捉点将被自动删除。

四、修改停止点状态

断点、观察点和捕捉点都算是 GDB 中的停止点。可以对这三类设置过的 GDB 停止点进行 维护 操作,包括:clear、delete、disable、enable 等。

(1) clear
表示清除所有的已定义的停止点,包括设置的断点、观察点和捕捉点

(2) clear fooclear filename:foo
用于清除所有设置在函数名为 foo 的函数上的停止点

(3) clear <linenum>clear <filename:linenum>
用于清除所有设置在指定行上的停止点

(4) delete [breakpoints] [range...]
表示删除指定的断点,breakpoints为断点号。如果不指定断点号,则表示删除所有的断点。range 表示断点号的范围 (如:1-5)

比删除更好的一种方法是 disable (缩写形式 dis) 停止点。对停止点执行 disable 操作后,GDB 不会将该停止点删除,而只是将其置为无效;当需要时,可以在该停止点上执行 enable 操作,从而重新激活该停止点。

(1) disable [breakpoints] [range...]
表示 disable 所指定的停止点,breakpoints为停止点号;如果什么都不指定,表示 disable 所有的停止点。

(2) enable [breakpoints] [range...]
表示 enable 所指定的停止点,breakpoints为停止点号。

(3) enable [breakpoints] once range...
表示 enable 所指定的停止点一次,当程序停止后,该停止点马上被 GDB 自动 disable

(4) enable [breakpoints] delete range...
表示 enable 所指定的停止点一次,当程序停止后,该停止点马上被 GDB 自动删除。

五、修改停止条件

在设置 GDB 断点观察点 时可以指定一个触发条件 (通过 if 关键词指定),表示仅当条件成立时,程序才会被 GDB 自动停止,这是一个非常强大的功能。

停止点条件设置好后,可以用 condition 命令来修改停止点的条件。(GDB 捕捉点暂不支持设置条件)

例如,condition <bnum> <expression> 命令表示将断点号为 bnum 的触发停止条件修改为 expression。

而 GDB 命令 condition <bnum> 则表示删除设置在断点号为 bnum 上的停止条件,使得程序进入该断点则必会被 GDB 暂停执行。

另外一个比较特殊的维护命令 ignore,可以用于指定程序运行时,GDB 忽略该停止条件几次。

如,ignore <bnum> <count> 命令表示 GDB 忽略设置在断点号为 bnum 上的停止条件 count 次。

六、为停止点设定运行命令

可以使用 GDB 提供的 commands 命令设置当对应停止点触发时需要自动执行的命令。这是一个常用于实现自动化调试 (如定位线上服务BUG) 的强大功能。

若想在断点号为 bnum 上的断点上指定一个自动运行命令 printf,可以使用命令:

1
2
3
commands [bnum]
printf "test condition commands"
end

更复杂一点,若想在函数 foo 上设置条件为 i > 0 的条件断点,且当程序被 GDB 暂停时,自动打印出 i 的值后继续运行程序 (通过关键词 continue 实现),则可以使用以下命令:

1
2
3
4
5
break foo if i>0
commands
printf "i is %d\n", i
continue
end

如果要删除设置在断点上的自动运行命令序列,只需要在对应断点上执行 commands 命令后再执行下 end 即可 (即重新设置一个空的运行命令序列)。

gdb条件断点

七、恢复程序运行

当进程被 GDB 暂停时,可以使用 info program 来查看程序进程号,被暂停的原因等信息。

可以用 continue 命令 (缩写形式 c) 继续运行程序,直到程序结束或下一个断点被触发;也可以使用 step 命令 (缩写形式 s) 或 next 命令 (缩写形式 n) 继续单步执行程序。

推荐阅读:《GDB入门教程之恢复程序执行》。

《女程序员说》

原创不易,希望能给小女子的公众号加个关注~