# GBD 调试学习记录
GDB 在 Windows 的 WSL 下进行代码调试.
# GDB 作用
GDB 主要完成下面四个方面的功能:
- 启动 的程序,可以按照 的自定义的要求随心所欲的运行程序。
- 可让被调试的程序在 所指定的调置的断点处停住。 (断点可以是条件表达式)
- 当程序被停住时,可以检查此时 的程序中所发生的事。
- 动态的改变 程序的执行环境。
# GDB 调试参数
# 简单使用 gdb 流程
- 使用
cc -g 文件名.c -o 调试文件
生成调试 文件 test - 使用
gdb 调试文件
在入调试文件到 GDB 中 - 设置断点
b 行数
p 变量
查看变量值,或watch 变量
# 认识 GDB
gdb 一般用于调试 C/C++ 的代码,使用 gdb 调试文件时,需要使用 -g
这个参数,
如果不使用是无法进行调试的,界面如下:
正常 使用 -g
参数
# 启动 GDB 的方式
- gdb <program>
program 也就是执行文件,一般在当然目录下 - gdb <program> core
用 gdb 同时调试一个运行程序和 core 文件,core 是程序非法执行后 core dump 后产生的文件 - gdb <program> <PID>
如果 所调试的程序是一个服务程序,那么可以指定这个服务程序运行时的进程 ID 来是 gdb 自动 attach 这个进程
program 应该在 PATH 环境变量中搜索得到,GDB 启动时,可以加上一些 GDB 的启动开关.
详细资料可以去寻找 使用 gdb -help 来查找
# 学会 help
进入 gdb 界面大致如下
输入 help
会得到相应的 命令类别,例如:
如果想查看某一类命令的说明可以 使用 help class
, 例如 help b
, 结果如下:
# 关于记性不好,如何偷懒这回事
gdb 中,输入命令时,可以不用打全命令,只用打命令的前几个字符就可以了,
当然,命令的前几个字符应该要标志着一个唯一的命令,
在 Linux 下,可以敲击两次 TAB 键来补齐命令的全称,如果有重复的,那么 gdb 会把其例出来.
例如,有个很多个 get 开头的函数,记不清是哪个了,这时候 输入 get 后按一次 TAB 就会看到所有 以 get 开头的函数
到这是不是会有一些疑惑,C 语言还好,但是对于 C++ 这种有函数重载特性的高级语言来说,重名函数是很自然的,如果用补全那么如何区分呢?
对于 gdb 他会直接显示出函数的参数来体现,例如:
# gdb 中运行 shell
在 gdb 环境中,可以执行 UNIX 的 shell 的命令,使用 gdb 的 shell 命令来完成:shell <command string>
调用 UNIX 的 shell 来执行 <command string>, 环境变量 SHELL 中定义的 UNIX 的 shell
将会被用来执行 <command string>,
如果 SHELL 没有定义,那就使用 UNIX 的标准 shell:/bin/sh
。 (在 Windows 中使用 Command.com 或 cmd.exe)
例如:
还有一个 gdb 命令是 make:make <make-args>
可以在 gdb 中执行 make 命令来重新 build 自己的程序。 这个命令等价于 “shell make <make-args>” 。
例如:
# 在 GDB 中运行程序
————————
当以 gdb <program>
方式启动 gdb 后,gdb 会在 PATH 路径和当前目录中搜索 <program> 的源文件。 如要确认 gdb
是否读到源文
件,可使用 l
或 list
命令,看看 gdb 是否能列出源代码。
在 gdb 中,运行程序使用 r
或是 run
命令。 程序的运行,需要设置下面四方面的事。
- 程序运行参数。
set args 可指定运行时参数
。 ( 如:set args 10 20 30 40 50
)show args
命令可以查看设置好的运行参数。
- 运行环境。
path <dir>
可设定程序的运行路径。show paths
查看程序的运行路径。set environment varname [=value]
设置环境变量。如:set env USER=hchen
show environment [varname]
查看环境变量。
- 工作目录。
cd <dir>
相当于 shell 的 cd 命令。pwd
显示当前的所在目录。
- 程序的输入输出。
info terminal
显示 程序用到的终端的模式。
使用重定向控制程序输出。如:run > outfile
tty 命令可以指写输入输出的终端设备。如:tty /dev/ttyb
# 调试运行中的程序
跑起来的目的是什么,当然是在线调试啦,
运行中的程序有两种方法:
- 在 UNIX 下用
ps
查看正在运行的程序的 PID (进程 ID), 然后用gdb <program> PID
格式挂接正在运行的程序。 - 先用
gdb <program>
关联上源代码, 并进行 gdb,在 gdb 中用attach
命令来挂接进程的 PID。 用detach
来取消挂接的进程。
# 暂停 / 恢复 程序运行
一直全速运行程序是查不出 bug 的,有问题肯定得暂停。在图像化的界面的时候咱往往是在一些地方设置断点,让程序停下来.
例如,状态机 的 标志位,每一次发生变化的时候都可以放置一个断点,观察是哪个状态无法触发.
当进程停住的时候,然后用 info program
来查看程序是否在运行,进程号,被暂停的原因.
例如:
在 gdb 中有以下几种暂停方式:
# 设置断点 ( BreakPoint
)
break <function>
在进入指定函数时停住.
C++ 中可以使用class::function
或function(type,type)
格式来指定函数名break <linenum>
在指定行号停住break ±offset
在当前行号的前面或后面的offset
行停住,offset
为自然数break filename:linenum
在源文件filename
的linenum
行处停住
这里是因为 45 行没有代码,而且 最近的 数据类型定义在 gcc 的编译的时候被优化了,所以直接转跳到 48 行break filename:function
在源文件filename
的function
函数的入口处停住break *address
在程序运行的内存地址处停住。
因为 0x12cf 处没有代码,所以出现了空提示break
break
命令没有参数时, 表示在下一条指令处停住。break ... if <condition>...
可以是上述的参数,condition
表示条件, 在条件成立时停住。
比如在循环境体中, 可以设置 break if i=100, 表示
当 i 为 100 时停住程序。
PS: 查看断点
有了断点,就会出现一系列的相关问题断点一多容易忘记自己在哪打过断点怎么办?
使用 info 命令, 如下所示: (注:n 表示断点号)info breakpoints [n]
info break [n]
# 观察点 ( WatchPoint
)
观察点一般来观察某个表达式 (变量也是一种表达式) 的值是否有变化了,如果有变化,马上停住程序.
- watch 变量的类型
- 整形变量:
int i; watch i;
- 指针类型:
char *p; watch p, watch *p;
- 区别:
- watch p 是查看 *(&p), 是 p 变量本身。
- watch (*p) 是 p 所指的内存的内容,查看地址,一般是我们所需要的。
PS:
存在char buf[128], watch buf
是对 buf 的 128 个数据进行了监视.
此时不是采用硬件断点,而是软中断实现的。软中断方式去检查内存变量是比较耗费 cpu 资源的。
精确的指明地址 (这玩意一般只有编译器能知道) 是硬件中断。
- 整形变量:
- 设置的观察点是一个局部变量时。局部变量无效后,观察点无效
例如,在主函数的变量,没有进入或返回主函数之前是无法被检测的,一般会在 主函数入口处设置一个 断点
有几种方法来设置观察点:
watch <expr>
为表达式 (变量) expr 设置一个观察点。 一量表达式值有变化时, 马上停住程序。rwatch <expr>
当表达式 (变量) expr 被读时, 停住程序。awatch <expr>
当表达式 (变量) 的值被读或被写时, 停住程序。当 设置的观察点是一个局部变量时。局部变量无效后,观察点无效
Watchpoint 2 deleted because the program has left the block in
which its expression is valid.
PS: 查看观察点info watchpoints
列出当前所设置了的所有观察点。
# 捕捉点 ( CatchPoint
)
PS: 这一块因为,自己很少写 CPP 代码,没有测试
可设置捕捉点来补捉程序运行时的一些事件.
设置捕捉点的格式如下:catcah <event>
, 当 event
发生时,程序 stop:event
可以是一下内容:
当 event 发生时, 停住程序。 event 可以是下面的内容:
- throw 一个 C++ 抛出的异常。(throw 为关键字)
- catch 一个 C++ 捕捉到的异常。(catch 为关键字)
- exec 调用系统调用 exec 时。(exec 为关键字, 目前此功能只在 HP-UX 下有用)
- fork 调用系统调用 fork 时。(fork 为关键字, 目前此功能只在 HP-UX 下有用)
- vfork 调用系统调用 vfork 时。(vfork 为关键字, 目前此功能只在 HP-UX 下有用)
- load 或 load <libname> 载入共享库 ( 动态链接库) 时。( load 为关键字, 目前此功能只在 HP-UX 下有用)
- unload 或 unload <libname> 卸载共享库 ( 动态链接库) 时。( unload 为关键字, 目前此功能只在 HP-UX 下有用)
tcatch <event>
只设置一次捕捉点, 当程序停住以后, 应点被自动删除
# 维护停止点
上面说了如何设置程序的停止点, GDB 中的停止点也就是上述的三类。
在 GDB 中,如果 觉得已定义好的停止点没有用或者暂时不想使用了
可以使用 delete
、 clear
、 disable
、 enable
这几个命令来进行维护。
clear
清除所有的已定义的停止点。(我在 WSL 上测试不出,无法清除所有断点)clear <function>
|clear <filename:function>
清除所有设置在函数上的停止点。clear <linenum>
|clear <filename:linenum>
清除所有设置在指定行上的停止点。delete [breakpoints] [range...]
删除指定的断点, breakpoints 为断点号。
如果不指定断点号, 则表示删除所有的断点。
range 表示断点号的范围(如:3-7), 其简写命令为 d 。disable [breakpoints] [range...]
disable 所指定的停止点, breakpoints 为停止点号。 如果什么都不指定, 表示 disable 所有的停止点。 简写命令是 dis.enable [breakpoints] [range...]
enable 所指定的停止点, breakpoints 为停止点号。enable [breakpoints] once range...
enable 所指定的停止点一次, 当程序停止后, 该停止点马上被 GDB 自动 disable。enable [breakpoints] delete range...
enable 所指定的停止点一次, 当程序停止后, 该停止点马上被 GDB 自动删除
# 信号 ( Signals
)
# 线程停止 ( Thread Stops
)
如果要恢复程序运行, 可以使用 c
或是 continue
命令
PS: 以上内容来自 GDB 完全手册以及个人总结和实践
# 附录:指令表
# 基本指令
指令 | 作用 |
---|---|
l | 查看源码 |
b | 设置断点 |
info b | 查看断点信息 |
r | 运行源码 |
p 变量名 | 显示变量值 |
watch 变量名 | 观察变量 |
n | 单步运行 |
c | 程序继续运行 |
q | 退出调试 |
# 断点调试
命令格式 | 例子 | 作用 |
---|---|---|
break + 设置断点的行号 | break n | 在 n 行处设置断点 |
tbreak + 行号或函数名 | tbreak n/func | 设置临时断点, |
到达后被自动删除 | ||
break + filename + 行号 | break main.c:10 | 用于在指定文件对应行设置断点 |
break + <0x...> | break 0x3400a | 用于在内存某一位置处暂停 |
break + 行号 + if + 条件 | break 10 if i==3 | 用于设置条件断点, |
在循环中使用非常方便 | ||
info breakpoints/watchpoints [n] | info break | n 表示断点号, |
查看断点 / 观察点的情况 | ||
clear + 要清除的断点行号 | clear 10 | 用于清除对应行的断点, |
要给出断点的行号,
清除时 GDB 会给出提示 |
| delete + 要清除的断点编号 | delete 3 | 用于清除断点和自动显示的表达式的命令,
要给出断点的编号,
清除时 GDB 不会给出任何提示 |
| disable/enable + 断点编号 | disable 3 | 让所设断点暂时失效 / 使能,
如果要让多个编号处的断点失效 / 使能,
可将编号之间用空格隔开 |
| awatch/watch + 变量 | awatch/watch i | 设置一个观察点,
当变量被读出或写入时程序被暂停 |
| rwatch + 变量 | rwatch i | 设置一个观察点,
当变量被读出时,
程序被暂停 |
| catch | - | 设置捕捉点来补捉程序运行时的一些事件。如:载入共享库 (动态链接库) 或是 C++ 的异常 |
| tcatch | - | 只设置一次捕捉点,
当程序停住以后,
应点被自动删除 |
# 数据命令
命令格式 | 例子 | 作用 |
---|---|---|
display + 表达式 | display a | 用于显示表达式的值, |
每当程序运行到断点处都会显示表达式的值 | ||
info display | 用于显示当前所有要显示值的表达式的情况 | |
delete + display 编号 | delete 3 | 用于删除一个要显示值的表达式, |
被删除的表达式将不被显示 | ||
disable/enable + display 编号 | disable/enable 3 | 使一个要显示值的表达式暂时失效 / 使能 |
undisplay + display 编号 | undisplay 3 | 用于结束某个表达式值的显示 |
whatis + 变量 | whatis i | 显示某个表达式的数据类型 |
print (p) + 变量 / 表达式 | p n | 用于打印变量或表达式的值 |
set + 变量 = 变量值 | set i = 3 | 改变程序中某个变量的值 |
PS:
- 在使用 print 命令时,
可以对变量按指定格式进行输出,
其命令格式为 print / 变量名 + 格式 - 其中常用的变量格式:x:十六进制;d:十进制;u:无符号数;o:八进制;c:字符格式;f:浮点数.
# 调试运行环境相关命令
命令格式 | 例子 | 作用 |
---|---|---|
set args | set args arg1 arg2 | 设置运行参数 |
show args | show args | 参看运行参数 |
set width + 数目 | set width 70 | 设置 GDB 的行宽 |
cd + 工作目录 | cd ../ | 切换工作目录 |
run | r/run | 程序开始执行 |
step(s) | s | 进入式 (会进入到所调用的子函数中) 单步执行, |
进入函数的前提是,
此函数被编译有 debug 信息 |
| next (n) | n | 非进入式 (不会进入到所调用的子函数中) 单步执行 |
| finish | finish | 一直运行到函数返回并打印函数返回时的堆栈地址和返回值及参数值等信息 |
| until + 行数 | u 3 | 运行到函数某一行 |
| continue (c) | c | 执行到下一个断点或程序结束 |
| return <返回值> | return 5 | 改变程序流程,
直接结束当前函数,
并将指定值返回 |
| call + 函数 | call func | 在当前位置执行所要运行的函数 |
# 堆栈相关命令
命令格式 | 例子 | 作用 |
---|---|---|
backtrace/bt | bt 用来打印栈帧指针, |
也可以在该命令后加上要打印的栈帧指针的个数,
查看程序执行到此时,
是经过哪些函数呼叫的程序,
程序 “调用堆栈” 是当前函数之前的所有已调用函数的列表 (包括当前函数)。每个函数及其变量都被分配了一个 “帧”,
最近调用的函数在 0 号帧中 (“底部” 帧) |
| frame | frame 1 用于打印指定栈帧 |
| info reg | info reg 查看寄存器使用情况 |
| up/down | up/down | 跳到上一层 / 下一层函数 |
# 跳转执行
jump 指定下一条语句的运行点。
可以是文件的行号,
可以是 file:line
格式,
可以是 +num
这种偏移量格式。
表式着下一条运行语句从哪里开始。
相当于改变了 PC 寄存器内容,
堆栈内容并没有改变,
跨函数跳转容易发生错误。
# 信号命令
命令格式 | 例子 | 作用 |
---|---|---|
signal | signal SIGXXX | 产生 XXX 信号, |
如 SIGINT。一种速查 Linux 查询信号的方法:# kill -l | ||
handle | - | 在 GDB 中定义一个信号处理。信号可以以 SIG 开头或不以 SIG 开头, |
可以用定义一个要处理信号的范围 (如:SIGIO-SIGKILL,
表示处理从 SIGIO 信号到 SIGKILL 的信号,
其中包括 SIGIO,
SIGIOT,
SIGKILL 三个信号),
也可以使用关键字 all 来标明要处理所有的信号。一旦被调试的程序接收到信号,
运行程序马上会被 GDB 停住,
以供调试。其可以是以下几种关键字的一个或多个: |
1. nostop/stop
当被调试的程序收到信号时,
GDB 不会停住程序的运行,
但会打出消息告诉 收到这种信号 / GDB 会停住 的程序
1. print/noprint
当被调试的程序收到信号时,
GDB 会显示出一条信息 / GDB 不会告诉 收到信号的信息
pass
noignore
当被调试的程序收到信号时,
GDB 不处理信号。这表示,
GDB 会把这个信号交给被调试程序会处理。
nopass
ignore
当被调试的程序收到信号时,
GDB 不会让被调试程序来处理这个信号。
info signals
info handle
可以查看哪些信号被 GDB 处理,
并且可以看到缺省的处理方式
single 命令和 shell 的 kill 命令不同,
系统的 kill 命令发信号给被调试程序时,
是由 GDB 截获的,
而 single 命令所发出一信号则是直接发给被调试程序的。
# 运行 Shell 命令
在 gdb 中运行 shell 命令
如 (gdb) shell ls 来运行 ls。
# 其他程序运行选项和调试
程序运行参数。
set args
可指定运行时参数。 (如:set args 10 20 30 40 50)show args
命令可以查看设置好的运行参数。运行环境。
path
可设定程序的运行路径。show paths
查看程序的运行路径。set environment varname [=value]
设置环境变量。如:set env USER=hchen
show environment [varname] 查看环境变量。工作目录。
cd
相当于 shell 的 cd 命令。pwd
显示当前的所在目录。程序的输入输出。
info terminal 显示 程序用到的终端的模式。
使用重定向控制程序输出。如:run > outfile
tty 命令可以指写输入输出的终端设备。如:tty /dev/ttyb
调试已运行的程序
两种方法:- (1) 在 UNIX 下用 ps 查看正在运行的程序的 PID (进程 ID),
然后用 gdb PID 格式挂接正在运行的程序。 - (2) 先用 gdb 关联上源代码,
并进行 gdb,
在 gdb 中用 attach 命令来挂接进程的 PID。并用 detach 来取消挂接的进程。`
- (1) 在 UNIX 下用 ps 查看正在运行的程序的 PID (进程 ID),
暂停 / 恢复程序运行
当进程被 gdb 停住时,
可以使用 info program 来查看程序的是否在运行,
进程号,
被暂停的原因。
在 gdb 中,
我们可以有以下几种暂停方式:断点 (BreakPoint)、观察点 (WatchPoint)、捕捉点 (CatchPoint)、信号 (Signals)、线程停止 (Thread Stops),如果要恢复程序运行,
可以使用 c 或是 continue 命令。线程 (Thread Stops)
如果程序是多线程,
可以定义断点是否在所有的线程上,
或是在某个特定的线程。break thread
break thread if ...
linespec
指定了断点设置在的源程序的行号。threadno
指定了线程的 ID,
注意,
这个 ID 是 GDB 分配的,
可以通过info threads
命令来查看正在运行程序中的线程信息。
如果不指定thread
则表示断点设在所有线程上面。还可以为某线程指定断点条件。
如:(gdb) break frik.c:13 thread 28 if bartab > lim
当程序被 GDB 停住时,
所有的运行线程都会被停住。这方便查看运行程序的总体情况。而在 恢复程序运行时,
所有的线程也会被恢复运行。