文章目录
前言
写这篇博客时候,看着手册,开着虚拟机…边尝试边总结。写了几章后发现,发现形式过于流水,于是再度整理了一下。
学习简单使用平时调试足矣。剩下的只需知道有这个功能,忘记了没关系,查查手册即可。
推荐文档:
使用
准备
以下均我以 makebmp.c
demo 为例:
gcc -g -o makebmp makebmp.c
-g
选项的作用是:在可执行文件中加入源码信息。
比如:可执行文件中第几条机器指令对应源代码的第几行,但并不是把整个源文件都嵌入到可执行文件中,而是在调试时必须保证 gdb
能找到源文件。
- 得到可执行文件
makebmp
启动
接着使用以下指令调用 gdb
gdb makebmp
默认会打印以上信息,若觉碍眼,可使用 -q
让它 quiet
gdb -q makebmp
开始调试
进入 gdb
以后,我们需要使用:
start
该指令相当于
//main_linenumber 为主函数行号
//tb:设置临时断点
tb main_linenumber
//run 缩写:r
run
那么我们怎么知道我们的源码是第几行呢?
查看源码
list
//缩写:l
默认只显示10行代码
list
相关的操作还有:
// 指定行号,函数
list 1
list main
// 向前向后打印
list -
list +
// 指定打印范围
list 1,10
单步调试
next
//缩写
n
当执行到自定义函数 WriteBMP( )
时,若继续使用单步执行,则该函数将在下一步中被执行完。
若想进入该函数中调试,需使用:
step
//缩写
s
函数堆栈帧信息
我们可以使用以下指令打印函数堆栈帧信息
info frame
//缩写
i frame
该指令输出的是当前函数堆栈帧的地址,指令寄存器的值,局部变量地址及值等信息,可以对照当前寄存器的值和函数的汇编指令看一下。
遇到函数嵌套时,当程序暂停后,可以用以下指令选择函数堆栈帧:
frame n
其中 n
是层数,最内层的函数帧为 第0帧
。
进入函数中调试,若突然想退出怎么办?
//执行完函数
finish
//不继续往下执行,直接返回
return
//可使用该函数指定返回值
return expression
既然是调试,那肯定少不了断点
断点
最简单的方法是在文件行号上打断点
b linenumber
此外我们还可设置临时断点
执行过后该断点会被删除
tbreak
//缩写
tb
还可以设置条件断点,这貌似比 IDE
强:
break … if cond
//例如:
break 5 if i==100
当调试汇编程序,或者没有调试信息的程序时,经常需要在程序地址上打断点,方法为:
b *address
那么怎么获取程序入口地址呢?
获取程序入口地址
// 非gdb内
strip makebmp
//or
readelf -h makebmp
// 在gdb中也可查看
info files
//or
i files
没有图形化界面,如何查看我们设置的断点呢
info breakpoints
//简写
i b
如何删除断点呢?
// 删除所有断点
delete
// 删除指定断点
delete linenumber
不少 IDE
会有保存断点的功能; gdb
也有,不过需要我们自己做:
//保存已设置的断点
save breakpoints file-name-to-save
//下次调试时可使用以下命令导入断点
source file-name-to-save
断点介绍大致完成,运行过程中怎么查看变量呢?
- 打印
- 设置观察点
查看变量
打印字符串
// 打印ASCII字符串
x/s str
//根据宽字符的长度决定如何打印
//4字节
x/ws
//2字节
x/hs
打印数组
// 打印大数组中的内容
print array
//缩写
p array
如果要打印大数组的内容,缺省最多会显示 200
个元素
可通过以下指令设置最大限制数
set print elements number-of-elements
// 也可通过该指令设置为没有限制
set print elements 0
set print elements unlimited
打印数组中任意连续元素值
// 其中 index 是数组索引(从0开始计数), num 是连续多少个元素。
p array[index]@num
默认情况下是不打印索引下标,可通过以下指令开启
set print array-indexes on
打印函数局部变量的值
//bt是backtrace的缩写
bt ful
bt full n
//从内向外显示n个栈桢,及其局部变量
bt full -n
//从外向内显示n个栈桢,及其局部变量
//如果只是想打印当前函数局部变量的值
info locals
打印内存的值
gdb
中使用 x
命令来打印内存的值,格式为 x/nfu addr
。含义为以 f
格式打印从 addr
开始的 n
个长度单元为 u
的内存值。参数具体含义如下:
-
n:输出单元的个数。
-
f:是输出格式。
比如 x 是以16进制形式输出;
o 是以8进制形式输出等等…
-
u:标明一个单元的长度
- b 是一个 byte
- h 是两个 byte (halfword)
- w 是四个 byte (word)
- g 是八个 byte(giantword)。
若想连续观察某值,这样的方法很麻烦!
gdb
为我们提供了另外一种方式即观察点。
观察点
//设置完观察点后,当一个变量值发生变化时,程序会停下来
watch a
//缩写
wa a
//也可以
watch *(data type*)address
值得注意的是:
观察点可以通过软件或硬件的方式实现,取决于具体的系统。但是软件实现的观察点会导致程序运行很慢,使用时需注意。
若系统支持硬件观测的话,当设置观测点是会打印如下信息: Hardwarewatchpoint num: expr
如何查看所设置的观察点呢?
info watchpoints
除了可以设置值被改写的观察点,我们还可以设置 读观察点
:
rwatch
//缩写
rw
需要注意的是 rwatch
命令只对硬件观察点才生效
//设置读写观察点
awatch
//缩写
aw
当发生读取变量或改变变量值的行为时,程序就会暂停住
gdb还有一个强大的功能 - 汇编
汇编
查看汇编代码
例如,我们可以通过以下指令自动反汇编出需要执行的代码:
set disassemble-next-line on
start
还可以将其与源码对于起来
//disas是disassemble命令缩写
disas /m fun
查看寄存器
在调试过程中,如果想查看寄存器的值,可以使用
//i是info命令缩写
i registers
//以上输出不包括浮点寄存器和向量寄存器的内容
//以下指令可输出所有寄存器的内容
i all-registers
//打印单个寄存器的值
i registers regname
//or
p $regname
修改变量
在调试过程中,能否干涉程序执行,例如临时修改某些值呢?
答案是肯定的,常见的有:
set var variable=expr
//寄存器也可以作为变量值去修改,例如:
set var $eax = 8
//既然如果我们可以修改cp寄存器修改下一帧执行的指令,解锁了奇怪的知识
gdb无法定位到源文件
当我们的程序用到其他源文件时,可能存在 gdb
无法定位到源文件的情况
这时候我们就要用 directory
设置查找源文件的路径
directory ../xxxxx
//缩写
dir ../xxx
如果希望在 gdb
启动时,加载 code
的位置,避免每次在 gdb
中再次输入命令,可以
使用 gdb
的 -d
参数
gdb -q a.out -d /search/code/some
图形化
精彩的总是留到最后
图形化启动gdb
gdb -tui projectname
也可以通过快捷键
Ctrl + X + A
来进行图形化与字符界面的切换
在图形化界面中,可以通过以下指令显示汇编代码窗口
layout split
显示寄存器窗口
//显示通用寄存器
ayout regs
//查看浮点寄存器
tui reg float
//查看系统寄存器
tui reg system
//换回显示通用寄存器内容
tui reg general
快捷键
//显示两个窗口
Ctrl + X + 2
//显示一个窗口
Ctrl + X + 1