Debugging Tools for Windows

改变上下文

在内核模式调试时,同时有很多进程、线程有时也有很多用户会话在运行。所以,例如"虚拟地址 0x80002000"或" eax 寄存器"这样的短语都是不明确的。必须指定这些短语所在的上下文,才可以被理解。

调试器在调试时有5种不同的上下文可以设置:

  1. 会话上下文(session context指定默认的用户会话。 (该上下文仅在Microsoft Windows XP和之后的Windows中具有。这些系统允许同时有多个登陆会话共存。)
  2. 进程上下文(process context) 用于决定调试器如何解释虚拟地址。
  3. 用户模式地址上下文(user-mode address context)几乎从来不会直接设置。该上下文在改变进程上下文时自动设置。
  4. 寄存器上下文(register context)决定调试器如何 解释寄存器,也用于控制堆栈跟踪的结果。该上下文也称为线程上下文,尽管这个术语不是完全准确。显式上下文( explicit context)也是一种寄存器上下文。如果指定了显式上下文,则它会取代当前的寄存器上下文。
  5. 局部上下文(local context)决定调试器如何解释局部变量。该上下文也称为作用域。

会话上下文

在Windows XP和之后版本的Windows中,同时可以运行多个登陆会话。每个登陆会话都有它自己的进程。

!session扩展命令显示所有登陆会话或改变当前会话上下文。

!sprocess!spoolused 扩展命令输入的会话号为"-2"时,会用到会话上下文。

会话上下文改变时,进程上下文会自动切换到该会话的活动进程。

进程上下文

每个进程都有自己的页目录,用于记录虚拟地址如何映射到物理地址的信息。当一个进程中的任何线程执行时,Windows操作系统使用页目录来解释虚拟地址。

用户模式调试时,当前进程决定了进程上下文。调试器命令、扩展命令和调试信息窗口都会使用当前进程的页目录来解释虚拟地址。

在内核模式调试时,使用.process (Set Process Context) 命令来设置进程上下文。该命令用来选择解释虚拟地址时使用哪个进程的页目录。设置了进程上下文滞后,任何用到地址的命令中都使用该上下文。也可以在该地址中设置断点。通过使用.process /i 选项来指定入侵式调试,在内核调试器里面也可以设置用户空间的断点。

也可以在内核空间函数上使用指定进程的断点来在内核调试器中设置用户模式断点。设置这种断点并等待切换到适合的上下文。

用户模式地址上下文是进程上下文的一部分。一般来说不用直接设置用户模式地址上下文。如果设置了进程上下文,用户模式地址上下文会自动改变为使用该进程关联的页表。但是,在基于Itanium的处理器上,一个进程可能拥有多于一个的页目录。这种情况下,可以使用 .context (Set User-Mode Address Context) 命令来改变用户模式地址上下文。

在内核模式调试时设置了进程上下文之后,它会一直保持到使用另一个.process命令改变为止。用户模式地址上下文也会保持到使用.process .context 命令改变为止。这些上下文在目标机运行时都不会改变,也不会因为改变寄存器上下文和局部上下文而受影响。

寄存器上下文

每个线程都有它自己的寄存器值。当线程执行时这些值保存在CPU寄存器中;当其他线程运行时,则保存在内存中。

用户模式调试时,当前线程一般决定了寄存器上下文。所有调试器命令、扩展命令和调试信息窗口中对寄存器的引用都根据当前线程的寄存器来解释。

使用下面的方法之一,在用户模式调试时可以将寄存器上下文改变为当前线程之外的值:

.cxr (Display Context Record)

.ecxr (Display Exception Context Record)

内核模式调试时,可以使用包括下面一些命令在内的多种命令来控制寄存器上下文:

.thread (Set Register Context)

.cxr (Display Context Record)

.trap (Display Trap Frame)

这些命令不改变CPU寄存器的值,调试器是从内存位置中获得指定的寄存器上下文。实际上,调试器只能找到已保存的寄存器值。(其他值是动态设置的并且不会保存下来。重建堆栈跟踪使用已保存的值就足够了。)

寄存器上下文设置之后,任何使用寄存器值的命令都会使用新的寄存器上下文,例如k (Display Stack Backtrace)r (Registers)

但是,当调试多处理器的计算机时,一些命令可以指定处理器。 (关于这些命令的更多信息,查看多多处理器语法。)如果为一条命令指定了处理器,该命令使用指定的处理器上的激活线程的寄存器上下文,而不是当前寄存器上下文,即使指定的处理器就是活动处理器。

同样,如果寄存器上下文和当前处理器模式设置不匹配,这些命令会产生错误或无意义的输出。要避免这种输出错误,在将处理器模式设置为和寄存器上下文匹配之前,这些依赖寄存器状态的命令都会失败。使用.effmach (Effective Machine) 命令改变处理器模式。

改变寄存器上下文也会改变局部上下文。所以,寄存器上下文会影响局部变量的显示。

如果任何程序发生执行、单步或跟踪的事件,寄存器上下文会立即重设为和程序计数器的位置匹配。在用户模式下,当前进程或线程改变时,寄存器上下文也会重设。

寄存器上下文也作用于堆栈跟踪,因为堆栈跟踪是从堆栈寄存器(基于x86处理器上的esp或基于Itanium处理器上的sp)的指向的位置开始的。如果寄存器上下文设置为非法或不可访问的值,就不能进行堆栈跟踪。

可以使用.apply_dbp (Apply Data Breakpoint to Context)命令在指定的寄存器上下文中设置数据断点。

局部上下文

程序运行时,局部变量的意义由程序计数器来决定,因为这些变量的作用域仅在定义它们的函数内部。

进行用户模式或内核模式调试时,调试器使用当前函数的作用域(堆栈上的当前帧)作为局部上下文。使用.frame (Set Local Context)命令或者在Calls window中双击需要的帧来改变局部上下文。

在用户模式调试时,局部上下文总是当前线程的堆栈回溯中的一帧。在内核模式调试时,局部上下文总是当前寄存器上下文的线程的堆栈回溯中的一帧。

同一时刻只能使用一个堆栈帧作为局部上下文。位于其他帧中的局部变量不能被访问。

下面这些事件发生时,局部上下文会被重置:

!for_each_frame扩展命令可以对堆栈中的每一个帧使用一条单独的命令。该命令为每一个帧改变局部上下文,执行命令,然后将局部上下文设置为原始值。

Build machine: CAPEBUILD