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 是 registers
与 process
关联的。寄存器的值应该随着操作系统调度不同的进程而不断变化?我参考了命令 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 I。 strace ./my_program
对于系统编程非常有用,尤其是在从手写 asm 进行系统调用时,解码您实际传递的参数和 return 值。
脚注:
- 在Linux中,实际切换到新线程发生在内核内部,从内核上下文到内核上下文。这会将 "only" 整数寄存器保存在内核堆栈上,将
rsp
设置到其他线程内核堆栈中的正确位置,然后恢复保存的寄存器。所以有一个函数调用,当它 returns 时,它在内核模式下为新线程执行,每个 CPU 内核变量设置得当。新线程的 User-space 状态最终会以相同的方式恢复,如果最初从 user-space 进入内核的系统调用或中断没有调用 returned调度程序。即从系统调用或中断内核入口点保存的状态。惰性/急切的 FPU 状态保存是另一个复杂问题;内核通常会避免接触 FPU,因此它可以在刚进入内核时避免 saving/restoring FPU 状态,然后 return 返回同一个用户-space 进程。
我用 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 是 registers
与 process
关联的。寄存器的值应该随着操作系统调度不同的进程而不断变化?我参考了命令 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 上都是正确的)。
请参阅
在多任务处理中 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 I。 strace ./my_program
对于系统编程非常有用,尤其是在从手写 asm 进行系统调用时,解码您实际传递的参数和 return 值。
脚注:
- 在Linux中,实际切换到新线程发生在内核内部,从内核上下文到内核上下文。这会将 "only" 整数寄存器保存在内核堆栈上,将
rsp
设置到其他线程内核堆栈中的正确位置,然后恢复保存的寄存器。所以有一个函数调用,当它 returns 时,它在内核模式下为新线程执行,每个 CPU 内核变量设置得当。新线程的 User-space 状态最终会以相同的方式恢复,如果最初从 user-space 进入内核的系统调用或中断没有调用 returned调度程序。即从系统调用或中断内核入口点保存的状态。惰性/急切的 FPU 状态保存是另一个复杂问题;内核通常会避免接触 FPU,因此它可以在刚进入内核时避免 saving/restoring FPU 状态,然后 return 返回同一个用户-space 进程。