GDB的基础和使用
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
pwndbg与Pwngdb需要一起搭配使用以下方法默认安装的是当前最新版
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 directoryCreating 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 dpkg -i pwndbg_2024.02.14_amd64.deb安装后,可以直接在终端启动:
pwndbgdeb 安装的 pwndbg 不再是以 GDB 插件的形式存在,而是一个集成了 GDB 的独立工具,其 GDB 版本为 GNU gdb (GDB) 13.2:
安装 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 (以自己的实际输出信息为主)
修改 ~/.gdbinit 中的内容:
sudo gedit ~/.gdbinit在 ~/.gdbinit 末尾加入一句:
set context-output xxx
# 这里的 xxx 就是用于输出调试信息的分屏,我这里是:/dev/pts/20注意:如果你开了多个终端,就设置为实际想要用于输出调试信息的分屏
保存退出
在先打开的终端中开启 gdb 并输入调试命令,在后打开的终端中即可输出调试信息
然后我们将屏幕调整一下:
设置分屏后,如果只开启一个终端,使用 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:
- 当启用 peda 时,会出现:
gdb-peda$- 当启用 pwndbg 时,会出现:
pwndbg>- 当启用 gef 时,会出现:
gef➤如果验证出现上述输出内容,并且在输出内容之前没有任何报错提示,则说明安装成功
如果没有安装成功,输入
gdb会显示默认的:(gdb)
脚本自动切换 GDB 插件
这个脚本可以自动定位
~/.gdbinit文件中"# this place is controled by user's shell"这一句所在的位置,并随着用户对于插件的选择,自动更改~/.gdbinit文件里的内容,更改的内容会放在"# this place is controled by user's shell"这一句的上一行如果你安装 gdb 插件是按照我的教程来的,那下面的东西你可以不需要做任何改动,否则可能会有所修改
- 这一步非常重要!!!
首先,你需要自己手动在 ~/.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- 新建一个名为 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- 把 gdb.sh 文件移到
/usr/bin/目录下,类似于 Windows 的环境变量,在这个目录下可以通过 cmd 直接调用:
sudo mv ./gdb.sh /usr/bin/- 给 gdb.sh 文件增加执行权限:
cd /usr/bin/
sudo chmod a+x gdb.sh- 以后再打开 gdb 的时候,直接输入
gdb.sh即可:
我的脚本也是根据 GDB插件控制——切换pwndbg,peda,gef - 简书 (jianshu.com) 修改的
所以,如果你是按照我的教程来安装的,那就没什么问题
但如果你不是,那可能需要对脚本做一些修改,可以自己去参考一下上面的简书链接
GDB 的使用方法
首先要保证使用 gcc 编译时加上参数 -g 生成调试信息
一些可缩写的等价指令:
| 操作 | 完整指令 | 简洁指令 |
|---|---|---|
| 下断点 | break | b |
| 下临时断点 | tbreak | tb |
| 为已设断点添加条件 | condition | cond |
| 删除断点 | detele | d |
| 运行程序,当遇到断点后,程序会在断点处停止运行,等待用户输入下一步的命令 | run | r |
| 单步执行,跳过子函数 | next | n |
| 单步执行,进入子函数 | step | s |
| 直接执行到下一断点或程序结束 | continue | c |
| 查看信息 | info | i |
| 列出程序源码,一次可列出 10 行 | list | l |
| 打印变量的值 | p | |
| 显示当前函数调用栈的完整信息,包括调用函数和它们的参数 | backtrace | bt |
| 查看栈帧中某一帧的信息 | frame | f |
| 设置监视点 | watch | wat |
| 设置要自动显示的变量、表达式或函数的值,可以在调试过程中持续监视特定变量的值 | display | disp |
| 运行程序,直到当前函数完成返回,并打印函数返回时的堆栈地址和返回值及参数值等信息 | finish | fi |
| 执行程序,直到达到指定行号,可用于退出循环体,而不必逐行执行 | until | u |
| 退出调试 | quit | q |
| 反汇编 | disassemble | disass |
其他指令:
| 操作 | 完整指令 |
|---|---|
| 启动程序并在 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 文件名包含的信息内容和目录(目录必须存在),注意使用 echo 或 sysctl 命令,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 的信号 |
%t | core 文件生成时的 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 参数 | 含义 |
|---|---|---|
parent | on | 只调试父进程(GDB 模式) |
child | on | 只调试子进程 |
parent | off | 同时调试两个进程, GDB 调试父进程,子进程阻塞在 fork 处 |
child | off | 同时调试两个进程, GDB 调试子进程,父进程阻塞在 fork 处 |
查看正在调试的进程:
(gdb) info inferior切换调试的进程,假设 info inferior 获取到的进程编号为 Num_id:
(gdb) inferior Num_idpthread 多线程调试
查看 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下断点的位置都是在将要执行这一句但还未执行的时候
- 直接断在
main()函数的入口处,因为 main 是一个全局的符号,也可以断在其他函数的入口处,例如:fun_name()
(gdb) break main
(gdb) break fun_name- 在源代码 test.c 的第 line 行下断点
(gdb) break /绝对路径/test.c:line
(gdb) break 'test.c':line
(gdb) break line- 根据运行时的地址设置断点,例如:0xDEADBEEF,地址前需要加上
*号
(gdb) break *0xDEADBEEF- 设置条件断点,当 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 只能用在已存在的断点上
- 在当前程序暂停位置的前/后 offset 行处下断点
(gdb) break -/+ offest删除断点
删除断点的指令有
detele xxx和clear xxx两种:delete 是全局的,不受栈帧的影响;
delete 命令可以删除所有断点,包括观察点和捕获点等clear 命令受到当前栈帧的制约,删除的是将要执行的下一处指令的断点;
clear 命令不能删除观察点和捕获点
- 删除所有断点
(gdb) delete- 删除编号为 index 的断点
(gdb) delete index- 删除编号为 index1 - index2 的断点(包括 index2)
(gdb) delete index1-index2- 删除编号为 index1 - index2 的断点(包括 index2)和编号为 index3 - index4 的断点(包括 index4)
(gdb) delete index1-index2 index3-index4- 删除源代码第 line 行的断点,无需断点编号
(gdb) clear line- 删除
fun_name()函数的断点(只会删除函数入口处的断点,即:b fun_name所创建的断点,函数内部断点不会删除)
(gdb) clear fun_name这会删除所有的 fun_name() 函数断点,如果有多个同名函数断点,这些同名函数断点都会被删除
禁用和启用断点
断点的 Enb 参数有 y 和 n 两种,y 表示断点启用,n 表示断点禁用
- 禁用或启用编号为 index 的断点
(gdb) disable index
(gdb) enable index- 禁用或启用编号为 index1 - index2 的断点(包括 index2)
(gdb) disable index1-index2
(gdb) enable index1-index2- 禁用或启用编号为 index1 - index2 的断点(包括 index2)和编号为 index3 - index4 的断点(包括 index4)
(gdb) disable index1-index2 index3-index4
(gdb) enable index1-index2 index3-index4运行程序
控制程序执行
以下指令主要用于控制程序执行,不需要参数
| 操作 | 完整指令 | 简洁指令 |
|---|---|---|
| 运行程序,当遇到断点后,程序会在断点处停止运行,等待用户输入下一步的命令 | run | r |
| 单步执行,跳过子函数 | next | n |
| 单步执行,进入子函数 | step | s |
| 直接执行到下一断点或程序结束 | continue | c |
| 执行程序,直到达到指定行号,可用于退出循环体,而不必逐行执行 | until | u |
| 退出调试 | quit | q |
另外,还有 ni 和 si,也是单步执行的命令,用法与 n 和 s 相同,只不过 ni 和 si 的单步执行是针对汇编代码的
注意:until 指令可以设置参数,让程序运行至源代码的某行,可以不仅仅用来跳出循环
- 运行程序至源代码第 line 行
(gdb) until line- 终止调试的程序
(gdb) kill跳转指令
jump:将当前程序执行流跳转到指定行或地址,简写为 j
jump 命令有两点需要注意的:
- 中间跳过的代码是不会执行的,例如变量的初始化等,因此很可能会导致程序崩溃或出现其它 Bug
- 如果 jump 跳转到的位置后续没有断点,那么 gdb 会直接执行自跳转处开始的后续代码
- 跳转到源代码第 line 行的位置
(gdb) jump line- 跳转到距离当前代码下 offest 行的位置
(gdb) jump +offest- 跳转到某个地址,例如:0xDEADBEEF,地址前需要加上
*号
(gdb) jump *0xDEADBEEF退出函数
实际调试时,在某个函数中调试一段时间后,可能不需要再一步步执行到函数返回处,希望直接执行完当前函数,这时可以使用 finish 命令
return 和 finish 都是退出函数,但也有差别:
- return 命令:立即退出当前函数,剩下的代码不会执行了,return 还可以指定函数的返回值
- finish 命令:会继续执行完该函数剩余代码再正常退出
查看程序相关信息
查看程序状态
gdb 查看程序状态的指令为:
info xxx,也可以简写为:i xxxinfo 命令用于检索有关程序状态的信息,例如变量、函数、堆栈、线程等
- 查看断点信息
(gdb) info break
(gdb) info b- 查看当前函数中的本地变量
(gdb) info locals- 查看当前函数的参数
(gdb) info args- 查看当前设置的监视点列表(用于监视变量的值变化)
(gdb) info watch- 查看当前已设置的 display
(gdb) info display- 查看程序中的线程列表以及它们的状态
(gdb) info threads
(gdb) info th- 查看函数调用栈帧的基本信息,包括堆栈帧的数量、堆栈帧的地址范围和其他有关堆栈帧的信息
(gdb) info stack效果与 backtrace 相同:
(gdb) backtrace
(gdb) bt其中,#0 项是当前执行的函数(栈帧)
- 查看当前函数调用的栈帧信息
(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- 查看所有寄存器的当前值
(gdb) info registers
(gdb) info r单独查看某个寄存器的值,例如:RAX
(gdb) info r rax- 查看已加载的共享库信息
(gdb) info sharedlibrary
(gdb) info sh- 查看有关目标程序和调试器的信息
(gdb) info target
(gdb) info tar- 查看可执行文件的所有函数名称
(gdb) info functions
(gdb) info fun只显示函数名带有 abcd 的函数名称:
(gdb) info functions abcd*
(gdb) info fun abcd*- 查看程序是否运行
(gdb) info program- 查看当前目录
(gdb) pwd
(pwndbg) !pwd查看变量的值
gdb 查看变量的值的指令为:
print xxx,也可以简写为:p xxx
print 命令可允许自定义输出格式:
(gdb) print/参数 变量名| 参数 | 功 能 |
|---|---|
| /x | 以十六进制的形式打印出整数 |
| /d | 以有符号、十进制的形式打印出整数 |
| /u | 以无符号、十进制的形式打印出整数 |
| /o | 以八进制的形式打印出整数 |
| /t | 以二进制的形式打印出整数 |
| /f | 以浮点数的形式打印变量或表达式的值 |
| /c | 以字符形式打印变量或表达式的值 |
- 查看变量、数组、结构体成员或任何合法的表达式的值
查看变量 variable 的值:
(gdb) print variable查看数组元素:
(gdb) print array[3]查看结构体成员:
(gdb) print struct.member查看合法表达式的值:
(gdb) print variable + 5- 查看 array[] 数组中,自第 m 个元素起总共 n 个数组元素的值
int array[5] = {1,2,3,4};(gdb) print array[m-1]@n当 m = 1,n = 2 时,输出:$1 = {1, 2}
- 当程序中包含多个作用域不同但名称相同的变量时(全局变量、局部变量),可以借助
::运算符明确指定要查看的目标变量
例如源文件 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
- 查看变量 variable 的地址
(gdb) print &variable- 查看某个地址 address 上的值
(gdb) print *address- 查看某个寄存器的值,例如:RAX
(gdb) print $rax- 还可以直接拿 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 | 以零结尾的字符串格式打印 |
- 查看内存地址 0x0804a010 开始的 5 个内存数据,以十六进制格式输出
(gdb) x/5x 0x0804a010- 从变量 len 的首地址开始,打印 4 个字节,以十六进制的形式
(gdb) x/4xb &len- 查看栈空间的内容,输出 20 条,以字(32 位)格式输出
(gdb) x/20w $rbp查看栈空间数据
- 查看程序栈空间的数据
(pwndbg) stack- 显示 n 条栈空间数据
(pwndbg) stack n查看栈上的返回地址
- 查看包含返回地址的栈地址
(pwndbg) retaddr查看 Canary 的值
- 查看栈上的 Canary 的值
(pwndbg) canary查看 PLT 和 GOT 表
- 查看程序的 PLT 表
(pwndbg) plt- 查看程序的 GOT 表
(pwndbg) got查看虚拟内存空间
- 查看程序各段地址的权限
(gdb) vmmap修改程序内的数据
set 用于更改或设置各种调试器选项和变量,以自定义 gdb 的行为和功能,这些选项和变量可以影响调试会话的方式
- 通过 set 修改某个变量 num 的值为 1 (需在变量前加上 variable,可以简写为 var)
(gdb) set variable num=1
(gdb) set var num=1修改数组元素的值同理
通过 print 指令也可以修改某个变量 num 的值为 1
(gdb) print num=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- 修改某个寄存器的值,例如:RAX
(gdb) set var $rax=1- 指定函数运行时的参数,例如:10、20、30
(gdb) set args 10 20 30查看运行参数:
(gdb) shows args查看 GDB 配置信息
可以通过 show 指令来获取 GDB 本身设置相关的一些信息
- 查看设置好的运行参数
(gdb) show args- 查看程序的运行路径
(gdb) show paths- 查看环境变量
(gdb) show environment 变量名监视程序
设置监视点
watch 用于设置监视点可以监视变量的值,当变量的值发生更改时,gdb 会中断程序的执行,简写为:wat
注意:当设置的观察点是一个局部变量时,局部变量失效后,观察点也会失效
- 为某个变量或表达式设置监视点
监视变量 variable 的值:
(gdb) watch variable监视数组元素的值:
(gdb) watch array[3]监视表达式的值:
(gdb) watch variable + 5- 指定条件监视点,只在特定条件 variable == 42 满足时中断:
(gdb) watch variable == 42监视点类似于断点,也可以通过 info 来进行查看,使用 delete 进行删除,管理方法与断点相同
持续监视变量
display 设置要自动显示的变量、表达式或函数的值,而无需手动输入 print 命令,可以在调试过程中持续监视特定变量的值,简写为:disp
每次 gdb 中断,都会自动输出这些被监视变量或内存的值
- 为某个变量或表达式设置监视点
监视变量 variable 的值:
(gdb) display variable监视数组元素的值:
(gdb) display array[3]监视表达式的值:
(gdb) display variable + 5- 删除设置的 display,假设编号为 index (可通过
info display查看)
(gdb) undisplay index查看源代码
list 命令默认只会输出 10 行源代码,也可以使用如下命令修改:
show listsize:查看 list 命令显示的代码行数set listsize count:设置 list 命令显示的代码行数为 count
- 列出程序的源代码,默认每次显示 10 行
(gdb) list- 显示以行号 line 为中心的 10 行源代码(前后各 5 行)
(gdb) list line- 显示函数名 fun_name 所在函数的源代码
(gdb) list fun_name- 显示从行号 x - y 的源代码(没有显示 10 行的限制)
(gdb) list x,y查看汇编代码
- 将某个函数
fun_name()完整反汇编
(gdb) disassemble fun_name- 指定要反汇编的地址
当仅指定一个地址 address1 时,将反汇编包含给定地址的整个函数,包括其上方的指令:
(gdb) disassemble address1指定要反汇编的起始地址 address1 和结束地址 address2,只会反汇编起始地址和结束地址之间的指令:
(gdb) disassemble address1,address2- 指定从地址 address1 或函数
fun_name()开始,长度为字节数 len 进行反汇编
(gdb) disassemble fun_name,+len
(gdb) disassemble address1,+len- 同时显示函数
fun_name()的反汇编指令和相对应的源代码
(gdb) disassemble /m fun_name- 显示函数
fun_name()的反汇编指令和汇编指令的字节码
(gdb) disassemble /r fun_name- 显示 RIP 寄存器所在位置的汇编代码
(gdb) disassemble $rip窗口布局
- 分割窗口,可以一边查看代码,一边测试
(gdb) layout- 显示源代码窗口
(gdb) layout src- 显示反汇编窗口
(gdb) layout asm- 显示源代码/反汇编和 CPU 寄存器窗口
(gdb) layout regs- 同时显示源代码和反汇编窗口
(gdb) layout split- 切换到下/上一个布局模式
(gdb) layout next
(gdb) layout prev一些关于窗口布局的快捷键:
- 刷新窗口:Ctrl + L
- 显示一个窗口:先按下 Ctrl + X,然后松手,再单独按一个 1
- 显示两个窗口:先按下 Ctrl + X,然后松手,再单独按一个 2
- 关闭窗口布局:先按下 Ctrl + X,然后松手,再单独按一个 A
堆相关命令
以下命令主要适用于 pwndbg 插件
参考文章:
pwndbg 基本操作指令 - MuRKuo - 博客园
pwndbg部分命令用法搬运_pwndbg vis-CSDN博客
- 显示
main_arena特定地址的arena的详细信息
(pwndbg) arena显示所有 arena 的基本信息:
(pwndbg) arenas(pwndbg) arenainfo- 查看所有种类的
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- 以数据结构的形式显示所有堆块
(pwndbg) heap可视化指定地址的堆块(默认大小为 10):
(pwndbg) vis_heap_chunks
(pwndbg) vis查看堆的起始地址:
(pwndbg) heapbase显示堆的信息:
# 和 bins 的挺像的,没有 bins 好用
(pwndbg) heapinfo
(pwndbg) heapinfoall显示堆结构:
(pwndbg) parseheap提示所有操作堆的地方:
(pwndbg) tracemalloc- 打印出 Glibc 中的
mp_ structure
(pwndbg) mp














