zoukankan      html  css  js  c++  java
  • 向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!
    " 
    }

    此脚本是多行的,必须使用$><或$$><命令执行:

    0:000> $$><c:scriptsHelloWorld.wds 
    Hello World!

    当我们在WinDbg命令窗口中键入或使用$<或$$<命令从文件加载单行脚本时,可以执行这些脚本:

    $$ Hello World script; .block { .printf "Hello World!
    " } 

    我们可以看到,在一行脚本中,除非命令或注释是最终的,否则注释和命令必须以分号结尾。如果命令位于单独的行上,则多行脚本不需要分号。

    0:000> $$<c:scriptsHelloWorld2.wds 
    0:000> $$ Hello World script; .block { .printf "Hello World!
    " } 
    Hello World! 

    从现在起,我们将只使用多行脚本,因为它们的可读性。您可能已经注意到,我故意将.printf 放在.block{}括起来,使第一个脚本比需要的更复杂,以显示与C样式函数的相似性:

    // Hello World function 
    void helloWorld () 
    { 
      printf ("Hello World!
    "); 
    } 

    简单算术

    考虑一个简单的C样式函数,它打印2个数字的和并使用局部变量:

    void sum () 
    { 
        unsigned long t1 = 2; 
        unsigned long t2 = 3; 
        unsigned long t0 = t1 + t2;  
        printf("Sum(%x,%x) = %x
    ", 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
    ", @$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
    ", ${$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
    ", ${$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
    ", @$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:scriptsFactorialR.wds ${$arg1}-1 
        r $t1 = $arg1 
        r $t0 = @$t1 * @$t0   
      } 
      .else 
      { 
        r $t0 = 1 
      }  
      .printf "Factorial(%p) = %p
    ", ${$arg1}, @$t0       
    }

    某些参数的脚本输出:

    0:000> $$>a<c:scriptsFactorialR.wds 1 
    Factorial(0000000000000001) = 0000000000000001 
    0:000> $$>a<c:scriptsFactorialR.wds 2 
    Factorial(0000000000000001) = 0000000000000001 
    Factorial(0000000000000002) = 0000000000000002 
    0:000> $$>a<c:scriptsFactorialR.wds 3 
    Factorial(0000000000000001) = 0000000000000001 
    Factorial(0000000000000002) = 0000000000000002 
    Factorial(0000000000000003) = 0000000000000006 
    0:000> $$>a<c:scriptsFactorialR.wds 4 
    Factorial(0000000000000001) = 0000000000000001 
    Factorial(0000000000000002) = 0000000000000002 
    Factorial(0000000000000003) = 0000000000000006 
    Factorial(0000000000000004) = 0000000000000018 
    0:000> $$>a<c:scriptsFactorialR.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
    ", ${$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
    ", ${$arg1}, @$t0       
    }

    其输出相同:

    0:000> $$>a<f:FactorialL2.wds 10
    Factorial(00000010) = 77758000
  • 相关阅读:
    Windows Server 2012 R2 英文版安装中文语言包教程
    远程连接提示要求的函数不受支持如何解决
    Authenticator App 两步验证会不会造成亚马逊账号关联?
    Linux环境下查看历史操作命令及清除方法(history -c)
    出现Strict Standards: Only variables should be passed by reference in的解决方法
    Mysql索引优化
    MySQL性能优化(四)-- MySQL explain详解
    接口性能指标TP90
    本地项目git初始化并提交远程仓库
    git强制推送命令
  • 原文地址:https://www.cnblogs.com/yilang/p/12155094.html
Copyright © 2011-2022 走看看