作者: 李云鹏(qqliyunpeng@sina.cn)
版本号: 20170424
更新时间: <2020-08-12>
原创时间: <2017-04-24>
版权: 本文采用以下协议进行授权,自由转载 - 非商用 - 非衍生 - 保持署名 | Creative Commons BY-NC-ND 3.0,转载请注明作者及出处.
更新内容: 2019-01-03, 主要是完善
更新内容: 2020-08-12, 完善 gdb -tui 时候的操作,并给出一些问题的解决办法
1. 简介
GDB,是GNU工程调试器,允许你去查看在程序运行的时候里边的东西,或者查看当程序crash的那一刻,其他程序正在做啥。为了获取行为中的bug,GDB可以做的主要有4件事:
- 开始你的程序,指定可能影响程序的任意行为
- 让你的程序停止在指定的条件下
- 检查你的程序停止时发生了什么
- 改变程序中的一些东西,然后可以查看改变的东西对一个bug和其他的影响
2. 开启图形化界面
2.1 简介
想要对应用程序用gdb调试需要在编译的时候添加-g选项
然后就可以将输出的文件进行gdb调试了,假如输出的文件是a.out。则
gdb -tui -q a.out 或者是 gdb a.out然后按ctrl+x+a 就如如下的界面
【1】-tui 选项是这个界面的关键,-q表示的是不打印(gdb)之前的好多声明
【2】此界面有好处也有坏处,好处是代码实时的显示在上边,缺点也很明显,供操作显示的界面小了,如果你想打印出一个函数的反汇编,那他的劣势就很明显了
2.2 更多操作
2.2.1 更多窗口
GDB TUI 模式下共有四个常用的窗口
- ( cmd ) command 命令窗口,可以输入调试命令
- ( src ) source 源码窗口,显示当前行,断点等信息
- ( asm ) assembly 汇编代码窗口
- ( reg ) register 寄存器窗口
在命令窗口中输入 layout + 窗口 类型来打开窗口,如
> layout asm --- > 汇编窗口打开
2.2.3 调整窗口大小
使用命令 winheight
使用格式: winheight <win_name> [+ | -] <#lines>
例子:将代码窗口的高度扩大5行代码
> winheight src + 5
2.2.4 命令行中的历史命令
在命令行窗口中也是有历史输入的命令的,使用方法是 ctrl + p,在历史命令中下一个是 ctrl + n
2.3 问题解决
当调试过程中有时候 src 界面中显示的混乱了,有了一些割裂的感觉,此时我们希望src界面刷新下,可以使用 ctrl + x + a 退出 tui 模式,然后重新键入 ctrl + x + a 调用 tui 模式,问题得到解决。
3. 基本操作(红色部分是简写)
3.1 基本命令
list [linenum/function] -- 显示linenum周围的源代码或者是显示函数名function的函数的源代码
”list -” -- 显示当前行之前的固定行,固定行的重新设定可以用如下命令 set listsize [count],如果只是想看一下用show listsize就可以
"list +" -- 显示后边
list [first], [last] -- 显示从first到last行之间的代码
list ,[last] -- 从当前行到last之间的代码
当单步运行后使用这个命令打印运行行周围的代码
file -- 加载某个可执行文件,当进入gdb时,用这个命令来加载要调试的程序
start -- 开始调试程序,程序指针会指向main中的第一行
run -- 如果是这个,相对于start来说会找断点,停到断点出,如果没有断点,则执行完
next -- 单步运行,如果遇到函数,不会进入函数执行
step -- 单步运行,如果有函数,进函数继续单步运行。如果后边跟数字,表示执行多步。
continue -- 继续执行,直到遇到断点
finish -- 执行到返回
3.2 断点相关操作:
break <function> 在进入指定函数时停住
break <linenum> 在指定行号停住。
break +/-offset 在当前行号的前面或后面的offset行停住。offiset为自然数。
break filename:linenum 在源文件filename的linenum行处停住。
break ... if <condition> ...可以是上述的参数,condition表示条件,在条件成立时停住。比如在循环语境中,可以设置break if i=100,表示当i为100时停住程序。
delete 删除所有断点
delete breakpoint [n] 删除某个断点
disable breakpoint [n] 禁用某个断点
enable breakpoint [n] 使能某个断点
clear 清除所有断点
3.3 显示相关
自动显示观察的变量: -- 就是在每运行一次,都会有显示
display {a,b...}
删除显示的变量
undisplay {a,b...}
delete display {a,b....}
不删除,只是使能和屏蔽
enable display
disable display
dispay部分的格式控制同print中的格式控制一样
还有一种需求是,当每个变量只在他变化的时候显示:
watch{a,b...}
info 命令: -- 列出相关的所有信息
info break :查看一共多少个break
info display:查看一共有多少个display
info locals: 查看当前程序栈的局部变量
info args: 查看当前程序栈的参数,即如果是一个函数,便是函数所有的传入的参数
info registers xx: 查看寄存器xx
print 命令: -- 打印
打印出数组:
①动态数组 -- 比如说当一个数组传到函数中,在函数中的入参是没有数据边界的指针
p *[数组首地址]@[数组中元素的长度]
②静态数组
p [数组首地址]
输出时的格式控制
x(hex) 按十六进制格式显示变量。
d(decimal) 按十进制格式显示变量。
u(unsigned decimal) 按十进制格式显示无符号整型。
o(octal) 按八进制格式显示变量。
t(binary) 按二进制格式显示变量。
a(address) 按十六进制格式显示变量。
c(char) 按字符格式显示变量。
f(float) 按浮点数格式显示变量。
使用时是这个样子:p/a [你要打印的变量]
backtrace 命令: -- 打印堆栈(函数调用栈的所有信息)
简写是 bt
当罗列出所有的堆栈后,想要跳转到相应的堆栈中去执行,需要用 f [bt命令罗列的数字]
3.4 显示源代码的内存
example <address> -- 查看指定地址的内存地址的值,具体格式是 x/[n][f][u]
n 表示需要显示的内存单元个数,内存单元指的是 u 定义的大小
f 表示需要显示的格式
x(hex) 按十六进制格式显示变量。
d(decimal) 按十进制格式显示变量。
u(unsigned decimal) 按十进制格式显示无符号整型。
o(octal) 按八进制格式显示变量。
t(binary) 按二进制格式显示变量。
a(address) 按十六进制格式显示变量。
c(char) 按字符格式显示变量。
f(float) 按浮点数格式显示变量
u 表示每个单元的大小,按字节数来计算,默认是4字节,可选值:b: 1 byte h: 2 bytes w: 4 bytes g: 8 bytes
如果想要查看某个函数在源文件中的 行号 和内存中的 起始地址 和 结束地址,info line 函数名字/*内存地址
你也可以查看源代码的当前执行的机器码,用disassemble命令会把目前内存中的指令dump出来,一般他后边跟的是行号,函数名。
3.5 线程相关命令
info threads -- 打印出现在所以线程
thread [上边罗列出来的线程的号] -- 跳转到相应的线程中去执行
当然,如果在线程中设置断点的话,当程序运行起来后会相应的跳转到相应的位置停下来,相应的线程也会跳过去,然后单步执行的时候会在相应的线程中继续执行。
4 问答
①当单步运行时,我运行到了函数的结尾 "}",函数有返回值,我如何查看此时函数的返回值?
(gdb) info registers eax
上边的命令的意思是打印 eax 寄存器,x86计算机会将函数返回值放在eax寄存器中。