Windbg命令的语法规则系列(三)

时间:2024-10-11 14:05:08

五、源文件行语法

可以将源文件行号指定为MASM表达式的全部或部分。这些数字计算出与该源代码行对应的可执行代码的偏移量。不能使用源代码行作为C++表达式的一部分。必须用重音符(`)将源文件和行号表达式括起来。以下示例显示源文件行号的完整格式。

`[[Module!]Filename][:LineNumber]`

如果有多个文件具有相同的文件名,则文件名应包括整个目录路径和文件名。此目录路径应该是编译时使用的路径。如果只提供文件名或路径的一部分,并且存在多个匹配项,则调试器将使用找到的第一个匹配项。如果省略了文件名,调试器将使用与当前程序计数器对应的源文件。除非在行号前面加上0x,否则行号将作为十进制数读取,而不考虑当前的默认基数。如果省略了linenumber,表达式将计算出与源文件对应的可执行文件的初始地址。除非发出.lines(切换源行支持)命令或在启动windbg时包含-lines命令行选项,否则不会在cdb中计算源行表达式。

六、地址和地址范围语法

在调试器中有几种指定地址的方法。地址始终是虚拟地址,除非文档专门指明另一种地址。在用户模式下,调试器根据当前进程的页目录解释虚拟地址。在内核模式下,调试器根据进程上下文指定的进程页目录解释虚拟地址。还可以直接设置用户模式地址上下文。

6.1、地址模式和段支持

在基于x86的平台上,CDB和KD支持以下寻址模式。这些模式通过前缀来区分。

前缀 名称 地址类型

%

平坦

32 位地址 (也 16 位选择器,指向 32 位段) 和 64 位系统上的 64 位地址。

&

virtual 86

实模式地址。 基于 x86 的仅。

#

实模式地址。 基于 x86 的仅。

普通模式和虚拟模式86的区别在于普通16位地址使用段值作为选择器,并查找段描述符。但一个虚拟的86地址不使用选择器,而是直接映射到较低的1 MB中。如果通过不是当前默认模式的寻址模式访问内存,则可以使用地址模式前缀覆盖当前地址模式。
6.2、地址参数
地址参数指定变量和函数的位置。下表说明了可以在CDB和KD中使用的各种地址的语法和含义。

语法 含义

offset

虚拟内存空间,含对应于当前的执行模式的类型中的绝对地址。 例如,如果当前的执行模式是 16 位,偏移量为 16 位。 如果执行模式是 32 位分段,偏移量为 32 位分段。

&[[段:]] 偏移量

实际地址。 基于 x86 和基于 x64 的。

%segment:[[ offset]]

分段的 32 位或 64 位地址。 基于 x86 和基于 x64 的。

%[[偏移量]]

一个绝对地址 (32 位或 64 位) 的虚拟内存空间中。 基于 x86 和基于 x64 的。

name[[ +| ]] offset

一个平面 32 位或 64 位地址。 名称可以是任何符号。 偏移量指定的偏移量。 此偏移量可以是任何其前缀表示的地址模式。 无前缀指定默认模式地址。 您可以指定偏移量为正 (+) 或负值 (−)。

使用dg (Display Selector)命令查看段描述符信息。在MASM表达式中,还可以使用POI运算符取消对任何指针的引用。例如,如果地址0x00123456处的指针指向地址位置0x004200,则以下两个命令是等效的。

:> dd
:> dd poi()

在C++表达式中,指针就像C++中的指针一样。然而,数字被解释为整数。如果必须遵从实际数字,则必须首先对其进行强制转换,如下例所示

:> dd *( (long*) 0x123456 ) 

一些伪寄存器还保存着公共地址,例如当前程序计数器位置。还可以通过指定原始源文件名和行号来指示应用程序中的地址。

6.3、地址范围

您可以通过一对地址或一个地址和对象计数来指定地址范围。要通过一对地址指定范围,请指定起始地址和结束地址。例如,下面的示例是一个8字节的范围,从地址0x0001000开始。
0x00001000  0x00001007

要通过地址和对象计数指定地址范围,请指定地址参数、字母L(大写或小写)和值参数。地址指定起始地址。该值指定要检查或显示的对象数。对象的大小取决于命令。例如,如果对象大小为1字节,下面的示例是8字节的范围,从地址0x0001000开始。

0x00001000  L8

但是,如果对象大小是一个双字(32位或4字节),则以下两个范围分别给出一个8字节的范围。

0x00001000  0x00001007
0x00001000 L2

指定值还有两种其他方法(lsize范围说明符):

  • L?大小(带问号)表示与l size相同,除了l?大小删除了调试器的自动范围限制。通常,范围限制为256 MB,因为较大的范围是印刷错误。如果要指定大于256 MB的范围,必须使用L?大小语法。
  • L-size(带连字符)指定以给定地址结尾的长度大小范围。例如,8000000 L20指定从0x8000000到0x800001F的范围,8000000 L-20指定从0x7fffffffe0到0x7fffffffff的范围。

一些要求地址范围的命令接受一个地址作为参数。在这种情况下,命令使用一些默认的对象计数来计算范围的大小。通常,地址范围是最终参数的命令允许使用这种语法。

七、线程操作语法

许多调试器命令的参数都是线程标识符。波浪线(~)出现在线程标识符之前。
线程标识符可以是以下值之一:

线程标识符 描述

~.

当前线程。

~#

导致当前异常或调试事件的线程。

~*

此进程中的所有线程。

~数量

其索引的线程

~~[TID]

其线程 ID 是在线程TID。 (括号是必需的和不能添加第二个波形符和左大括号之间有空格)。

~[表达式]

线程的线程 ID 是到整数数值表达式解析。

线程在创建时被分配索引。请注意,此数字与Microsoft Windows操作系统使用的线程ID不同。调试开始时,当前线程是导致当前异常或调试事件(或调试器附加到进程时的活动线程)的线程。在使用~s(设置当前线程)命令或使用windbg中的“进程和线程”窗口指定新线程之前,该线程一直是当前线程。线程标识符通常作为命令前缀出现。请注意,并非所有通配符都在使用线程标识符的所有命令中可用。~[expression]语法的一个例子是~[@$t0]。在本例中,线程根据用户定义的伪寄存器的值进行更改。此语法允许调试器脚本以编程方式选择线程。

在内核模式下控制线程

在内核模式下,不能使用线程标识符控制线程。在用户模式调试期间,可以使用波浪线字符(~)指定线程。在内核模式调试中,可以使用tilde指定处理器。

八、进程操作语法

许多调试器命令的参数都是进程标识符。一个竖条(|)出现在进程标识符之前。进程标识符可以是以下值之一。

进程标识符 描述

|.

当前进程。

|#

引起当前异常或调试事件的进程。

|*

所有进程。

|数量

进程的第几

|~[PID]

进程的进程 ID PID。 (括号是必需的和不能添加颚化符 (~) 和左大括号之间有空格)。

|[Expression]

其进程 ID 的整数进程数值表达式解析。

进程在创建时被分配序号。请注意,此数字与Microsoft Windows操作系统使用的进程ID(PID)不同。当前进程定义内存空间和使用的线程集。调试开始时,当前进程是导致当前异常或调试事件(或调试器附加到的进程)的进程。在使用(设置当前进程)命令或使用windbg中的“进程和线程”窗口指定新进程之前,该进程一直是当前进程。进程标识符在多个命令中用作参数,通常用作命令前缀。注意,windbg和cdb可以调试原始进程创建的子进程。windbg和cdb也可以附加到多个不相关的进程。|[表达式]语法的一个例子是[|@$t0]。在本例中,进程根据用户定义的伪寄存器的值而变化。此语法允许调试器脚本以编程方式选择进程。在内核模式下,不能使用进程标识符来控制进程。
九、系统操作语法
许多调试器命令的参数都是进程标识符。系统标识符前会出现两个竖线(||)。系统标识符可以是以下值之一。
系统标识符 描述

||.

当前系统

||#

导致当前异常或调试事件系统。

||*

所有系统。

||ddd

系统其序号ddd

系统按照调试器附加到它们的顺序分配序号。调试开始时,当前系统是导致当前异常或调试事件的系统(或是调试器最近附加到的系统)。在使用(||s (Set Current System))命令或使用windbg中的“进程和线程”窗口指定新系统之前,该系统将保持当前系统。
示例
此示例演示三个转储文件已加载。 系统 1 处于活动状态,并且系统 2 导致调试事件。
||1:1:017> ||
0 User mini dump: c:\notepad.dmp
. 1 User mini dump: c:\paint.dmp
# 2 User mini dump: c:\calc.dmp
要使用多个系统,可以同时使用.opendump调试多个崩溃转储。

注意,在一起调试活动目标和转储目标时会有一些复杂的情况,因为对于每种调试类型,命令的行为都不同。例如,如果在当前系统是转储文件时使用g(go)命令,则调试器将开始执行,但不能重新进入调试器,因为break命令不被识别为对转储文件调试有效。

十、多处理器操作语法
 kd和内核模式windbg支持多处理器调试。您可以在任何多处理器平台上执行这种调试。处理器编号为0到N。如果当前处理器是处理器0(即,如果是当前导致调试器处于活动状态的处理器),则可以检查其他非当前处理器(从一个处理器到n个处理器)。但是,您不能更改非当前处理器中的任何内容。您只能查看它们的状态。
10.1、选择处理器
可以使用.echocpnum(show cpu number)命令显示当前处理器的处理器号。此命令的输出使您能够通过内核调试提示中的文本立即判断您在多处理器系统上工作的时间。
在下面的示例中,0:在kd>提示前表示正在调试计算机中的第一个处理器。
0: kd>

使用~s(更改当前处理器)命令在处理器之间切换,如下例所示。

0: kd> ~1s
1: kd>

现在正在调试的计算机中的第二个处理器。

如果遇到中断而无法理解堆栈跟踪,则可能需要更改多处理器系统上的处理器。中断可能发生在其他处理器上。

10.2、在其他命令中指定处理器
您可以在多个命令之前添加处理器编号。除了在~s命令中,此数字前面没有波浪号(~)。在用户模式调试中,tilde用于指定线程。有关此语法的详细信息,请参阅线程语法。不必显式引用处理器ID。相反,可以使用解析为与处理器ID对应的整数的数值表达式。若要指示应将表达式解释为处理器,请使用以下语法。
||[Expression]

在这种语法中,方括号是必需的,表达式表示任何解析为与处理器ID对应的整数的数值表达式。在下面的示例中,处理器根据用户定义的伪寄存器的值进行更改。

||[@$t0]

示例

下面的示例使用k(显示堆栈回溯)命令显示来自处理器2的堆栈跟踪。
1: kd> 2k

下面的示例使用r(registers)命令显示处理器3的EAX寄存器。

1: kd> 3r eax
但是,以下命令给出了一个语法错误,因为您不能更改当前处理器以外的处理器的状态。
1: kd> 3r eax=808080

10.3、断点

在内核调试期间,bp、bu、bm(设置断点)和ba(访问中断)命令适用于多处理器计算机的所有处理器。例如,如果当前处理器是三个,则可以输入以下命令在someaddress处放置断点。

1: kd> bp SomeAddress

然后,在该地址执行的任何处理器(不仅是处理器一个)都会导致断点陷阱。

10.4、显示处理器信息

您可以使用!运行扩展以显示目标计算机上每个处理器的状态。对于每个处理器,!running还可以显示进程控制块(prcb)中的当前和下一个线程字段、16个内置排队spinlocks的状态以及堆栈跟踪。 您可以使用!cpuinfo和!cpuid扩展以显示有关处理器本身的信息。