NetRoc's Blog

N-Tech

 

WinDbg 文档翻译----45

cc682/NetRoc/小喂

http://netroc682.spaces.live.com/

各种语法形式直接使用了小喂翻译的文档。新版本WinDbg文档中更新过的部分作了一些修改。非常感谢小喂的共享。他的主页在

http://www.ztssoft.com/

 

字符串通配符语法

某些调试器命令带有接受多种通配符的字符串参数。这些参数在各自的参考页中解释。

这些参数支持下列语法特点:

  • 一个星号(*)表示零个或多个字符。
  • 一个问号(?)表示任意单个字符。
  • 包含一列字符的中括号([ ])表示该列字符中的任意单个字符。正好匹配该列中的一个字符。在这些中括号中你可以用一个连字号(-)表示范围。例如,Prog[er-t7]am 将和 "Progeam", "Program", "Progsam", "Progtam" 以及 "Prog7am" 匹配。
  • 一个井号(#)匹配零个或多个前一个字符。例如,Lo#p 将匹配 "Lp", "Lop", "Loop", "Looop" 等等。可以和中括号组合起来使用 - 于是 m[ia]#n 将匹配 "mn", "min", "man", "maan", "main", "mian", "miin", "miain" 等等。(译注:可以使用 ? $spat("miaiaiaaain", "m[ia]#n") 测试是否匹配)。
  • 一个加号(+)匹配一个或多个前一个字符。所以,除了不匹配 "Lp" 本身外 Lo+p Lo#p 一样。类似的,m[ia]+n 也和 m[ia]#n 一样,除了不匹配 "mn" 本身。a?+b 也和 a*b 一样,除了不匹配 "ab" 本身。
  • 如果你需要使用 #? [, ] *+ 字符本身,需要在这些字符前面加一个反斜杠(\)。连字号如果不在中括号里总是表示本来意义,但没有办法在中括号里使用一个本来意义的连字号。

指定符号的参数还支持一些额外的特点。除了标准的字符串通配符之外,用来指定符号的文本表达式还可以添加一个下划线(_)前缀,当匹配符号时,调试器会把下划线前缀当做零个或多个下划线。这个特点仅在匹配符号时使用 ,它不能用在一般的字符串通配符表达式中。有关符号语法的详细信息,请看符号语法和符号匹配

寄存器语法

调试器能够操作寄存器和浮点寄存器。(操作寄存器的命令概述,请看读写寄存器和标志。)

在表达式中使用寄存器时,最好加一个 @ 前缀,暗示调试器紧接着的文本是一个寄存器的名字。

如果你使用 MASM 表达式语法,对于某些很常用的寄存器可以忽略 @ 前缀。在 x86 系统上,@ 前缀可忽略的寄存器有 eax, ebx, ecx, edx, esi, edi, ebp, eipefl。然而,如果你指定一个很少使用的寄存器,不带 @ 前缀,调试器将首先把该文本解析为十六进制数值。如果该文本包含非十六进制字符,接下来会被解析为符号。最后,如果没有匹配的符号,它将被解析为寄存器。

如果你使用 C++ 表达式语法,这个 @ 前缀总是必需的。

r (Registers)命令是个例外。它的第一个参数总是被解析为寄存器或者伪寄存器(@ 标记不是必须的,有也是允许的)。如果有第二个参数,会根据缺省的表达式语法来解释。所以,如果缺省的表达式语法是 C++,你应该采用下面的命令把 ebx 寄存器拷贝给 eax 寄存器。

0:000> r eax = @ebx

各个处理器特有的寄存器和指令信息,请看处理器架构

x86 处理器上的标志位

x86 处理器同样使用许多一位的寄存器称为标志位。查看x86标志,获得所有标志位以及查看修改标志位的语法。

寄存器和线程

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

在用户模式下,任何对寄存器的参考都被解析为和当前线程相关的寄存器。详细请看控制进程和线程

在内核模式下,任何对寄存器的参考都被解析为和当前寄存器上下文(register context)相关的寄存器。寄存器上下文可设置为和特定的线程、上下文记录或者异常帧相匹配。对指定的寄存器上下文只有最重要的寄存器能够显示,并且它们的值不能修改。

伪寄存器语法(Pseudo-Register Syntax)

调试器支持许多具有特定值的伪寄存器

自动伪寄存器(Automatic pseudo-registers)被调试器设置为特定的有用值。自定义伪寄存器(User-defined pseudo-registers)是能够被调试器操作码(operator)读写的整型变量。

所有伪寄存器都以一个美元标记($)打头。如果你使用 MASM 语法,你可以在 $ 标记前加一个 at 标记(@)。这告诉调试器紧接着的记号是一个寄存器或者伪寄存器,不是一个符号。如果省略 @ 标记,调试器反映会慢一点,因为它要搜索整个符号表。

例如,下面两条命令产生一样的输出结果,但第二条命令更快:

0:000> ? $exp
Evaluate expression: 143 = 0000008f
0:000> ? @$exp
Evaluate expression: 143 = 0000008f

如果有一个实际的符号和伪寄存器名字相同,则 @ 标记是必须的。

如果你使用 C++ 表达式语法,则 @ 标记始终是必须的。

r (Registers)命令是一个例外。它的第一个参数总是被解释为寄存器或者伪寄存器;@ 标记不是必须的,有也是允许的。如果有第二个参数,会根据缺省的表达式语法来解释。所以,如果缺省的表达式语法是 C++,你应该采用下面的命令把 $t2 伪寄存器的值复制给 $t1 伪寄存器。

0:000> r $t1 = @$t2

自动伪寄存器(Automatic Pseudo-Registers)

下表列出了会自动赋值的伪寄存器。

伪寄存器

描述

$ea

最后一条被执行指令的有效地址(effective address)。如果这条指令没有一个有效地址,将显示"Bad register error"。如果这条指令有两个有效地址,则显示第一个地址。

$ea2

最后一条被执行指令的第二个有效地址,如果这条指令没有两个有效地址,将显示"Bad register error"。

$exp

最后一个被求值的表达式。

$ra

当前堆栈的返回地址。

 

这个在执行命令中特别有用。例如,g @$ra 将一直执行到返回地址处(虽然,对于"步出(stepping out)"当前函数 gu (Go Up) 是一个更加准备有效的方法)。

$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

$csp 当前调用堆栈指针,是一个通常表示调用堆栈深度的寄存器。

 

x86 处理器:和 esp 相同

Itanium 处理器:和 bsp 相同

 

x64 处理器:和 rsp 相同

$p

最后一条 d* (Display Memory)命令打印的值。

$proc

当前进程的地址(换句话说,就是 EPROCESS 块的地址)。

$thread

当前线程的地址(换句话说,就是 ETHREAD 块的地址)。

$peb

当前进程的进程环境块(PEB)的地址。

$teb

当前线程的线程环境块(TEB)的地址。

$tpid

当前线程所在进程的进程 ID(PID)。

$tid

当前线程的线程 ID。

$bpNumber

对应断点的地址。例如,$bp3(或者 $bp03)引用断点 ID 为 3 的断点。Number 总是一个十进制数,如果没有哪个断点的 ID 为 Number,则 $bpNumber 求值为 0,详细请看 使用断点

$frame

当前帧索引,这个和.frame (Set Local Context)命令常用的 frame number 相同。

$dbgtime

当前时间,根据调试器运行的计算机。

$callret

.call (Call Function)命令调用的或者被 .fnret /s命令使用的最后函数得到的返回值,$callret 的数据类型就是返回值的数据类型。

$ptrsize

指针大小。在内核模式下,指目标计算机上的指针大小。

$pagesize

一个内存页的大小(也就是占用的字节数目),在内核模式下,指目标计算机上的页大小。

 

在某些调试情景下这些伪寄存器有一部分是不可用的。例如,在调试一个用户模式 minidump 或者调试某些内核模式 dump 文件时 $peb, $tid$tpid 不能用 - 所以这种情况你能够从 ~ (Thread Status)得到线程信息,而不能从 $tid 得到。当第一个真正的调试器事件发生时 $previp 伪寄存器不能用,除非你正在跟踪分支指令,否则 $relip 伪寄存器也不能用。使用一个无效的伪寄存器会导致一个语法错误。

在 Itanium 处理器上,iip 寄存器是紧凑对齐的(bundle-aligned),所以不是一个完整的指令指针。$ip 伪寄存器是有效的指令指针(actual instruction pointer),包括有效位和空位(the bundle and the slot)。其它具有地址指针的伪寄存器($ra, $retreg, $eventip, $previp, $relip $exentry)在所有的处理器上都和 $ip 有相同的结构。

你可以用 r 命令改变 $ip 的值,它会自动修改对应的寄存器值。当恢复执行时,会恢复到新的指令指针地址处执行。这是唯一一个可以手动修改的自动变化伪寄存器。

注解  在 MASM 语法中,$ip 伪寄存器也可以用单个点号(.)表示。这个点号前面不需要跟一个 @ 标记,不能做为 r 命令的第一个参数。在 C++ 表达式中不允许使用该语法。

自动伪寄存器与自动别名相似,除了自动别名可以使用别名相关的记号如 ${ },而伪寄存器不能使用之外。

自定义伪寄存器(User-Defined Pseudo-Registers)

有二十个自定义伪寄存器:$t0, $t1, ..., $t19。它们是可以通过调试器读写的变量。能用来保存任意整数值。做为循环变量时非常有用。

要赋值给伪寄存器,使用r (Registers)命令:

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(译注:这里好像是 UNICODE_STRING 类型吧!另外,如果 UNICODE_STRING 解析不了,可以用 _UNICODE_STRING)。

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

当调试器启动时,自定义伪寄存器缺省为零。

注解  别名 $u0, $u1, ..., $u9 不是伪寄存器,尽管它们外表很相似,详细请看 使用别名

示例

下面的例子设置了一个断点,每次当前线程调用 NtOpenFile 函数时会断下来,其它线程的调用则不会。

kd> bp /t @$thread nt!ntopenfile

示例

该命令重复执行一条命令,直到寄存器的值变为指定值。首先将下面的条件语句放入名为"eaxstep"的脚本文件。

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

然后执行如下命令。

t "$<eaxstep"

调试器进行单步执行并运行指定的命令。运行脚本时会显示1234或重复该过程。

源码行语法

源文件行数值可以做为 MASM 表达式的一部分使用。它们计算出对应源码行的可执行代码偏移值。

注解  源代码行数值不能做为 C++ 表达式的一部分使用。关于什么时候使用MASM和C++表达式语法,查看表达式求值

源文件和行数表达式需要用重音符号(`)括住。完整的源码行号的格式如下:

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

如果你有多个文件同名,Filename 应该包含完整的路径和文件名。路径使用编译时的路径。如果你仅提供文件名或者一部分路径,而且有多个匹配项,调试器将使用第一个找到的匹配项。

如果省略Filename,调试器假定使用对应当前程序计数器(program counter)对应的源文件。

不管当前的缺省基数是多少,除非带有 0x 前缀,否则 LineNumber 将被读作十进制数值。如果省略 LineNumber,表达式将被计算为对应源文件的起始可执行地址。

在 CDB 中不会计算源码行表达式,除非使用了.lines (Toggle Source Line Support)命令或者调试器带有-lines 命令行选项启动。

有关源码调试的更详细信息,请看源码模式调试

地址和地址范围语法

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

除非特别说明,否则地址总是虚拟地址。在用户模式下,虚拟地址根据当前进程的页目录来解析。在内核模式下,虚拟地址根据进程上下文(process context)指定进程的页目录来解析。也可能直接设置用户模式地址上下文(user-mode address context)。关于用户模式地址上下文的更多信息,查看.context (Set User-Mode Address Context)

地址模式和段支持

在 x86 平台上,CDB 和 KD 支持两种地址模式。通过前缀区分这两种模式

前缀

名字

地址类型

%

flat

32 位地址(也可以是 16 位选择子指向 32 位段)和64位系统上的64位地址。

&

virtual 86

实模式地址 - 仅针对 x86。

#

plain

实模式地址。仅针对x86。

 

virtual 86模式和plain模式的区别在于plain 的16位地址将段值作为选择子用来查找段描述符。但是virtual 86地址不使用选择子而是直接映射到低1 MB内存。

如果你使用非当前缺省的地址模式存取内存,可以使用地址模式前缀掩盖当前的地址模式。

地址参数(Address Arguments)

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

语法

含义

offset

虚拟内存空间的绝对地址,具有和当前执行模式匹配的类型。例如,如果当前执行模式是16位,则偏移是16位。如果当前执行模式是32位,则偏移是32位。

&[[ segment:]] offset

实地址。x86和x64。

%segment:[[ offset]]

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

%[[ offset]]

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

name[[ +| ]] offset

32位或64位平坦地址。name可以是任意符号。offset指定偏移。偏移可以由不同的前缀来表示不同的地址模式。不带前缀表示使用默认地址模式。可以指定正(+)偏移或负(−)偏移。

 

使用dg (Display Selector)命令查看段描述符(segment descriptor)信息。

在 MASM 表达式中,你也可以用 poi 运算符取任意指针指向的值。例如,如果地址 0x00123456 处的指针指向地址 0x00420000,下面两条命令等效:

0:000> dd 420000 
0:000> dd poi(123456) 

在 C++ 表达式中,指针行为就像 C++ 中一样。然而,数值被解析为整数,所以,如果你需要取一个数值指向的值,需要先把该数值强制转换为指针:

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

也有许多伪寄存器保存着常用的地址,像当前程序地址计数等。

也可以使用原始源文件名和行数指定程序中的一个地址。详细请看源码行语法

地址范围

有两种方法可以指定一个地址范围:一对地址,或者一个地址和一个对象数目(object count)。

要使用第一种方法,需要指定起始地址和结束地址。例如,下面是一个从 0x00001000 地址开始的 8 字节的范围。

0x00001000  0x00001007

第二种形式包括一个地址参数,字母 L(大小写都行)和一个数值参数。地址指定起始地址;数值指定要检查或显示的对象的数目。依根据命令不同对象大小也不一样。例如,如果对象大小是 1 字节,下面是一个从 0x00001000 地址开始的 8 字节的范围。

0x00001000  L8

然而,如果对象大小是一个 双字(32 位或者 4 字节),下面两个都给出了 8 字节的范围:

0x00001000  0x00001007
0x00001000  L2

还有另外两种方法用来指定范围(LSize范围指示符):

  • L?Size(带一个问号)和 LSize 意思相同,除了会去掉调试器的自动范围限制。通常,有一个 256 MB 的范围限制,因为更大的范围经常打印出错。如果你实际上想要指定一个比 256 MB 大的范围,需要使用 L?Size 语法。
  • L-Size(带一个连字号)指定一个长度为 Size 到给定地址结束的范围。例如,80000000 L20 表示 0x80000000 到 0x8000001F,而 80000000 L-20 表示 0x7FFFFFE0 到 0x7FFFFFFF。

某些命令需要地址范围时也接受单个地址作为参数。这种情况下,该命令将使用某些缺省的对象数目来计算范围大小。典型的,那些地址范围是最后一个参数的命令将接受这种语法。参考单个命令说明得到准确的语法和每个命令的缺省范围大小。

线程语法

许多调试器命令带有线程标识符作为参数。在线程标识符前面有一个波形符(~)作为前缀。

线程标识符可以是下面的值中的一个。

线程标识符

说明

~.

当前线程。

~#

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

~*

进程中的所有线程。

~Number

序号为 Number 的线程。

~~[TID]

线程 ID 为 TID 的线程。(中括号是必需的,而且在第二个~和左括号间不能有空格)

 

线程被创建时就分配了序号。注意这和Microsoft Windows 使用的线程 ID 不一样。

调试开始时,导致当前异常或调试事件的是当前线程(或者是调试器附加到进程时的活动线程)。当前线程保持不变,直到使用~s (Set Current Thread)命令或者 WinDbg 中的进程和线程窗口指定了新的线程。

线程标志符经常用作命令前缀。注意在使用线程标志符的所有命令中不是所有的通配符都可以使用。

在内核模式下控制线程

在内核模式下,不能使用线程标识符控制线程。关于在内核模式下如何访问线程相关信息的描述,请看改变上下文

注解  波形符( ~ )在用户模式调试时用来指定线程。在内核模式调试时用来指定处理器。更多请看多处理器语法

进程语法

许多调试命令带有进程标识符作为参数。进程标识符前面带一个竖线( | )。

进程标识符可以是以下的值。

进程标识符

说明

|.

当前进程。

|#

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

|*

所有进程。

|Number

序号为 Number 的进程。

|~[PID]

进程 ID 为 PID 的进程。(中括号是必需的,而且在波形符和左括号间不能有空格)

 

进程被创建时就分配了序号。注意这个序号和Microsoft Windows 使用的进程 ID(PID)不一样。

当前进程定义了用到的内存空间和线程集合。调试开始时,导致当前异常或调试事件的是当前进程(或者是调试器所附加的进程)。当前进程保持不变,直到使用|s (Set Current Process)命令或者 WinDbg 中的进程和线程窗口指定了新的进程。

在某些命令中使用进程标志符作为参数,大部分是作为命令前缀。注意,WinDbg 和 CDB 可以调试原进程产生的子进程,也可以附加到多个不相关的进程上。

在内核模式下控制进程

在内核模式下,不能使用进程标识符控制进程。关于在内核模式下如果访问进程相关信息的描述,请看改变上下文

系统语法

很多调试器命令使用进程标识符(译者注,疑为系统标识符)作为它们的参数。

在系统标志符前带有两个竖线( || ),可以为下面任一种形式:

系统标识符

描述

||.

当前系统

||#

产生当前异常或调试事件的系统

||*

所有系统

||ddd

序号为 ddd 的系统

 

系统按照调试器附加上的顺序分配序号。

当调试开始时,产生当前异常或调试事件的是当前系统,或者调试器最近附加上的是当前系统。当前系统保持不变,直到使用 ||s (Set Current System)命令或者使用 WinDbg 中的进程和线程窗口指定了新的系统。

多处理器语法

KD 和内核模式 WinDbg 支持多处理器调试。可以在任何多处理器平台实现。

处理器从 0 到 n 编号。

如果当前处理器是处理器 0(也就是说,当前导致调试器激活的是处理器 0),你可以查看其它非当前处理器(处理器 1 到 n)。然而,你不能修改非当前处理器上的任何事情 , 只能查看它们的状态。

选择处理器

你可以使用.echocpunum (Show CPU Number)命令显示当前处理器的序号。当你正处在多处理器系统上时通过内核调试提示符的形式马上表现出来。

下面的例子中,kd>提示符前的0:表示正在调试目标机的第一个处理器。

0: kd>

使用 ~s (Change Current Processor)命令在处理器之间切换。

0: kd> ~1s
1: kd>

现在变成调试电脑上的第二个处理器。

在一个多处理器系统上,如果你遇到断点而且堆栈回溯杂乱无章,有可能该断点发生在不同的处理器上。

在其它命令中指定处理器

许多命令都可以带一个处理器序号前缀。该序号前面需要跟波形符(~),~S 命令除外。

注解  在用户模式调试下,颚化符号常用来指定线程。详细请看线程语法

示例

下例使用k (Display Stack Backtrace)命令显示处理器 2 上的堆栈回溯。

1: kd> 2k 

下例使用 r (Registers)命令显示处理器 3 上的 eax 寄存器:

1: kd> 3r eax 

但是,下面的命令会引起语法错误,因为不允许改变当前激活处理器以外的处理器状态:

1: kd> 3r eax=808080 

断点

内核调试过程中,bp, bu, bm (Set Breakpoint)ba (Break on Access)命令会影响多处理器电脑上的所有处理器。

例如,如果当前有 3 个处理器,可以通过下面的命令在SomeAddress 设置断点。

1: kd> bp SomeAddress 

任何处理器(不单是处理器 1)执行到该地址都将引起断点陷阱(breakpoint trap)。

显示处理器信息

!running扩展命令可用来显示目标计算机上各个处理器的状态。对每个处理器,!running 也可以显示进程控制块(PRCB)中的当前和下一个线程域,16 个内建 spinlocks 队列,以及堆栈回溯。

!cpuinfo!cpuid扩展命令可以用来显示处理器本身的信息。

posted on 2008-06-05 15:07 NetRoc 阅读(729) 评论(1)  编辑 收藏 引用

评论

# re: WinDbg 文档翻译----45 2008-06-06 14:20 windows7

非常滴感谢,学到了很多……  回复  更多评论   

只有注册用户登录后才能发表评论。

导航

统计

常用链接

留言簿(7)

随笔档案(99)

文章分类(35)

文章档案(32)

Friends

Mirror

搜索

最新评论

阅读排行榜

评论排行榜