gdb
相信大家或多或少已经接触过 IDE 中集成的调试工具。今天介绍一款接近命令行、灵活强大的调试工具 gdb(GNU debugger), 它是由 GNU 提供的软件调试工具,提供了 TUI
界面,用好 gdb 可以极大提高我们定位问题的速度。这里推荐一个 GDB QUICK REFERENCE 方便查阅命令。
开启调试
这部分不详述,网上相关信息已经很多。
gdb attach pid
// start gdb
aarch64-linux-gnu-gdb
// load symbol
(gdb) file xxx.elf
// attach to the remote target
(gdb) target remote :portnum
// playground
...
栈信息
-
bt backtrace
bt , n 是一个正整数,表示只打印栈顶上算起 n 层的栈信息。也就是从最后的被调函数开始,从后往前打印 n 层。
bt <-n>,-n 是一个负整数,表示只打印栈底下算起 n 层的栈信息。也就是从最先的 caller 开始,从前往后打印 n 层。 -
f frame
f ,查看当前栈层的信息,可以用 frame 或 f 会打印出这些信息:栈的层编号,当前的函数名,函数参数值,函数所在文件及行号,函数执行到的语句。
#0 __test_MATH_NEON () at math/test_math_neon.c:76
76 {
- info f,这个命令会打印出更为详细的当前栈层的信息,只不过大多数都是运行时的内存地址。
比如:函数地址,调用函数的地址,被调用函数的地址,目前的函数是由什么样的程序语言写成的、函数参数地址及值、局部变量的地址等等。
(gdb) info f
Stack level 0, frame at 0x5cc8324ea0:
pc = 0x4500b0 in __test_MATH_NEON (math/test_math_neon.c:76); saved pc = 0x44f4dc
called by frame at 0x5cc8324ec0
source language c.
Arglist at 0x5cc8324ea0, args:
Locals at 0x5cc8324ea0, Previous frame's sp is 0x5cc8324ea0
- info args,打印出当前函数的参数名及其值。
- info locals,打印出当前函数中局部变量及其值。
- up 表示栈帧向调用栈的上面移动 n 层,可以不打 n,表示向上移动一层。
- down 表示栈帧向调用栈的下面移动 n 层,可以不打 n,表示向下移动一层。
上面的命令,都会打印出移动到的栈层的信息。如果不想让其打出这些信息。可以使用这三个命令:
select-frame 对应于 frame 命令。
up-silently 对应于 up 命令。
down-silently 对应于 down 命令。
源码显示
- list
一般是打印当前行的上 5 行和后 4 行,如果显示函数是是上 2 行后 8 行,默认总共 10 行。当然,你也可以定制显示的范围,使用下面命令可以设置一次显示源程序的行数。
// 查看当前的设置值
show listsize
// 设置新值
set listsize
// 已 offset 为中心,前后显示 5 行
list <+offset>
list <-offset>
源码搜索
- forward-search,向前面搜索。
- reverse-search,全部搜索。
- show directories,显示定义了的源文件搜索路径。
源码内存
-
info line xxx
查看源代码在内存中的地址。这个命令会打印出所指定的源码在运行时的内存地址。
info line后面可以跟 “行号”,“函数名”,“文件名:行号”,“文件名:函数名”。 -
disassemble funcname
查看源程序的当前执行时的机器码,这个命令会把目前内存中的指令dump出来。
运行时数据显示
- p <variable>, print
- p/<format> <variable>, i.e., p /x a, 16进制显示变量 a 的值
- p <file>::<variable>, 显示某个文件的某个变量
- p <function>::<variable>, 显示某个函数的某个变量
x 按十六进制格式显示变量。
d 按十进制格式显示变量。
u 按十六进制格式显示无符号整型。
o 按八进制格式显示变量。
t 按二进制格式显示变量。
a 按十六进制格式显示变量,如果对应了某个变量的话,同时会显示变量名。
c 按字符格式显示变量。
f 按浮点数格式显示变量。
查看数据类型
# can veiw function prototype
(gdb) whatis variable
(gdb) ptype foo
查看数组
int *array = (int *) malloc(len * sizeof (int));
可以用该命令显示出这个动态数组的取值:p *array@len
@
左边是内存起始地址,@
右边是想查看内存的长度。
查看内存
可使用 examine
命令(简写是 x)来查看内存地址中的值。x 命令的语法如下所示:
x/nfu address
n: number of memory block
f: format letter
u: size letter
上述命令会从指定的内存地址开始,读写 n 个 u 指定格式的数据,并使用 f 指定的格式显示出来。
- n 是一个正整数,表示显示的内存单元个数。
- f 表示显示的格式 format,参见上面。如果地址所指的是字符串,那么格式可以是 s,如果是指令地址,那么格式可以是 i。
- u 表示一个地址单位的长度。u 参数可以用下面的字符来代替,b 表示单字节,h 表示半字,即双字节,w 表示四字节,g(giant) 表示八字节。默认为 8 字节,即 g。
(gdb) x/i $pc
=> 0x45003c <add_float_neon1+20>: ldr q3, [x0, #32]
因此如果想按单字节的方式读取 buffer 地址开始的 8 个字节的数据,并按16进制显示,可以使用 x/8xb buffer
。
查看寄存器
- info registers 查看通用寄存器的情况。
- info all-registers 查看所有寄存器的情况。包括浮点寄存器
- info registers 查看所指定的寄存器组的情况。
(gdb) maint print reggroups
Group Type
general user
float user
system user
vector user
all user
save internal
restore internal
显示寄存器的值和变量有些差异,需要加上 $
符号。如 p $x0
。
断点
// break -> b
break file:line
break funcname
// 条件断点
b xxx if <condition>
// 查看断点
info b
// 禁用断点
disable num
enable num
disable // disable all brk
enable // enable all brk
// 删除断点
d num // delete brk num
d // delete all brk
// continue
c
// suspend
ctrl + c
// step to next
next
// step into
s
// step out
finish / fin
// run until
until / u
变量修改
修改被调试程序运行时的变量值,使用 print
或 set
命令即可完成。print x=4
或者 set x=4
。
在某些时候,很有可能你的变量和GDB中的参数冲突,如 width,set width 是 GDB 的命令,所以会出现 “Invalid syntax inexpression” 的错误,此时,你可以使用 set var
命令来告诉GDB width 是程序的变量名,如:set var width=10
。
执行流更改
跳转执行
jump <address>
注意,jump命令不会改变当前的程序栈中的内容,所以当从一个函数跳到另一个函数时,返回时进行弹栈操作时必然会发生错误,可能结果还非常奇怪,所以最好是同一个函数中进行跳转。
当然也可以使用 set $pc
来更改跳转执行的地址。如:set $pc = 0x485
call <...>
表达式中可以是函数,以此达到强制调用函数的目的。如果有返回值则显示函数的返回值。
print
后面可以跟表达式,也可以用来调用函数。
返回值修改
return
return exp
信号
signal
自动显示
-
display
display/i $pc
$pc
表示指令的地址,/i
则表示输出格式为机器指令码。
于是当程序停下后,就会出现源代码和机器指令码相对应的情形,这是一个很有意思的功能。
-
info display
查看display设置的自动显示的信息。GDB会打出一张表格,报告调试中设置了多少个自动显示设置,其中包括设置的编号,表达式,是否enable。 -
display 的控制命令
undisplay
delete display
disable display num
enable display num
显示选项
-
set print address
set print address on
show print address
打开地址输出,当程序显示函数信息时,GDB 会显出函数的参数地址。系统默认为打开的。 -
set print array
set print array on
show print array
打开数组显示,打开后当数组显示时,每个元素占一行,如果不打开的话,每个元素则以逗号分隔。这个选项默认是关闭的。 -
set print elements
show print elements
这个选项主要是设置数组的,如果数组太大,那么就可以指定一个来指定数据显示的最大长度,当到达这个长度时,GDB就不再往下显示了。如果设置为0,则表示不限制。默认是200。 -
set print null-stop
show print null-stop
如果打开了这个选项,那么当显示字符串时,遇到结束符则停止显示。默认为off。 -
set print pretty on
show print pretty on
如果打开print pretty这个选项,那么当GDB显示结构体时会比较漂亮。默认off。 -
set print sevenbit-strings
show print sevenbit-strings
设置字符显示,是否按“\nnn”的格式显示,如果打开,则字符串或字符数据按\nnn显示,如“\065”。默认 off。 -
set print union
show print union
设置显示结构体时,是否显式其内的联合体数据。默认 on。 -
set print object
show print object
在C++中,如果一个对象指针指向其派生类,如果打开这个选项,GDB会自动按照虚方法调用的规则显示输出,如果关闭这个选项的话,GDB就不管虚函数表了。这个选项默认是off。 -
set print static-members
show print static-members
这个选项表示,当显示一个C++对象中的内容时是否显示其中的静态数据成员。默认是on。 -
set print vtbl
show print vtbl
当此选项打开时,GDB将用比较规整的格式来显示虚函数表时。默认 off。
历史记录
当你用GDB的print查看程序运行时的数据时,你每一个print都会被GDB记录下来。GDB会以$1, $2, $3 …这样的方式为你每一个print命令编上号。于是,你可以使用这个编号访问以前的表达式,如$1。这个功能所带来的好处是,如果你先前输入了一个比较长的表达式,如果你还想查看这个表达式的值,你可以使用历史记录来访问,省去了重复输入。
GDB环境变量
可以在GDB的调试环境中定义自己的变量,用来保存一些调试程序中的运行数据。要定义一个GDB的变量很简单只需使用GDB的set命令,GDB的环境变量和UNIX一样,也是以$起头。如:set $foo = *object_ptr
使用环境变量时,GDB会在你第一次使用时创建这个变量,而在以后的使用中,则直接对其賦值。环境变量没有类型,你可以给环境变量定义任一的类型。包括结构体和数组。
show convenience
该命令查看当前所设置的所有的环境变量。
环境变量和程序变量的交互使用,将使得程序调试更为灵活便捷。例如:
set $i = 0
print bar[$i++]->contents
不必 print bar[0]->contents, printbar[1]->contents地输入命令了。
只用敲回车,重复执行上一条语句,完成逐个输出的功能。