GDB 的安装与配置

安装 gdb

如果系统已经预装好了就不要再安装了

sudo apt install gdb

安装 gdb-multiarch

sudo apt install gdb-multiarch

安装 GDB 插件

这些类似的插件其实大同小异,安装插件只是因为原生的 GDB 没有高亮、观察寄存器和堆栈信息不方便

注意:这几个插件并不兼容,但可以通过更改 .gdbinit 文件来切换使用,当你要使用某一个插件的时候,将 .gdbinit 文件中其他两个插件注释掉

在后面会介绍怎么使用脚本来进行切换 gdb 插件,就不需要手动去注释了

我这里统一将 GDB 插件安装在 /opt/gdb_plugins/ 目录下


如果是 Ubuntu 16.04 这样的老版本安装 GDB 插件,请参照《Ubuntu16.04虚拟机环境搭建》一文的《GDB 配置》部分


安装 peda

安装方法:

sudo git clone https://github.com/longld/peda.git /opt/gdb_plugins/peda
sudo echo "source /opt/gdb_plugins/peda/peda.py" >> ~/.gdbinit

安装 pwndbg

pwndbgPwngdb 需要一起搭配使用

以下方法默认安装的是当前最新版 pwndbg,如果想要使用旧版本,请到官网手动下载指定版本的源代码Releases · pwndbg/pwndbg

安装方法:

sudo git clone https://github.com/pwndbg/pwndbg /opt/gdb_plugins/pwndbg
cd /opt/gdb_plugins/pwndbg
sudo ./setup.sh

在安装 pwndbg 2024.02.14 这种较新版本时,需要安装 python 3.12 环境以及虚拟环境,否则可能会报如下错误:

Creating virtualenv in path: ./.venv
./setup.sh: line 199: /usr/bin/python3.12: No such file or directory
Creating virtualenv in path: ./.venv
The virtual environment was not created successfully because ensurepip is not available.  On Debian/Ubuntu systems, you need to install the python3-venv package using the following command.

apt install python3.12-venv

You may need to use sudo with that command.  After installing the python3-venv
package, recreate your virtual environment.

Failing command: /opt/gdb_plugins/pwndbg/.venv/bin/python3.12

安装所需环境后,再次执行 sudo ./setup.sh 即可解决:

sudo apt install python3.12 python3.12-venv

另外,现在新版本的 pwndbg 支持以 deb 的形式安装使用:

wget https://github.com/pwndbg/pwndbg/releases/download/2024.02.14/pwndbg_2024.02.14_amd64.deb
sudo dkpg -i pwndbg_2024.02.14_amd64.deb

安装后,可以直接在终端启动:

pwndbg

GDB的基础和使用5.png

deb 安装的 pwndbg 不再是以 GDB 插件的形式存在,而是一个集成了 GDB 的独立工具,其 GDB 版本为 GNU gdb (GDB) 13.2

GDB的基础和使用6.png


安装 Pwngdb

安装方法:

sudo git clone https://github.com/scwuaptx/Pwngdb.git /opt/gdb_plugins/Pwngdb
cd /opt/gdb_plugins/Pwngdb
sudo cp .gdbinit ~/
sudo vim ~/.gdbinit

~/.gdbinit 的第二行插入:(记得插入在 source /opt/gdb_plugins/Pwngdb/pwngdb.py 这一句的前面)

source /opt/gdb_plugins/pwndbg/gdbinit.py

如果 pwndbg 的路径不是 /opt/gdb_plugins/pwndbg,请按照自己的实际路径修改


配置 pwndbg 分屏调试

由于 pwndbg 输出的信息较多,经常在一页上看不全,需要上下翻找,眼花缭乱

我们可以设置 pwndbg 分屏调试,一边屏幕输入命令,一边屏幕查看输出信息,提高效率

方法一:修改 gdbinit

配置很简单,先后打开两个终端

假设先打开的一个终端用于开启 gdb 调试并输入调试命令,后打开的一个终端用于输出调试信息

在两个终端分别输入 tty,先打开的终端为 /dev/pts/19,后打开的为 /dev/pts/20 (以自己的实际输出信息为主)

GDB的基础和使用2.png

修改 ~/.gdbinit 中的内容:

sudo gedit ~/.gdbinit

~/.gdbinit 末尾加入一句:

set context-output xxx

# 这里的 xxx 就是用于输出调试信息的分屏,我这里是:/dev/pts/20

注意:如果你开了多个终端,就设置为实际想要用于输出调试信息的分屏

保存退出

在先打开的终端中开启 gdb 并输入调试命令,在后打开的终端中即可输出调试信息

GDB的基础和使用3.png

然后我们将屏幕调整一下:

GDB的基础和使用4.png

设置分屏后,如果只开启一个终端,使用 gdb 可能会遇到如下报错:

Exception occurred: context: [Errno 13] 权限不够: '/dev/pts/20' (<class 'PermissionError'>)  
For more info invoke `set exception-verbose on` and rerun the command  
or debug it by yourself with `set exception-debugger on`

再开启一个终端即可解决 (新开启的终端需为 /dev/pts/20)


方法二:gdb 临时设置

由于有时候新开启的终端并不是我们在 ~/.gdbinit 中设置的那个终端,频繁更改 ~/.gdbinit 中的内容未免太过麻烦

所以,我们也可以不在 ~/.gdbinit 中设置,而是先在一个终端中启动 gdb 调试,然后再另开一个新的终端,使用 tty 查看新的终端的分屏信息:

tty

# 假设输出为:/dev/pts/18

然后在 gdb 中直接设置输出调试信息的分屏: (以自己上一步实际的分屏信息为主)

(gdb) set context-output /dev/pts/18

这样就可以避免新打开的终端与我们在 ~/.gdbinit 中设置的终端不一致的问题


安装 gef

安装方法:

sudo git clone https://github.com/hugsy/gef /opt/gdb_plugins/gef
sudo echo "source /opt/gdb_plugins/gef/gef.py" >> ~/.gdbinit

验证安装

  • 自用的 ~/.gdbinit 文件内容示例:
# source /opt/gdb_plugins/peda/peda.py

source /opt/gdb_plugins/pwndbg/gdbinit.py

# source /opt/gdb_plugins/gef/gef.py

source /opt/gdb_plugins/Pwngdb/pwngdb.py
source /opt/gdb_plugins/Pwngdb/angelheap/gdbinit.py

define hook-run
python
import angelheap
angelheap.init_angelheap()
end
end

终端输入 gdb

  1. 当启用 peda 时,会出现:gdb-peda$
  2. 当启用 pwndbg 时,会出现:pwndbg>
  3. 当启用 gef 时,会出现:gef➤

如果验证出现上述输出内容,并且在输出内容之前没有任何报错提示,则说明安装成功

如果没有安装成功,输入 gdb 会显示默认的:(gdb)


脚本自动切换 GDB 插件

这个脚本可以自动定位 ~/.gdbinit 文件中 "# this place is controled by user's shell" 这一句所在的位置,并随着用户对于插件的选择,自动更改 ~/.gdbinit 文件里的内容,更改的内容会放在 "# this place is controled by user's shell" 这一句的上一行

如果你安装 gdb 插件是按照我的教程来的,那下面的东西你可以不需要做任何改动,否则可能会有所修改

  1. 这一步非常重要!!!

首先,你需要自己手动在 ~/.gdbinit 文件的第二行加上 "# this place is controled by user's shell" 这一句

然后把原本 ~/.gdbinit 文件中的内容全部放到 "# this place is controled by user's shell" 这一句后面

记得把插件全部注释掉

修改好的 ~/.gdbinit 文件类似于我这样:

(第一行空出来,什么都不写)
# this place is controled by user's shell

# source /opt/gdb_plugins/peda/peda.py

# source /opt/gdb_plugins/pwndbg/gdbinit.py

# source /opt/gdb_plugins/gef/gef.py

source /opt/gdb_plugins/Pwngdb/pwngdb.py
source /opt/gdb_plugins/Pwngdb/angelheap/gdbinit.py

define hook-run
python
import angelheap
angelheap.init_angelheap()
end
end
  1. 新建一个名为 gdb.sh 的文件:
sudo vim gdb.sh

并将下面的脚本内容添加进去

可能改动的地方都做了标注,如果你的插件是按照我的教程来安装的,那就直接用就行了

#!/bin/bash
function Mode_change {
    name=$1
    gdbinitfile=~/.gdbinit    # 这个路径按照你的实际情况修改

    peda="source /opt/gdb_plugins/peda/peda.py"   # 这个路径按照你的实际情况修改
    gef="source /opt/gdb_plugins/gef/gef.py"   # 这个路径按照你的实际情况修改
    pwndbg="source /opt/gdb_plugins/pwndbg/gdbinit.py"   # 这个路径按照你的实际情况修改

    sign=$(cat $gdbinitfile | grep -n "# this place is controled by user's shell")    
    # 此处上面的查找内容要和你自己的保持一致

    pattern=":# this place is controled by user's shell"
    number=${sign%$pattern}
    location=$[number-1]

    parameter_add=${location}i
    parameter_del=${location}d

    message="TEST"

    if [ $name -eq "1" ];then
        sed -i "$parameter_del" $gdbinitfile
        sed -i "$parameter_add $pwndbg" $gdbinitfile
        echo -e "Please enjoy the pwndbg!\n"

    elif [ $name -eq "2" ];then
        sed -i "$parameter_del" $gdbinitfile
        sed -i "$parameter_add $peda" $gdbinitfile
        echo -e "Please enjoy the peda!\n"

    else
        sed -i "$parameter_del" $gdbinitfile
        sed -i "$parameter_add $gef" $gdbinitfile
        echo -e "Please enjoy the gef!\n"

    fi
}

echo -e "Please choose one mode of GDB?\n1.pwndbg    2.peda    3.gef"

read -p "Input your choice:" num

if [ $num -eq "1" ];then
    Mode_change $num
elif [ $num -eq "2" ];then
    Mode_change $num
elif [ $num -eq "3" ];then
    Mode_change $num
else
    echo -e "Error!\nPleasse input right number!"
fi

gdb $1 $2 $3 $4 $5 $6 $7 $8 $9
  1. 把 gdb.sh 文件移到 /usr/bin/ 目录下,类似于 Windows 的环境变量,在这个目录下可以通过 cmd 直接调用:
sudo mv ./gdb.sh /usr/bin/
  1. 给 gdb.sh 文件增加执行权限:
cd /usr/bin/
sudo chmod a+x gdb.sh
  1. 以后再打开 gdb 的时候,直接输入 gdb.sh 即可:

GDB的基础和使用1.png

我的脚本也是根据 GDB插件控制——切换pwndbg,peda,gef - 简书 (jianshu.com) 修改的

所以,如果你是按照我的教程来安装的,那就没什么问题
但如果你不是,那可能需要对脚本做一些修改,可以自己去参考一下上面的简书链接


GDB 的使用方法

首先要保证使用 gcc 编译时加上参数 -g 生成调试信息

一些可缩写的等价指令:

操作完整指令简洁指令
下断点breakb
下临时断点tbreaktb
为已设断点添加条件conditioncond
删除断点deteled
运行程序,当遇到断点后,程序会在断点处停止运行,等待用户输入下一步的命令runr
单步执行,跳过子函数nextn
单步执行,进入子函数steps
直接执行到下一断点或程序结束continuec
查看信息infoi
列出程序源码,一次可列出 10 行listl
打印变量的值printp
显示当前函数调用栈的完整信息,包括调用函数和它们的参数backtracebt
查看栈帧中某一帧的信息framef
设置监视点watchwat
设置要自动显示的变量、表达式或函数的值,可以在调试过程中持续监视特定变量的值displaydisp
运行程序,直到当前函数完成返回,并打印函数返回时的堆栈地址和返回值及参数值等信息finishfi
执行程序,直到达到指定行号,可用于退出循环体,而不必逐行执行untilu
退出调试quitq
反汇编disassembledisass

其他指令:

操作完整指令
启动程序并在 main() 函数处停止(等价于先 b main 再 r)start
清除断点,与 delete 类似,但只需要行号,无需断点编号clear
禁用断点disable
启用断点enable
终止调试kill
将当前程序执行流跳转到指定行或地址jump
查看当前栈帧上面的栈帧up
查看当前栈帧下面的栈帧down
查看当前目录pwd
修改数值set

gdb 中什么都不输入,直接回车,表示:重复上一步的指令


启动 GDB 调试

启动 GDB 可添加的参数:(可选)
--args :指定启动参数
--quiet :不打印 gdb 版本信息
--directory=DIR:指定源码的搜索路径

示例:gdb --args --quiet --directory=DIR ./testApp a b c


一般程序调试与附加调试

  • 调试可执行文件
gdb 文件名
  • 调试正在运行的进程(附加调试)
gdb attach 进程pid

其他指令:

(gdb) gdb 文件名 进程pid
(gdb) gdb -p 进程pid

也可以先进入 GDB,再调试进程:

gdb
(gdb) attach 进程pid

快速获取进程号 pid:

pidof 文件名
  • 通过 Pwntools 附加 GDB 调试:
io = process("程序路径")
gdb.attach(io)
pause()

core 文件调试

gdb 出现崩溃的文件名 core文件名

程序在运行之后发生了错误,会生成一个 core 文件,通过调试 core 文件,可以分析程序崩溃的原因

其中,core 文件名大多数是 core.进程pid 的形式

  • 设置 core 文件的大小,直接在命令终端输入命令即可,大小可作为开关使用

Linux 系统默认 core 文件的大小限制为 0,即产生 segmentation-fault 段错误时不会生成 core 文件

ulimit -c 0          # 将 core 文件大小设置为 0,此时将不生成 core 文件
ulimit -c 2          # 将 core 文件大小设置为 2 KB,自动生成 core 文件,文件大小达到 2 KB 时发生截断
ulimit -c unlimited  # 将 core 文件大小设置为无上限,自动生成 core 文件,文件大小不添加特殊限制
  • 设置 core 文件生成目录、命名规则

修改 /proc/sys/kernel/core_pattern 文件内容可以设置 core 文件名包含的信息内容和目录(目录必须存在),注意使用 echosysctl 命令,vim 可能不能成功编辑内容

echo "/home/core/core_%p_%t" > core_pattern   # 将 core 文件统一生成到 /home/core 目录下,产生的文件名为 core_进程pid_时间

sysctl -w "kernel.core_pattern=core_%p_%t" >/dev/null   # 文件生成到默认目录(进程目录),文件名格式为 core_进程pid_时间

信息内容参数包括:

参数含义
%p进程 pid
%u当前 uid
%g当前 gid
%s导致产生 core 的信号
%tcore 文件生成时的 unix 时间
%h主机名
%e命令名
  • 设置文件名中以进程 PID 作为扩展名

修改 /proc/sys/kernel/core_uses_pid 文件,文件内容为 1,添加进程 PID 作为文件扩展,为 0 不添加

echo "1" > /proc/sys/kernel/core_uses_pid  # 以进程 PID 作为 core 文件扩展名,生成的文件名称类似于 core.13125

远程 GDB 调试

  • 在被调试主机上,启动 gdbserver:
gdbserver IP地址:端口号 文件名 传递参数(可选)

# 远程调试正在运行的进程
gdbserver IP地址:端口号 --attach 进程pid

# 利用 ps | grep 进程名 | awk '{print $1}' 快速获取进程 pid
gdbserver IP地址:端口号 --attach `ps | grep 进程名 | awk '{print $1}'`

一般可以使用 127.0.0.1:端口号localhost:端口号,也可以省略 IP 地址直接使用 :端口号

端口号可以任意指定,一般选择大于 1024 的端口号(尽量避免端口号冲突)

  • 在调试主机上,通过 gdb 连接:
gdb 文件名
(gdb) target remote IP地址:端口号
  • 如果无法连接,可能是被调试主机的防火墙问题

查看 gdbserver 端口是否被防火墙过滤(在调试主机上运行,端口号为 gdbserver 对应端口号):

nmap -p 端口号 IP地址

如果显示端口状态为 filtered 说明被过滤

Linux 关闭 gdbserver 对应端口的防火墙(在被调试主机上运行,端口号为 gdbserver 对应端口号):

iptables -A INPUT -p tcp --dport 端口号 -j ACCEPT

或者在本地的情况下,直接关闭防火墙也可以(真实环境下不建议)


fork 多进程调试

查看 GDB 当前设置的多进程调试状态:

(gdb) show follow-fork-mode

(gdb) show detach-on-fork

设置 fork 子进程调试的参数:

(gdb) set follow-fork-mode parent|child
# parent: fork 之后继续调试父进程,子进程不受影响
# child: fork 之后调试子进程,父进程不受影响

(gdb) set detach-on-fork on|off
# on: 只有当前被调试的进程能够执行
# off: gdb 将控制父进程和子进程

参数的详细说明见下表:

follow-fork-mode 参数detach-on-fork 参数含义
parenton只调试父进程(GDB 模式)
childon只调试子进程
parentoff同时调试两个进程, GDB 调试父进程,子进程阻塞在 fork 处
childoff同时调试两个进程, GDB 调试子进程,父进程阻塞在 fork 处

查看正在调试的进程:

(gdb) info inferior

切换调试的进程,假设 info inferior 获取到的进程编号为 Num_id

(gdb) inferior Num_id

pthread 多线程调试

查看 GDB 当前设置的多线程调试状态:

(gdb) show follow-fork-mode

(gdb) show detach-on-fork

设置 fork 子进程调试的参数:

(gdb) set scheduler-locking on|off|on step
# on: 只有当前被调试的线程能够执行
# off: gdb 将控制父线程和子线程
# on step:在单步执行的时候,除了next过一个函数的情况以外,只有当前线程会执行

查看正在调试的线程:

(gdb) info thread

切换调试的线程,假设 info threads 获取到的线程编号为 Num_id

(gdb) thread Num_id

让一个或者多个线程执行 GDB 命令 command

(gdb) thread apply Num_id1 Num_id2 ... command

让所有被调试线程执行GDB命令 command

(gdb) thread apply all command

在所有线程中相应的行上设置断点:

(gdb) break thread_test.c:123 thread all

断点

下断点是调试程序时的重要手段,能够帮助定位和修复程序中的错误和问题。它允许开发者在程序执行期间以精细的粒度检查程序的运行状态,从而更轻松地进行调试

断点除 break 外,还有 tbreak,简写为:tb
tbreak 用于添加一个临时断点,断点一旦被触发就自动删除,使用方法同 break


下断点

gdb 下断点的指令为:break xxx,也可以简写为:b xxx

下断点的位置都是在将要执行这一句但还未执行的时候

  1. 直接断在 main() 函数的入口处,因为 main 是一个全局的符号,也可以断在其他函数的入口处,例如:fun_name()
(gdb) break main
(gdb) break fun_name
  1. 在源代码 test.c 的第 line 行下断点
(gdb) break /绝对路径/test.c:line
(gdb) break 'test.c':line
(gdb) break line
  1. 根据运行时的地址设置断点,例如:0xDEADBEEF,地址前需要加上 *
(gdb) break *0xDEADBEEF
  1. 设置条件断点,当 a > b 时设置函数 fun_name() 断点 (条件断点只支持简单的数据判断)
(gdb) break fun_name if a>b

为已设断点添加条件,假设已存在的断点编号为 index:

(gdb) condition index a>b
(gdb) cond index a>b

break if 类似,只是 condition 只能用在已存在的断点上

  1. 在当前程序暂停位置的前/后 offset 行处下断点
(gdb) break -/+ offest

删除断点

删除断点的指令有 detele xxxclear xxx 两种:

delete 是全局的,不受栈帧的影响;
delete 命令可以删除所有断点,包括观察点和捕获点等

clear 命令受到当前栈帧的制约,删除的是将要执行的下一处指令的断点;
clear 命令不能删除观察点和捕获点

  1. 删除所有断点
(gdb) delete
  1. 删除编号为 index 的断点
(gdb) delete index
  1. 删除编号为 index1 - index2 的断点(包括 index2)
(gdb) delete index1-index2
  1. 删除编号为 index1 - index2 的断点(包括 index2)和编号为 index3 - index4 的断点(包括 index4)
(gdb) delete index1-index2 index3-index4
  1. 删除源代码第 line 行的断点,无需断点编号
(gdb) clear line
  1. 删除 fun_name() 函数的断点(只会删除函数入口处的断点,即:b fun_name 所创建的断点,函数内部断点不会删除)
(gdb) clear fun_name

这会删除所有的 fun_name() 函数断点,如果有多个同名函数断点,这些同名函数断点都会被删除


禁用和启用断点

断点的 Enb 参数有 y 和 n 两种,y 表示断点启用,n 表示断点禁用

  1. 禁用或启用编号为 index 的断点
(gdb) disable index
(gdb) enable index
  1. 禁用或启用编号为 index1 - index2 的断点(包括 index2)
(gdb) disable index1-index2
(gdb) enable index1-index2
  1. 禁用或启用编号为 index1 - index2 的断点(包括 index2)和编号为 index3 - index4 的断点(包括 index4)
(gdb) disable index1-index2 index3-index4
(gdb) enable index1-index2 index3-index4

运行程序

控制程序执行

以下指令主要用于控制程序执行,不需要参数

操作完整指令简洁指令
运行程序,当遇到断点后,程序会在断点处停止运行,等待用户输入下一步的命令runr
单步执行,跳过子函数nextn
单步执行,进入子函数steps
直接执行到下一断点或程序结束continuec
执行程序,直到达到指定行号,可用于退出循环体,而不必逐行执行untilu
退出调试quitq

另外,还有 nisi,也是单步执行的命令,用法与 n 和 s 相同,只不过 ni 和 si 的单步执行是针对汇编代码的

注意:until 指令可以设置参数,让程序运行至源代码的某行,可以不仅仅用来跳出循环

  1. 运行程序至源代码第 line 行
(gdb) until line
  1. 终止调试的程序
(gdb) kill

跳转指令

jump:将当前程序执行流跳转到指定行或地址,简写为 j

jump 命令有两点需要注意的:

  1. 中间跳过的代码是不会执行的,例如变量的初始化等,因此很可能会导致程序崩溃或出现其它 Bug
  2. 如果 jump 跳转到的位置后续没有断点,那么 gdb 会直接执行自跳转处开始的后续代码
  1. 跳转到源代码第 line 行的位置
(gdb) jump line
  1. 跳转到距离当前代码下 offest 行的位置
(gdb) jump +offest
  1. 跳转到某个地址,例如:0xDEADBEEF,地址前需要加上 *
(gdb) jump *0xDEADBEEF

退出函数

实际调试时,在某个函数中调试一段时间后,可能不需要再一步步执行到函数返回处,希望直接执行完当前函数,这时可以使用 finish 命令

return 和 finish 都是退出函数,但也有差别:

  • return 命令:立即退出当前函数,剩下的代码不会执行了,return 还可以指定函数的返回值
  • finish 命令:会继续执行完该函数剩余代码再正常退出

查看程序相关信息

查看程序状态

gdb 查看程序状态的指令为:info xxx,也可以简写为:i xxx

info 命令用于检索有关程序状态的信息,例如变量、函数、堆栈、线程等

  1. 查看断点信息
(gdb) info break
(gdb) info b
  1. 查看当前函数中的本地变量
(gdb) info locals
  1. 查看当前函数的参数
(gdb) info args
  1. 查看当前设置的监视点列表(用于监视变量的值变化)
(gdb) info watch
  1. 查看当前已设置的 display
(gdb) info display
  1. 查看程序中的线程列表以及它们的状态
(gdb) info threads
(gdb) info th
  1. 查看函数调用栈帧的基本信息,包括堆栈帧的数量、堆栈帧的地址范围和其他有关堆栈帧的信息
(gdb) info stack

效果与 backtrace 相同:

(gdb) backtrace
(gdb) bt

其中,#0 项是当前执行的函数(栈帧)

  1. 查看当前函数调用的栈帧信息
(gdb) info frame
(gdb) info f

查看栈帧第 n 帧的信息:

(gdb) frame n
(gdb) f n
(gdb) info frame n

查看地址为 address 的桢的相关信息:

(gdb) frame address
(gdb) info frame address

查看当前栈帧上/下面第 n 桢的信息:

(gdb) up n
(gdb) down n
  1. 查看所有寄存器的当前值
(gdb) info registers
(gdb) info r

单独查看某个寄存器的值,例如:RAX

(gdb) info r rax
  1. 查看已加载的共享库信息
(gdb) info sharedlibrary
(gdb) info sh
  1. 查看有关目标程序和调试器的信息
(gdb) info target
(gdb) info tar
  1. 查看可执行文件的所有函数名称
(gdb) info functions
(gdb) info fun

只显示函数名带有 abcd 的函数名称:

(gdb) info functions abcd*
(gdb) info fun abcd*
  1. 查看程序是否运行
(gdb) info program
  1. 查看当前目录
(gdb) pwd
(pwndbg) !pwd

查看变量的值

gdb 查看变量的值的指令为:print xxx,也可以简写为:p xxx

print 命令可允许自定义输出格式:

(gdb) print/参数 变量名
参数功 能
/x以十六进制的形式打印出整数
/d以有符号、十进制的形式打印出整数
/u以无符号、十进制的形式打印出整数
/o以八进制的形式打印出整数
/t以二进制的形式打印出整数
/f以浮点数的形式打印变量或表达式的值
/c以字符形式打印变量或表达式的值
  1. 查看变量、数组、结构体成员或任何合法的表达式的值

查看变量 variable 的值:

(gdb) print variable

查看数组元素:

(gdb) print array[3]

查看结构体成员:

(gdb) print struct.member

查看合法表达式的值:

(gdb) print variable + 5
  1. 查看 array[] 数组中,自第 m 个元素起总共 n 个数组元素的值
int array[5] = {1,2,3,4};
(gdb) print array[m-1]@n

当 m = 1,n = 2 时,输出:$1 = {1, 2}

  1. 当程序中包含多个作用域不同但名称相同的变量时(全局变量、局部变量),可以借助 :: 运算符明确指定要查看的目标变量

例如源文件 main.c :

1. #include <stdio.h>
2. 
3. int num = 10;
4. 
5. int main()
6. {
7.     int num = 20;
8.     return 0;
9. }

当程序执行到第 8 行 return 0;

  • 指定具体的文件名,查看全局变量 num
(gdb) print 'main.c'::num
  • 指定具体的函数的函数名,查看局部变量 num
(gdb) print main::num

当然,由于程序执行到第 8 行 return 0; 时,gdb 调试就暂停在 main() 函数中
因此即便不指明 main::,直接使用 print num,这里的 num 默认指代的也是局部变量 num

  1. 查看变量 variable 的地址
(gdb) print &variable
  1. 查看某个地址 address 上的值
(gdb) print *address
  1. 查看某个寄存器的值,例如:RAX
(gdb) print $rax
  1. 还可以直接拿 gdb 当计算器用,例如:计算偏移 0xdd - 0x55 的值
(gdb) print 0xdd-0x55

查看内存数据

x 用于查看内存中的数据,允许以不同的格式打印内存中的内容

(gdb) x/[n/f/u] [表达式]

其中,n、f、u 是可选的参数

  • n 是一个正整数,表示从当前地址开始,向后显示 n 个地址的内容

  • f 表示显示的格式,具体可选参数如下:

参数含义
x按十六进制格式显示变量
d按十进制格式显示变量
u按十六进制格式显示无符号整型
o按八进制格式显示变量
t按二进制格式显示变量
a按十六进制格式显示变量
c按字符格式显示变量
f按浮点数格式显示变量

除此之外,s 可指定以字符串形式输出,i 可指定以汇编形式输出
例如:

(gdb) x/20s $rbp
(gdb) x/20i $rip
  • u 表示从当前地址往后请求的字节数,如果不指定的话,GDB 默认是 4 字节,可指定的字节数格式如下:
格式含义
b以字节(8 位)格式打印
h以半字(16 位)格式打印
w以字(32 位)格式打印
g以长字(64 位)格式打印
s以零结尾的字符串格式打印
  1. 查看内存地址 0x0804a010 开始的 5 个内存数据,以十六进制格式输出
(gdb) x/5x 0x0804a010
  1. 从变量 len 的首地址开始,打印 4 个字节,以十六进制的形式
(gdb) x/4xb &len
  1. 查看栈空间的内容,输出 20 条,以字(32 位)格式输出
(gdb) x/20w $rbp

查看栈空间数据

  1. 查看程序栈空间的数据
(pwndbg) stack
  1. 显示 n 条栈空间数据
(pwndbg) stack n

查看栈上的返回地址

  1. 查看包含返回地址的栈地址
(pwndbg) retaddr

查看 Canary 的值

  1. 查看栈上的 Canary 的值
(pwndbg) canary

查看 PLT 和 GOT 表

  1. 查看程序的 PLT 表
(pwndbg) plt
  1. 查看程序的 GOT 表
(pwndbg) got

查看虚拟内存空间

  1. 查看程序各段地址的权限
(gdb) vmmap

修改程序内的数据

set 用于更改或设置各种调试器选项和变量,以自定义 gdb 的行为和功能,这些选项和变量可以影响调试会话的方式

  1. 通过 set 修改某个变量 num 的值为 1 (需在变量前加上 variable,可以简写为 var)
(gdb) set variable num=1
(gdb) set var num=1

修改数组元素的值同理

通过 print 指令也可以修改某个变量 num 的值为 1

(gdb) print num=1
  1. 修改存储地址在 0x7fffffffde90,指定类型为 int 的值为 1
(gdb) set {int}0x7fffffffde90=1

# 修改前:
# 0x7fffffffde90:	0x010203040506
# 修改后:
# 0x7fffffffde90:	0x010200000001

也可以通过指针来实现:

(gdb) set *0x7fffffffde90=1

注意:set 命令一次修改 4 个字节

例如:将内存地址 0x7fffffffde90 处存放的 0x010203040506 改为 0xabcdef070809

# 如果对内存数据不熟悉的话,可以先查看每个字节对应的地址(一般为小端序)
(gdb) x/8xb

# 输出为:
# 0x7fffffffde90: 0x06 0x05 0x04 0x03 0x02 0x01 0x00 0x00
# 则,对应的地址关系为:
# 0x7fffffffde90 处存放 0x06,0x7fffffffde91 处存放 0x05,0x7fffffffde92 处存放 0x04 ...... 以此类推,0x7fffffffde97 处存放 0x00

# 由于一次修改 4 字节,这里需要修改 6 字节(其实一个内存地址处一般存放的是 8 字节,这里的 6 字节其实最高位还有 2 字节为 0x00 0x00 省略没写),因此可以分为两步

# 1. 首先修改低位的 4 字节
(gdb) set *0x7fffffffde90 = 0xef070809
# 2. 然后修改高位的 2 字节,如果加上最高位的 2 字节就是 0x0000abcd
(gdb) set *0x7fffffffde94 = 0xabcd

通过指针寄存器来指定内存地址,例如地址为 rbp - 10:(寄存器前注意加上 '$'

(gdb) set {int}($rbp-10)=1

也可以通过指针来实现: (注意要转换 $rbp-10 的类型,使用 int 还是 long 根据程序位数来确定)

(gdb) set *((unsigned int)$rbp-10)=1
(gdb) set *((unsigned long)$rbp-10)=1

具体变量的类型和地址可以通过 print 指令查看,例如变量 num:

(gdb) print &num

# 输出为:
# $1 = (int *) 0x7fffffffde90
  1. 修改某个寄存器的值,例如:RAX
(gdb) set var $rax=1
  1. 指定函数运行时的参数,例如:10、20、30
(gdb) set args 10 20 30

查看运行参数:

(gdb) shows args

查看 GDB 配置信息

可以通过 show 指令来获取 GDB 本身设置相关的一些信息

  1. 查看设置好的运行参数
(gdb) show args
  1. 查看程序的运行路径
(gdb) show paths
  1. 查看环境变量
(gdb) show environment 变量名

监视程序

设置监视点

watch 用于设置监视点可以监视变量的值,当变量的值发生更改时,gdb 会中断程序的执行,简写为:wat

注意:当设置的观察点是一个局部变量时,局部变量失效后,观察点也会失效

  1. 为某个变量或表达式设置监视点

监视变量 variable 的值:

(gdb) watch variable

监视数组元素的值:

(gdb) watch array[3]

监视表达式的值:

(gdb) watch variable + 5
  1. 指定条件监视点,只在特定条件 variable == 42 满足时中断:
(gdb) watch variable == 42

监视点类似于断点,也可以通过 info 来进行查看,使用 delete 进行删除,管理方法与断点相同


持续监视变量

display 设置要自动显示的变量、表达式或函数的值,而无需手动输入 print 命令,可以在调试过程中持续监视特定变量的值,简写为:disp

每次 gdb 中断,都会自动输出这些被监视变量或内存的值

  1. 为某个变量或表达式设置监视点

监视变量 variable 的值:

(gdb) display variable

监视数组元素的值:

(gdb) display array[3]

监视表达式的值:

(gdb) display variable + 5
  1. 删除设置的 display,假设编号为 index (可通过 info display 查看)
(gdb) undisplay index

查看源代码

list 命令默认只会输出 10 行源代码,也可以使用如下命令修改:

show listsize:查看 list 命令显示的代码行数
set listsize count:设置 list 命令显示的代码行数为 count

  1. 列出程序的源代码,默认每次显示 10 行
(gdb) list
  1. 显示以行号 line 为中心的 10 行源代码(前后各 5 行)
(gdb) list line
  1. 显示函数名 fun_name 所在函数的源代码
(gdb) list fun_name
  1. 显示从行号 x - y 的源代码(没有显示 10 行的限制)
(gdb) list x,y

查看汇编代码

  1. 将某个函数 fun_name() 完整反汇编
(gdb) disassemble fun_name
  1. 指定要反汇编的地址

当仅指定一个地址 address1 时,将反汇编包含给定地址的整个函数,包括其上方的指令:

(gdb) disassemble address1

指定要反汇编的起始地址 address1 和结束地址 address2,只会反汇编起始地址和结束地址之间的指令:

(gdb) disassemble address1,address2
  1. 指定从地址 address1 或函数 fun_name() 开始,长度为字节数 len 进行反汇编
(gdb) disassemble fun_name,+len
(gdb) disassemble address1,+len
  1. 同时显示函数 fun_name() 的反汇编指令和相对应的源代码
(gdb) disassemble /m fun_name
  1. 显示函数 fun_name() 的反汇编指令和汇编指令的字节码
(gdb) disassemble /r fun_name
  1. 显示 RIP 寄存器所在位置的汇编代码
(gdb) disassemble $rip

窗口布局

  1. 分割窗口,可以一边查看代码,一边测试
(gdb) layout
  1. 显示源代码窗口
(gdb) layout src
  1. 显示反汇编窗口
(gdb) layout asm
  1. 显示源代码/反汇编和 CPU 寄存器窗口
(gdb) layout regs
  1. 同时显示源代码和反汇编窗口
(gdb) layout split
  1. 切换到下/上一个布局模式
(gdb) layout next
(gdb) layout prev

一些关于窗口布局的快捷键:

  • 刷新窗口:Ctrl + L
  • 显示一个窗口:先按下 Ctrl + X,然后松手,再单独按一个 1
  • 显示两个窗口:先按下 Ctrl + X,然后松手,再单独按一个 2
  • 关闭窗口布局:先按下 Ctrl + X,然后松手,再单独按一个 A

堆相关命令

以下命令主要适用于 pwndbg 插件

参考文章:
pwndbg 基本操作指令 - MuRKuo - 博客园
pwndbg部分命令用法搬运_pwndbg vis-CSDN博客

  1. 显示 main_arena 特定地址的 arena 的详细信息
(pwndbg) arena

显示所有 arena 的基本信息:

(pwndbg) arenas
(pwndbg) arenainfo
  1. 查看所有种类的 bins 堆块的链表情况
(pwndbg) bins

查看 top_chunk 的地址和大小:

(pwndbg) top_chunk

查看指定地址处的 malloc_chunk

(pwndbg) malloc_chunk address fake   # 如果这个 chunk 是一个 fake chunk 的时候需要加上 fake 选项

单独查看 fast bin 链表:

(pwndbg) fastbins

单独查看 large bin 链表:

(pwndbg) largebins

单独查看 small bin 链表:

(pwndbg) smallbins

单独查看 unsorted bin 链表:

(pwndbg) unsortedbin

单独查看 tcache bin 链表:

(pwndbg) tcachebins

查看 malloc 线程缓存信息 tcache 的详细信息:

(pwndbg) tcache
  1. 以数据结构的形式显示所有堆块
(pwndbg) heap

可视化指定地址的堆块(默认大小为 10):

(pwndbg) vis_heap_chunks
(pwndbg) vis

查看堆的起始地址:

(pwndbg) heapbase

显示堆的信息:

# 和 bins 的挺像的,没有 bins 好用
(pwndbg) heapinfo
(pwndbg) heapinfoall

显示堆结构:

(pwndbg) parseheap

提示所有操作堆的地方:

(pwndbg) tracemalloc
  1. 打印出 Glibc 中的 mp_ structure
(pwndbg) mp