向C/C++程序员介绍Windbg 脚本

来自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+@$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

posted on 2020-01-06 10:28  活着的虫子  阅读(1023)  评论(0编辑  收藏  举报

导航