windbg 调试器简明手册——第 7 章——调试器命令——第 1 节——语法规则

目录

[1 基本语法规则](#1 基本语法规则)

[2 数值表达式语法](#2 数值表达式语法)

[2.1 MASM 数和运算符](#2.1 MASM 数和运算符)

[2.1.1 将表达式求值器设置为 MASM](#2.1.1 将表达式求值器设置为 MASM)

[2.1.2 调试器 MASM 表达式中的数](#2.1.2 调试器 MASM 表达式中的数)

[2.1.3 调试器 MASM 表达式中的符号](#2.1.3 调试器 MASM 表达式中的符号)

[2.1.4 MASM 表达式中的数值运算符](#2.1.4 MASM 表达式中的数值运算符)

[2.1.5 MASM 表达式中的非数值运算符](#2.1.5 MASM 表达式中的非数值运算符)

[2.1.6 MASM 表达式中的寄存器和伪寄存器](#2.1.6 MASM 表达式中的寄存器和伪寄存器)

[2.1.7 MASM 表达式中的源代码行号](#2.1.7 MASM 表达式中的源代码行号)

[2.2 C++ 数和运算符](#2.2 C++ 数和运算符)

[2.2.1 将表达式求值器设置为 C++](#2.2.1 将表达式求值器设置为 C++)

[2.2.2 C++表达式中的寄存器和伪寄存器](#2.2.2 C++表达式中的寄存器和伪寄存器)

[2.2.3 C++ 表达式中的数](#2.2.3 C++ 表达式中的数)

[2.2.4 C++ 表达式中的字符和字符串](#2.2.4 C++ 表达式中的字符和字符串)

[2.2.5 C++ 表达式中的符号](#2.2.5 C++ 表达式中的符号)

[2.2.6 C++ 表达式中的结构体](#2.2.6 C++ 表达式中的结构体)

[2.2.7 C++ 表达式中的运算符](#2.2.7 C++ 表达式中的运算符)

[2.2.8 引用和类型转换](#2.2.8 引用和类型转换)

[2.2.9 值运算](#2.2.9 值运算)

[2.2.10 算术运算](#2.2.10 算术运算)

[2.2.11 赋值](#2.2.11 赋值)

[2.2.12 计算](#2.2.12 计算)

[2.2.13 C++ 表达式中的宏](#2.2.13 C++ 表达式中的宏)

[2.3 MASM表达式对比C++ 表达式](#2.3 MASM表达式对比C++ 表达式)

[2.4 混合表达式的例子](#2.4 混合表达式的例子)

[2.4.1 条件断点](#2.4.1 条件断点)

[2.4.2 条件表达式](#2.4.2 条件表达式)

[2.4.3 MASM和C++混合表达式例子](#2.4.3 MASM和C++混合表达式例子)

[2.5 符号扩展](#2.5 符号扩展)

[2.5.1 MASM中的符号扩展](#2.5.1 MASM中的符号扩展)

[2.5.2 C++ 表达式中的符号扩展](#2.5.2 C++ 表达式中的符号扩展)

[2.5.3 显示符号扩展和64位数](#2.5.3 显示符号扩展和64位数)

[2.6 ? (求值表达式)(Evaluate Expression)](#2.6 ? (求值表达式)(Evaluate Expression))

[2.6.1 参数](#2.6.1 参数)

[2.6.2 环境](#2.6.2 环境)

[2.6.3 评注](#2.6.3 评注)

[2.7 选择表达式求值器](#2.7 选择表达式求值器)

[2.7.1 参数](#2.7.1 参数)

[2.7.2 环境](#2.7.2 环境)

[2.7.3 评注](#2.7.3 评注)

[3 字符串通配符语法](#3 字符串通配符语法)

[4 寄存器语法](#4 寄存器语法)

[4.1 基于X86 处理器的标识](#4.1 基于X86 处理器的标识)

[4.2 寄存器和线程](#4.2 寄存器和线程)

[5 伪寄存器语法](#5 伪寄存器语法)

[5.1 自动伪寄存器](#5.1 自动伪寄存器)

[5.2 用户定义伪寄存器](#5.2 用户定义伪寄存器)

[5.3 使用示例](#5.3 使用示例)

[6 源代码行号语法](#6 源代码行号语法)

[7 地址和地址范围语法](#7 地址和地址范围语法)

[7.1 显示内存地址之例子](#7.1 显示内存地址之例子)

[7.2 地址范围](#7.2 地址范围)

[7.3 L大小范围指定符(L大概是Length的首字母?)](#7.3 L大小范围指定符(L大概是Length的首字母?))

[7.4 搜索内存地址范围之例子](#7.4 搜索内存地址范围之例子)

[7.5 反汇编内存之例子](#7.5 反汇编内存之例子)

[7.6 寻址模式和段支持](#7.6 寻址模式和段支持)

[7.7 寻址参数](#7.7 寻址参数)

[7.8 其它地址相关命令](#7.8 其它地址相关命令)

[7.8.1 显示内存信息命令 !adress](#7.8.1 显示内存信息命令 !adress)

[7.8.2 搜索内存命令 s](#7.8.2 搜索内存命令 s)

[7.8.3 显示内存内容命令](#7.8.3 显示内存内容命令)

[8 线程语法](#8 线程语法)

[9 进程语法](#9 进程语法)

[10 系统语法](#10 系统语法)

[11 多处理器语法](#11 多处理器语法)

[11.1 选择一个处理器](#11.1 选择一个处理器)

[11.2 用其它命令指定一个处理器](#11.2 用其它命令指定一个处理器)

[11.3 应用举例](#11.3 应用举例)

[11.4 断点](#11.4 断点)

[11.5 显示处理器断点](#11.5 显示处理器断点)


1 基本语法规则

(1) 命令和参数可以使用任意大小写字母组合,除非本节主题另有特别说明。

(2) 多个命令参数之间可以用一个或多个空格或逗号 (,) 分隔。

(3) 通常可以省略命令与其第一个参数之间的空格。如果省略其他空格不会造成歧义,通常也可以省略。

本节中的命令参考主题使用了以下项目:

**(1)**粗体字表示必须逐字输入的内容。

**(2)**斜体字表示参考主题"参数"部分中解释的参数。

**(3)**方括号 ([xxx]) 中的参数是可选的。带竖线的方括号 ([xxx|yyy]) 表示可以使用其中一个参数,也可以不使用任何参数。

**(4)**带竖线的花括号 ({xxx|yyy}) 表示必须使用其中一个参数。

命令分为3种:

(1) 常规命令(regular commands)。

(2) 逾命令 (meta commands)(即区别于常规命令),带前缀 ( 实心点)

逾命令控制调试器本身的行为和环境,并不直接以调试的方式与目标程序的执行或内存进行交互,而是配置调试会话。

(3) 扩展命令 (regular commands),带前缀 ! (叹号)

扩展命令(也称 bang 命令)在扩展 DLL(例如用于 .NET 调试的 sos.dll 或各种内核模式扩展)中实现,并提供用于检查特定数据结构或执行复杂分析的专用功能。它们用于高级调试任务,尤其适用于分析崩溃转储或特定系统组件。

2 数值表达式语法

调试器接受两种不同的数值表达式:C++ 表达式和MASM表达式。每一种表达式都有其自身的输入和输出语法规则。

使用 .expr (选择表达式求值器)命令可以在调试器运行后显示或更改表达式求值器。

2.1 MASM 数和运算符

本主题介绍如何在 Windows 调试工具中使用 Microsoft 宏汇编程序 (MASM) 表达式语法。

调试器接受两种不同的数值表达式:C++ 表达式和MASM表达式。每种表达式都有其自身的输入和输出语法规则。

有关何时使用每种语法类型的更多信息,请参阅"求值表达式"和"?"(求值表达式)。

在下述例子中,"?"命令使用MASM表达式求值器显示指令指针寄存器的值。

0:000> ? @rip

Evaluate expression: 140709230544752 = 00007ff9`6bb40770

2.1.1 将表达式求值器设置为MASM

使用 .expr(选择表达式求值器)查看默认表达式求值器,并将其更改为 MASM。

0:000> .expr /s masm

Current expression evaluator: MASM - Microsoft Assembler expressions

现在默认表达式求值器已更改,可以使用 `?`(求值表达式)命令来显示 MASM 表达式。此示例将十六进制值 8 添加到 rip 寄存器。

0:000> ? @rip + 8

Evaluate expression: 140709230544760 = 00007ff9`6bb40778

@rip 的寄存器引用在寄存器语法中有更详细的描述。

2.1.2 调试器 MASM表达式中的数

在 MASM 表达式中,你可以输入 16 进制、10 进制、8 进制或 2 进制的数。

使用 n(设置数的基数)命令可以将默认基数设置为 16、10 或 8。所有未加前缀的数都将按该基数解释。你可以通过指定 0x 前缀(十六进制)、0n 前缀(十进制)、0t 前缀(八进制)或 0y 前缀(二进制)来覆盖默认基数。

你还可以通过在数后添加 h 来指定十六进制数 **。数中可以使用大写或小写字母。**例如,"0x4AB3"、"0X4aB3"、"4AB3h"、"4ab3h"和"4aB3H"具有相同的含义。

如果在表达式的前缀后不添加数,则该数将被读取为 0 。因此,你可以将 0 写成 0,前缀后跟 0,或者只写前缀。例如,在十六进制中,"0"、"0x0"和"0x"具有相同的含义。

你可以输入 xxxxxxxx ` xxxxxxxx 格式的 64 位十六进制值。你也可以省略反引号 ( ` ) 。如果包含反引号,则自动符号扩展将被禁用。

下述示例演示如何将十进制、八进制和二进制值添加到寄存器 10。

? @r10 + 0x10 + 0t10 + 0y10

Evaluate expression: 26 = 00000000`0000001a

2.1.3 调试器 MASM表达式中的符号

MASM 表达式中,任何符号的数值都是其内存地址 。根据符号所指代的对象,该地址可以是全局变量、局部变量、函数、段、模块或任何其他可识别标签的地址。

要指定地址关联的模块,请在符号名称前加上模块名称和一个感叹号(!)。如果符号可以解释为十六进制数,则在符号名称前加上模块名称和一个感叹号,或者只加上一个感叹号。有关符号识别的更多信息,请参阅"符号语法"和"符号匹配"。

使用两个冒号 (::) 或两个下划线 (__) 来表示类的成员。

仅当在符号名称前添加了模块名称和感叹号时,才在符号名称中使用反引号 (`) 或撇号 (')。

2.1.4 MASM表达式中的数值运算符

**你可以使用一元运算符修改表达式的任何组成部分。你可以使用二元运算符组合任意两个组成部分。一元运算符的优先级高于二元运算符。**当你使用多个二元运算符时,运算符遵循下表所述的固定优先级规则。

你始终可以使用括号来覆盖优先级规则。

如果 MASM 表达式的一部分包含在括号中,并且表达式前面出现两个 @ 符号 (@@),则该表达式将根据 C++ 表达式规则进行解释你不能在两个 @ 符号和左括号之间添加空格。你还可以使用 @@c++( ... ) 或 @@masm( ... ) 来指定表达式求值器。

执行算术运算时,MASM 表达式求值器会将所有数和符号视为 ULONG64 类型。

一元地址运算符假定 DS 为地址的默认段(译注:X64架构处理器无此段)。表达式按运算符优先级顺序求值。如果相邻运算符的优先级相同,则表达式从左到右求值。

你可以使用以下一元运算符。

|---------|------------------------------------------------------------------------------------|
| 运算符 | 用途 |
| + | 一元加 |
| - | 一元减 |
| not | 某参数为0,则返回 1 ,若参数为任意非零值,则返回0 |
| hi | 高16位 |
| low | 低16位 |
| by | 来自具体地址的低序字节 |
| pby | 同by,只不过其取物理地址。只有使用默认缓存行为的物理地址可以读取。 | | wo | 来自具体地址的低序字 | | pwo | 同wo,只不过其取物理地址。只有使用默认缓存行为的物理地址可以读取。 |
| dwo | 来自具体地址的双字 |
| pdwo | 同dwo,只不过其取物理地址。只有使用默认缓存行为的物理地址可以读取。 | | qwo | 来自具体地址的四字 | | pqwo | 同qwo,只不过其取物理地址。只有使用默认缓存行为的物理地址可以读取。 |
| poi | 从具体地址获取指针大小的数据。指针大小为 32 位或 64 位。在内核调试中,此大小取决于目标计算机的处理器。因此,如果你需要指针大小的数据,poi 是最佳运算符。 |
| $ppoi | 同poi,只不过其取物理地址。只有使用默认缓存行为的物理地址可以读取。 |

示例:

以下示例展示了如何使用 poi 解引用指针来查看存储在该内存位置的值。

(1) **首先确定目标内存地址。**例如,我们可以查看线程结构,并决定要查看 CurrentLocale 的值。

0:000> dx @$teb

@$teb : 0x8eed57b000 [Type: _TEB *]

+0x000\] NtTib \[Type: _NT_TIB

+0x038\] EnvironmentPointer : 0x0 \[Type: void \*

+0x040\] ClientId \[Type: _CLIENT_ID

+0x050\] ActiveRpcHandle : 0x0 \[Type: void \*

+0x058\] ThreadLocalStoragePointer : 0x1f8f9d634a0 \[Type: void \*

+0x060\] ProcessEnvironmentBlock : 0x8eed57a000 \[Type: _PEB \*

+0x068\] LastErrorValue : 0x0 \[Type: unsigned long

+0x06c\] CountOfOwnedCriticalSections : 0x0 \[Type: unsigned long

+0x070\] CsrClientThread : 0x0 \[Type: void \*

+0x078\] Win32ThreadInfo : 0x0 \[Type: void \*

+0x080\] User32Reserved \[Type: unsigned long \[26\]

+0x0e8\] UserReserved \[Type: unsigned long \[5\]

+0x100\] WOW32Reserved : 0x0 \[Type: void \*

+0x108\] CurrentLocale : 0x409 \[Type: unsigned long

CurrentLocale 位于 TEB 起始位置之后 0x108 处。

0:000> ? @$teb + 0x108

Evaluate expression: 613867303176 = 0000008e`ed57b108

(2) 使用 poi 解引用这个地址,获得存储内容。

0:000> ? poi(0000008e`ed57b108)

Evaluate expression: 1033 = 00000000`00000409

返回值 409() 与 TEB 结构中的 CurrentLocale 值匹配。

或者使用 poi 和括号来解引用计算出的地址。

0:000> ? poi(@$teb + 0x108)

Evaluate expression: 1033 = 00000000`00000409

使用 by 或 wo 一元运算符从目标地址返回一个字节或一个字。

0:000> ? by(0000008e`ed57b108)

Evaluate expression: 9 = 00000000`00000009

0:000> ? wo(0000008e`ed57b108)

Evaluate expression: 1033 = 00000000`00000409

二元运算符:

你可以使用以下二元运算符。每一个单元格中的运算符优先级高于其下方单元格中的运算符。同一单元格中的运算符优先级相同,并按从左到右的顺序解析。

|-----------|--------------|
| 运算符 | 用途 |
| * | 乘 |
| / | 整除 |
| mod(或%) | 模运算(求余) |
| + | 加 |
| - | 减 |
| << | 按位左移 |
| >> | 位位右移 |
| >>> | 算术右移 |
| = (或 == ) | 等于 |
| < | 小于 |
| > | 大于 |
| <= | 小于等于 |
| >= | 大于等于 |
| != | 不等于 |
| and(或&) | 按位与(AND) |
| xor(或^) | 按位或(XOR)(异或) |
| or(|) | 按位或(OR) |

< 、> 、= 、== 和 != 比较运算符在表达式为真时计算结果为 1,在表达式为假时计算结果为 0。单个等号 (=) 与两个等号 (==) 等效。MASM 表达式中不能使用副作用或赋值语句。

无效操作(例如除以零)会导致"操作数错误"返回到调试器命令窗口。

我们可以使用 == 比较运算符来检查返回值是否与 0x409 匹配。

0:000> ? poi(@$teb + 0x108)==0x409

Evaluate expression: 1 = 00000000`00000001

2.1.5 MASM表达式中的非数值运算符

你还可以在 MASM 表达式中使用以下扩展运算符。

|----------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 运算符 | 用途 |
| **fnsucc(** *FnAddress* , *RetVal* , *Flag* **)** | 将 RetVal 值解释为位于 FnAddress 地址处的函数的返回值。如果此返回值符合成功代码的条件,则 fnsucc 返回 TRUE。否则,fnsucc 返回 FALSE。 如果返回类型为 BOOL、bool、HANDLE、HRESULT 或 NTSTATUS,则 fnsucc 可以正确判断指定的返回值是否符合成功代码的条件。如果返回类型为指针,则除 NULL 以外的所有值都符合成功代码的条件。对于任何其他类型,成功与否由 Flag 的值决定。如果 Flag 为 0,则 RetVal 的非零值表示成功。如​​果 Flag 为 1,则 RetVal 的零值表示成功。 |
| iment (** *Address* **)** | 返回已加载模块列表中镜像入口点的地址。Address 指定可移植可执行文件 (PE) 镜像的基址。该入口点通过在 Address 指定的镜像的 PE 镜像头中查找镜像入口点来找到。 你可以使用此函数对已在模块列表中的模块进行操作,也可以使用 bu 命令设置未解析的断点。 | | **scmp(" String1 ", " String2 ") | 使用 strcmp C 函数,结果为 -1、0 或 1,就像 strcmp 一样。 |
| sicmp("** *String1* **", "** *String2* **")** | 计算结果为 -1、0 或 1,类似于 Microsoft Win32 函数 stricmp。 | | **spat(" String ", " Pattern ") | 根据字符串是否与模式匹配,返回 TRUE 或 FALSE。匹配不区分大小写。模式可以包含各种通配符和说明符。有关语法的更多信息,请参阅字符串通配符语法。 |
| **vvalid(** *Address* **,** *Length* **)** | 判断从地址 (Address) 开始并延伸 Length 字节的内存范围是否有效。如果内存有效,则 vvalid 的值为 1;如果内存无效,则 $vvalid 的值为 0。 |

示例:

以下展示了如何使用该功能来调查已加载模块周围的有效内存范围。

(1) 首先确定感兴趣区域的地址,例如使用 lm ( 列出已加载模块)命令

0:000> lm

start end module name

00007ff6`0f620000 00007ff6`0f658000 notepad (deferred)

00007ff9`591d0000 00007ff9`5946a000 COMCTL32 (deferred)

...

(2) 使用 $vvalid 检查内存范围。

0:000> ? $vvalid(0x00007ff60f620000, 0xFFFF)

Evaluate expression: 1 = 00000000`00000001

(3) 使用 $vvalid 来确认这个较大的范围是否为无效的内存范围。

0:000> ? $vvalid(0x00007ff60f620000, 0xFFFFF)

Evaluate expression: 0 = 00000000`00000000

这也是一个无效范围。

(4) 当内存范围有效时,使用 not 来返回 0 。

0:000> ? not($vvalid(0x00007ff60f620000, 0xFFFF))

Evaluate expression: 0 = 00000000`00000000

(5) 使用 $imnet 查看 COMCTL32 的入口点,我们之前使用 lm 命令确定了该地址。它从 00007ff9`591d0000 开始。

0:000> ? $iment(00007ff9`591d0000)

Evaluate expression: 140708919287424 = 00007ff9`59269e80

(6) 对返回的地址进行反编译,以检查入口点代码。

0:000> u 00007ff9`59269e80

COMCTL32!DllMainCRTStartup:

00007ff9`59269e80 48895c2408 mov qword ptr [rsp+8],rbx

00007ff9`59269e85 4889742410 mov qword ptr [rsp+10h],rsi

00007ff9`59269e8a 57 push rdi

输出中显示 COMCTL32,确认这是该模块的入口点。

2.1.6 MASM表达式中的寄存器和伪寄存器

你可以在 MASM 表达式中使用寄存器和伪寄存器。你可以在所有寄存器和伪寄存器前添加 @ 符号@ 符号可以使调试器更快地访问该值。对于大多数常见的基于 x86 的寄存器,@ 符号并非必需。对于其他寄存器和伪寄存器,我们建议你添加 @ 符号,但实际上并非必须。如果你省略不太常用寄存器的 @ 符号,调试器会尝试将文本解析为十六进制数,然后解析为符号,最后解析为寄存器值。

你也可以使用**句点 (.)**来指示当前指令指针。句点前不应添加 @ 符号,且句点不能用作 r 命令的第一个参数。此句点与 $ip 伪寄存器具有相同的含义。

有关寄存器和伪寄存器的更多信息,请参阅寄存器语法和伪寄存器语法。

使用 r 寄存器命令查看 @rip 寄存器的值,发现其为 00007ffb`7ed00770。

0:000> r

rax=0000000000000000 rbx=0000000000000010 rcx=00007ffb7eccd2c4

rdx=0000000000000000 rsi=00007ffb7ed61a80 rdi=00000027eb6a7000

rip=00007ffb7ed00770 rsp=00000027eb87f320 rbp=0000000000000000

r8=00000027eb87f318 r9=0000000000000000 r10=0000000000000000

r11=0000000000000246 r12=0000000000000040 r13=0000000000000000

r14=00007ffb7ed548f0 r15=00000210ea090000

iopl=0 nv up ei pl zr na po nc

cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246

ntdll!LdrpDoDebuggerBreak+0x30:

00007ffb`7ed00770 cc int 3

使用句点快捷键可以显示相同的值。

0:000> ? .

Evaluate expression: 140718141081456 = 00007ffb`7ed00770

我们可以使用以下 MASM 表达式确认这些值都是等效的,如果等效则返回零。

0:000> ? NOT((ip = .) AND (ip = @rip) AND (@rip =. ))

Evaluate expression: 0 = 00000000`00000000

2.1.7 MASM表达式中的源代码行号

你可以在 MASM 表达式中使用源文件和行号表达式。这些表达式必须用反引号 (`) 括起来。有关语法的更多信息,请参阅"源行语法"。

2.2 C++ 数和运算符

C++ 表达式解析器支持所有形式的 C++ 表达式语法。该语法涵盖所有​​数据类型,包括指针、浮点数和数组,以及所有 C++ 一元运算符和二元运算符。

调试器中的"监视( Watch ) "和"局部变量( Locals ) "窗口始终使用 C++ 表达式求值器。

在以下示例中,?? 求值C++ 表达式命令显示指令指针寄存器的值。

0:000> ?? @eip

unsigned int 0x771e1a02

我们可以使用 C++ 的 sizeof 函数来确定结构体的大小。

0:000> ?? (sizeof(_TEB))

unsigned int 0x1000

2.2.1 将表达式求值器设置为 C++

使用 .expr 选择表达式求值器查看默认表达式求值器并将其更改为 C++。

0:000> .expr

Current expression evaluator: MASM - Microsoft Assembler expressions

0:000> .expr /s c++

Current expression evaluator: C++ - C++ source expressions

更改默认表达式求值器后,可以使用 ? 求值表达式命令显示 C++ 表达式。以下示例显示指令指针寄存器的值。

0:000> ? @eip

Evaluate expression: 1998461442 = 771e1a02

要了解有关 @eip 寄存器参考的更多信息,请参阅寄存器语法。

在下述例子中,十六进制值 0xD 被添加到 eip 寄存器中。

0:000> ? @eip + 0xD

Evaluate expression: 1998461455 = 771e1a0f

2.2.2 C++ 表达式中的寄存器和伪寄存器

你可以在 C++ 表达式中使用寄存器和伪寄存器。寄存器或伪寄存器名称前必须加上 @ 符号。

**表达式求值器会自动执行正确的类型转换。实际寄存器和整数值伪寄存器会转换为 ULONG64 类型。**所有地址会被转换为 PUCHAR 类型,thread 会转换为 ETHREAD\* 类型,proc 会转换为 EPROCESS* 类型,teb 会转换为 TEB\* 类型,peb 会转换为 PEB* 类型。

下述例子显示 TEB :

0:000> ?? @$teb

struct _TEB * 0x004ec000

+0x000 NtTib : _NT_TIB

+0x01c EnvironmentPointer : (null)

+0x020 ClientId : _CLIENT_ID

+0x028 ActiveRpcHandle : (null)

+0x02c ThreadLocalStoragePointer : 0x004ec02c Void

+0x030 ProcessEnvironmentBlock : 0x004e9000 _PEB

+0x034 LastErrorValue : 0xbb

+0x038 CountOfOwnedCriticalSections : 0

不能通过赋值运算符或副作用运算符来更改寄存器或伪寄存器的值。必须使用 r 寄存器命令来更改这些值。

下述示例将伪寄存器的值设置为 5,然后显示该值。

0:000> r $t0 = 5

0:000> ?? @$t0

unsigned int64 5

有关寄存器和伪寄存器的更多信息,请参阅寄存器语法和伪寄存器语法。

2.2.3 C++ 表达式中的数

除非你另行指定,否则 C++ 表达式中的数会被解释为十进制数。要指定十六进制整数,请在数前加 0x。要指定八进制整数,请在数前加 0(零)。

默认调试器进制不会影响你输入 C++ 表达式的方式。不能直接输入二进制数,除非在 C++ 表达式中嵌套 MASM 表达式。

你可以输入 xxxxxxxx`xxxxxxxx 格式的 64 位十六进制值。也可以省略反引号 (`)。两种格式都会生成相同的值。

你可以将 L、U 和 I64 后缀与整数值一起使用。生成的数的实际大小取决于后缀和输入的数。有关此解释的更多信息,请参阅 C++ 语言参考。

C++ 表达式求值器的输出将保持 C++ 表达式规则指定的数据类型。但是,如果将此表达式用作命令的参数,则始终会进行类型转换。例如,当整数值用作命令参数中的地址时,无需将其转换为指针。如果表达式的值无法有效地转换为整数或指针,则会发生语法错误。

你可以将 0n(十进制)前缀用于某些输出,但不能将其用于 C++ 表达式输入。

2.2.4 C++ 表达式中的字符和字符串

你可以通过用单引号 (') 将字符括起来来输入字符。允许使用标准的 C++ 转义字符。

你可以通过用双引号 (") 将字符串字面量括起来来输入字符串字面量。可以在字符串中使用反斜杠 (\") 作为转义序列。但是,字符串对于表达式求值器没有实际意义。

2.2.5 C++ 表达式中的符号

在 C++ 表达式中,每一个符号都会根据其类型进行解释。根据符号所指代的对象,它可以解释为整数、数据结构、函数指针或任何其他数据类型。如果在 C++ 表达式中使用与 C++ 数据类型不对应的符号(例如未经修改的模块名称),则会发生语法错误。

**只有在符号名称前添加模块名称和感叹号时,才能在符号名称中使用反引号 (`) 或撇号 (')。**在模板名称后添加 < 和 > 分隔符时,可以在这些分隔符之间添加空格。

如果符号可能存在 歧义,可以在符号前添加模块名称和感叹号 (!) **,或者仅添加感叹号。**要指定符号是局部符号,请省略模块名称,并在符号名称前添加美元符号和感叹号 ($!)。有关符号识别的更多信息,请参阅"符号语法和符号匹配"。

2.2.6 C++ 表达式中的结构体

C++表达式求值器会将伪寄存器强制转换为相应的类型。例如,$teb 会强制转换为 TEB* 类型。

0:000> ?? @$teb

struct _TEB * 0x004ec000

+0x000 NtTib : _NT_TIB

+0x01c EnvironmentPointer : (null)

+0x020 ClientId : _CLIENT_ID

+0x028 ActiveRpcHandle : (null)

+0x02c ThreadLocalStoragePointer : 0x004ec02c Void

+0x030 ProcessEnvironmentBlock : 0x004e9000 _PEB

+0x034 LastErrorValue : 0xbb

+0x038 CountOfOwnedCriticalSections : 0

以下示例显示了 TEB 结构中的进程 ID,展示了如何使用指向被引用结构成员的指针。

0:000> ?? @$teb->ClientId.UniqueProcess

void * 0x0000059c

2.2.7 C++ 表达式中的运算符

可以使用括号来覆盖优先级规则。

如果你将 C++ 表达式的一部分用括号括起来,并在表达式前添加两个 @ 符号 (@@),则该表达式将根据 MA SM **表达式规则进行解释。不能在两个 @ 符号和左括号之间添加空格。**此表达式的最终值将作为 ULONG64 值传递给 C++ 表达式求值器。还可以使用 @@c++( ... ) 或 @@masm( ... ) 来指定表达式求值器。

数据类型的表示方式与 C++ 语言中的惯例相同。数组 ([ ])、指针成员 (->)、UDT 成员 (.) 和类成员 (::) 的符号均可识别。 所有算术运算符均受支持,包括赋值运算符和副作用运算符。但是,不能使用 new delete throw 运算符,也不能实际调用函数

指针运算受支持,偏移量会正确缩放。请注意,不能向函数指针添加偏移量。如果必须向函数指针添加偏移量,请先将偏移量强制转换为字符指针。

与 C++ 一样,如果使用无效数据类型的运算符,则会发生语法错误。调试器的 C++ 表达式解析器使用的规则比大多数 C++ 编译器略微宽松,但所有主要规则仍然强制执行。例如,不能对非整数值进行移位操作。

可以使用以下运算符。每个单元格中的运算符优先级高于下层单元格中的运算符。同一单元格中的运算符优先级相同,并从左到右进行解析。

与 C++ 一样,表达式求值在其值确定时结束。这种结束方式使你能够有效地使用诸如 `?? myPtr && *myPtr` 之类的表达式。

2.2.8 引用和类型转换

|-------------------------------------------|------------|
| 运算符 | 用途 |
| Expression // Comment | 忽略所有子序列文本 |
| Class :: Member | 类成员 |
| Class ::~Member | 类成员(析构) |
| :: Name | 全局 |
| Structure . Field | 结构体字段 |
| Pointer -> Field | 引用结构中的字段 |
| Name [integer] | 数组下标 |
| LValue ++ | 递增(在后计算) |
| LValue -- | 递减(在后计算) |
| dynamic_cast <type >(Value) | 类型转换(总是执行) |
| static_cast <type >(Value) | 类型转换(总是执行) |
| reinterpret_cast <type >(Value) | 类型转换(总是执行) |
| const_cast <type >(Value) | 类型转换(总是执行) |

2.2.9 值运算

|----------------------------|--------------|
| 运算符 | 用途 |
| (type ) Value | 类型转换(总是执行) |
| sizeof value | 计算表达式的大小 |
| sizeof ( type ) | 计算数据类型的大小 |
| ++ LValue | 递增(在前计算) |
| -- LValue | 递减(在前计算) |
| ~ Value | 位补码 |
| ! Value | 非(Boolean) |
| -Value | 一元减 |
| + Value | 一元加 |
| & LValue | 数据类型的地址 |
| Value | 解引用 |
| Structure . Pointer | 指向结构成员的指针 |
| Pointer -> * Pointer | 指向引用结构的成员的指针 |

2.2.10 算术运算

|----------------------|-----------|
| 运算符 | 用途 |
| Value * Value | 乘 |
| Value / Value | 除 |
| Value % Value | 取模 |
| Value + Value | 加 |
| Value - Value | 减 |
| Value << Value | 按位左移 |
| Value >> Value | 按位右移 |
| Value < Value | 小于(比较) |
| Value <= Value | 小于等于(比较) |
| Value > Value | 大于(比较) |
| Value >= Value | 大于等于(比较) |
| Value == Value | 等于 |
| Value != Value | 不等于 |
| Value & Value | 按位AND |
| Value ^ Value | 按位XOR(异或) |
| Value | Value | 按位OR |
| Value && Value | 逻辑AND |
| Value || Value | 逻辑 OR |

以下示例假设伪寄存器按所示方式设置。

0:000> r $t0 = 0

0:000> r $t1 = 1

0:000> r $t2 = 2

0:000> ?? @t1 + @t2

unsigned int64 3

0:000> ?? @t2/@t1

unsigned int64 2

0:000> ?? @t2\|@t1

unsigned int64 3

2.2.11 赋值

|------------------------|---------|
| 运算符 | 用途 |
| LValue = Value | 赋值 |
| LValue *= Value | 乘并赋值 |
| LValue /= Value | 除并赋值 |
| LValue %= Value | 取模并赋值 |
| LValue += Value | 加并赋值 |
| LValue -= Value | 减并赋值 |
| LValue <<= Value | 按位左移并赋值 |
| LValue >>= Value | 按位右移并赋值 |
| LValue &= Value | AND 并赋值 |
| LValue |= Value | OR并赋值 |
| LValue ^= Value | XOR并赋值 |

2.2.12 计算

|-----------------------------|------------------------|
| 运算符 | 用途 |
| Value ? Value : Value | 条件计算 |
| Value , Value | 计算所有值,然后舍弃除最右边值之外的所有值。 |

2.2.13 C++ 表达式中的宏

你可以在 C++ 表达式中使用宏。宏名前必须加上井号 (#)

你可以使用以下宏。这些宏的定义与同名的 Microsoft Windows 宏相同。Windows 宏定义在 Winnt.h 文件中。

|--------------------------------------------|-----------------------------------|
| 宏名 | 返回值 |
| #CONTAINING_RECORD(Address, Type, Field) | 根据结构体的类型和结构体中某个字段的地址,返回结构体实例的基地址。 |
| #FIELD_OFFSET(Type, Field) | 返回已知结构体类型中指定字段的字节偏移量。 |
| #RTL_CONTAINS_FIELD(Struct, Size, Field) | 指示给定的字节大小是否包含所需的字段。 |
| #RTL_FIELD_SIZE(Type, Field) | 返回已知类型结构中字段的大小,而无需指定字段类型。 |
| #RTL_NUMBER_OF(Array) | 返回静态大小数组中的元素数量。 |
| #RTL_SIZEOF_THROUGH_FIELD(Type, Field) | 返回已知类型的结构体的大小,直至指定字段(包括该字段)。 |

下例展示了如何使用 #FIELD_OFFSET 宏来计算结构体中字段的字节偏移量。

0:000> ?? #FIELD_OFFSET(_PEB, BeingDebugged)

long 0n2

2.3 MASM表达式对比 C++ 表达式

MASM表达式求值与C++表达式求值之间最显著的区别如下:

(1) 在 MASM 表达式中,任何符号的数值都是其内存地址 。**在 C++ 表达式中,变量的数值是其实际值,而非其地址。**数据结构没有数值。它们被视为实际的结构,必须按结构体的方式使用。函数名或任何其他入口点的值是其内存地址,并被视为函数指针。如果使用与 C++ 数据类型不对应的符号(例如未经修改的模块名),则会发生语法错误。

**(2)**MASM表达式求值器将所有数都视为ULONG64值。C++表达式求值器将数强制转换为ULONG64,并保留所有数据类型的类型信息。

**(3)**MASM表达式求值器允许你将任何运算符与任何数一起使用。而C++表达式求值器在将运算符与错误的数据类型一起使用时会生成错误。

**(4)**在 MASM 表达式求值器中,所有算术运算都按字面值执行。在 C++ 表达式求值器中,指针运算会进行适当的缩放,并且在不适当的情况下会被禁止。

**(5)**MASM表达式可以使用两个下划线(__)或两个冒号(::)来表示类的成员。C++表达式求值器仅使用两个冒号语法。调试器输出始终使用两个冒号。

**(6)**在 MASM 表达式中,除了最常用的寄存器之外,所有寄存器名称前都应该添加 @ 符号。如果省略 @ 符号,寄存器名称可能会被解释为十六进制数或符号。在 C++ 表达式中,所有寄存器名称都必须添加此前缀。

**(7)**MASM表达式可能包含对源代码行的引用。这些引用用反引号(`)表示。你不能在C++表达式中引用源代码行号。

2.4 混合表达式的例子

本主题包含各种命令中使用的 MASM 和 C++ 表达式示例。

本帮助文档的其他所有章节的示例均使用 MASM 表达式语法(除非另有说明)。C++ 表达式语法对于操作结构体和变量非常有用,但它对调试器命令的参数解析效果不佳。

如果你将调试器命令用于一般用途或使用调试器扩展,则应将 MASM 表达式语法设置为默认语法,例如使用 `.expr`(选择表达式求值器)。如果你必须指定参数才能使用 C++ 表达式语法,请使用 `@@()` 语法。

如果 `myInt` 是一个 ULONG32 值,并且你正在使用 MASM 表达式求值器,则以下两个示例显示了 `myInt` 的值。

0:000> ?? myInt

0:000> dd myInt L1

但是,以下示例显示了 myInt 的地址。

0:000> ? myInt

2.4.1 条件断点

可以使用比较运算符创建条件断点。以下代码示例使用了 MASM 表达式语法。由于当前默认基数为 16,因此该示例使用了 0n 前缀,以便将数 20 识别为十进制数。

0:000> bp MyFunction+0x43 "j ( poi(MyVar)>0n20 ) ''; 'gc' "

在前面的例子中,MyVar 是 C 源代码中的一个整数。由于 MASM 解析器将所有符号都视为地址,因此该示例必须使用 poi 运算符来解引用 MyVar。

2.4.2 条件表达式

以下示例输出 ecx 的值:如果 eax 大于 ebx,则输出 ecx 的值;如果 eax 小于 ebx,则输出 7;如果 eax 等于 ebx,则输出 3。此示例使用 MASM 表达式求值器,因此等号 (=) 是比较运算符,而不是赋值运算符。

0:000> ? ecx*(eax>ebx) + 7*(eax<ebx) + 3*(eax=ebx)

在 C++ 语法中,@ 符号表示寄存器,双等号 (==) 是比较运算符,并且代码必须显式地将 BOOL 类型转换为 int 类型。因此,在 C++ 语法中,前面的命令变为以下形式。

0:000> ?? @ecx*(int)(@eax>@ebx) + 7*(int)(@eax<@ebx) + 3*(int)(@eax==@ebx)

2.4.3 MASM和C++混合表达式例子

你不能在 C++ 表达式中使用源代码行表达式。以下示例使用 @@() 替代求值器语法将 MASM 表达式嵌套在 C++ 表达式中。此示例将 MyPtr 设置为 Myfile.c 文件第 43 行的地址。

0:000> ?? MyPtr = @@( `myfile.c:43` )

以下示例将默认表达式求值器设置为 MASM,然后将 Expression2 作为 C++ 表达式求值,并将 Expression1 和 Expression3 作为 MASM 表达式求值。

0:000> .expr /s masm

0:000> bp Expression1 + @@( Expression2 ) + Expression3

如果 myInt 是一个 ULONG64 值,并且你知道该值在内存中紧接着另一个 ULONG64 值,则可以使用以下示例之一在该位置设置访问断点。(注意指针运算的使用。)

0:000> ba r8 @@( &myInt + 1 )

0:000> ba r8 myInt + 8

2.5 符号扩展

当一个 32 位有符号整数为负数时,其最高位为 1。当这个 32 位有符号整数被转换为 64 位数时,最高位可以设置为 0(保留该数的无符号整数和十六进制值),也可以设置为 1(保留该数的有符号值)。后一种情况称为符号扩展。

调试器在 MASM 表达式、C++ 表达式和显示数时,对符号扩展遵循不同的规则

2.5.1 MASM中的符号扩展

在特定条件下,MASM 表达式求值器会自动对数进行符号扩展。符号扩展仅影响 0x80000000 到 0xFFFFFFFF 之间的数。也就是说,符号扩展仅影响最高位为 1 的 32 位数(译注:因为这一位表示符号)。

当调试器将数 0x12345678 视为 64 位数时,它始终保持为 0x00000000`12345678。在另一方面,当 0x890ABCDE 视为 64 位值时,它可能保持为 0x00000000`890ABCDE,也可能被 MASM 表达式求值器扩展为 0xFFFFFFFF`890ABCDE。

0x80000000 到 0xFFFFFFFF 之间的数会根据以下条件进行符号扩展:

(1) 在用户模式下,数值常量永远不会进行符号扩展。在内核模式下,除非数值常量在低字节前包含反引号(`),否则都会进行符号扩展。例如,在内核模式下,十六进制数 EEAA1122 和 00000000EEAA1122 会进行符号扩展,但 00000000`EEAA1122 和 0`EEAA1122 则不会。

**(2)**在两种模式下,32 位寄存器都进行符号扩展。

**(3)**伪寄存器始终以 64 位值存储。它们在求值时不会进行符号扩展。当伪寄存器被赋值时,所使用的表达式将按照标准的 C++ 规则进行求值。

**(4)**表达式中的单个数和寄存器可以进行符号扩展,但表达式求值期间的其他计算不会进行符号扩展。因此,你可以使用以下语法屏蔽数或寄存器的高位。

( 0x0`FFFFFFFF & expression )

2.5.2 C++ 表达式中的符号扩展

当调试器执行 C++ 表达式时,适用以下规则:

**(1)**寄存器和伪寄存器永远不会进行符号扩展。

**(2)**所有其他值都按照 C++ 处理其类型值的方式进行处理。

2.5.3 显示符号扩展和64位数

除了 32 位和 16 位寄存器,调试器内部所有数都以 64 位值存储。但是,当某个数满足特定条件时,调试器会在命令输出中将其显示为 32 位数。

调试器使用以下条件来确定如何显示数:

**(1)**如果一个数的高 32 位全部为零(即,如果该数介于 0x00000000`00000000 到 0x00000000`FFFFFFFF 之间),调试器会将该数显示为 32 位数。

**(2)**如果一个数的高 32 位全部为 1,且低 32 位的最高位也为 1(即,如果该数介于 0xFFFFFFFF`80000000 到 0xFFFFFFFF`FFFFFFFF 之间),调试器会假定该数为符号扩展的 32 位数,并将其显示为 32 位数。

**(3)**如果前两个条件不适用(即,如果数介于 0x00000001`00000000 到 0xFFFFFFFF`7FFFFFFFF 之间),调试器会将该数显示为 64 位数。

由于这些显示规则,当一个数显示为 0x80000000 到 0xFFFFFFFF 之间的 32 位数时,你无法确认其高 32 位是全 1 还是全 0。为了区分这两种情况,你必须对该数执行额外的计算(例如,屏蔽一个或多个高位并显示结果)。

2.6 ? ( 求值表达式)( Evaluate Expression**)**

问号 (?) 命令用于计算并显示表达式的值

单独的问号 (?) 用于显示命令帮助。"? Expression" 命令用于计算给定表达式的值。

? Expression

2.6.1 参数

Expression

指定需要计算的表达式。

2.6.2 环境

|-------|-------------|
| | 说明 |
| 模式 | 用户模式,内核模式 |
| 目标 | 实时调试,崩溃转储 |
| 平台 | 所有 |

2.6.3 评注

`?` **命令的输入和输出取决于你使用的是 MASM 表达式语法还是 C++ 表达式语法。**有关这些表达式语法的更多信息,请参阅"求值表达式"和"数值表达式语法"。

如果你使用的是 MASM 语法,则输入和输出取决于当前基数。要更改基数,请使用 `n`(设置数基数)命令。

`?` 命令在当前线程和进程的上下文中计算表达式中的符号。

某些字符串可能包含转义字符,例如 `\n`、`\r` 和 `\b`,这些转义字符应按字面意思读取,而不是由求值器解释。如果字符串中的转义字符被求值器解释,则可能会出现求值错误。例如:

0:000> as AliasName c:\dir\name.txt

0:000> al

Alias Value


AliasName c:\dir\name.txt

0:001> ? $spat( "c:\dir\name.txt", "*name*" )

Evaluate expression: 0 = 00000000

0:001> ? spat( "{AliasName}", "*name*" )

Evaluate expression: 0 = 00000000

0:001> ? $spat( "c:\dir\", "*filename*" )

Syntax error at '( "c:\dir\", "*filename*" )

在前两个示例中,即使字符串与模式匹配,求值器也返回 FALSE 值。在第三个示例中,由于字符串以反斜杠 (\) 结尾,求值器无法进行比较,因此反斜杠 (\) 会被求值器转换。

要使求值器按字面意思解释字符串,必须使用 @"String" 语法。以下代码示例显示了正确的结果:

0:000> ? $spat( @"c:\dir\name.txt", "*name*" )

Evaluate expression: 1 = 00000000`00000001

0:000> ? spat( @"{AliasName}", "*name*" )

Evaluate expression: 1 = 00000000`00000001

0:001> ? $spat( @"c:\dir\", "*filename*" )

Evaluate expression: 0 = 00000000

在前面的示例中,$spat MASM 运算符检查第一个字符串,以确定它是否(不区分大小写)与第二个字符串的模式匹配。有关 MASM 运算符的更多信息,请参阅"MASM 数字和运算符"主题。

2.7 选择表达式求值器

.expr 命令指定默认表达式求值器。

.expr /s masm

.expr /s c++

.expr /q

.expr

例如:

0:000> .expr

Current expression evaluator: MASM - Microsoft Assembler expressions

2.7.1 参数

/s masm------将默认表达式类型更改为 Microsoft 汇编表达式求值器 (MASM)。此类型是启动调试器时的默认值。

/s c++ ------ 将默认表达式类型更改为 C++ 表达式求值器。

/q ------显示可能的表达式类型列表。例如:

0:000> .expr /q

Available expression evaluators:

MASM - Microsoft Assembler expressions

C++ - C++ source expressions

Current expression evaluator: MASM - Microsoft Assembler expressions

2.7.2 环境

|-------|-------------|
| | 说明 |
| 模式 | 用户模式,内核模式 |
| 目标 | 实时调试,崩溃转储 |
| 平台 | 所有 |

2.7.3 评注

当你使用不带参数的 `.expr` 命令时,调试器会显示当前默认表达式类型。

`??`( **计算 C++ 表达式)命令、"监视"窗口和"局部变量"窗口始终使用 C++ 表达式语法。**所有其他命令和调试信息窗口均使用默认表达式求值器。

有关如何控制所用语法的更多信息,请参阅"求值表达式"。有关语法的更多信息,请参阅"数值表达式语法"。

3 字符串通配符语法

某些调试器命令具有字符串参数,这些参数接受各种通配符。这些参数在其各自的参考页面中有详细说明。

这类参数支持以下语法特性:

**(1)**星号 (*) 代表零个或多个字符。

**(2)**问号 (?) 代表任意单个字符。

**(3)**包含字符列表的方括号 ([ ]) 代表列表中的任意单个字符。列表中只会匹配一个字符。在这些方括号内,你可以使用连字符 (-) 来指定一个范围。例如,Prog[er-t7]am 匹配 "Progeam", "Program", "Progsam", "Progtam", 和 "Prog7am"。

**(4)**井号 (#) 代表零个或多个前面的字符。例如,Lo#p 匹配 "Lp", "Lop", "Loop", "Looop", 等等。你还可以将井号与方括号组合使用,例如 m[ia]#n 匹配 "mn", "min", "man", "maan", "main", "mian", "miin", "miain", 等等。

**(5)**加号 (+) 代表一个或多个前面的字符。例如,Lo+p 与 Lo#p 相同,区别在于 Lo+p 不匹配"Lp"。类似地,m[ia]+n 与 m[ia]#n 相同,区别在于 m[ia]+n 不匹配"mn"。a?+b 也与 a*b 相同,区别在于 a?+b 不匹配"ab"。

**(6)**如果必须指定字面意义上的井号 (#)、问号 (?)、左方括号 ([)、右方括号 (])、星号 (*) 或加号 (+) 字符,则必须在字符前添加反斜杠 (\)。未用方括号括起来的连字符始终是字面意义上的。但是,不能在带方括号的列表中指定字面意义上的连字符。

指定符号的参数还支持一些附加功能。除了标准的字符串通配符之外,你还可以在用于指定符号的文本表达式前使用下划线 (_)。在将此表达式与符号匹配时,调试器会将该下划线视为任意数量的下划线,甚至零个。此功能仅适用于匹配符号的情况,不适用于一般的字符串通配符表达式。有关符号语法的更多信息,请参阅"符号语法"和"符号匹配"。

4 寄存器语法

调试器可以控制寄存器浮点寄存器在表达式中使用寄存器时,应在寄存器名称前添加 @ 符号。@ 符号告诉调试器,后面的文本是寄存器的名称。

如果你使用的是 MASM 表达式语法,对于某些非常常用的寄存器,可以省略 @ 符号。 在基于 x86 的系统中,你可以省略 eax、ebx、ecx、edx、esi、edi、ebp、eip 和 efl 寄存器的 @ 符号。但是,如果你指定了一个不太常用的寄存器而没有添加 @ 符号,调试器首先会尝试将文本解释为十六进制数。如果文本包含非十六进制字符,调试器接下来会将文本解释为符号。最后,如果调试器找不到匹配的符号,则会将文本解释为寄存器。

如果使用的是 C++ 表达式语法,则始终需要 @ 符号

r (寄存器)命令是此规则的例外 。调试器始终将其第一个参数解释为寄存器。(不需要也不允许使用 @ 符号。) 如果 r 命令有第二个参数,则会根据默认表达式语法进行解释。如果默认表达式语法是 C++,则必须使用以下命令将 ebx 寄存器复制到 eax 寄存器。

0:000> r eax = @ebx

有关每一种处理器特有的寄存器和指令的更多信息,请参阅相关处理器架构。

4.1 基于X86 处理器的标识

基于 x86 的处理器还使用一些称为标志位的 1 位寄存器。有关这些标志位以及可用于查看或更改它们的语法的更多信息,请参阅 x86 标志位(寄存器)。

4.2 寄存器和线程

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

在用户模式下,对寄存器的任何引用都解释为与当前线程关联的寄存器。有关当前线程的更多信息,请参阅"控制进程和线程"。

在内核模式下,对寄存器的任何引用都解释为与当前寄存器上下文关联的寄存器。你可以将寄存器上下文设置为与特定线程、上下文记录或陷阱帧匹配。你可以仅显示指定寄存器上下文中最重要的寄存器,但不能更改它们的值。

5 伪寄存器语法

调试器支持多个伪寄存器,这些伪寄存器可以保存某些值。

调试器会自动将伪寄存器设置为某些有用的值。用户自定义的伪寄存器是整型变量,你可以对其进行写入或读取操作。

所有伪寄存器都以美元符号 ($) 开头如果你使用 MASM 语法,可以在美元符号前添加一个 at 符号 (@)。这个 @ 符号告诉调试器,后面的标记是一个寄存器或伪寄存器,而不是一个符号。如果省略 at 符号,调试器的响应速度会变慢,因为它需要搜索整个符号表。

例如,以下两个命令产生相同的输出,但第二个命令速度更快。

0:000> ? $exp

Evaluate expression: 143 = 0000008f

0:000> ? @$exp

Evaluate expression: 143 = 0000008f

如果存在与伪寄存器同名的符号,则必须添加 @ 符号。如果使用的是 C++ 表达式语法,则始终需要 @ 符号。

r**(** 寄存器)命令是此规则的例外 。调试器始终将其第一个参数解释为寄存器或伪寄存器。(不需要也不允许使用 at 符号。) 如果 r 命令有第二个参数,则会根据默认表达式语法进行解释。如果默认表达式语法是 C++,则必须使用以下命令将 t2 伪寄存器复制到 t1 伪寄存器。

0:000> r t1 = @t2

5.1 自动伪寄存器

调试器会自动设置以下伪寄存器。

|--------------------|--------------------------------------------------------------------------------------------------------------------|
| 伪寄存器 | 说明 |
| ea** | 最后一条已执行指令的有效地址。如果该指令没有有效地址,调试器将显示"寄存器错误"。如果该指令有两个有效地址,调试器将显示第一个地址。 | | **ea2 | 最后一条已执行指令的第二个有效地址。如果该指令没有两个有效地址,调试器将显示"寄存器错误"。 |
| exp** | 最后求值的表达式。 | | **ra | 当前栈上的返回地址。 这个地址在执行命令中特别有用。例如,g @ra 会一直执行直到找到返回地址(尽管 gu(向上跳转)是更精确有效的"跳出"当前函数的方法)。 | | **ip** | 指令指针寄存器。 基于 x86 的处理器:与 eip 相同。基于 Itanium 的处理器:与 iip 相关。(更多信息,请参阅此表后的注释。)基于 x64 的处理器:与 rip 相同。 |
| eventip** | 当前事件发生时的指令指针。除非你切换了线程或手动更改了指令指针的值,否则此指针通常与 ip 匹配。 |
| previp** | 上一次事件发生时的指令指针。(进入调试器也算作一次事件。) | | **relip | 与当前事件相关的指令指针。在分支跟踪中,此指针指向分支源。 |
| scopeip** | 当前本地上下文(也称为作用域)的指令指针。 | | **exentry | 当前进程第一个可执行文件的入口点地址。 |
| retreg** | 主返回值寄存器。 x86 架构处理器:与 eax 相同。Itanium 架构处理器:与 ret0 相同。x64 架构处理器:与 rax 相同。 | | **retreg64 | 主返回值寄存器,64 位格式。x86 处理器:与 edx:eax 对相同。 |
| csp** | 当前调用栈指针。该指针是能够最准确地反映调用栈深度的寄存器。x86 架构处理器:与 esp 寄存器相同。Itanium 架构处理器:与 bsp 寄存器相同。x64 架构处理器:与 rsp 寄存器相同。 | | **p | 最后一个 d*(显示内存)命令打印的值。 |
| proc** | 当前进程的地址(即 EPROCESS 块的地址)。 | | **thread | 当前线程的地址。在内核模式调试中,此地址是 ETHREAD 块的地址。在用户模式调试中,此地址是线程环境块 (TEB) 的地址。 |
| peb** | 当前进程的进程环境块(PEB)的地址。 | | **teb | 当前线程的线程环境块(PEB)的地址。 |
| tpid** | 拥有当前线程的进程的进程 ID (PID)。 | | **tid | 当前主题的线程 ID。 |
| dtid** | | | **dpid | |
| dsid** | | | **bp Number | 对应断点的地址。例如,bp3(或 bp03)指的是断点 ID 为 3 的断点。Number 始终为十进制数。如果没有断点的 ID 为 Number,则 bpNumber 的值将为零。有关断点的更多信息,请参阅"使用断点"。 | | **frame
| 当前帧索引。此索引与 .frame(设置本地上下文)命令使用的帧号相同。 |
| dbgtime** | 根据调试器运行所在计算机的当前时间。 | | **callret | .call(调用函数)调用的最后一个函数或在 .fnret /s 命令中使用的最后一个函数的返回值。callret 的数据类型与此返回值的数据类型相同。 | | **extret** | |
| extin** | | | **clrex | |
| lastclrex** | 仅限托管调试:上次遇到的公共语言运行时 (CLR) 异常对象的地址。 | | **ptrsize | 指针的大小。在内核模式下,此大小为目标计算机上的指针大小。 |
| pagesize** | 内存页中一个字节数。在内核模式下,此大小即为目标计算机上的页面大小。 | | **pcr | |
| pcrb** | | | **argreg | |
| exr_chance** | 当前异常记录的概率。 | | **exr_code | 当前异常记录的异常代码。 |
| exr_numparams** | 当前异常记录中的参数数量。 | | **exr_param0 | 当前异常记录中参数 0 的值。 |
| exr_param1** | 当前异常记录中参数 1 的值。 | | **exr_param2 | 当前异常记录中参数 2 的值。 |
| exr_param3** | 当前异常记录中参数 3 的值。 | | **exr_param4 | 当前异常记录中参数 4 的值。 |
| exr_param5** | 当前异常记录中参数 5 的值。 | | **exr_param6 | 当前异常记录中参数 6 的值。 |
| exr_param7** | 当前异常记录中参数 7 的值。 | | **exr_param8 | 当前异常记录中参数 8 的值。 |
| exr_param9** | 当前异常记录中参数 9 的值。 | | **exr_param10 | 当前异常记录中参数 10 的值。 |
| exr_param11** | 当前异常记录中参数 11 的值。 | | **exr_param12 | 当前异常记录中参数 12 的值。 |
| exr_param13** | 当前异常记录中参数 13 的值。 | | **exr_param14 | 当前异常记录中参数 14 的值。 |
| bug_code** | 如果发生了错误检查,则此为错误代码。适用于实时内核模式调试和内核崩溃转储。 | | **bug_param1 | 如果发生了错误检查,则参数 1 的值即为该值。适用于实时内核模式调试和内核崩溃转储。 |
| bug_param2** | 如果发生了错误检查,则参数 2 的值即为该值。适用于实时内核模式调试和内核崩溃转储。 | | **bug_param3 | 如果发生了错误检查,则参数 3 的值即为该值。适用于实时内核模式调试和内核崩溃转储。 |
| $bug_param4 | 如果发生了错误检查,则参数 4 的值即为该值。适用于实时内核模式调试和内核崩溃转储。 |

某些伪寄存器在某些调试场景下可能无法使用。例如,调试用户模式小型转储文件或某些内核模式转储文件时,不能使用 peb ,tid 和 tpid。**有些情况下,可以从** **\~** **(** **线程状态)获取线程信息**,但无法从 tid 获取。在第一个调试器事件中,不能使用伪寄存器 previp。除非正在进行分支跟踪,否则不能使用伪寄存器 relip。如果使用不可用的伪寄存器,则会发生语法错误。

存储结构体地址的伪寄存器(例如 thread , proc, teb ,peb 和 lastclrex) 在 C++ 表达式求值器中会根据正确的数据类型进行求值,但在 MASM 表达式求值器中则不会。例如,命令 **? teb 显示 TEB 的地址** ,而命令 ??@$teb 显示整个 TEB 结构 ( Thread Environment Block )。有关更多信息,请参阅"表达式求值"部分。

在基于安腾(Itanium)的处理器上,iip 寄存器是按指令包对齐的,这意味着它指向包含当前指令的指令包中的第 0 个槽位,即使正在执行的是其他槽位。因此,iip 并非完整的指令指针。伪寄存器 ip 才是真正的指令指针,它包含了指令包和槽位信息。其他保存地址指针的伪寄存器(ra ,retreg , eventip ,previp ,relip 和 exentry) 在所有处理器上都与 ip 具有相同的结构。

你可以使用 r 命令来更改 $ip 的值。此更改也会自动更改相应的寄存器。当程序恢复执行时,它将从新的指令指针地址继续执行。此寄存器是唯一一个你可以手动更改的自动伪寄存器。

**注意:在 MASM 语法中,可以使用句点 (.) 来表示 $ip 伪寄存器。无需在此句点前添加 @ 符号,也无需将句点用作 r 命令的第一个参数。**此语法在 C++ 表达式中是不允许的。

自动伪寄存器类似于自动别名。但是,你可以将自动别名与别名相关的标记(例如 ${})一起使用,而不能将伪寄存器与此类标记一起使用。

5.2 用户定义伪寄存器

共有 20 个用户自定义伪寄存器(t0, t1, ..., $t19)。这些伪寄存器是变量,你可以通过调试器对其进行读写操作。你可以将任何整数值存储在这些伪寄存器中。它们尤其适用于作为循环变量。

要写入这些伪寄存器之一,请使用r(寄存器)命令,如下例所示。

0:000> r $t0 = 7

0:000> r $t1 = 128*poi(MyVar)

与所有伪寄存器一样,你可以在任何表达式中使用用户定义的伪寄存器,如下例所示。

0:000> bp $t3

0:000> bp @$t4

0:000> ?? @t1 + 4\*@t2

伪寄存器始终被指定为整数类型,除非你同时使用 ? 开关和 r 命令。如果使用此开关,伪寄存器将获得分配给它的值的类型。例如,以下命令将 UNICODE_STRING** 类型和值 0x0012FFBC 分配给 $t15 。

0:000> r? $t15 = * (UNICODE_STRING*) 0x12ffbc

用户自定义伪寄存器在调试器启动时使用零作为默认值。

注意:别名 u0 , u1 , ... , $u9 虽然外观相似,但它们并非伪寄存器。有关这些别名的更多信息,请参阅"使用别名"。

5.3 使用示例

示例1:

以下示例设置了一个断点,每次当前线程调用 NtOpenFile 时都会触发该断点。但当其他线程调用 NtOpenFile 时,该断点不会被触发。

kd> bp /t @$thread nt!ntopenfile

示例2:

以下示例会执行一条命令,直到寄存器保持指定值为止。首先,将以下条件单步执行代码放入名为"eaxstep"的脚本文件中。

.if (@eax == 1234) { .echo 1234 } .else { t "$<eaxstep" }

接下来,发出以下命令。

t "$<eaxstep"

调试器执行一个步骤,然后运行你的命令。在本例中,调试器运行脚本,该脚本要么显示 1234,要么重复该过程。

6 源代码行号语法

你可以将源文件行号指定为 MASM 表达式的一部分或全部内容。这些行号的计算结果为与该源文件行对应的可执行代码的偏移量。

**注意:你不能在 C++ 表达式中使用源文件行号。**有关何时使用 MASM 和 C++ 表达式语法的更多信息,请参阅"表达式求值"部分。

源文件和行号表达式必须用反引号 (`) 括起来。以下示例显示了源文件行号的完整格式。

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

如果存在多个文件名相同的文件,则 Filename 应包含完整的目录路径和文件名。此目录路径应为编译时使用的路径。如果仅提供文件名或部分路径,且存在多个匹配项,调试器将使用找到的第一个匹配项。

如果省略 Filename,调试器将使用与当前程序计数器对应的源文件。

除非在 LineNumber 前加上 0x,否则无论当前默认进制如何,LineNumber 都将读取为十进制数。如果省略 LineNumber,则表达式的计算结果为与源文件对应的可执行文件的初始地址。

除非发出 .lines(切换源行支持)命令或在启动 WinDbg 时包含 -lines 命令行选项,否则 CDB 不会计算源行表达式。

7 地址和地址范围语法

在调试器中指定地址有多种方法。

除非文档另有明确说明,否则地址通常是虚拟地址。在用户模式下,调试器会根据当前进程的页目录来解释虚拟地址。在内核模式下,调试器会根据进程上下文指定的进程页目录来解释虚拟地址。你也可以直接设置用户模式地址上下文。有关用户模式地址上下文的更多信息,请参阅 .context(设置用户模式地址上下文)。

MASM 表达式中,可以使用 poi 运算符来解引用任何指针。例如,如果地址 0x0000008e`ed57b108 处的指针指向地址 0x805287637256,则以下两个命令等效。

0:000> dd 805287637256

0:000> dd poi(000000bb`7ee23108)

7.1 显示内存地址之例子

要查看 poi 的使用示例,请确定线程环境块 (TEB) 的 CurrentLocale 偏移量。使用 dx 命令显示 @$teb,这是一个伪寄存器的示例,用于保存常用地址,例如当前程序计数器的位置。

0:000> dx @$teb

@$teb : 0x1483181000 [Type: _TEB *]

...

+0x108\] CurrentLocale : 0x409 \[Type: unsigned long

CurrentLocale 位于 TEB 起始位置 +0x108 处。接下来确定该位置的内存地址。

0:000> ? @$teb + 0x108

Evaluate expression: 613867303176 = 0000008e`ed57b108

使用 poi 对该地址进行解引用,可以看到它包含 CurrentLocale 值 0x409。

0:000> ? poi(0000008e`ed57b108)

Evaluate expression: 1033 = 00000000`00000409

在 C++ 调试器表达式中,指针的行为与 C++ 中的指针相同。但是,数会被解释为整数。如果需要解引用一个实际的数,可能需要先进行类型转换,如下例所示。

要尝试此功能,请使用 .expr 将表达式求值器设置为 C++。

0:000> .expr /s C++

Current expression evaluator: C++ - C++ source expressions

将表达式求值器设置为 C++ 后,我们可以使用 long 进行类型转换

0:000> d *((long*)0x00000014`83181108 )

00000000`00000409 ???????? ???????? ???????? ????????

有关数值转换的更多信息,请参阅 C++ 数和运算符。

如果表达式求值器设置为 c++,我们可以用 @@masm() 包装 poi 指针,以便仅由 MASM 表达式求值器对表达式的该部分进行求值。

0:000> .expr /s c++

Current expression evaluator: C++ - C++ source expressions

0:000> ? @@masm(poi(00000078`267d7108))

Evaluate expression: 1033 = 00000000`00000409

有关这两个表达式求值器的更多信息,请参阅"表达式求值"部分。

你还可以通过指定原源文件名和行号来指示应用程序中的地址。有关如何指定此信息的更多信息,请参阅"源代码行语法"部分。

7.2 地址范围

( 译注:该命令必须与其它命令结合使用,见下述 7.4节 。)

你可以通过一对地址或通过地址和对象计数来指定地址范围。

要通过一对地址指定一个范围,请指定起始地址和结束地址。例如,以下示例是一个 8 字节的范围,起始地址为 0x00001000。

0x00001000 0x00001007

要通过一对地址指定一个范围,请指定起始地址和结束地址。例如,以下示例是一个 8 字节的范围,起始地址为 0x00001000。

0x00001000 L8

但是,如果对象大小为双字( 32 位或 4 字节 ),则以下两个范围各为 8 字节范围。

0x00001000 0x00001007

0x00001000 L2

7.3 L 大小范围指定符(L大概是Length的首字母?)

还有两种其他方法可以指定该值( LSize 范围说明符) ( 译注:该命令必须与其它命令结合使用,见下述 7.4节 ):

(1) L? Size (带问号)与 LSize 的含义相同,区别在于 L? Size 会移除调试器的自动范围限制。 通常,范围限制为 256 MB,因为更大的范围会判定为输入错误。如果要指定大于 256 MB 的范围,则必须使用 L? Size 语法。

(2) L-Size(带连字符)指定一个长度为 Size 的范围,该范围以给定的地址结束。例如,80000000 L20 指定从 0x80000000 到 0x8000001F 的范围,而 80000000 L-20 指定从 0x7FFFFFE0 到 0x7FFFFFFF 的范围。

某些需要指定地址范围的命令接受单个地址作为参数。在这种情况下,命令会使用默认的对象计数来计算范围的大小。通常,地址范围作为最后一个参数的命令允许使用这种语法。有关每个命令的确切语法和默认范围大小,请参阅各命令的参考主题。

7.4 搜索内存地址范围之例子

首先,我们将使用 MASM 表达式求值器确定 rip 指令指针寄存器的地址。

0:000> ? @rip

Evaluate expression: 140720561719153 = 00007ffc`0f180771

然后,我们将使用 s(搜索内存)命令,从 00007ffc`0f180771 开始搜索,范围为 100000。我们使用 L100000 指定搜索范围。

0:000> s -a 00007ffc`0f180771 L100000 "ntdll"

00007ffc`0f1d48fa 6e 74 64 6c 6c 5c 6c 64-72 69 6e 69 74 2e 63 00 ntdll\ldrinit.c.

00007ffc`0f1d49c2 6e 74 64 6c 6c 5c 6c 64-72 6d 61 70 2e 63 00 00 ntdll\ldrmap.c..

00007ffc`0f1d4ab2 6e 74 64 6c 6c 5c 6c 64-72 72 65 64 69 72 65 63 ntdll\ldrredirec

00007ffc`0f1d4ad2 6e 74 64 6c 6c 5c 6c 64-72 73 6e 61 70 2e 63 00 ntdll\ldrsnap.c.

...

我们也可以使用两个内存地址来指定相同的范围。

0:000> s -a 0x00007ffc`0f180771 0x00007ffc`0f280771 "ntdll"

00007ffc`0f1d48fa 6e 74 64 6c 6c 5c 6c 64-72 69 6e 69 74 2e 63 00 ntdll\ldrinit.c.

00007ffc`0f1d49c2 6e 74 64 6c 6c 5c 6c 64-72 6d 61 70 2e 63 00 00 ntdll\ldrmap.c..

00007ffc`0f1d4ab2 6e 74 64 6c 6c 5c 6c 64-72 72 65 64 69 72 65 63 ntdll\ldrredirec

00007ffc`0f1d4ad2 6e 74 64 6c 6c 5c 6c 64-72 73 6e 61 70 2e 63 00 ntdll\ldrsnap.c.

...

最后,我们可以使用 L 长度参数在内存范围内向后搜索。

0:000> s -a 00007ffc`0f1d4ad2 L-100000 "ntdll"

00007ffc`0f1d48fa 6e 74 64 6c 6c 5c 6c 64-72 69 6e 69 74 2e 63 00 ntdll\ldrinit.c.

00007ffc`0f1d49c2 6e 74 64 6c 6c 5c 6c 64-72 6d 61 70 2e 63 00 00 ntdll\ldrmap.c..

00007ffc`0f1d4ab2 6e 74 64 6c 6c 5c 6c 64-72 72 65 64 69 72 65 63 ntdll\ldrredirec

7.5 反汇编内存之例子

本例使用 u(unassemble)命令和 L 参数来反汇编一个3个字节码。

0:000> u 00007ffc`0f1d48fa L3

ntdll!`string'+0xa:

00007ffc`0f1d48fa 6e outs dx,byte ptr [rsi]

00007ffc`0f1d48fb 7464 je ntdll!`string'+0x21 (00007ffc`0f1d4961)

00007ffc`0f1d48fd 6c ins byte ptr [rdi],dx

或者像这样指定需要反汇编的一个3字节内存范围。

0:000> u 00007ffc`0f1d48fa 00007ffc`0f1d48fd

ntdll!`string'+0xa:

00007ffc`0f1d48fa 6e outs dx,byte ptr [rsi]

00007ffc`0f1d48fb 7464 je ntdll!`string'+0x21 (00007ffc`0f1d4961)

00007ffc`0f1d48fd 6c ins byte ptr [rdi],dx

7.6 寻址模式和段支持

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

|----|-----------------|-----------------------------------------------|
| 前缀 | 名称 | 地址类型 |
| % | 平坦型(flat) | 32 位地址(以及指向 32 位段的 16 位选择器)和 64 位系统上的 64 位地址。 |
| & | 虚86(virtual 86) | 实模式地址,仅基于x86 |
| # | 朴素型(plain) | 实模式地址,仅基于x86 |

朴素 86 模式和虚 86 模式的区别在于,朴素 16 位地址使用段值作为选择器,并查找段描述符。而虚 86 地址不使用选择器,而是直接映射到低 1 MB 内存。

如果你通过非当前默认寻址模式访问内存,可以使用地址模式前缀来覆盖当前地址模式。

7.7 寻址参数

地址参数用于指定变量和函数的位置。下表解释了 CDB 和 KD 中各种地址的语法和含义。

|------------------------------------|-----------------------------------------------------------------------------------------------------------|
| 语法 | 含义 |
| 偏移(offset) | 虚内存空间中的绝对地址,其类型与当前执行模式相对应。例如,如果当前执行模式为 16 位,则偏移量为 16 位;如果执行模式为 32 位分段,则偏移量为 32 位分段。 |
| & [[ 段:]] 偏移 | 实地址。支持 x86 和 x64 架构。 |
| % :[[偏移]] | 分段式 32 位或 64 位地址。支持 x86 和 x64 架构。 |
| % [[ 偏移]] | 虚内存空间中的绝对地址(32 位或 64 位)。支持 x86 和 x64 架构。 |
| name[[ + | ]] offset | 一个平坦的 32 位或 64 位地址。name 可以是任何符号。offset 指定偏移量。此偏移量可以是其前缀所指示的任何地址模式。没有前缀则表示默认模式地址。你可以将偏移量指定为正 (+) 或负 (−) 值。 |

使用dg(显示选择器(display selector))命令查看段描述符信息(译注:为何缩写为dg?)。

7.8 其它地址相关命令

7.8.1 显示内存信息命令 !adress

!address扩展命令显示有关目标进程或目标计算机使用的内存的信息。

用户模式:

!address Address

!address -summary

!address [-f:F1,F2,...] {[-o:{csv | tsv | 1}] | [-c:"Command"]}

!address -? | -help

内核模式:

!address Address

!address

7.8.2 搜索内存命令 s

7.8.3 显示内存内容命令

d, da, db, dc, dd, dD, df, dp, dq, du, dw

d* 命令显示给定范围内的内存内容(d-display)。

d{a|b|c|d|D|f|p|q|u|w|W} [Options] [Range]

dy{b|d} [Options] [Range]

d [Options] [Range]

8 线程语法

许多调试器命令都以线程标识符作为参数。线程标识符前会有一个波浪号( ~

线程标识符可以是下述值之一:

|-------------------------|---------------------------------------------------|
| 线程标识符 | 说明 |
| ~. | 显示当前线程信息 |
| ~# | 显示引发当前异常或调试事件的线程 |
| ~* | 显示进程中的所有线程 |
| ~ Number | 显示其线程索引号是 Number 的线程 |
| ~~ [TID] | 显示线程 ID 为 TID 的线程。(方括号是必需的,并且第二个波浪号和左方括号之间不能有空格。) |
| ~ [Expression] | 线程 ID 为数值表达式解析结果的整数的线程。 |

线程在创建时会分配一个索引。请注意,此索引与 Microsoft Windows 操作系统使用的线程 ID 不同。

调试开始时,当前线程是引发当前异常或调试事件的线程(或者调试器附加到进程时的活动线程)。该线程将一直保持为当前线程,直到你使用 ~s(设置当前线程)命令或通过 WinDbg 中的"进程和线程"窗口指定新的线程为止。

线程标识符通常作为命令前缀出现。请注意,并非所有使用线程标识符的命令都支持所有通配符。

~[Expression] 语法的示例是 ~[@$t0]。在本例中,线程会根据用户定义的伪寄存器的值而改变。此语法允许调试器脚本以编程方式选择线程。

在内核模式下,无法使用线程标识符来控制线程。有关如何在内核模式下访问线程特定信息的更多信息,请参阅"更改上下文"。

注意:在用户模式调试期间,可以使用波浪号 ( ~ ) 来指定线程。在内核模式调试期间,可以使用波浪号来指定处理器。有关如何指定处理器的更多信息,请参阅"多处理器语法"。

9 进程语法

许多调试器命令都以进程标识符作为参数。进程标识符前面会显示一个竖线(|)

进程标识符可以是下列值之一。

|------------------------------|-------------------------------------------------|
| 进程标识符 | 说明 |
| |. | 显示当前进程信息 |
| |# | 显示引发当前异常或调试事件的进程 |
| |* | 显示所有进程 |
| | Number | 显示其序数号是 Number 的进程 |
| |~[ PID ] | 显示进程 ID 为 PID 的线程。(方括号是必需的,并且在波浪号和左方括号之间不能有空格。) |
| |[ Expression ] | 进程 ID 为数值表达式解析结果的整数的进程。 |

进程在创建时会分配一个序号。请注意,此序号与 Microsoft Windows 操作系统使用的进程 ID (PID) 不同。

当前进程定义了所使用的内存空间和线程集。调试开始时,当前进程是引发当前异常或调试事件的进程(或调试器附加到的进程)。该进程将一直保持为当前进程,直到你使用 |s(设置当前进程命令)或 WinDbg 中的"进程和线程"窗口指定新的进程为止。

进程标识符用作多个命令的参数,通常用作命令前缀。请注意,WinDbg 和 CDB 可以调试原始进程创建的子进程。WinDbg 和 CDB 还可以附加到多个不相关的进程。

|[表达式] 语法的示例是 |[@$t0]。在此示例中,进程会根据用户定义的伪寄存器的值而改变。此语法允许调试器脚本以编程方式选择进程。

在内核模式下,你无法使用进程标识符来控制进程。有关如何在内核模式下访问进程特定信息的更多信息,请参阅"更改上下文"。

10 系统语法

许多调试器命令都以进程标识符作为参数。

系统标识符前会显示两条竖线(||)。系统标识符可以是下列值之一。

|----------------|------------------|
| 系统标识符 | 说明 |
| ||. | 显示当前系统信息 |
| ||# | 显示引发当前异常或调试事件的系统 |
| ||* | 显示所有系统 |
| || ddd | 显示其序数号是 ddd的系统 |

系统会根据调试器附加到它们的顺序分配序号。

调试开始时,当前系统是导致当前异常或调试事件的系统(或调试器最近附加到的系统)。该系统将一直保持为当前系统,直到你使用 ||s(设置当前系统)命令或 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 命令不识别为转储文件调试的有效命令。

11 多处理器语法

KD 和内核模式 WinDbg 支持多处理器调试。可以在任何多处理器平台上执行此类调试。

处理器编号为 0 到 n。

如果当前处理器是处理器 0 (即,当前激活调试器的处理器),你可以检查其他非当前处理器( 处理器 1 到 n )。但是,你无法更改非当前处理器的任何设置,只能查看其状态

11.1 选择一个处理器

你可以使用 .echocpunum(显示 CPU 编号)命令来显示当前处理器的处理器编号。通过内核调试提示符中的文本,你可以立即判断自己是否正在多处理器系统上工作。

在以下示例中,kd> 提示符前的 0: 表示你正在调试计算机中的第一个处理器。

0: kd>

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

0: kd> ~1s

1: kd>

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

如果在多处理器系统中遇到程序崩溃且无法理解堆栈跟踪信息,你可能需要更换处理器。崩溃可能发生在不同的处理器上。

11.2 用其它命令指定一个处理器

你可以在多个命令前添加处理器编号。除 ~S 命令外,此编号前面无需加波浪号 (`~`)。

注意:在用户模式调试中,波浪号用于指定线程。有关此语法的更多信息,请参阅"线程语法"。

处理器 ID 不必显式引用。你可以使用解析为与处理器 ID 对应的整数的数值表达式。要指示该表达式应解释为处理器,请使用以下语法。

||[Expression]

在此语法中,方括号是必需的,表达式代表任何解析为与处理器 ID 对应的整数的数值表达式。

在以下示例中,处理器会根据用户定义的伪寄存器的值而改变。

||[@$t0]

11.3 应用举例

以下示例使用 k(显示堆栈回溯)命令显示来自处理器二的堆栈跟踪。

1: kd> 2k

以下示例使用 r(寄存器)命令显示处理器三的 eax 寄存器。

1: kd> 3r eax

但是,以下命令会产生语法错误,因为你不能更改当前处理器以外的处理器的状态。

1: kd> 3r eax=808080

11.4 断点

在内核调试期间,bp,bu,bm(设置断点)和 ba(访问时中断)命令适用于多处理器计算机的所有处理器。

例如,如果当前处理器为 3,则可以输入以下命令在 SomeAddress 处设置断点。

1: kd> bp SomeAddress

11.5 显示处理器断点

可以使用 !running 扩展来显示目标计算机上每一个处理器的状态。对于每一个处理器,!running 还可以显示进程控制块 (PRCB) 中的当前线程和下一个线程字段,16 个内置排队自旋锁的状态以及堆栈跟踪。

你可以使用 !cpuinfo 和 !cpuid 扩展来显示有关处理器本身的信息。

相关推荐
mahuifa4 个月前
C++(Qt)软件调试---binutils工具集详解(39)
linux·c++·软件调试·binutils
mahuifa5 个月前
C++(Qt)软件调试---bug排查记录(36)
c++·bug·软件调试
qwertyuiop_i6 个月前
windows内核研究(软件调试-内存断点)
软件调试·windows内核研究·内存断点
qwertyuiop_i6 个月前
windows内核研究(软件调试-软件断点)
软件调试·windows内核研究·软件断点
qwertyuiop_i6 个月前
windows内核研究(软件调试-调试事件的处理)
软件调试·windows内核研究·调试事件的处理
捕鲸叉1 年前
Linux/C/C++下怎样进行软件性能分析(CPU/GPU/Memory)
c++·软件调试·软件验证
捕鲸叉1 年前
在Windows/Linux/MacOS C++程序中打印崩溃调用栈和局部变量信息
c++·软件调试·软件验证·软件诊断
捕鲸叉1 年前
MacOS/C/C++下怎样进行软件性能分析(CPU/GPU/Memory)
软件调试·软件验证
捕鲸叉1 年前
怎样在Linux PC上调试另一台PC的内核驱动程序,以及另一台Arm/Linux上的程序和驱动程序
linux·arm开发·软件调试·诊断调试