来自 Windows 调试工具包的所有调试程序都使用相同的引擎 dbgeng.dll. 它包含一种特殊语言的脚本解释器, 我们称之为 WinDbg 脚本语言以方便使用, 我们对 WinDbg 脚本文件使用 WDS 文件扩展名. 下面是在分析一个脚本时捕获的 WinDbg 线程的调用堆栈:
- 0:000> ~1kL 100
- ChildEBP RetAddr
- 037cd084 6dd28cdc dbgeng!TypedData::ForceU64+0x3
- 037cd0ec 6dcbd08c dbgeng!GetPseudoOrRegVal+0x11c
- 037cd134 6dcbceff dbgeng!MasmEvalExpression::GetTerm+0x12c
- 037cd198 6dcbca23 dbgeng!MasmEvalExpression::GetMterm+0x36f
- 037cd1d4 6dcbc873 dbgeng!MasmEvalExpression::GetAterm+0x13
- 037cd220 6dcbc783 dbgeng!MasmEvalExpression::GetShiftTerm+0x13
- 037cd254 6dcbc523 dbgeng!MasmEvalExpression::GetLterm+0x13
- 037cd2c0 6dcbc443 dbgeng!MasmEvalExpression::GetLRterm+0x13
- 037cd2f4 6dcbc424 dbgeng!MasmEvalExpression::StartExpr+0x13
- 037cd308 6dcbbc2f dbgeng!MasmEvalExpression::GetCommonExpression+0xc4
- 037cd31c 6dccdca3 dbgeng!MasmEvalExpression::Evaluate+0x4f
- 037cd390 6dccd83d dbgeng!EvalExpression::EvalNum+0x63
- 037cd3d0 6dd293cc dbgeng!GetExpression+0x5d
- 037cd458 6dd2a7e2 dbgeng!ScanRegVal+0xfc
- 037cd4ec 6dd17502 dbgeng!ParseRegCmd+0x422
- 037cd52c 6dd194e8 dbgeng!WrapParseRegCmd+0x92
- 037cd608 6dc8ed19 dbgeng!ProcessCommands+0x1278
- 037cd644 6dc962af dbgeng!DotFor+0x1d9
- 037cd658 6dd1872e dbgeng!DotCommand+0x3f
- 037cd738 6dd19b49 dbgeng!ProcessCommands+0x4be
- 037cd77c 6dc5c879 dbgeng!ProcessCommandsAndCatch+0x49
- 037cdc14 6dd19cc3 dbgeng!Execute+0x2b9
- 037cdc64 6dc89db0 dbgeng!ProcessCurBraceBlock+0xa3
- 037cdc74 6dc962af dbgeng!DotBlock+0x10
- 037cdc88 6dd1872e dbgeng!DotCommand+0x3f
- 037cdd68 6dd19b49 dbgeng!ProcessCommands+0x4be
- 037cddac 6dc5c879 dbgeng!ProcessCommandsAndCatch+0x49
- 037ce244 6dd173ca dbgeng!Execute+0x2b9
- 037ce2c4 6dd1863c dbgeng!ParseDollar+0x29a
- 037ce3a0 6dd19b49 dbgeng!ProcessCommands+0x3cc
- 037ce3e4 6dc5c879 dbgeng!ProcessCommandsAndCatch+0x49
- 037ce87c 6dc5cada dbgeng!Execute+0x2b9
- 037ce8ac 00318693 dbgeng!DebugClient::ExecuteWide+0x6a
- 037ce954 00318b83 windbg!ProcessCommand+0x143
- 037cf968 0031ae46 windbg!ProcessEngineCommands+0xa3
- 037cf97c 76fa19f1 windbg!EngineLoop+0x366
- 037cf988 77c8d109 kernel32!BaseThreadInitThunk+0xe
- 037cf9c8 00000000 ntdll!_RtlUserThreadStart+0x23
在这里, 我假设你已经知道 C 或 C++ 语言, 或者任何像 C 语言或 C 语言这样的 C 风格语言. 因此, 当我们查看和比较等价的 C/C++ 和 Windbg 脚本代码时, 我省略了对于似乎具有相似语法和语义的语言元素的解释.
Hello World
让我们写第一个脚本来打印这个著名的消息.
- $$ HelloWorld.wds - Hello World script
- .block
- {
- .printf "Hello World!\n"
- }
此脚本是多行的, 必须使用 $>< 或 $$>< 命令执行:
- 0:000> $$><c:\scripts\HelloWorld.wds
- Hello World!
当我们在 WinDbg 命令窗口中键入或使用 $< 或 $$< 命令从文件加载单行脚本时, 可以执行这些脚本:
$$ Hello World script; .block { .printf "Hello World!\n" }
我们可以看到, 在一行脚本中, 除非命令或注释是最终的, 否则注释和命令必须以分号结尾. 如果命令位于单独的行上, 则多行脚本不需要分号.
- 0:000> $$<c:\scripts\HelloWorld2.wds
- 0:000> $$ Hello World script; .block {
- .printf "Hello World!\n"
- }
- Hello World!
从现在起, 我们将只使用多行脚本, 因为它们的可读性. 您可能已经注意到, 我故意将. printf 放在. block{} 括起来, 使第一个脚本比需要的更复杂, 以显示与 C 样式函数的相似性:
- // Hello World function
- void helloWorld ()
- {
- printf ("Hello World!\n");
- }
简单算术
考虑一个简单的 C 样式函数, 它打印 2 个数字的和并使用局部变量:
- void sum ()
- {
- unsigned long t1 = 2;
- unsigned long t2 = 3;
- unsigned long t0 = t1 + t2;
- printf("Sum(%x,%x) = %x\n", t1, t2, t0);
- }
在 WinDbg 脚本中, 我们可以使用 20 个不同的用户定义变量, 称为伪寄存器. 他们的名字是 $t0-$t19. 如果要获取伪寄存器值, 请使用 @符号, 例如,@$t0. 我们可以使用. printf 中的 %p 类型字段字符将该值解释为指针. 这是等效的 WinDbg 脚本及其输出:
- $$ Arithmetic1.wds - Calculate the sum of two predefined variables
- .block
- {
- r $t1 = 2
- r $t2 = 3
- r $t0 = @$t1 + @$t2
- .printf "Sum(%p, %p) = %p\n", @$t1, @$t2, @$t0
- }
- 0:000> $$><f:\Arithmetic1.wds
- Sum(00000002, 00000003) = 00000005
使用硬编码值是没有用的. 让我们重写同一个函数来使用参数. 与 WinDbg 脚本中的函数参数等价的是字符串的 $arg1-$argN 别名. 要获取别名值, 请将其括在 ${...} 中, 例如 ${$arg1}. 但是, 如果在某些表达式中使用它, 并且参数的类型可以从其他参与操作数推断出来, 则不需要将其括起来.
- $$ Arithmetic2.wds - Calculate the sum of two function arguments
- .block
- {
- r $t0 = $arg1 + $arg2
- .printf "Sum(%p, %p) = %p\n", ${$arg1}, ${$arg2}, @$t0
- }
现在我们可以调用脚本并指定参数:
- 0:000> $$>a<f:\Arithmetic2.wds 1 2
- Sum(00000001, 00000002) = 00000003
如果缺少某些参数, 则会出现错误:
- 0:000> $$><f:\Arithmetic2.wds
- Couldn't resolve error at'$arg1 + $arg2 ; .printf "Sum(%p, %p) = %p\n", ${
- $arg1
- }, ${
- $arg2
- }, @$t0 ;'
WinDbg 允许我们检查是否定义了参数. 这可以通过别名解释器 ${/d:...} 的特殊形式完成:
- $$ Arithmetic3.wds - Calculate the sum of two optional function arguments
- .block
- {
- r $t1 = 0
- .if (${/d:$arg1})
- {
- r $t1 = $arg1
- }
- r $t2 = 0
- .if (${/d:$arg2})
- {
- r $t2 = $arg2
- }
- .printf "Sum(%p, %p) = %p\n", @$t1, @$t2, @$t1[email protected]$t2
- }
以下是一些参数的脚本输出:
- 0:000> $$>a<f:\Arithmetic3.wds
- Sum(00000000, 00000000) = 00000000
- 0:000> $$>a<f:\Arithmetic3.wds 1
- Sum(00000001, 00000000) = 00000001
- 0:000> $$>a<f:\Arithmetic3.wds 1 2
- Sum(00000001, 00000002) = 00000003
递归 & 循环
让我们编写更复杂的脚本来计算给定数字的阶乘. 回想一下阶乘函数的以下定义:
n! = 1*2*3*4*...*(n-2)*(n-1)*n
此函数可以使用以下代码递归计算:
- // C-style factorial function using recursion
- unsigned long factorial (unsigned long n)
- {
- unsigned long f = 0;
- if (n> 1)
- {
- f = n*factorial(n-1);
- }
- else
- {
- f = 1;
- }
- return f;
- }
或者, 可以使用 while 或 for 循环计算:
- // C-style factorial function using a "while" loop
- unsigned long factorial (unsigned long n)
- {
- unsigned long k=1;
- while (n-1)
- {
- k = k * n;
- --n;
- }
- return k;
- }
- // C-style factorial function using a "for" loop
- unsigned long factorial2 (unsigned long n)
- {
- unsigned long k=1;
- for (; n-1; --n)
- {
- k = k * n;
- }
- return k;
- }
WinDbg 脚本也可以递归调用. 我们可以将 C 风格的代码映射到 WinDbg 脚本, 其中 $t0 伪寄存器用于模拟函数返回值:
- $$ FactorialR.wds - Calculate factorial using recursion
- .block
- {
- .if (${$arg1}> 1)
- {
- $$>a<c:\scripts\FactorialR.wds ${$arg1}-1
- r $t1 = $arg1
- r $t0 = @$t1 * @$t0
- }
- .else
- {
- r $t0 = 1
- }
- .printf "Factorial(%p) = %p\n", ${$arg1}, @$t0
- }
某些参数的脚本输出:
- 0:000> $$>a<c:\scripts\FactorialR.wds 1
- Factorial(0000000000000001) = 0000000000000001
- 0:000> $$>a<c:\scripts\FactorialR.wds 2
- Factorial(0000000000000001) = 0000000000000001
- Factorial(0000000000000002) = 0000000000000002
- 0:000> $$>a<c:\scripts\FactorialR.wds 3
- Factorial(0000000000000001) = 0000000000000001
- Factorial(0000000000000002) = 0000000000000002
- Factorial(0000000000000003) = 0000000000000006
- 0:000> $$>a<c:\scripts\FactorialR.wds 4
- Factorial(0000000000000001) = 0000000000000001
- Factorial(0000000000000002) = 0000000000000002
- Factorial(0000000000000003) = 0000000000000006
- Factorial(0000000000000004) = 0000000000000018
- 0:000> $$>a<c:\scripts\FactorialR.wds 10
- Factorial(0000000000000001) = 0000000000000001
- Factorial(0000000000000002) = 0000000000000002
- Factorial(0000000000000003) = 0000000000000006
- Factorial(0000000000000004) = 0000000000000018
- Factorial(0000000000000005) = 0000000000000078
- Factorial(0000000000000006) = 00000000000002d0
- Factorial(0000000000000007) = 00000000000013b0
- Factorial(0000000000000008) = 0000000000009d80
- Factorial(0000000000000009) = 0000000000058980
- Factorial(000000000000000a) = 0000000000375f00
- Factorial(000000000000000b) = 0000000002611500
- Factorial(000000000000000c) = 000000001c8cfc00
- Factorial(000000000000000d) = 000000017328cc00
- Factorial(000000000000000e) = 000000144c3b2800
- Factorial(000000000000000f) = 0000013077775800
- Factorial(0000000000000010) = 0000130777758000
现在我们准备使用 while 循环重写脚本.
- $$ FactorialL.wds - Calculate factorial using a "while" loop
- .block
- {
- r $t0 = 1
- r $t1 = $arg1
- .while (@$t1-1)
- {
- r $t0 = @$t0 * @$t1
- r $t1 = @$t1 - 1
- }
- .printf "Factorial(%p) = %p\n", ${$arg1}, @$t0
- }
脚本参数输出:
- 0:000> $$>a<f:\FactorialL.wds 10
- Factorial(00000010) = 77758000
我们可以使用. for 循环令牌简化脚本:
- $$ FactorialL2.wds - Calculate factorial using a "for" loop
- .block
- {
- .for (r $t0 = 1, $t1 = $arg1; @$t1-1; r $t1 = @$t1 - 1)
- {
- r $t0 = @$t0 * @$t1
- }
- .printf "Factorial(%p) = %p\n", ${$arg1}, @$t0
- }
其输出相同:
- 0:000> $$>a<f:\FactorialL2.wds 10
- Factorial(00000010) = 77758000
来源: http://www.bubuko.com/infodetail-3367808.html