windbg 脚本简单入门
http://blog.csdn.net/superliuxing/article/details/19206985Windbg的功能自然不必说,集内核调试,应用程序调试,远程调试,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调试,算是抛砖引玉,希望打开大家的视野,其实好多时候在某些领域我们是可以做一些事情的,不要总跟在老外的后面走,由于需要使用这些扩展命令的人不多,本文不会附上相关的扩展文件,如果有人需要,请私信。
========