要基于源码调试程序,gcc
的编译选项至少要加入 '-g'
。某些时候,为了便于调试,可把优化级别调低,使用 '-O0 -g'
。
在 gdb
状态,可以使用 help <cmd>
查询 gdb
命令的使用帮助,例如:
(gdb) help breakpoints # 显示所有断点 (break) 相关的命令
同时,也可以像 Linux
命令行一样,敲入命令时,可使用 "Tab
" 键查询所有可能的命令。
例如,敲入 b
,再按两次 "Tab
" 键,你会看到所有 b
打头的命令:
(gdb) b
backtrace break bt
(gdb)
使用 gdb
时,最好加上 "--tui
" 选项,否则很大可能会出现花屏现象。
$ gdb --tui <your_exec_file>
如果你已经启动了 gdb
,可使用下面的命令启用 tui
:
(gdb) tui enable # 启用 tui
(gdb) tui disable # 关闭 tui
(gdb) tui reg # 显示相关的寄存器状态
(gdb) tui reg general # 显示标准寄存器的状态
(gdb) tui reg mmx # 显示 MMX 寄存器的状态
(gdb) tui reg sse # 显示 SSE 寄存器的状态
# tui reg 可选的参数有:
# sse, mmx, general, float, all, save, restore, vector, system
打开 gdb
后,可使用如下命令设置 layout
(布局):
注:使用 X-Shell
等终端登录的时候,如果 gdb
的界面乱了,可以拖动 X-Shell
的窗口,会重新更新 gdb
的界面。
-
能够同时看到源代码和汇编
layout split # 分屏同时显示源代码,汇编
-
能够实时看到寄存器值的变化
layout regs # 显示当前调试状态的寄存器状态
-
源代码或汇编
layout src # 显示当前调试状态关联的源代码(如果有的话) layout asm # 显示当前调试状态关联的汇编代码
在
gdb
中运行set disassemble-next-line on
,表示自动反汇编后面要执行的代码。
两种方法:
-
在
UNIX
下用ps
查看正在运行的程序的PID (进程 ID)
,然后用gdb <your_exec_file> PID
格式挂接正在运行的程序。$ gdb ./test_exec 6022 # 关联要调试的程序,并挂接到进程 PID
-
先用
gdb <your_exec_file>
关联要调试的运行的程序(源代码),然后在gdb
中用attach
命令来挂接进程的PID
。并用detach
来取消挂接的进程。$ gdb ./test_exec # 关联要调试的运行的程序 (gdb) attach 6022 # 挂接进程的 PID (gdb) detach # 取消挂接的进程
先用 gdb <your_exec_file>
关联要调试的运行的程序:
$ gdb ./test_exec # 关联要调试的运行的程序
或者在启动 gdb
之后,使用 exec-file
命令指定要运行的程序(带路径):
$ gdb
(gdb) exec-file ./your_exec_file
注意:推荐使用第一种方式,因为使用 exec-file
命令虽然能调试程序,但是不能关联调试信息和源代码。
指定要调试程序的运行参数
可使用 run
命令启动要调试的程序,并指定运行的参数,:
(gdb) run -f -d your_exec_params
调试程序中,暂停程序运行是必须的, gdb
可以方便地暂停程序的运行。你可以设置程序的在哪行停住,在什么条件下停住,在收到什么信号时停往等等。以便于你查看运行时的变量,以及运行时的流程。
当进程被 gdb
停住时,你可以使用 info program
来查看程序的是否在运行,进程号,被暂停的原因。
在 gdb
中,我们可以有以下几种暂停方式:断点(BreakPoint
)、观察点(WatchPoint
)、捕捉点(CatchPoint
)、信号(Signals
)、线程停止(Thread Stops
)。如果要恢复程序运行,可以使用 c
或是 continue
命令。
gdb
断点分类:
-
breakpoint
可以根据行号、函数、条件生成断点。
-
watchpoint
监测变量或者表达式的值发生变化时产生断点。
-
catchpoint
监测信号的产生。例如c++的throw,或者加载库的时候。
-
设置断点
-
break <function>
b / break <function>,例如:b main,把断点设置在 main() 函数上。
-
break <linenum>
在指定行号停住。
-
break <filename:linenum>
在源文件 filename 的 linenum 行处停住。
-
break <filename:function>
在源文件 filename 的 function 函数的入口处停住。
-
break *address
在程序运行的内存地址处停住。
-
break
break 命令没有参数时,表示在下一条指令处停住
-
-
查看断点信息
-
info break [break num]
列出断点 break num 的信息。
-
info break
列出所有断点信息。
-
恢复程序运行和单步调试
-
单步跟踪:
s
/step <count>
,例如:s 10
。单步跟踪,如果有函数调用,他会进入该函数。
-
单步跟踪:
n
/next <count>
,例如:n 10
。同样单步跟踪,如果有函数调用,他不会进入该函数,很像 VC 等工具中的 step over 。
-
设置
step-mode
-
set step-mode on
打开 step-mode 模式,于是,在进行单步跟踪时,程序不会因为没有 debug 信息而不停住。这个参数有很利于查看机器码。
-
set step-mode off
关闭 step-mode 模式。
-
当程序被停住了,你可以用 continue 命令恢复程序的运行直到程序结束,或下一个断点到来。也可以使用 step 或 next 命令单步跟踪程序。
-
continue [ignore-count]
-
c [ignore-count]
-
fg [ignore-count]
恢复程序运行,直到程序结束,或是下一个断点到来。 ignore-count 表示忽略其后的断点次数。 continue,c,fg 三个命令都是一样的意思。
观察点一般来观察某个表达式(变量也是一种表达式)的值是否有变化了,如果有变化,马上停住程序。我们有下面的几种方法来设置观察点:
-
watch <expr>
为表达式(变量)expr 设置一个观察点。一量表达式值有变化时,马上停住程序。
-
rwatch <expr>
当表达式(变量)expr 被读时,停住程序。
-
awatch <expr>
当表达式(变量)的值被读或被写时,停住程序。
-
info watchpoints
列出当前所设置了的所有观察点。
你可设置捕捉点来补捉程序运行时的一些事件。如:载入共享库(动态链接库)或是 C++
的异常。设置捕捉点的格式为:
-
catch <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
清除所有的已定义的停止点。
-
clear <function>
-
clear <filename:function>
清除所有设置在函数上的停止点。
-
clear <linenum>
-
clear <filename:linenum>
清除所有设置在指定行上的停止点。
-
delete [breakpoints] [range...]
删除指定的断点, breakpoints 为断点号。如果不指定断点号,则表示删除所有的断点。 range 表示断点号的范围(如: 3-7 )。其简写命令为 d 。
比删除更好的一种方法是 disable 停止点,disable 了的停止点, GDB 不会删除,当你还需要时, enable 即可,就好像回收站一样。
-
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 自动删除。
查看函数调用栈的几个函数:
-
bt
显示函数调用栈所有的帧信息,每个帧一行。
-
bt n
显示栈顶的 n 个帧信息。
-
bt -n
显示栈底的 n 个帧信息。
-
bt full
显示函数调用栈中所有帧的完全信息如:函数参数,本地变量。
-
bt full n
-
bt full -n
同上。
如果要查看指定帧内的信息、变量、参数,首先要移动到指定帧。
-
frame n
或f n
通过帧编号来选择帧,帧编号可以通过
bt
命令来查看。 -
frame addr
或f addr
通过帧地址来选择帧,帧编号可以通过
bt
命令来查看。 -
up n
在栈中向上移动 n 个帧,即向着最外层移动 n 个帧,目标帧 = 当前帧 + n。
-
down n
在栈中向下移动 n 个帧,即向着最内层移动 n 个帧,目标帧 = 当前帧 - n。
-
up-silently n
-
down-silently n
同上,在栈中移动 n 个帧,但是不打印信息。
-
frame
或f
打印帧内函数的信息。
-
info frame
或info f
打印帧的信息。
-
info frame addr
或info f addr
打印通过 addr 指定帧的信息。
-
info args
打印函数变量的值。
-
info locals
打印本地变量的信息。
-
info catch
打印异常捕获信息。
可使用 print <var_name>
查看变量的值,print
命令的格式为:
print [options --] [/fmt] expr
或者
p [options --] [/fmt] expr
-
@
是一个和数组有关的操作符,在后面会有更详细的说明。
-
::
指定一个在文件或是一个函数中的变量。
-
{}
表示一个指向内存地址的类型为type的一个对象。
- 全局变量(所有文件可见的)
- 静态全局变量(当前文件可见的)
- 局部变量(当前
Scope
可见的)
如果你的局部变量和全局变量发生冲突(重名),一般情况下,局部变量会覆盖全局变量。如果此时你想查看全局变量的值,可以使用 "::
" 操作符:
file::variable
function::variable
例如:
查看文件 f2.c
中的全局变量 x
的值:
(gdb) p 'f2.c'::x
注:如果你的程序编译时开启了优化选项,那么在用 GDB
调试被优化过的程序时,可能会发生某些变量不能访问,或是取值错误的情况。对付这种情况时,需要在编译程序时关闭编译优化。例如 GCC
,你可以使用 "-gstabs
" 选项来解决这个问题。
-
动态数组
格式:
print array@len array: 数组的首地址,len: 数据的长度
例如:
(gdb) print array@len $1 = {2, 4, 6, 8, 10}
-
静态数组
可以直接用 "
print 数组名
",就可以显示数组中所有数据的内容了。
打印变量的时候可以设置变量的输出格式,命令格式:
print [/fmt] expr
所有 /fmt
格式:
/x 按 十六进制 格式显示
/d 按 十进制 格式显示 有符号整型
/u 按 十六进制 格式显示 无符号整型
/o 按 八进制 格式显示
/t 按 二进制 格式显示
/a 按 十六进制 格式显示
/c 按 字符 格式显示
/f 按 浮点数 格式显示
例如:
按十六进制的格式显示变量 num
的值:
(gdb) print/x num
$1 = 0x80001001
使用 examine
(简写 x
)来查看内存地址中的值。
语法:
x/
n、f、u 是可选的参数。
n
是一个正整数,表示显示内存的长度,也就是说从当前地址向后显示几个地址的内容。f
表示显示的格式,参见上面。如果地址所指的是字符串,那么格式可以是s
,如果地十是指令地址,那么格式可以是i
。u
表示从当前地址往后请求的字节数,如果不指定的话,GDB
默认是 4 个 bytes。u
参数可以用下面的字符来代替,b
表示单字节,h
表示双字节,w
表示四字 节,g
表示八字节。当我们指定了字节长度后,GDB
会从指内存定的内存地址开始,读写指定字节,并把其当作一个值取出来。
例如:
x/3uh 0x54320 :从内存地址 0x54320 读取内容,h 表示以双字节为一个单位,3 表示三个单位,u 表示按十六进制显示。
-
要查看寄存器的值,很简单,可以使用如下命令:
info registers
-
查看寄存器的情况。(除了浮点寄存器)
info all-registers
-
查看所有寄存器的情况。(包括浮点寄存器)
info registers
-
查看所指定的寄存器的情况。
寄存器中放置了程序运行时的数据,比如程序当前运行的指令地址(ip),程序的当前堆栈地址(sp)等等。你同样可以使用
print
命令来访问寄存器的情况,只需要在寄存器名字前加一个
-
格式:
display[/i|s] [expression | addr]
例如:
display/i disable display enable display
disable
和enalbe
不删除自动显示的设置,而只是让其失效和恢复。 -
info display
查看
display
设置的自动显示的信息。GDB
会打出一张表格,向你报告当然调试中设置了多少个自动显示设置,其中包括,设置的编号,表达式,是否enable
。
-
set print address
set print address on
打开地址输出,当程序显示函数信息时,
GDB
会显出函数的参数地址。 -
set print array
set print array on
打开数组显示,打开后当数组显示时,每个元素占一行,如果不打开的话,每个元素则以逗号分隔,默认值为
off
。 -
set print array-indexes
set print array-indexes on
对于非字符类型数组,在打印数组中每个元素值的同时,是否同时显示每个元素对应的数组下标,默认值为
off
。 -
set print elements
set print elements 100 # 设置最大的显示元素个数, 这里设置为 100 set print elements 0 # 没有限制 set print elements unlimited # 没有限制, 跟设置为 0 效果一样
这个选项是设置数组元素的最大显示个数的,如果你的数组太大了,那么就可以指定一个最大长度,当超过这个长度时,
GDB
就不再往下显示了,默认最多只显示 200 个元素。如果设置为 0,则表示不限制。 -
set print null-stop
set print null-stop on
如果打开了该选项,那么当显示字符串时,遇到结束符则停止显示,默认值为
off
。 -
set print pretty on
set print pretty on
如果打开了该选项,以更美观的格式(便于阅读)打印结构体变量的值,默认值为
off
。例如:
$1 = { next = 0x0, flags = { sweet = 1, sour = 1 }, meat = 0x54 "Pork" }
-
set print union
set print union on
设置显示结构体时,是否显式其中的
union
(联合体) 数据。 -
set print object
set print object on
在
C++
中,如果一个对象指针指向其派生类,如果打开这个选项,GDB
会自动按照虚方法调用的规则显示输出,如果关闭这个选项的话,GDB
就不管虚函数表了。
-
查看变量的类型
在
gdb
中,可以使用如下命令查看变量的类型:(gdb) whatis he type = struct child
-
详细的类型信息
如果想查看详细的类型信息:
(gdb) ptype he type = struct child { char name[10]; enum {boy, girl} gender; }
-
定义变量的文件
如果想查看定义该变量的所有文件:
(gdb) i variables he All variables matching regular expression "he": File variable.c: struct child he; Non-debugging symbols: 0x0000000000402030 she 0x00007ffff7dd3380 __check_rhosts_file
-
Linux 下 gdb 单步调试
-
GDB 单步调试汇编
-
gdb print 详解
https://blog.csdn.net/litanglian9839/article/details/84966813
-
gdb 打印技巧
-
gdb break 断点设置(一)
-
gdb调试(四)函数调用栈之 BackTraces