ZEVORN.blog

May 19, 2024

三步轻松“定制”GDB

article5.1 min to read

GDB 是 GNU Debugger 的简称,属于 GNU 项目的一部分,是一款功能强大且广泛使用的命令行调试工具,专为 Unix 和类 Unix 操作系统设计,尤其在 Linux 环境下是程序员首选的调试工具之一。GDB 主要用于调试 C、C++、Pascal、FORTRAN、Ada、Objective-C、Free Pascal、Go 等多种编程语言编写的程序。

掌握了 GDB ,就等于掌握了一把神兵利器,可以帮我们快速定位问题,极大的降低时间成本。从包管理器安装或者源码编译的 GDB 是一个毛坯房,“装修装修”再入住会更舒服,所以接下来手把手教你定制属于自己的 GDB 。

一、定制 GDB 配置文件

GDB 有一个配置文件,叫做 .gdbinit ,支持写入 GDB 命令和用户自定义命令。GDB 启动时,会查找是否存在 .gdbinit 配置文件,先检索 ~/. 目录,再检索当前 GDB 启动时所在的目录。文件中的 GDB 命令会在 GDB 启动时预执行。

根据需要,在 GDB 配置文件中写入不同的 GDB 命令,可以达到定制化 GDB 的目的。

比如:

set debuginfod enabled onset print pretty onset max-value-size unlimited

以上 3 个命令,设置了默认下载 GDB 调试符号、打开结构体格式化打印、不限制打印信息长度的功能。

除了设置 GDB 原生命令,还可以定义用户命令(宏编码语言),可以将所有其他标准 GDB 命令与流控制指令结合使用并向其传递参数,以允许为正在调试的特定应用程序而自定义调试器的行为。通过用户命令实现对原生 GDB 命令的组合封装,可进一步适应不同的调试场景。建议将通用的用户命令定义在 ~/.gdbinit ,针对不同项目的用户命令可以放在各自项目文件夹下的 .gdbinit 当中,只需要在对应文件夹下启动 GDB 即可使用。

GDB 用户命令遵循如下基本格式:

define <command>    <code>enddocument <command>    <help text>end

从简单开始:清屏

GDB 没有用于清屏的内置命令,但它可以调用 shell 函数;下面的代码跳到调试器之外以使用 cls 命令来清除 xterm 控制台:

define clsshell clearenddocument clsClears the screen with a simple command.end

此定义的上半部分(在 define ... end 动词所界定的范围内)构成了在调用该命令时所执行的代码。此定义的下半部分(在 document ... end 所界定的范围内)由 GDB 命令解释器使用,用于键入 help cls 时显示与 cls 命令关联的文本。

如果输入 help user 命令,可以看到在 .gdbinit 文件中输入的所有用户命令的摘要。.gdbinit 用户定义命令的设计者提供了一个重要特性,在编写自己的命令时不应忽略该特性:document ... end 子句。随着这些命令数量的增加,维护有关命令如何工作的功能文档将变得非常关键。

断点别名

许多 GDB 命令太繁琐,这是众所周知的事实。尽管可以对它们进行缩写,但是 GDB 宏语言允许实现进一步的简化。诸如 info breakpoints 这样的命令可以变得像 bpl 一样简单,这里给出常见的断点别名命令封装:

define bplinfo breakpointsenddocument bplList breakpointsenddefine bpabreak * $arg0enddocument bpaSet a breakpoint on addressUsage: bpa addrenddefine bpcclear $arg0enddocument bpcClear breakpoint at function/addressUsage: bpc addrenddefine bpeenable $arg0enddocument bpeEnable breakpoint #Usage: bpe numenddefine bpddisable $arg0enddocument bpdDisable breakpoint #Usage: bpd numenddefine bpttbreak $arg0enddocument bptSet a temporary breakpoint on addressUsage: bpt addrenddefine bpmawatch $arg0enddocument bpmSet a read/write breakpoint on addressUsage: bpm addrend

二、善用 GDB 的 python 脚本

GDB 的用户命令是一种宏语言,语法比较简陋,只能实现对已有命令的封装,对一些复杂调试场景和应对一些特殊调试需求,比如记录指令流,比如自动化处理某些调试任务,就显得有些捉襟见肘。但是好在,GDB 支持 python 脚本执行,可以把一些调试流程编程脚本自动化执行,尽可能解放我们的双手,让开发人员可以专注于调试思路(但想要在 GDB 中正常使用 python,需要先确保你已经安装好它)。

下面举例一个简单的使用思路:

(gdb) python  # 1,进入 gdb 以后,输入 python 可以进入 python 解释器环境>print(1 + 1) # 2. 这里打印 1 + 1>end          # 3. 输入结束命令2             # 打印执行结果(gdb)

如果觉得麻烦,也可以把命令写在一行:

(gdb) python print(1+1)2(gdb)

更进一步的,可以将 python 代码放在一个 .py 文件,直接在 GDB 中调用:

(gdb) source script.py

下面给出一个记录指令流的 GDB-python 脚本代码示例:

import gdb # 如果在 gdb 里面直接执行 python 代码,不需要 import gdb# 定义要写入的文件名logfile = "gdb_jump_instr_log.txt"# 清空或创建新文件with open(logfile, 'w') as f:    f.write('')def is_jump_instruction(instruction):    # 这里仅提供了一个基础示例,实际实现需要根据目标架构和具体的指令集进行扩展    # 例如,x86架构下,call、jmp、j* 等都是跳转指令    jump_instructions = ["call", "jmp", "je", "jne", "jz", "jnz"]  # 示例,不完整列表    return any(inst.lower() in instruction.lower() for inst in jump_instructions)while True:    # 获取当前PC值    pc_value = gdb.parse_and_eval("$pc")    # 获取当前地址的汇编指令    disassembly_line = gdb.execute(f"x/i {pc_value}", to_string=True).split(None, 1)[1]    if is_jump_instruction(disassembly_line):        with open(logfile, 'a') as f:            f.write('%s\n' % pc_value)    # 执行单步指令(这里假设你希望每次检查后都单步执行一次)    gdb.execute("si")    # 可以在这里添加更多的调试信息,如寄存器状态等    # 如果你想在达到某个条件时结束脚本,可以在这里添加相应的逻辑

三、玩转 GDB 插件

如果还是觉得用户命令和 python 脚本编写起来比较麻烦,想获得开箱即用的体验,那么你可以了解一下 GDB 插件(基本是基于用户命令和 python 脚本实现),笔者常用的插件是 peda ,安装流程如下:

git clone https://github.com/longld/peda.git ~/pedaecho "source ~/peda/peda.py" >> ~/.gdbinitecho "DONE! debug your program with gdb and enjoy"

使用界面如下,比较适合逆向工程:

[----------------------------------registers-----------------------------------]EAX: 0xb0c ('\x0c\x0b')EBX: 0x1804e990 --> 0x91085c7ECX: 0xb0EDX: 0x568ESI: 0xf7ff24d4 ("DHRYSTONE PROGRAM, 2'ND STRING")EDI: 0xf7ff2504 ("M, 1'ST STRING")EBP: 0x1a000000 (0x1a000000)ESP: 0xffffd6e8 --> 0x0EIP: 0x402a2269 (add    esi,0x10)EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)[-------------------------------------code-------------------------------------]   0x402a225e:  add    edi,0x10   0x402a2261:  pxor   xmm0,xmm0   0x402a2265:  pcmpeqb xmm2,xmm1=> 0x402a2269:  add    esi,0x10   0x402a226c:  pcmpeqb xmm0,xmm1   0x402a2270:  pmovmskb eax,xmm2   0x402a2274:  pmovmskb edx,xmm0   0x402a2278:  xor    eax,0xffff[------------------------------------stack-------------------------------------]0000| 0xffffd6e8 --> 0x00004| 0xffffd6ec --> 0x00008| 0xffffd6f0 --> 0x105a3 (inc    esi)0012| 0xffffd6f4 --> 0x00016| 0xffffd6f8 --> 0x1804ea17 --> 0xc7c089410020| 0xffffd6fc --> 0x00024| 0xffffd700 --> 0xf7ff24f4 ("DHRYSTONE PROGRAM, 1'ST STRING")0028| 0xffffd704 --> 0xf7ff24d4 ("DHRYSTONE PROGRAM, 2'ND STRING")[------------------------------------------------------------------------------]Legend: code, data, rodata, valueStopped reason: SIGINT0x402a2269 in ?? ()gdb-peda$

以上是对 GDB 一些客制化内容的介绍,后面会结合应用场景,讲讲如何灵活运用这些工具。