4. GDB寄存器查看与修改

任务目标

编写一个简单C程序,通过GDB在断点处查看寄存器值,并使用set $eax=...修改寄存器,观察修改后对程序执行流程或结果的影响,从而理解寄存器在程序执行中的作用。

环境与依赖

  • Linux环境(本文以Ubuntu/Debian为例)
  • GCC编译器(含-g调试信息)
  • GDB调试器

示例程序

创建一个名为reg_demo.c的文件,内容如下:

#include <stdio.h>

int main() {
    int a = 10;
    int b = 20;
    int result;

    // 断点位置1:查看a和b的寄存器映射
    result = a + b;   // 断点1

    // 断点位置2:修改eax后观察result变化
    printf("result = %d\n", result);  // 断点2

    return 0;
}

编译与调试

gcc -g -o reg_demo reg_demo.c
gdb ./reg_demo

GDB操作步骤

  1. 在GDB中设置断点:break 8(对应result = a + b;行)
  2. 运行程序:run
  3. 查看所有寄存器:info registers
  4. 查看特定寄存器:info registers eax ebx ecx edx
  5. 查看寄存器与变量关系:info registers eax后,用print $eax确认值
  6. 单步执行:next(执行完加法后,result已存入寄存器或内存)
  7. 查看result当前值:print result(预期为30)
  8. 修改寄存器:set $eax = 100(注意:此时eax可能已不再保存result,需结合反汇编确认)

验证修改寄存器对执行的影响

为了清晰演示寄存器修改的影响,我们使用一个更直接的场景——修改循环计数器或条件跳转相关寄存器。修改代码如下:

#include <stdio.h>

int main() {
    int i;
    for (i = 0; i < 5; i++) {
        printf("i = %d\n", i);
    }
    return 0;
}

重新编译并调试:

gcc -g -o loop_demo loop_demo.c
gdb ./loop_demo
  1. break 6(printf行)
  2. run
  3. info registers ecx——观察循环变量i可能存储在ecx或栈上,需用print i确认
  4. 若i存储在寄存器中(如ecx),执行set $ecx = 3,然后continue,观察循环次数变化
  5. 若i在栈上,则用set {int} &i = 3修改变量,但本案例聚焦寄存器修改,因此需通过disassemble main查看汇编,找到i对应的寄存器

反汇编辅助定位寄存器

(gdb) disassemble main

在循环场景中,通常ecx或edx可能被用作计数器。通过汇编代码确认后,直接修改对应寄存器即可改变循环次数。

验证方法与判定标准

  • 查看寄存器:info registers能列出所有寄存器及其十六进制值,且eax等通用寄存器值在断点处与代码逻辑一致(如函数参数、返回值等)。
  • 修改寄存器:使用set $eax = 100后,立即用print $eax确认值变为100。
  • 影响程序执行:在循环示例中,若将计数器寄存器改为大于等于5的值,程序将提前退出循环,输出行数减少;若改为负数或0,则循环不执行。观察输出即可判定修改生效。
  • 注意:修改寄存器可能影响程序状态,若修改了返回地址或栈指针可能导致崩溃,这是正常现象,说明寄存器对执行路径有直接控制力。

常见问题

  • 修改eax后程序无变化:可能因为该寄存器在当前断点处已不再被使用,或变量已存入内存。需结合disassemble和info registers确认哪个寄存器正承载关键值。
  • info registers输出过长:可用info all-registers查看全部,或指定寄存器名查看单个。
  • 修改浮点寄存器:使用set $st0 = 3.14等,但需注意浮点栈寄存器用法不同。

预期结果示例

在循环示例中,若反汇编发现ecx保存循环变量,且断点处ecx=0,执行set $ecx=3后继续运行,程序将只打印i=3和i=4两行(因为循环条件i<5,修改后从3开始)。这是由代码逻辑直接推出的确定性结果,可用于验证修改是否生效。