gdb 如何读取正在调试的程序/进程的寄存器值?寄存器如何与进程相关联?

How does gdb read the register values of a program / process it's debugging? How are registers associated with a process?

我用 c++ 写了一个小程序:

#include<iostream>
using namespace std;

    int main(){
    int x=10;
    int y=20;
    cout<< x+y <<endl;
    return 0;
    }

出于好奇,我想了解引擎盖背后的程序,所以我在玩 gdb 并遇到了 info registers 命令。当我在 gdb 中使用 info registers 时,我得到输出如下:

(gdb) info registers
rax            0x400756 4196182
rbx            0x0  0
rcx            0x6  6
rdx            0x7fffffffd418   140737488344088
rsi            0x7fffffffd408   140737488344072
rdi            0x1  1
rbp            0x7fffffffd320   0x7fffffffd320
rsp            0x7fffffffd320   0x7fffffffd320
r8             0x7ffff7ac1e80   140737348640384
r9             0x7ffff7dcfea0   140737351843488
r10            0x7fffffffd080   140737488343168
r11            0x7ffff773a410   140737344939024
r12            0x400660 4195936
r13            0x7fffffffd400   140737488344064
r14            0x0  0
r15            0x0  0
rip            0x40075a 0x40075a <main+4>
eflags         0x246    [ PF ZF IF ]
cs             0x33 51
ss             0x2b 43
ds             0x0  0
es             0x0  0
fs             0x0  0
gs             0x0  0

我知道这些是寄存器及其值,但我想知道的是 how/why 是 registersprocess 关联的。寄存器的值应该随着操作系统调度不同的进程而不断变化?我参考了命令 info registers & 这是我发现的,但这仍然令人困惑。

info registers -> Prints the names and values of all registers except floating-point and vector registers (in the selected stack frame).

寄存器一直在变化。事实上,即使是调试器也会更改寄存器值,因为它必须 运行 自己。

但是,当您使用调试器查看程序时,调试器暂停您的运行ning 进程。作为挂起的一部分,CPU 状态被保存到 RAM 中。调试器理解这一点,并且可以只查看 RAM 中的挂起状态。假设寄存器 R1 在挂起时保存到地址 0x1234,那么调试器可以只打印存储在该地址的字节。

每个 thread/process 都有自己的寄存器值。 user-space "architectural state"(寄存器值)在通过系统调用或中断进入内核时保存。 (这在所有 OSes 上都是正确的)。

请参阅 查看 Linux 的系统调用入口点,其中手写的 asm 实际上将寄存器保存在进程的内核堆栈上。 (每个线程都有自己的内核堆栈,在Linux)。

在多任务处理中 OS 通常,每个 process/thread 都有自己的内存 space 用于保存状态,因此上下文切换通过从线程恢复保存的状态来工作切换到。这有点简化,因为有内核状态与保存的用户-space。状态1


因此,任何时候一个进程实际上 运行 在 CPU 核心上,它的寄存器值都保存在内存中。

OS 为reading/writing 其他进程.[=20= 保存的寄存器状态和内存提供API ]

在Linux中,这个API是ptrace(2)系统调用; GDB 就是用它来读取寄存器值和单步执行的。因此,GDB 通过内核间接地从内存 中读取目标进程 保存的寄存器值。 GDB 自己的代码不使用任何特殊的 x86 指令,甚至不从任何特殊地址加载/存储;它只是进行系统调用,因为访问另一个进程的状态必须通过内核。 (嗯,我认为一个进程 可以 将另一个进程的内存映射到它自己的地址 space,如果 Linux 甚至有一个系统调用,但我认为内存 reads/writes 实际上就像寄存器访问一样通过 ptrace。)

(我认为)如果目标进程当前正在执行(而不是挂起),而当另一个进程进行 ptrace 系统调用读取或写入其寄存器值之一时,内核将不得不中断它所以它的当前状态将被保存到内存中。 GDB 通常不会发生这种情况:它只会在目标进程挂起时尝试​​读取寄存器值。


ptrace 也是 strace 用来跟踪系统调用的。请参阅 Linux 期刊中的 Playing with ptrace, Part Istrace ./my_program 对于系统编程非常有用,尤其是在从手写 asm 进行系统调用时,解码您实际传递的参数和 return 值。


脚注:

  1. 在Linux中,实际切换到新线程发生在内核内部,从内核上下文到内核上下文。这会将 "only" 整数寄存器保存在内核堆栈上,将 rsp 设置到其他线程内核堆栈中的正确位置,然后恢复保存的寄存器。所以有一个函数调用,当它 returns 时,它在内核模式下为新线程执行,每个 CPU 内核变量设置得当。新线程的 User-space 状态最终会以相同的方式恢复,如果最初从 user-space 进入内核的系统调用或中断没有调用 returned调度程序。即从系统调用或中断内核入口点保存的状态。惰性/急切的 FPU 状态保存是另一个复杂问题;内核通常会避免接触 FPU,因此它可以在刚进入内核时避免 saving/restoring FPU 状态,然后 return 返回同一个用户-space 进程。