Linux工具 - GDB新手指南

前言

写这篇博客时候,看着手册,开着虚拟机…边尝试边总结。写了几章后发现,发现形式过于流水,于是再度整理了一下。

学习简单使用平时调试足矣。剩下的只需知道有这个功能,忘记了没关系,查查手册即可。

推荐文档

官方文档(生肉)

100个gdb小技巧(绝对实用)

Debugging with gdb(中文版,开头有惊喜)

使用

准备

以下均我以 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 强:

breakif 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

猜你喜欢

转载自blog.csdn.net/weixin_40774605/article/details/106188484