如何在同一个 CPU 上使用调试器 运行 读取 CPU 寄存器?
How is it possible to read the CPU registers using a debugger running on the same CPU?
在学习汇编时,我按以下方式使用 GDB:
gdb ./a.out (a is a compiled C script that only prints hello world)
break main
run
info registers
为什么我自己使用相同的CPU打印寄存器,却能看到程序使用的寄存器? GDB(或操作系统)的使用不应该覆盖寄存器并且只显示被覆盖的寄存器吗?
我能想到的唯一答案就是我的CPU是双核,其中一个内核正在使用,另一个保留给程序。
操作系统维护每个执行线程的寄存器状态。当您检查 gdb 中的寄存器时,调试器实际上要求 OS 从保存的状态中读取寄存器值。你的程序在那个时间点不是 运行,而是调试器。
假设您的系统上没有其他进程。以下是所发生情况的简化视图:
- 调试器启动并获取 cpu
- 调试器要求 OS 加载您的程序
- 调试器要求 OS 放置断点
- 调试器要求 OS 开始执行您的程序。 OS 保存 gdb 寄存器状态并将控制转移到您的程序。
- 您的程序遇到了断点。 OS 取得控制权,保存程序的寄存器状态,重新加载 gdb 寄存器并将 cpu 返回给 gdb。
- 调试器要求 OS 从保存的状态中读取程序的寄存器。
请注意,此机制是多任务操作系统的正常职责的一部分,并非特定于调试。当 OS 调度程序决定应该执行一个不同的程序时,它会保存当前状态并加载另一个。这称为上下文切换,每秒可能发生多次,给人一种程序同时执行的错觉,即使您只有一个 cpu 核心。
回到过去的单任务处理时代 OSses,唯一可能妨碍程序执行的事情就是中断。现在,中断处理程序遇到了您正在谈论的相同问题,您的程序正在计算某些东西,用户按下一个键 - 中断 - 中断服务例程 有 做一些工作但是 不得修改进程中的单个寄存器。这就是主要原因,首先发明了堆栈。通常的 80x86 DOS 中断服务例程如下所示:
push ax
push cx
push dx
push bx
push si
push di
push bp
// no need to push sp
[do actual work, caller registers avaiable on stack if needed]
pop bp
pop di
pop si
pop bx
pop dx
pop cx
pop ax
iret
这甚至是如此普遍,以至于创建了新的指令对 pusha
和 popa
(对于 push/pop 所有人)来简化这项任务。
在今天的 CPUs 中,操作系统和应用程序之间的地址 space 隔离,CPUs 提供一些任务状态系统并允许操作系统切换任务(中断可能仍会像上面概述的那样工作,但也可以通过任务切换来处理)。所有现代 OSses 都使用这种任务状态系统,其中 CPU 在进程未被主动执行时保存进程的所有寄存器。就像 Jester 已经解释过的那样,gdb
只是向 OS 询问要调试的进程的这个值,然后打印它们。
在学习汇编时,我按以下方式使用 GDB:
gdb ./a.out (a is a compiled C script that only prints hello world)
break main
run
info registers
为什么我自己使用相同的CPU打印寄存器,却能看到程序使用的寄存器? GDB(或操作系统)的使用不应该覆盖寄存器并且只显示被覆盖的寄存器吗? 我能想到的唯一答案就是我的CPU是双核,其中一个内核正在使用,另一个保留给程序。
操作系统维护每个执行线程的寄存器状态。当您检查 gdb 中的寄存器时,调试器实际上要求 OS 从保存的状态中读取寄存器值。你的程序在那个时间点不是 运行,而是调试器。
假设您的系统上没有其他进程。以下是所发生情况的简化视图:
- 调试器启动并获取 cpu
- 调试器要求 OS 加载您的程序
- 调试器要求 OS 放置断点
- 调试器要求 OS 开始执行您的程序。 OS 保存 gdb 寄存器状态并将控制转移到您的程序。
- 您的程序遇到了断点。 OS 取得控制权,保存程序的寄存器状态,重新加载 gdb 寄存器并将 cpu 返回给 gdb。
- 调试器要求 OS 从保存的状态中读取程序的寄存器。
请注意,此机制是多任务操作系统的正常职责的一部分,并非特定于调试。当 OS 调度程序决定应该执行一个不同的程序时,它会保存当前状态并加载另一个。这称为上下文切换,每秒可能发生多次,给人一种程序同时执行的错觉,即使您只有一个 cpu 核心。
回到过去的单任务处理时代 OSses,唯一可能妨碍程序执行的事情就是中断。现在,中断处理程序遇到了您正在谈论的相同问题,您的程序正在计算某些东西,用户按下一个键 - 中断 - 中断服务例程 有 做一些工作但是 不得修改进程中的单个寄存器。这就是主要原因,首先发明了堆栈。通常的 80x86 DOS 中断服务例程如下所示:
push ax
push cx
push dx
push bx
push si
push di
push bp
// no need to push sp
[do actual work, caller registers avaiable on stack if needed]
pop bp
pop di
pop si
pop bx
pop dx
pop cx
pop ax
iret
这甚至是如此普遍,以至于创建了新的指令对 pusha
和 popa
(对于 push/pop 所有人)来简化这项任务。
在今天的 CPUs 中,操作系统和应用程序之间的地址 space 隔离,CPUs 提供一些任务状态系统并允许操作系统切换任务(中断可能仍会像上面概述的那样工作,但也可以通过任务切换来处理)。所有现代 OSses 都使用这种任务状态系统,其中 CPU 在进程未被主动执行时保存进程的所有寄存器。就像 Jester 已经解释过的那样,gdb
只是向 OS 询问要调试的进程的这个值,然后打印它们。