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

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

I wrote a short program in 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 时,我得到如下输出:

just out of curiosity i wanted to understand a program behind the hood so i was playing with gdb & came acrooss info registers command .when i use info registers in gdb i get output like this:

(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

我知道这些是寄存器及其值,但我想知道的是 registers 如何/为什么与 process 相关联.随着操作系统调度不同的进程,寄存器的值应该不断变化吗?我提到了命令 info registers &这是我发现的,但这仍然令人困惑.

I understand these are registers and their values but what I want to know is how/why are registers associated with a process. the values of registers should be changing continuously as different processes are scheduled by the operating system? I referred to the command info registers & this is what I found but this is still confusing.

info registers -> 打印所有寄存器的名称和值,除了浮点和向量寄存器(在选定的堆栈帧中).

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

推荐答案

每个线程/进程都有自己的寄存器值.用户空间架构状态"(寄存器值)是通过系统调用或中断进入内核时保存.(在所有操作系统上都是如此).

Each thread/process has its own register values. The user-space "architectural state" (register values) is saved on entering the kernel via a system call or interrupt. (This is true on all OSes).

参见 如果你在 64 位代码中使用 32 位 int 0x80 Linux ABI 会发生什么? 看看 Linux 的系统调用入口点,手写的 asm 实际上将寄存器保存在进程的内核堆栈上.(在 Linux 中,每个线程都有自己的内核堆栈).

See What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code? for a look at Linux's system-call entry points, with the hand-written asm that actually saves registers on the process's kernel stack. (Each thread has its own kernel stack, in Linux).

在一般的多任务操作系统中,每个进程/线程都有自己的内存空间来保存状态,因此上下文切换通过从被切换到的线程恢复保存的状态来工作.这有点简化,因为存在内核状态与节省的用户空间.状态1

In multi-tasking OSes in general, every process/thread has its own memory space for saving state, so context switches work by restoring the saved state from the thread being switched to. This is a bit of a simplification, because there's kernel state vs. saved user-space. state1

因此,只要进程实际上不在 CPU 内核上运行,它的寄存器值就会保存在内存中.

So any time a process isn't actually running on a CPU core, its register values are saved in memory.

操作系统提供了一个 API,用于读取/写入其他进程保存的寄存器状态和内存.

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

In Linux, this API is the ptrace(2) system call; it's what GDB uses to read register values and to single-step. Thus, GDB reads saved register values of the target process from memory, indirectly via the kernel. GDB's own code doesn't use any special x86 instructions, or even load / store from any special addresses; it just makes system calls because access to another process's state has to go through the kernel. (Well I think a process could map another process's memory into its own address space, if Linux even has a system call for that, but I think memory reads/writes actually go through ptrace just like register accesses.)

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

(I think) If the target process was currently executing (instead of suspended) when another process made a ptrace system call that read or wrote one of its register values, the kernel would have to interrupt it so its current state would be saved to memory. This doesn't normally happen with GDB: it only tries to read register values when it's suspended the target process.

ptrace 也是 strace 用来跟踪系统调用的.请参阅 Linux Journal 中的 使用 ptrace,第一部分.strace ./my_program 对于系统编程非常有用,尤其是在从手写 asm 进行系统调用以解码您实际传递的参数和返回值时.

ptrace is also what strace uses to trace system calls. See Playing with ptrace, Part I from Linux Journal. strace ./my_program is fantastically useful for systems programming, especially when making system calls from hand-written asm, to decode the args you're actually passing, and the return values.

脚注:

  1. 在 Linux 中,实际切换到新线程发生在内核内部,从内核上下文到内核上下文.这将仅"保存在内核堆栈上的整数寄存器,将 rsp 设置到另一个线程的内核堆栈中的正确位置,然后恢复保存的寄存器.所以有一个函数调用,当它返回时,它在内核模式下为新线程执行,每个 CPU 内核变量设置得当.如果最初从用户空间进入内核的系统调用或中断在没有调用调度程序的情况下返回时,新线程的用户空间状态最终会以相同的方式恢复.即来自系统调用或中断内核入口点保存的状态.Lazy/Eager FPU 状态保存是另一个复杂因素;内核通常会避免接触 FPU,因此它可以避免在刚进入内核并返回相同的用户空间进程时保存/恢复 FPU 状态.
  1. In Linux, the actual switch to a new thread happens inside the kernel, from kernel context to kernel context. This saves "only" the integer registers on the kernel stack, sets rsp to the right place in the other thread's kernel stack, then restores the saved registers. So there's a function call that, when it returns, is executing in kernel mode for the new thread, with per-CPU kernel variables set appropriately. User-space state for the new thread is eventually restored the same way it would have been if the system call or interrupt that originally entered the kernel from user-space had returned without calling the scheduler. i.e. from the state saved by the system call or interrupt kernel entry point. Lazy / eager FPU state saving is another complication; the kernel generally avoids touching the FPU so it can avoid saving/restoring FPU state when just entering the kernel and returning back to the same user-space process.

相关文章