windbg 脚本学习总结

592 篇文章 9 订阅 ¥99.90 ¥299.90
165 篇文章 18 订阅
本文总结了Windbg脚本的学习,从简单入门到高级脚本编写,通过实例讲解如何利用Windbg脚本进行文件访问,并展示了脚本在调试中的应用,还探讨了STL容器的扩展命令工具。
摘要由CSDN通过智能技术生成

windbg 脚本简单入门

http://blog.csdn.net/superliuxing/article/details/19206985

Windbg的功能自然不必说,集内核调试,应用程序调试,远程调试,dump分析等于一身,真是杀人灭口必备利器。但是也由于其太过强大,命令太多,导致很多新手对windbg望而生畏,觉得Windbg很高深。好在已经有很多有识之士将Windbg的基础用法分享了出来,入门应该不是一件难事了。今天就不再重复谈Windbg如何入门,来探讨一下高级点的用法:脚本。其实,脚本不应该是一个很复杂的东西,但是,在Windbg的帮助里想过的内容却太过于分散了,导致学习起来颇有些海底捞针的感觉,所以我觉得有必要把我的心得跟大家分享一下。

先来解释一下,Windbg的脚本是什么?你可以理解为脚本就是一种语言,就像c或者汇编,但是他不需要编译器将其编译为可执行文件,而是由解释器将其内容翻译为对应的动作。而Windbg的脚本就是利用Windbg作为解释器,将脚本内容翻译为实际的动作。也许这个解释还是有些晦涩,那让我们跳过这些晦涩的概念,来一个简单的例子:
代码:
.echo “hello windbg”
这条命令会显示“hello windbg”这个字符串,把它保存到c:\1.txt文件,然后在Windbg的命令窗口里输入:
><c:\1.txt回车,看看屏幕上出现了什么?没错,Windbg将1.txt里的内容当做一条Windbg的命令执行了。这就是一个简单的脚本。也许有人说,这确实是一个脚本,但是他太弱了,只能打印字符串而已。别急,饭要一口一口吃,脚本要一点一点扩展。先来看看这个
><,根据前面的例子,很容易看出他的作用是将脚本文件交给Windbg解释,由他完成了将一个txt变成Windbg命令的关键转换。其实你知道了这个,Windbg脚本就算入门了,因为你可以把很多命令写在这个文件里,然后用
><装载执行。这应该能完成一些功能,不过,这样的用法充其量应该叫做batch,而不是script,因为他只能批量执行命令。那么怎么才能升级到script呢?接下来我们一步一步分解,不过在此之前,还是先把
><了解透彻。$$>< 其实有5个孪生兄弟,在windbg中给出的形式和用法如下:

代码:
$<Filename 
$><Filename 
$$< Filename 
$$>< Filename 
$$>a< Filename [arg1 arg2 arg3 ... ]
妈呀,眼都花了,看上去长得都很像。别急,他们是有规律的,归纳一下:

1.'$'的表示'<'和脚本名之间不可以有空格。
2.'
′的表示可以有空格(其实我有点不太理解这个操蛋设定,为什么不能自动检测)。3.′<′表示不会自动把脚本文件压缩为一行。4.′><′表示会把他们压缩为一行,并将原来的换行变成′;′。5.最后一个表示可以给脚本传递参数。为什么要压缩成一行?问的好,Windbg执行某些命令的时候需要他们是一行,比如bp后面可以添加其他命令,但是所有命令写一行又太长了,不容易阅读,于是帮你压缩一下。一般我们用
><就够了。

好了,接下来是脚本的时刻了。要用好脚本,先要转变自己的态度,要像学习一门编程语言一样学习他,像写代码一样写他,总之,你的思路应该和编程的思路一样。写Windows的应用程序需要哪些知识?首先需要一门编程语言,比如c,另外需要了解Windows的API。好了,我们现在对应到Windbg的脚本。Windbg提供了一些脚本的语法,相当于一门编程语言,而脚本里用到的那些命令相当于系统的API。要学好开发,先要学好一门编程语言,而语言学好以后,API就是现用现查的,所以我们就主要从语法入手。

新学一门编程语言,入门的时候都会学以下几个方面:数据类型,变量,表达式,语句,内建函数,我们也从这几个方面来了解Windbg的脚本。

1.  数据类型:
关于数据类型,Windbg的帮助里没有明确列举,但是,在使用时一般会遇到,数值和字符串这两种。
数值
数值没有太多需要解释的,和所有编程语言里的整数含义一样,在表示的时候有进制之分。
代码:
2进制  0x
8进制  0n
10进制  0t
16进制  0y
字符串
字符串用一对 ” 括起来。比如上面的 ”hello windbg”。

2.  变量:
在windbg中变量的定义很特别,实际上,他并没有变量这个概念,所以,你学习的时候会觉得很别扭。不过,我们换个思路就容易了,变量实际上就是为了保存临时结果, 如果你只想保存一些数值,那么伪寄存器应该是比较好的选择,windbg提供了20个伪寄存器$t0-$t19,供命令保存临时数值变量。称他们为伪寄存器是有原因的,首先对他们的操作和寄存器一样,都是使用r命令,在C++表达式里都前面需要加@符,但是他们又不是真正的寄存器,只是windbg定义的名字而已。使用这些伪寄存器也是很方便的:

代码:
0:000> r $t0=0x123
0:000> r $t0
$t0=00000123

0:000> r eax
eax=004c1b89

0:000> r $t0=@eax
0:000> r $t0
$t0=004c1b89
  从上面的例子也可以看出r命令后面的@是可以省略的。

3.  别名
别名和变量还有些区别,变量是在执行过程中取他的值,而别名更像是宏,在解释时直接用内容替换原始操作数。别名有两种,一种是固定名字的,一种是自定义的。
固定名字别名
固定名字别名和伪寄存器很类似,Windbg提供了10个,$u0-$u9。使用的时候依然是r命令,不过要在“u”前面加个“.”,像下面这样:

代码:
0:000> r $.u0 = "123"
0:000> .echo $u0
123
从上面的例子可以看出一旦别名被定义了,到使用他的时候,Windbg会把别名替换为内容。
自定义别名
自定义别名会复杂一些,但是,有了它的存在,我们才可以为内存中的一些字符串定义别名。操作自定义别名有3个命令:as,ad,al。
As 定义一个别名,其强大之处在于,可以指定一个内存地址,然后将内存中的内容定义为别名。

代码:
0:000> .dvalloc 10
Allocated 1000 bytes starting at 00010000
0:000> ea 00010000 "123456"
0:000> as /ma ${/v:test} 0x00010000
0:000> .echo test
123456
上面的命令将0x00010000地址的定义为一个别名,由于as使用了/ma选项,所以将内容当做一个’\0’结尾的ASCII字符串来解析,${}是别名解释器,后面再讲。除了/ma选项以外as还有一些其他强大的选项:

代码:
/ma  参数指定的内存地址当做ASCII字符串。
/mu  参数指定的内存地址当做Unicode字符串。
/msa  参数指定的内存地址当做ANSI_STRING字符串。
/msu  参数指定的内存地址当做UNICODE_STRING字符串。
/f  别名等于参数指定文件的内容。
/e  别名等于参数指定的环境变量。
al显示已经定义的别名,ad删除已经定义的别名,接着刚才的例子继续输入以下命令:
代码:
0:000> al
  Alias            Value  
 -------          ------- 
 test             123456 
0:000> ad ${/v:test}
0:000> al
No aliases
可以看的很明显吧。
现在我们来解释一下例子里那个长得很奇怪的${},这个东西叫别名解释器,把别名放在后面的大括号里面,Windbg就知道里面是个别名,需要被翻译。其实不用这个符号也可以,不过写到复杂脚本的时候就可能出问题,谁用谁知道,我就不再发散了,建议是最好用。这个解释器也有选项,上面的/v:就是一个。
/v:  保持别名原样,不翻译,在定义和删除的时候用。
/n:  如果别名定义就翻译为内容,否则不做任何翻译。
/f:  如果别名定义就翻译为内容,否则翻译为空。
/d:  如果别名被定义,翻译为1,否则翻译为0,相当于#ifdef。

4.  表达式
Windbg提供了两种表达式:汇编表达式和C++表达式。两种表达式的操作符和操作数都略有区别。

默认是汇编表达式,求汇编表达式的值用?,求C++表达式的值用??。
汇编表达式里能用的操作符除了+、-、*、/这些算数运算符以外还有一些类似转型运算符,比如poi,有时候大家断到一个函数,第一参数是个字符串指针,想打印这个字符串怎么办?可以这样 dd esp+4,然后再从结果中da一次,有了poi,一行命令就可以做到,dd poi(esp+4)。

C++表达式就更加丰富了,几乎所有的C++表达式都可以用,包括.和->操作符,想让Windbg将表达式按C++方式解释,需要在表达式前面加@@c++()。

5.  语句
都说了脚本要按照编程的思想来写,既然是编程,怎么能少得了流程控制语句呢?Windbg支持以下流程控制语句。
代码:
.if
.else
.elif
.for
.while
.break
.continue
.do
我觉得都可以不用解释,看名字就应该知道是什么,大家都是写程序的嘛,对吧。
另外还有几个比较有用的语句
代码:
.printf  格式化输出,熟悉吧。
.block  语句块
$$  注释,长得好奇怪
这里面,.block要单独说说,所谓语句块,其实就是用{}括起开的一堆语句,包括.if、.else后面的语句其实都是语句块,语句块内部的别名(还记得吗)在进入块的时候会被翻译,进入块以后,如果修改了别名的定义,那么在本块内的后续语句中是无效的(还记得别名是原样替换吗),所以,如果需要在后续语句中生效,需要把后面的语句放到一个单独的语句块里,也就是用{}把他们包含起来,但是Windbg又不能识别直接用{}包含起来的东西,于是就出现了.block,看到这里,请切记,如果需要别名被翻译,一定要把他放到语句块里。

6.  内建函数
这里只讲两个内建函数$scmp和$sicmp都是字符串比较,一个区分大小写,一个不区分大小写。这两个函数有一个毛病,那就是参数只接受字符串字面量,就是说,你只能写$scmp(“123”,”123”),不能写$scmp(poi(esp+4),”123”),好了,有人急了,不能这样写,要这两个函数有什么用?不急,我们可以利用别名(这就是别名最有用的地方),还是接着刚才那个例子:

代码:
0:000> as /ma ${/v:test} 00010000
0:000> ? $scmp("${test}","123456")
Evaluate expression: 0 = 00000000
0:000> ? $scmp("${test}","123457")
Evaluate expression: -1 = ffffffff
这样就可以比较变量字符串了。

好了,有了以上知识,写一个windbg脚本应该就有基础了,剩下的就是要看大家知道多少“API”了,更详细的信息需要在Windbg的帮助里挖掘了。

最后贴一个完整的例子,利用脚Hook CreateFileW,这个例子虽然不长,但是都是精华啊,哈哈。

代码:
.dvalloc /b 0x79990000 30
ew 0x79990000 0xc033
ed 0x79990002 0x00001cc2
bp kernel32!CreateFileW "
as /mu ${/v:filename} poi(esp+4);
.block{
    .if ($sicmp(\"${filename}\", \"c:\\1.txt\") == 0){
        .echo \"open 1.txt\";
        r eip=0x79990000
    }
}
ad ${/v:filename};
gc;
"

稍微解释一下,一开始分配了一段内存,选了一个几乎不会被用到的地址,然后填充为

代码:
xor eax,eax
ret 0x1c
之后设置一个条件断点,断到以后判断参数中的文件名,如果文件是c:\1.txt就将执行流程转移到分配的指令处,相当于直接返回,于是打开文件失败。效果如下:
名称:  QQ图片20131031221522.jpg查看次数: 1文件大小:  21.9 KB

码了这么多字,还是挺累的,本文作者evil.eagle,转载的时候烦请注明出处,如果大家希望继续交流,欢迎加QQ群151843490,另外吐槽一下,看雪的排版真的好难用啊。
========

如何写windbg高级脚本---以访问文件的windbg脚本为例说明

http://www.pediy.com/kssd/pediy10/83946.html

最近需要在访问指定文件时中断下来,但不知道如何下断,在网上搜索了一番无果,只好自己摸索了。听大侠说windbg的条件断点功能异常强大,可以实现,不禁心痒,特尝试一番,顺便熟悉一下windbg的脚本语法。

先来了解简单的,得到当前访问的文件名
先写段C代码,创建C:\a.txt并往文件中写任意几个字符,代码如下:
    HANDLE hFile=CreateFile("C:\\a.txt",
      GENERIC_WRITE|GENERIC_READ,
      0,
      NULL,
      OPEN_ALWAYS,
      FILE_ATTRIBUTE_NORMAL,
      NULL);
    if (hFile == INVALID_HANDLE_VALUE) 
    {
      return ;
    }
    char Buffer[]={"abcdefghijklemn"};
    DWORD dwReturn;
    WriteFile(hFile,Buffer,strlen(Buffer),&dwReturn,NULL);

    CloseHandle(hFile);
把以上代码放到对话框的按钮点击响应事件中去。编译链接得到test.Exe.
文件名为CreateFile API的第一个参数,因而在执行到该API的入口时,esp+4即表示第一个参数的地址。故可这样下断:
bp kernel32!CreateFileW "r $t1=poi(esp+4);.echo;.printf\"FileName:%mu\",$t1;.echo;g"

$t0~$t19为伪寄存器,可用来存储临时值,poi表示取地址的值,
也可将脚本保存为文件,然后在windbg中输入: $$><脚本文件路径 来运行。
我把以上脚本保存为 ”C:\script.txt”
用windbg打开以上代码编译得到的test.exe, 
Ctrl+Break中断windbg,然后下断,即输入:$$><C:\script.txt,再输入g,让进程运行起来,
点击按钮,果其然,得到文件名了,见下图,
 
访问的文件获取了,那如何在访问指定文件时中断下来呢?字符串比较的脚本如何写呀?
上网查资料吧,不大一会,发现了$scmp/$sicmp/$spat是用来字符串操作的。$spat正合我意呀。
  $spat("string1”, "pattern”):判断参数1指定的字符串是否符合参数2指定的模式。模式字符串中可以包含?、*、#等特殊符号,WinDBG帮助文件中String Wildcard Syntax一节包含了详细的说明;
  这样摸索了一番,写了如下脚本:
bp kernel32!CreateFileW "
r $t1=poi(esp+4)
 as /mu $FileName $t1
 .echo
 .printf\"File:%mu\",$t1
 .echo
.block
{
 .if($spat(\"${$FileName}\",\"*a.txt\")) 
 {
  .echo 'find...';
  ad ${/v:$FileName}
 }
 .else 
 {
  .echo no find...
  ad ${/v:$FileName}
  gc
 }
}"

以上脚本,不复杂,实现当访问文件名类似“a.txt”时,windbg中断执行。

对脚本解析几个要点:
1.  使用伪寄存器,更快速的方法是在$前加上一个@符号。这样,WinDBG就知道@后面是一个伪寄存器,不需要搜索其他符号;
2.  r $t1=poi(esp+4),poi(esp+4)取地址的值,并赋给伪寄存器$t1 ;
3.  as /mu $FileName $t1 ,定义$t1 所指地址一个别名$FileName,用来在下面的$spat中使用。别名会在脚本加载时被解析程序替换一次。为何要用别名?你不用试试就知道了,直接用$t1不行,windbg提示你语法错误;
4.  .block是啥东西,看windbg帮助就知道了。代码块,如果需要每次运行进入替换,请用.block括起来;我这里有个别名,需要每次替换,所以用了个.block括起来;
5.  $spat为模式匹配函数,其他类似函数$scmp/$sicmp。
6.  ${$FileName}、${/v:$FileName}这呢?听我说来,${ aliase} 明确的指出了, 大括号 {} 内的变量名是可以被替换的,即使 aliase 和其它文本相连。如果要求 ${} 这个别名不被替换, 即不被解析程序替换成其他值, 只保留它当前的字面值.如下面的ad ${/v:$FileName},删除别名,此时用/v 选项来了阻止对该别名的替换, 保留它原来的字面值;
7.  别名用完记得要删除;删除方法用ad命令。
试试看看结果,在访问a.txt时断下来了。
 
如果我要在往该文件写数据时断下来,如何设断?直接在writeFile下断?但不知道当前访问的是哪个文件呀?在脚本里,通过文件句柄能得到相应的文件名吗?我反正还不知道,如果你知道请一定不要忘了告我呀?以下为我的脚本:
$$Written by shakesky
$$10:44 2009-2-12
$$访问文件之windbg下断脚本

bp kernel32!CreateFileW "
r $t0=poi(esp+4)
  as /mu $FileName $t0
.echo
.printf \"Prepare to visit file:%mu\",$t0
.echo
.block
{
  $$ 判断是否是访问我所需观察的文件
  .if($spat(\"${$FileName}\",\"*a.txt\")) 
  {
    .echo 'Match...'
    ~.gu
    $$ 得到文件句柄
    r @$t1=eax
    .if(@$t1!=0xFFFFFFFF)
    {
      $$往该文件写数据时下断
      .printf \"File Handle:%08x\",$t1
      .echo
      bp kernel32!WriteFile \"
      r @$t2=poi(esp+4)
      .if(@$t2!=@$t1) {gc}
      \"
    }
    ad ${/v:$FileName}
    gc  
  }
  .else 
  {
    .echo No Match...
    ad ${/v:$FileName}
    gc
  }
}"

运行结果截图:

 
全文完,不当处请不吝指教。
========

利用windbg脚本调试简单实例

http://blog.csdn.net/superliuxing/article/details/19505777

之前想写一篇用windbg脚本调试的东西,但是一直比较懒,懒得动手,懒得想例子,把时间都放在了游戏上面,所以想法虽好一直没有实施...
    这次写一个非常简单的利用windbg脚本进行动态调试的小文,博君一笑.
    // Windbgscript.cpp : Defines the entry point for the console application.
//

代码:
#include "stdafx.h"

#include <iostream>
using namespace std;

int ret100()
{
  int x = 2;  //2-3
  x*=33;
  ++x;

  return x;
}

int add(int x, int y)
{
  x = 0; //多余的
  return x+y;

}


int _tmain(int argc, _TCHAR* argv[])
{

  int x = 100;

  int y = add( x, ret100());

  cout << y;

  getchar();
  return 0;
}
    上面代码都非常简单,ret100里面做了一个非常简单的操作,我希望它的返回值是100,但现在的返回值是67, 当然改法太多了,我这里假设是初始化时 x 应该初始化为3. 
    同样add里面也有问题,就是对于参数x赋值0,认为它是一条多余的操作, 因为我们的目的是返回x+y的结果.
现在执行结果是:
   
如何利用windbg去动态的修改并且不需要我们始终去手动交互是我们的主要目的, 首先肯定是要了解这两个函数的内部实现.
uf伺候:

代码:
0:001> uf windbgscript!ret100
   11 00401000 55              push    ebp
   11 00401001 8bec            mov     ebp,esp
   11 00401003 51              push    ecx
   12 00401004 c745fc02000000  mov     dword ptr [ebp-4],2
   13 0040100b 8b45fc          mov     eax,dword ptr [ebp-4]
   13 0040100e 6bc021          imul    eax,eax,21h
   13 00401011 8945fc          mov     dword ptr [ebp-4],eax
   14 00401014 8b4dfc          mov     ecx,dword ptr [ebp-4]
   14 00401017 83c101          add     ecx,1
   14 0040101a 894dfc          mov     dword ptr [ebp-4],ecx
   16 0040101d 8b45fc          mov     eax,dword ptr [ebp-4]
   17 00401020 8be5            mov     esp,ebp
   17 00401022 5d              pop     ebp
   17 00401023 c3              ret
 mov     dword ptr [ebp-4],2
对应的就是 x=2;的指令, 我们的目的是将x赋值3, 这里改法很多,比如将ebp-4处赋值3并跳过该指令...
这里选择最简单的,让该指令执行,再执行下一条指令前修改x的值.
代码:
bp windbgscript!ret100 + 0xb " $$ 0xb代表从函数ret100开始处到当前断点位置的偏移
  ed (ebp-4) 3; $$当代码执行到此处时,ebp-4即代表x的内存地址, ed命令为在制定地指处 赋值
 gc;  $$gc命令表示脚本执行后,程序会继续执行
"
代码:
0:001> uf windbgscript!add
   20 00401030 55              push    ebp
   20 00401031 8bec            mov     ebp,esp
   21 00401033 c7450800000000  mov     dword ptr [ebp+8],0
   22 0040103a 8b4508          mov     eax,dword ptr [ebp+8]
   22 0040103d 03450c          add     eax,dword ptr [ebp+0Ch]
   24 00401040 5d              pop     ebp
   24 00401041 c3              ret
mov     dword ptr [ebp+8],0
即为那条多余的指令x=0; 我们的目的是让cpu忽略这条指令, 做法是在这条指令被执行前修改eip的值,始之跳转到下一条指令处继续执行
代码:
bp windbgscript!add + 0x03 "
 r @eip = @eip+0xa; $$ @符号会让脚本解释程序认为后面跟随的名称代表为寄存器, 节省查找时间,提高效率
 gc;
"
加载windbg脚本后程序的执行结果:
 


实在是找不出好的例子来,所以就想出了这么个下三滥的例子,莫笑。。。
   实际中这么简单的程序谁也不会用windbg调试的,编写脚本的时间可能远远比重新编译,部署,调试所消耗的时间更长。
   个人认为这种脚本还是比较适合用在重现问题比较困难, 尤其是一种场景, 一个进程开始的时候没有任何问题,但是跑一段时间(比如2-3个小时)开始不断重复出现一连串的错误(非崩溃),这种场景用windbg脚本还是非常合适的。
   windbg脚本里的命令非常丰富,基本平时能用到的功能都有了,感兴趣翻翻帮助文档学学还是很不错的。
========

Windbg脚本和扩展命令工具示例

http://www.cr173.com/html/18401_1.html

类型:专业工具大小:216KB语言:中文 评分:6.6标签:立即下载
好长一段时间没写文章了,最近一直忙于为项目的可调式性做一些脚本和扩展工具,鉴于对windbg强大威力的震撼,以及相对较少的资料,笔者决定写一系列关于如何开发Windbg脚本和扩展命令的文章,您的支持是我最大的动力,希望本系列文章对您有所帮助。

那么一个完整的windbg script是什么样子的呢?首先让我们看如下示例:

$$ 该脚本是列出用户进程和栈

r $t0 = nt!PSActiveProcessHead 
.for (r $t1 = poi(@$t0); (@$t1 != 0) & (@$t1 != @$t0); r $t1 = poi(@$t1)) 

  r? $t2 = #CONTAINING_RECORD(@$t1, nt!_EPROCESS, ActiveProcessLinks); 
  .process @$t2 
  .reload 
  !process @$t2
}

相对于Windbg脚本,windbg扩展比较复杂,而且通常需要花费更大的精力写出同样的功能,但是它带来的一个好处就是你可以获得更多的功能,你甚至可以通过这些扩展写一个调试器,那么一个完整的windbg扩展又是什么样子的呢?该扩展dll打印出一个全局字符串的值。

以C++语言编写的windbg扩展示例:

HRESULT CALLBACK 
PrintPTR(PDEBUG_CLIENT pDebugClient, PCSTR args)
{
    UNREFERENCED_PARAMETER(args);

    IDebugSymbols* pDebugSymbols;
    if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugSymbols), (void **)&pDebugSymbols)))
    {    // Resolve the symbol
        ULONG64 ulAddress = 0;
        if (SUCCEEDED(pDebugSymbols->GetOffsetByName("TestSTLMap!g_wString", &ulAddress)))
        {
            IDebugDataSpaces* pDebugDataSpaces;
            if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugDataSpaces), (void **)&pDebugDataSpaces)))
            {    // Read the value of the pointer from the target address space
                ULONG64 ulPtr = 0;
                if (SUCCEEDED(pDebugDataSpaces->ReadPointersVirtual(1, ulAddress, &ulPtr)))
                {
                    PDEBUG_CONTROL pDebugControl;
                    if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugControl), (void **)&pDebugControl)))
                    {    // Output the values
                        pDebugControl->Output(DEBUG_OUTPUT_NORMAL, "%p TestSTLMap!g_wString= 0x%p\n", ulAddress, ulPtr);
                        pDebugControl->Output(DEBUG_OUTPUT_NORMAL, "%mu\n", ulPtr);
                        pDebugControl->Release();
                    }
                }
                pDebugDataSpaces->Release();
            }
            pDebugSymbols->Release();
        }
    }
    return S_OK;
}

总结

基于笔者的研究发现,国内做相关研究的人并不多,其实国外也就几个业内比较牛的人做的相对比较好,但是这些工具的作用足以让你震撼,今天开个头,有兴趣的朋友可以继续关注后续文章。
========

Windbg脚本和扩展工具之一STL容器扩展命令

http://www.tuicool.com/articles/aqmmAz

--by solidmango

想写一篇关于windbg的STL容器扩展命令的文章已经有一段时间了,但是最近项目比较忙,再加上有几本书特别想读,所以就耽误下来了,以至于整个三月都没来得及写,今天终于有时间可以把这篇文章写完。至于windbg和STL都是什么在这里我就不细说了,能打开我这篇文章的想必都是行家,那么我为什么想写着么一个主题的文章呢?


STL容器在调试的时候内部实现相对来说还是比较复杂的,而在某些生产情况下和一些极端的问题分析的时候visual studio 是登不了大雅之堂的,当然我不是说vs不好,在很多时候他是神器,但是它不是万能的。

举个例子:

如下类型的map

std::map<int,string>

实际的内部节点类型可能是这样的:

std::_Tree_nod<std::_Tmap_traits<int,std::basic_string<char,std::char_traits<char>,

std::allocator<char>>,

std::less<int>,

std::allocator<

std::pair<

int const , std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >,0> >::_Node

STL内部traits的应用遍地都是,但是甚至好多谢了好久代码的人可能都还是理解不了其巧妙之处。可能是由于其实现相对比较复杂,windbg内部的和现有的扩展对这块的调试支持都相对较差,当然如果你是高手,在某些简单的情况下,这块是有方法绕过去的,但是问题如果想对复杂就恐怕不行了。

那么我都做了什呢?我实现了如下的几个扩展命令,只要给出地址和类型就可以将整个容器打出来。

0:000> !help

PRTSTLMap          <Address>      Type      


PRTSTLMap2        <Address>      Type      


PRTSTLList           <Address>      Type      


PRTSTLVector       <Address>      Type


详细的每个命令的使用情况请参照如下的demo:

如下为!PRTSTLMap使用方法:

0:000>!PRTSTLMap (0x0012fd14+0x194) std::pair<int const ,std::basic_string<char,std::char_traits<char>,std::allocator<char> > >
Size of map is: 5
TestSTLMap!std::pair<int const ,std::basic_string<char,std::char_traits<char>,std::allocator<char> > >
   +0x000 first            : 11
   +0x004 second           : std::basic_string<char,std::char_traits<char>,std::allocator<char> >
      +0x000 _Alval           : std::allocator<char>
      =00400000 npos             : 0x905a4d
      +0x004 _Bx              : std::basic_string<char,std::char_traits<char>,std::allocator<char> >::_Bxty
         +0x000 _Buf             : [16]  "aaa"
         +0x000 _Ptr             : 0x00616161  ""
      +0x014 _Mysize          : 3
      +0x018 _Myres           : 0xf
TestSTLMap!std::pair<int const ,std::basic_string<char,std::char_traits<char>,std::allocator<char> > >
   +0x000 first            : 12
   +0x004 second           : std::basic_string<char,std::char_traits<char>,std::allocator<char> >
      +0x000 _Alval           : std::allocator<char>
      =00400000 npos             : 0x905a4d
      +0x004 _Bx              : std::basic_string<char,std::char_traits<char>,std::allocator<char> >::_Bxty
         +0x000 _Buf             : [16]  "bbb"
         +0x000 _Ptr             : 0x00626262  ""
      +0x014 _Mysize          : 3
      +0x018 _Myres           : 0xf
TestSTLMap!std::pair<int const ,std::basic_string<char,std::char_traits<char>,std::allocator<char> > >
   +0x000 first            : 13
   +0x004 second           : std::basic_string<char,std::char_traits<char>,std::allocator<char> >
      +0x000 _Alval           : std::allocator<char>
      =00400000 npos             : 0x905a4d
      +0x004 _Bx              : std::basic_string<char,std::char_traits<char>,std::allocator<char> >::_Bxty
         +0x000 _Buf             : [16]  "ccc"
         +0x000 _Ptr             : 0x00636363  "w"
      +0x014 _Mysize          : 3
      +0x018 _Myres           : 0xf
TestSTLMap!std::pair<int const ,std::basic_string<char,std::char_traits<char>,std::allocator<char> > >
   +0x000 first            : 14
   +0x004 second           : std::basic_string<char,std::char_traits<char>,std::allocator<char> >
      +0x000 _Alval           : std::allocator<char>
      =00400000 npos             : 0x905a4d
      +0x004 _Bx              : std::basic_string<char,std::char_traits<char>,std::allocator<char> >::_Bxty
         +0x000 _Buf             : [16]  "dddd"
         +0x000 _Ptr             : 0x64646464  "--- memory read error at address 0x64646464 ---"
      +0x014 _Mysize          : 4
      +0x018 _Myres           : 0xf
TestSTLMap!std::pair<int const ,std::basic_string<char,std::char_traits<char>,std::allocator<char> > >
   +0x000 first            : 15
   +0x004 second           : std::basic_string<char,std::char_traits<char>,std::allocator<char> >
      +0x000 _Alval           : std::allocator<char>
      =00400000 npos             : 0x905a4d
      +0x004 _Bx              : std::basic_string<char,std::char_traits<char>,std::allocator<char> >::_Bxty
         +0x000 _Buf             : [16]  "eeeee"
         +0x000 _Ptr             : 0x65656565  "--- memory read error at address 0x65656565 ---"
      +0x014 _Mysize          : 5
      +0x018 _Myres           : 0xf
 


如下为扩展名伶!PRTSTLList的使用方法:


0:000> !PRTSTLList (0x0012fd14+0x178) CPoint
    Size of list is: 3
TestSTLMap!CPoint
   +0x000 x                : 7
   +0x004 y                : 8
TestSTLMap!CPoint
   +0x000 x                : 4
   +0x004 y                : 6
TestSTLMap!CPoint
   +0x000 x                : 6
   +0x004 y                : 9
 

如下为扩展名伶! PRTSTLVector的使用方法:

0:000> !PRTSTLVector (0x0012fd14+0x1d4) CPoint
Size of Vector is: 6
TestSTLMap!CPoint
   +0x000 x                : 17
   +0x004 y                : 88
TestSTLMap!CPoint
   +0x000 x                : 16
   +0x004 y                : 87
TestSTLMap!CPoint
   +0x000 x                : 15
   +0x004 y                : 86
TestSTLMap!CPoint
   +0x000 x                : 14
   +0x004 y                : 85
TestSTLMap!CPoint
   +0x000 x                : 13
   +0x004 y                : 84
TestSTLMap!CPoint
   +0x000 x                : 12
   +0x004 y                : 83
总结

本文实现了几种扩展WINDBG STL调试支持的扩展命令,旨在方便STL调试,算是抛砖引玉,希望打开大家的视野,其实好多时候在某些领域我们是可以做一些事情的,不要总跟在老外的后面走,由于需要使用这些扩展命令的人不多,本文不会附上相关的扩展文件,如果有人需要,请私信。
========
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值