gdb使用技巧
GDB调试工具
0x00 介绍
gdb是用来debug c/c++程序的工具,用来检测程序的逻辑错误
要想使用必须在gcc编译的时候加上-g选项
加了-g之后会生成调试表
GDB的三种调试方式如下:
- 运行并调试一个新进程:
- 运行GDB, 并通过命令行或
file命令指定目标程序; - 输入
run命令, GDB将进行以下操作:- 通过
fork()系统调用创建一个子进程; - 在新创建的子进程中执行操作
ptrace(PTRACE_TRACEME, 0, 0, 0); - 在子进程中通过
execv()系统调用加载用户指定的可执行文件。
- 通过
- 运行GDB, 并通过命令行或
- attach 并调试一个已经运行的进程:
- 用户确定需要进行调试的进程PID
- 运行GDB, 输入
attach <pid>, 或者gdb -p <pid> - GDB会对其执行操作
ptrace(PTRACE_ATTACH, 0, 0, 0);
- 远程调试目标机上新创建的进程:
- gdb 运行在调试机上,gdbserver 运行在被调试机上,两者之间的通信数据格式为GDB串行协议(Remote Serial Protocol)定义;
- RSP协议数据基本格式为
$..........#xx; - gdbserver的启动方式相当于第一种GDB调试方式.
note:
GDB attach到一个进程时,可能会出现权限问题
1 | gef➤ attach 1 |
这是由于内核参数 ptrace_scope=1 被设置了,因此普通用户没有权限对其他进程进行 attach 操作,而 root 权限启动GDB不会受权限所限制,但更好的解决方案是关闭它:
1 | ╰──➤ cat /proc/sys/kernel/yama/ptrace_scope |
0x01 gdb简单使用
e.g. 调试hello程序的步骤
编译生成ELF文件 并加上
-g选项1
gcc hello.c -o hello -g
使用gdb进行调试
1
2
3gdb hello
## 或者使用tui界面的gdb
gdb -tui hello
note: 若编译时忘记加-g 选项而且已经在gdb中打开了一个程序,可以重新编译之后在用file 命令加载可执行程序
gdb中常用命令如下表
| 命令 | 简写 | 作用 | 例 |
|---|---|---|---|
| help | h | 按模块列出命令类 | |
| help class | 查看某一类型的具体命令 | ||
| list | l | 查看代码,可跟行号或函数名 | |
| quit | q | 退出gdb | |
| run | r | 全速运行程序 | |
| run arg[1] arg[2] | 调试时命令行传参 | run aa bb cc | |
| start | 单步执行,运行程序,停在第一行执行语句 | ||
| next | n | 逐过程执行代码,跳到下一行代码继续执行 | |
| nexti | ni | 逐过程执行机器指令,跳到下一行代码继续执行 | |
| step | s | 逐语句执行代码,遇见函数,跳到函数内执行 | |
| stepi | si | 逐语句执行机器指令,遇见函数,跳到函数内执行 | |
| backtrace | bt | 查看函数的调用栈帧和层级关系 | |
| info | i | 查看GDB内部局部变量的的数值 | info breakpoints //(可以简写成 i b) 查看断点信息表info locals //查看局部变量 |
| frame | f | 切换函数的栈帧 | |
| finish | fin | 跳出当前循环(跳出当前栈) | |
| util |
运行到指定行结束 | ||
| return |
结束当前函数,返回到函数调用点, 并决定返回值 | return 233 |
|
| set | 设置变量的值 | set var n=100 | |
| set args | 设置main函数的参数值(在start、run之前) | set args aa bb cc | |
| p | 打印变量和地址 | p *数组名 @10 // 列出数组前10个元素p 数组名[n] @m// 从下标n开始向后打印m个元素 |
|
| print type | ptype | 查看变量的类型 | |
| break | b | 设置断点,可根据行号和函数名 | b 20 if i = 5 设置条件断点break * 0x1234 在地址0x1234处设置断点 |
| delete | d | 删除断点 | d breakpoints NUM |
| display | 设置观察变量 | display testvar | |
| undisplay | 取消观察变量 | undisplay 观察变量的编号 | |
| continue | c | 继续全速运行剩下的代码 | |
| enable breakpoints | 启用断点 | ||
| disable breakpoints | 禁用断点 | ||
| x | 查看内存 | x /20xw 显示20个单元,16进制,4字节每单元 | |
| watch | 被设置观察点的变量发生修改时,打印显示 | ||
| core file | ulimit -c 1024 开启core文件, 调试时 gdb a.out core |
||
| tui enable/disable | 打开关闭tui界面 | ||
| disas | 反汇编当前函数 | disas foo 反汇编函数foo |
|
| info watch | i watch | 显示观察点 | |
| info frame | 有关栈帧的信息 | ||
| info registers | 所有寄存器的值 |
其他一些常用命令
set disassembly-flavor intel转换为intel格式的汇编set disassembly-flavor att转换为att格式的汇编set solib-search-path <path>设置动态库路径set solib-absolute-prefix <path>设置绝对路径系统动态库路径layout asm显示汇编窗口layout regs显示寄存器窗口layout src显示源码窗口
多线程调试
1 | ## 列出所有线程 |
多进程调试
1 | ## 查看当前所有进程 |
反向调试(gdb7.0以上)
1 | ## 开启记录 |
断点的储存与恢复
这个文件本质上存的是命令列表
同理,也可以将一些命令写入文件,用source命令加载
类似批处理
1 | ## 存储断点到文件 |
可以把一些设置写在 ~/.gdbinit 中,以便让gdb下次启动时自动加载你的设置
0x02 gdb增强
gef 是一组用于 X86、ARM、MIPS、PowerPC 和 SPARC 的命令集,可让 GDB 为漏洞利用开发人员提供更多更酷炫的功能。它主要由漏洞利用开发人员和逆向工程师使用,使用 Python API 为 GDB 提供附加功能,以协助动态分析和漏洞利用开发过程.
gef 的主要功能包括:
- 一个
GDB脚本(用python写的,有1w多行代码) - 完全与操作系统无关,无依赖
- 提供种类繁多的命令来彻底改变您在
GDB中的体验。 - 完整的
python3支持 - 提供
GDB python API来自定义插件
安装
gef 是一个开箱即用的工具,可以通过下面命令通过脚本安装
1 | bash -c "$(curl -fsSL http://gef.blah.cat/sh)" |
也可以通过git安装
1 | git clone https://github.com/hugsy/gef.git |
为了更好的体验,可以安装一些依赖(可选)
1 | pip3 install capstone unicorn keystone-engine ropper |
只需确保您使用的pip是与编译 GDB 时所用的 Python 版本相对应的版本。
用户写基于gef的插件
GEF 的构建还为外部脚本提供了坚实的基础。该存储库gef-extras是一个开放的存储库,任何人都可以通过 GEF 的 API 自由提交自己的命令来扩展 GDB。
1 | ## via the install script |
gef使用小技巧
- 将输出重定向到一个tty
若是从tmux会话中启动的gdb可以用下面命令 自动分屏指定tty
1 | (gdb) gef tmux-setup |
更加通用的办法是 手工指定tty
首先开一个新终端并查看其tty号
1 | $ tty |
然后在配置gef的context.redirect 选项为上一步的输出结果
1 | gef➤ gef config context.redirect /dev/pts/0 |
