如何使用在同一CPU上运行的调试器读取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中的寄存器时,调试器实际上是要求操作系统从保存的状态读取寄存器值。 你的程序在那个时间点没有运行,它是调试器。

假设您的系统上没有其他进程。 以下是发生的事情的简化视图:

  1. 调试器启动并获取cpu
  2. 调试器要求操作系统加载您的程序
  3. 调试器要求操作系统放置断点
  4. 调试器要求操作系统开始执行程序。 操作系统保存gdb寄存器状态并将控制权转移到您的程序。
  5. 你的程序遇到了断点。 操作系统控制,保存程序的寄存器状态,重新加载gdb寄存器并将cpu返回给gdb。
  6. 调试器要求操作系统从保存的状态读取程序的寄存器。

请注意,此机制是多任务操作系统的正常职责的一部分,它不是特定于调试。 当OS调度程序决定应该执行不同的程序时,它会保存当前状态并加载另一个。 这称为上下文切换,它可能每秒发生多次,从而产生一种错觉,即即使您只有一个cpu核心,程序也会同时执行。

在过去的单任务操作系统中,唯一可能阻碍程序执行的因素是中断。 现在,中断处理程序遇到的问题与你所说的相同,你的程序正在计算某些东西,用户按下一个键 – 中断 – 中断服务程序必须做一些工作,但不能修改过程中的单个寄存器。 这是主要原因,堆栈是首先发明的。 通常的80×86 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 

这甚至是如此常见,创建了一个新的指令对pushapopa (用于push / pop all)来简化这项任务。

在今天的操作系统和应用程序之间具有地址空间隔离的CPU中,CPU提供一些任务状态系统并允许操作系统切换任务(中断可能仍然类似于上面概述的工作,但也可以通过任务切换来处理)。 所有现代操作系统都使用这个任务状态系统的系统,其中CPU在没有被主动执行时保存进程的所有寄存器。 就像Jester已经解释过的那样, gdb只是询问操作系统在要调试的进程上的这些值,然后打印它们。