问题描述
我用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 -> 打印所有寄存器的名称和值,除了浮点和向量寄存器(在选定的堆栈帧中).
推荐答案
每个线程/进程都有自己的寄存器值.用户空间架构状态"(寄存器值)是通过系统调用或中断进入内核时保存.(在所有操作系统上都是如此).
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).
在一般的多任务操作系统中,每个进程/线程都有自己的内存空间来保存状态,因此上下文切换通过从被切换到的线程恢复保存的状态来工作.这有点简化,因为存在内核状态与节省的用户空间.状态
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. state
因此,只要进程实际上不在 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.
脚注:
- 在 Linux 中,实际切换到新线程发生在内核内部,从内核上下文到内核上下文.这将仅"保存在内核堆栈上的整数寄存器,将
rsp
设置到另一个线程的内核堆栈中的正确位置,然后恢复保存的寄存器.所以有一个函数调用,当它返回时,它在内核模式下为新线程执行,每个 CPU 内核变量设置得当.如果最初从用户空间进入内核的系统调用或中断在没有调用调度程序的情况下返回时,新线程的用户空间状态最终会以相同的方式恢复.即来自系统调用或中断内核入口点保存的状态.Lazy/Eager FPU 状态保存是另一个复杂因素;内核通常会避免接触 FPU,因此它可以避免在刚进入内核并返回相同的用户空间进程时保存/恢复 FPU 状态.
- 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.
这篇关于gdb 如何读取它正在调试的程序/进程的寄存器值?寄存器如何与进程关联?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!