2014.04.29

感谢博主 http://book.51cto.com/art/200711/59731.htm

    汇编语言是凭借于电脑的。不同的总括机方面的汇编语言是不相同的。

《Windows用户态程序高效排错》第二章重点介绍用户态调试相关的学问和工具。本文主要讲了排错的工具:调试器Windbg。

   
语言的前行有一个倾向,就是离硬件越来越远。从汇编,到C,C++,面向对象的Java等,可以察觉,前面的语言更是具备可移植性,不相对依赖于底层硬件。比如跨平台的Java,其卖点之一就是其跨平台可移植性。还有一个点,就是尤为抽象,语言本身的思维点尤其显然。比如高档语言里面的面向对象思想,多线程,设计格局,通信模型等等,都是随着语言本身发展而来的。在低级语言中,如此抽象结构的东西是一直不的。

 

 

 

    统计机游戏。

其次章 汇编、卓殊、内存、同步和调试器
——首要的知识点和神兵利器

   
总计机游戏只是总结机应用的点而已。统计机应用大了去了,自动化控制,信息处理,数值总结,工程设计等等。虽然自己对那一个世界一点认识都未曾,但自身晓得,每一个领域都是本身大到高大穷经而不可及的。认同自己研究的局限,不否认超出自己认知的东西,尊重他们,这也是我近年的一个认识。

这一部分着重介绍用户态调试相关的学问和工具。包括:汇编、非凡(exception)、内存布局、堆(heap)、栈(stack)、CRT(C
Runtime)、handle/Criticalsection/thread context/windbg/ dump/live
debug和Dr 沃特son等。
书中不会对知识点作全面的牵线,而是针对知识点在调节中经过中应当什么行使进行表达。知识点本身在底下两本书中有这一个详细的介绍:

最早的计量游戏,据说是总计机工程师在示波器上写的一个乒乓球游戏。类似的乒乓球游戏本身也玩过。在此之前有一个小的手掌游戏机,屏幕的像素点都是一个一个的小方块,里面有俄Rose四方,赛车,乒乓球等小游戏。那一个小游戏机的开机音乐至今还是能哼出来。现在估计,当时玩赛车,发现一个BUG,赛车调到最高级别,速度快的超过人类的反射。不过此时按住赛车加速键,反而速度慢了下来,只要手指够快,完全可以由此最高级另外跑车。也许这就是程序在处理赛车加速速度与级别速度时,逻辑搞混了。

Programming Applications for Microsoft Windows 
Debugging Applications for Windows     

   
游戏从最简便易行的猜数字,俄Rose方块这样的小游戏发展到魔兽争霸这样的灿烂的Vedio
Game,也有自己循序渐进,逐渐前行的规律。其实,游戏的武当山真面目就是创设一套规则,把人限制在那些规则里,只要在这么些规则里,随你怎么玩吧。跟打篮球,打牌下棋是一个道理。从眼前看,金融期货的各种规则又何尝不是一套又一套人为创立的游戏规则呢?

2.1  排错的工具:调试器Windbg

本节介绍调试器Windbg的连锁知识。Windbg的使用贯穿本书很多章节,它是分析问题的很快工具。
Windbg的下载地址是:

Install Debugging Tools for Windows 32-bit Version
http://www.microsoft.com/whdc/devtools/debugging/installx86.mspx

提出设置到C:\Debuggers目录,后边的例证默认用这个目录。

开发人士写完代码,常常会在Visual Studio中按F5一贯开展调节。使用Visual
Studio自带调试器可以十分有利地在源代码上设定断点,检查程序的中游变量,单步骤执行。在做到代码阶段,Visual
Studio自带的调试器可以分外便利地做源代码级其余排错。

Visual
Studio调试器的出类拔萃用例是源代码级此外排错。与其相比,Windbg并不是一款针对特殊用例的调试器。Windbg提供了一个GUI界面,也得以在源代码上一向用F5设定断点,但更多的景色下,调试人士会一贯用文件的艺术输入调试命令,Windbg执行相应的操作,用文件的不二法门赶回对应的结果。Windbg的调试命令覆盖了Windows平台提供的拥有调试功用。

本节首先对调试器和标记文件作大致的牵线,然后针对常用的Windbg调试命令作示范。接下来介绍Windbg中强有力而灵活的尺度断点,最终介绍调试器目录下的相干工具。

对调试器深切的垂询后,相信读者就能体会到Windbg和Visual
Studio调试器设计上的区别,选取最合适的调试器来化解问题。

书中不会从Windbg的主干采纳方法说起,而是着重介绍调试器原理,常用的下令,Windbg的高档用法和相关的工具。倘若读者一贯没有应用过Windbg,下边的作品可以提供增援:

DebugInfo:
http://www.debuginfo.com/
Windows Debuggers: Part 1: A WinDbg Tutorial
http://www.codeproject.com/debug/windbg_part1.asp

2.1.1  调试器的意义:检查代码和材料,保存dump文件,控制程序的进行

调试器,无论是Visual
Studio调试器依旧Windbg,都是用来考察和控制目的进程的工具。对于用户态的进程,调试器可以查看用户态内存空间和寄存器上的材料。对于不同类其它数目和代码,调试器能方便地把那几个音信用特定的格式区分和呈现出来。调试器还足以把一个目的经过某一时时的所有信息写入一个文件(dump),直接打开这多少个文件分析。调试器仍是可以经过设置断点的建制来控制目的程序如何时候停下来接受检查,什么日期继续运行。

关于调试器的劳作规律,请参见Debugging Applications for Windows这本书。

Windbg及其相关工具的下载地址:
http://www.microsoft.com/whdc/devtools/debugging/installx86.mspx

在安装好Windbg后,可以在windbg.exe的主窗口按F1弹出协理。这是询问和应用Windbg的最好文档。每个命令的详实表明,都可以在中间找到。

调试器可以直观地来看下边一些音信:

经过运行的状态和系统状态,比如进程运行了略微日子,环境变量是怎么。
眼下历程加载的所有EXE/DLL的详细音讯。
某一个地点上的汇编指令。
翻开内存地址的情节和性质,比如是否可写。
每个的call stack(需要symbol)。
Call stack上每个函数的有些变量。
格式化地出示程序中的数据结构(需要symbol)。
查看和改动内存地址上的材料仍旧寄存器上的素材。
部分操作系统管理的数据结构,比如Heap、Handle、CriticalSection等。

在Visual
Studio调试器中,要翻看上边的信息,需要在广大调节窗口中切换。而在Windbg中,只需要简单的一声令下就足以成功。

调试器的此外一个意义是设定标准断点。能够设定在某一个命令地址上停下来,也足以设定当某一个内存地址等于多少的时候停下来,或者当某一个exception/notification爆发的时候停下来。还足以进来一个函数调用的时候停下来,或跳出当前函数调用的时候停下来。停下来后可以让调试器自动运行某些命令,记录某些消息,然后让调试器自动判断某些原则来控制是否要持续运行。通过简单的原则断点功用,可以很有益地贯彻下面一些职责:

当某一个函数被调用的时候,在调试器输出窗口中打印出函数参数。
算算某一个变量被改动了有点次。
蹲点一个函数调用了哪些子函数,分别被调用了稍稍次。
老是抛C++非凡的时候自动发出dump文件。

在Visual
Studio调试器中也可以设定条件断点,但灵活性和意义远无法跟Windbg比较。

2.1.2  符号文件(Symbol file),把二进制和源代码对应起来

当用VC/VB编译生成EXE/DLL后,往往会同时生成PDB文件。PDB里面包含的是EXE/DLL的标记音讯。
标记是指代码中拔取到的序列和名字。比如下边这个都是符号包含的始末:

代码所定义的Class的名字,Class的持有成员的名字和有着成员的品种。
变量的名字和变量的档次。
函数的名字,函数所有参数的名字和项目,以及函数的再次来到值。

PDB文件除了饱含符号外,还担负把符号和该符号所处的二进制地址联系起来。比如有一个全局变量叫做gBuffer,PDB文件不但记录了gBuffer的系列,还是能让调试器找到保存gBuffer的内存地址。

有了符号文件,当在调试器中试图读取某一个内存地址的时候,调试器会尝试在对应的PDB文件中配对,看那个内存地址是否有标志对应。如若可以找到,调试器就可以把相应的标记突显出来。这样,极大程度上有利于了开发人员的体察。
对于操作系统EXE/DLL,微软也提供了相应的符号文件下载地址。

默认情形下,符号文件中带有了装有的协会、函数,以及相应的源代码音讯。微软提供的Windows符号文件去掉了源代码信息、函数参数定义和一部分内部数据结构的概念。

2.1.3  一个简约的左手程序

接下去用一个简便的例子演示一下Windbg的为主采取。下边这段代码的目的是把字符串”6969,3p3p”中的所有3都修改为4。

 

 

 #include "stdafx.h"
#include "stdlib.h"

char* getcharBuffer()
{
return "6969,3p3p";
}

void changeto4p(char * buffer)
{
while(*buffer)
{
if(*buffer == '3')
*buffer='4';
buffer++;
} 
}
int _tmain(int argc, _TCHAR* argv[])
{
printf("%s\n","Any key continue...");
getchar();
char *str=getcharBuffer();
changeto4p(str);
printf("%s",str);
return 0;
}   

 

 

这段牖岬贾卤览!1览:罂吹降慕涌谌缤?.1所示。

 

图2.1

接下去,一起用Windbg来探视上述对话框的现实性意思是怎么样。

在开行Windbg调试从前,首先把程序对应的PDB文件放到一个点名的文本夹。下面程序的EXE叫做crashscreen-shot.exe,把编译时候生成的crashscreen-shot.pdb文件拷贝到C:\PDB文件夹。同时把程序的主CPP文件拷贝到C:\SRC文件夹。

接下去启动Windbg。像用Visual
Studio调试程序一样,大家需要在调试器中运行对应的EXE。所以在Windbg的主窗口中,使用File→Open
Executable菜单找到crashscreen-shot.exe,然后打开。

Windbg不会让对象经过顿时先河运行。相反,Windbg这时会停下来,让用户有机会对经过启动过程举办排错,或者举行一些备选干活,比如设定断点,如图2.2所示。

 

图2.2

 

2.1.4  用Internet Explorer来磨炼调试器的主题命令

下边用Internet Explorer作为靶子经过演示Windbg中更多的调剂命令。

用Windbg来调节目的经过,有三种方法,分别是经过调试器启动,和用调试器直接监视(attach)正在运作的长河。
通过File→Open
Executable菜单,可以拔取相应的EXE在调试器中启动。通过File→Attach to a
process可以选取一个正值周转的过程展开调试。

打开IE,访问www.msdn.com,
然后开行Windbg,按F6,接纳刚刚启动的(最上面)iexplorer.exe进程。
IE的PDB文件也需要从微软的网站上下载。具体做法请参考上一节的链接。在自家本地,我的symbol路径设定如下:

 

 

SRV*D:\websymbols*http://msdl.microsoft.com/download/symbols;D:\MyAppSymbol

 

 

这里的D:\websymbols目录是用来保存从msdl.microsoft.com上自行下载的操作系统符号文件。而自己要好编译生成的符号文件,我都手动拷贝到D:\MyAppSymbol路径下。
接下去,在Windbg的指令窗口中(如若看不到可以用Alt+1打开),运行下边发号施令。

 

 

vertarget检查进程概况
vertarget命令显示当前进程的大致信息:
0:026> vertarget
Windows Server 2003 Version 3790 (Service Pack 1) MP (2 procs) Free x86 compatible
Product: Server, suite: Enterprise TerminalServer SingleUserTS
kernel32.dll version: 5.2.3790.1830 (srv03_sp1_rtm.050324-1447)
Debug session time: Thu Apr 27 13:53:50.414 2006 (GMT+8)
System Uptime: 15 days 1:59:13.255
Process Uptime: 0 days 0:07:34.508
Kernel time: 0 days 0:00:01.109
User time: 0 days 0:00:00.609    

 

 

地点的0:026>是命令指示符,026代表如今的线程ID。前边会介绍切换线程的下令,到时候就足以见到指示符的更动。

跟大部分的通令输出一样,vertarget的输出分外通晓直观,显示当前系统的本子和运行时刻。

 

 

!peb 显示Process Environment Block  

 

 

随后可以用!peb命令来体现Process Environment
Block。由于出口太长,这里就简单了。

lmvm 检查模块的加载信息

用lmvm命令可以看任意一个DLL/EXE的详细音信,以及symbol的意况:

 

 

0:026> lmvm msvcrt
start    end        module name
77ba0000 77bfa000   msvcrt     (deferred)             
Image path: C:\WINDOWS\system32\msvcrt.dll
Image name: msvcrt.dll
Timestamp:        Fri Mar 25 10:33:02 2005 (4243785E)
CheckSum:         0006288A
ImageSize:        0005A000
File version:     7.0.3790.1830
Product version:  6.1.8638.1830
File flags:       0 (Mask 3F)
File OS:          40004 NT Win32
File type:        1.0 App
File date:        00000000.00000000
Translations:     0409.04b0
CompanyName:      Microsoft Corporation
ProductName:      Microsoft® Windows® Operating System
InternalName:     msvcrt.dll
OriginalFilename: msvcrt.dll
ProductVersion:   7.0.3790.1830
FileVersion:      7.0.3790.1830 (srv03_sp1_rtm.050324-1447)
FileDescription:  Windows NT CRT DLL
LegalCopyright:   © Microsoft Corporation. All rights reserved.   

 

 

命令的第二行彰显deferred,表示近来并不曾加载msvcrt的symbol,可以用.reload命令来加载。在加载前,能够用!sym命令来开辟symbol加载过程的详尽输出:

 

 

.reload / !sym 加载符号文件

默认情状下,调试器不会加载所有的symbol文件。只有某个调试器命令需要选取symbol的时候,调试器才在设定的标志文件路径中检查和加载。!sym命令可以让调试器在机动搜索symbol的时候给出详细的音信,比如寻找和下载的路子。.reload命令可以让调试器加载指定模块的symbol。

 

 

 

 

0:026> !sym noisy 
noisy mode - symbol prompts on
0:026> .reload /f msvcrt.dll
SYMSRV:  msvcrt.pd_ from http://msdl.microsoft.com/download/symbols: 
80847 bytes copied         
DBGHELP: msvcrt - public symbols  
c:\websymbols\msvcrt.pdb\62B8BDC3CC194D2992DCFAED78B621FC1\msvcrt.pdb
0:026> lmvm msvcrt
start    end        module name
77ba0000 77bfa000   msvcrt     (pdb symbols)          
c:\websymbols\msvcrt.pdb\62B8BDC3CC194D2992DCFAED78B621FC1\msvcrt.pdb
Loaded symbol image file: C:\WINDOWS\system32\msvcrt.dll
Image path: C:\WINDOWS\system32\msvcrt.dll
Image name: msvcrt.dll
Timestamp:        Fri Mar 25 10:33:02 2005 (4243785E)
CheckSum:         0006288A
ImageSize:        0005A000
File version:     7.0.3790.1830
Product version:  6.1.8638.1830
File flags:       0 (Mask 3F)
File OS:          40004 NT Win32
File type:        1.0 App
File date:        00000000.00000000
Translations:     0409.04b0
CompanyName:      Microsoft Corporation
ProductName:      Microsoft® Windows® Operating System
InternalName:     msvcrt.dll
OriginalFilename: msvcrt.dll
ProductVersion:   7.0.3790.1830
FileVersion:      7.0.3790.1830 (srv03_sp1_rtm.050324-1447)
FileDescription:  Windows NT CRT DLL
LegalCopyright:   © Microsoft Corporation. All rights reserved.   

 

 

可以看来,symbol从msdl.microsoft.com自动下载后加载。

lmf 列出目前经过中加载的持有模块

lmf命令可以列出当前进程中加载的有着DLL文件和呼应的路子:

 

 

0:018> lmf
start    end        module name
00d40000 00dda000   iexplore C:\Program Files\Internet Explorer\iexplore.exe
04320000 043c9000   atiumdva C:\Windows\system32\atiumdva.dll
10000000 1033d000   googletoolbar2 c:\program files\google\googletoolbar2.dll
37f00000 37f0f000   Cjktl32  E:\Program Files\Powerword 2003\Cjktl32.dll  

r,d,e 寄存器,内存的检讨和修改
r命令突显和改动寄存器上的值。
d命令呈现内存地址上的值。
e命令修改内存地址上的值。
显示寄存器:

 

 

 

 

0:018> r
eax=7ffdc000 ebx=00000000 ecx=00000000 edx=7707f06d esi=00000000 edi=00000000
eip=77032ea8 esp=054efc14 ebp=054efc40 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!DbgBreakPoint:
77032ea8 cc              int     3

若果急需修改寄存器,比如把eax的值修改为0x0,可以用 r eax=0。
用d命令展现esp 寄存器指向的内存,默认为byte格式。

 

 

 

 

0:018> d esp
054efc14  a9 f0 07 77 e9 ef 4e 05-00 00 00 00 00 00 00 00  ...w..N.........
054efc24  00 00 00 00 18 fc 4e 05-00 00 00 00 7c fc 4e 05  ......N.....|.N.
054efc34  f2 8b ff 76 a1 f5 03 77-00 00 00 00 4c fc 4e 05  ...v...w....L.N.
054efc44  33 38 b4 75 00 00 00 00-8c fc 4e 05 bd a9 02 77  38.u......N....w
054efc54  00 00 00 00 25 ef 4e 05-00 00 00 00 00 00 00 00  ....%.N.........
054efc64  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
054efc74  58 fc 4e 05 00 00 00 00-ff ff ff ff f2 8b ff 76  X.N............v
054efc84  a1 e2 03 77 00 00 00 00-00 00 00 00 00 00 00 00  ...w............ 

 

 

用dd命令直接指定054efc14
地址,第二个d表示用DWORD格式。除了DWORD外,还有db(byte),du(Unicode),dc(char)等等。详细音信请参考帮忙文档中d命令的注明。

 

 

0:018> dd 054efc14 
054efc14  7707f0a9 054eefe9 00000000 00000000
054efc24  00000000 054efc18 00000000 054efc7c
054efc34  76ff8bf2 7703f5a1 00000000 054efc4c
054efc44  75b43833 00000000 054efc8c 7702a9bd
054efc54  00000000 054eef25 00000000 00000000
054efc64  00000000 00000000 00000000 00000000
054efc74  054efc58 00000000 ffffffff 76ff8bf2
054efc84  7703e2a1 00000000 00000000 00000000   

 

 

e命令可以用来修改内存地址。跟d命令一样,e命令前面也足以跟项目后缀。比如ed命令表示用DWORD的方法修改。下边的吩咐把054efc14
地址上的值修改为11112222。

 

0:018> ed 054efc14  11112222

修改形成后,用dd命令检查054efc14 地址上的值。后边的
L4参数指定内存区间的长短长度为4个DWORD。这样输出就只有1行,而不是默认的8行。

0:018> dd 054efc14  L4
054efc14  11112222 40a15c00 00000000 40a15c00

 

 

有了地点几个指令,就足以访问和修改当前过程中的所有内存。这个命令的参数和格式相当灵活,详细内容参考帮忙文档。

 

!address显示内存页信息

该命令在面前的事例中就用到过,可以显得某一个地址上的页消息:

0:001> !address 7ffde000 
7ffde000 : 7ffde000 - 00001000
Type     00020000 MEM_PRIVATE
Protect  00000004 PAGE_READWRITE
State    00001000 MEM_COMMIT
Usage    RegionUsagePeb  

 

 

一旦不带参数,可以显示更详尽的总结消息。

S 搜索内存

S命令可以搜索内存。比如用在下边的地点:

1.
找寻内存泄漏的线索。比如知道当前内存泄漏的内容是部分稳定的字符串,就足以在
DLL区域搜索这一个字符串出现的地方,然后再找找这一个地点用到哪边代码中,找出这一个内存是从什么地方开端分配的。
2.
寻找错误代码的来自。比如知道当前先后再次回到了0x80074015这样的一个代码,不过不知底那一个代码是由哪一个内层函数再次回到的,就足以在代码区搜索0x80074015,找到可能回到那些代码的函数。
下边就是访问sina.com的时候,用Windbg搜索ie里面www.sina.com.cn的结果:

 

 

0:022> s -u 0012ff40 L?80000000 "www.sina.com.cn"
001342a0  0077 0077 0077 002e 0073 0069 006e 0061  w.w.w...s.i.n.a.
00134b82  0077 0077 0077 002e 0073 0069 006e 0061  w.w.w...s.i.n.a.
00134f2e  0077 0077 0077 002e 0073 0069 006e 0061  w.w.w...s.i.n.a.
0013570c  0077 0077 0077 002e 0073 0069 006e 0061  w.w.w...s.i.n.a.

 

 

结合S命令和前边介绍的修改内存命令,根本不需要用什么样金山游侠就足以搜索/修改游戏中主角的人命了
:-)
接下去,看看跟线程相关的一声令下。

!runaway 检查线程的CPU消耗

!runaway可以呈现每一个线程所耗费usermode CPU时间的总结信息:

 

 

0:001> !runaway
User Mode Time
Thread       Time
0:83c       0 days 0:00:00.406
13:bd4       0 days 0:00:00.046
10:ac8       0 days 0:00:00.046
24:4f4       0 days 0:00:00.031
11:d8c       0 days 0:00:00.015
26:109c      0 days 0:00:00.000
25:1284      0 days 0:00:00.000
23:12cc      0 days 0:00:00.000
22:16c0      0 days 0:00:00.000
21:57c       0 days 0:00:00.000
20:c00       0 days 0:00:00.000
19:14e8      0 days 0:00:00.000
18:1520      0 days 0:00:00.000
16:9dc       0 days 0:00:00.000
15:1654      0 days 0:00:00.000
14:13f4      0 days 0:00:00.000
9:104c      0 days 0:00:00.000
8:1760      0 days 0:00:00.000
7:cc8       0 days 0:00:00.000
6:530       0 days 0:00:00.000
5:324       0 days 0:00:00.000
4:178c      0 days 0:00:00.000
3:1428      0 days 0:00:00.000
2:1530      0 days 0:00:00.000
1:448       0 days 0:00:00.000

 

 

下边输出的首先列是线程编号和线程id。后一列对应的是该线程在用户态情势中的总繁忙时刻。在该命令加上f参数,还是可以看看内核态的农忙时刻。当进程内存占用率高的时候,通过该命令可以一本万利地找到呼应的繁忙线程。

~ 切换目的线程

用~命令,可以来得线程信息和在不同线程之间切换:

 

 

0:001> ~
0  Id: c0.83c Suspend: 1 Teb: 7ffdd000 Unfrozen
.  1  Id: c0.448 Suspend: 1 Teb: 7ffdb000 Unfrozen
2  Id: c0.1530 Suspend: 1 Teb: 7ffda000 Unfrozen
3  Id: c0.1428 Suspend: 1 Teb: 7ffd9000 Unfrozen
4  Id: c0.178c Suspend: 1 Teb: 7ffd8000 Unfrozen
5  Id: c0.324 Suspend: 1 Teb: 7ffdc000 Unfrozen
6  Id: c0.530 Suspend: 1 Teb: 7ffd7000 Unfrozen
7  Id: c0.cc8 Suspend: 1 Teb: 7ffd6000 Unfrozen
8  Id: c0.1760 Suspend: 1 Teb: 7ffd5000 Unfrozen
9  Id: c0.104c Suspend: 1 Teb: 7ffd4000 Unfrozen
10  Id: c0.ac8 Suspend: 1 Teb: 7ffd3000 Unfrozen
11  Id: c0.d8c Suspend: 1 Teb: 7ff9f000 Unfrozen
13  Id: c0.bd4 Suspend: 1 Teb: 7ff9d000 Unfrozen
14  Id: c0.13f4 Suspend: 1 Teb: 7ff9c000 Unfrozen
15  Id: c0.1654 Suspend: 1 Teb: 7ff9b000 Unfrozen
16  Id: c0.9dc Suspend: 1 Teb: 7ff9a000 Unfrozen
18  Id: c0.1520 Suspend: 1 Teb: 7ff96000 Unfrozen
19  Id: c0.14e8 Suspend: 1 Teb: 7ff99000 Unfrozen
20  Id: c0.c00 Suspend: 1 Teb: 7ff97000 Unfrozen
21  Id: c0.57c Suspend: 1 Teb: 7ff95000 Unfrozen
22  Id: c0.16c0 Suspend: 1 Teb: 7ff94000 Unfrozen
23  Id: c0.12cc Suspend: 1 Teb: 7ff93000 Unfrozen
24  Id: c0.4f4 Suspend: 1 Teb: 7ff92000 Unfrozen
25  Id: c0.1284 Suspend: 1 Teb: 7ff91000 Unfrozen
26  Id: c0.109c Suspend: 1 Teb: 7ff90000 Unfrozen
0:001> ~0s
eax=0013e7c4 ebx=00000000 ecx=0013e7c4 edx=0000000b esi=001642e8 edi=00000000
eip=7c82ed54 esp=0013eb3c ebp=0013ed98 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
ntdll!KiFastSystemCallRet:
7c82ed54 c3               ret
0:000>

 

 

上面的~0s命令,把近来线程切换来0号线程,也就是主线程。切换后提示符会变为0:000。

k,kb,kp,kv,kn 检查call stack

k命令呈现当前线程的call
stack。跟d命令一样,k前面能够跟很多后缀,比如kb、kp、kn、kv、kL等。这些后缀控制了展示的格式和消息量。具体新闻请参考协理文档和发轫实践。

 

 

0:000> k
ChildEBP RetAddr  
0013eb38 7739d02f ntdll!KiFastSystemCallRet
0013ed98 75ecb30f USER32!NtUserWaitMessage+0xc
0013ee24 75ed7ce5 BROWSEUI!BrowserProtectedThreadProc+0x44
0013fea8 779ac61e BROWSEUI!SHOpenFolderWindow+0x22c
0013fec8 0040243d SHDOCVW!IEWinMain+0x129
0013ff1c 00402748 iexplore!WinMain+0x316
0013ffc0 77e523cd iexplore!WinMainCRTStartup+0x186
0013fff0 00000000 kernel32!BaseProcessStart+0x23

 

 

可以组成~和k命令,来显示所无线程的call stack. 输入~*k试一下。

u 反汇编

u命令把指定地方上的代码翻译成汇编输出。

 

 

0:000> u 7739d023 
USER32!NtUserWaitMessage:
7739d023 b84a120000       mov     eax,0x124a
7739d028 ba0003fe7f       mov     edx,0x7ffe0300
7739d02d ff12             call    dword ptr [edx]
7739d02f c3               ret

 

 

若果符号文件加载正确,可以用uf命令间接反汇编整个函数,比如uf USER32!
NtUserWaitMessage。

x 查找符号的二进制地址

有了标记文件,调试器就能查找源代码符号和该符号所处的二进制地址之间的映照。倘若要找一个符号保存在咋样二进制地址上,可以用x命令:

 

 

0:000> x msvcrt!printf
77bd27c2 msvcrt!printf = <no type information>

 

 

下面的一声令下找到了printf函数的输入地址在77bd27c2。

 

 

0:001> x ntdll!GlobalCounter
7c99f72c ntdll!GlobalCounter = <no type information>

 

 

下面的指令表示ntdll!GlobalCounter那多少个变量保存的地点是7c99f72c。

(注意:
符号对应的是变量和变量所在的地点,不是变量的值。下面的命令不是说ntdll!GlobalCounter这些变量的值是7c99f72c。要找到变量的值,需要用d命令读取内存地址来收获。)

x命令还扶助通配符,比如 x
ntdll!*命令列出ntdll模块中颇具的标记,以及对应的二进制地址。由于出口太长,这里就大概了。

dds 对应二进制地址的符号

dds打印内存地址上的二进制值,同时活动检索二进制值对应的记号。比如要看看当前stack
中保留了哪些函数地址,就可以检查ebp指向的内存:

 

 

0:000> dds ebp
0013ed98  0013ee24
0013ed9c  75ecb30f BROWSEUI!BrowserProtectedThreadProc+0x44
0013eda0  00163820
0013eda4  0013ee50
0013eda8  00163820
0013edac  00000000
0013edb0  0013ee10
0013edb4  75ece83a BROWSEUI!__delayLoadHelper2+0x23a
0013edb8  00000005
0013edbc  0013edcc
0013edc0  0013ee50
0013edc4  00163820
0013edc8  00000000
0013edcc  00000024
0013edd0  75f36d2c BROWSEUI!_DELAY_IMPORT_DESCRIPTOR_SHELL32
0013edd4  75f3a184 BROWSEUI!_imp__SHGetInstanceExplorer
0013edd8  75f36e80 BROWSEUI!_sz_SHELL32
0013eddc  00000001
0013ede0  75f3726a BROWSEUI!urlmon_NULL_THUNK_DATA_DLN+0x116
0013ede4  7c8d0000 SHELL32!_imp__RegCloseKey <PERF> (SHELL32+0x0)
0013ede8  7c925b34 SHELL32!SHGetInstanceExplorer

 

 

这边dds命令从ebp指向的内存地址0013ed98
起初打印。第1列是内存地址的值,第2列是地方上相应的二进制数据,第3列是二进制数据对应的标志。上边的下令自动找到了75ecb30f对应的符号是BROWSEUI!BrowserProtectedThreadProc+0x44。

COM Interface和C++
Vtable里面的积极分子函数都是顺序排列的,所以dds命令可以方便地找到虚函数表中切实的函数地址。比如用下边的命令可以找到OpaqueDataInfo类型中虚函数对应的实际函数地址。

先是用x命令找到OpaqueDataInfo虚函数表地址:

 

 

0:000> x ole32!OpaqueDataInfo::`vftable'
7768265c ole32!OpaqueDataInfo::`vftable' = <no type information>
77682680 ole32!OpaqueDataInfo::`vftable' = <no type information> 

 

 

接下去dds命令可以打印出虚函数表中的函数名字:

 

0:000> dds 7768265c 7768265c 77778245 ole32!ServerLocationInfo::QueryInterface 77682660 77778254 ole32!ScmRequestInfo::AddRef 77682664 77778263 ole32!ScmRequestInfo::Release 77682668 77779d26 ole32!OpaqueDataInfo::Serialize 7768266c 77779d3d ole32!OpaqueDataInfo::UnSerialize 77682670 77779d7a ole32!OpaqueDataInfo::GetSize 77682674 77779dcb ole32!OpaqueDataInfo::GetCLSID 77682678 77779deb ole32!OpaqueDataInfo::SetParent 7768267c 77779e18 ole32!OpaqueDataInfo::SerializableQueryInterface 77682680 777799b5 ole32!InstantiationInfo::QueryInterface 77682684 77689529 ole32!ServerLocationInfo::AddRef 77682688 776899cc ole32!ScmReplyInfo::Release 7768268c 77779bcd ole32!OpaqueDataInfo::AddOpaqueData 77682690 77779c43 ole32!OpaqueDataInfo::GetOpaqueData 77682694 77779c99 ole32!OpaqueDataInfo::DeleteOpaqueData 77682698 776a8cf6 ole32!ServerLocationInfo::GetRemoteServerName 7768269c 776aad96 ole32!OpaqueDataInfo::GetAllOpaqueData 776826a0 77777a3b ole32!CDdeObject::COleObjectImpl::GetClipboardData 776826a4 00000021 776826a8 77703159 ole32!CClassMoniker::QueryInterface 776826ac 77709b01 ole32!CErrorObject::AddRef 776826b0 776edaff ole32!CClassMoniker::Release 776826b4 776ec529 ole32!CClassMoniker::GetUnmarshalClass 776826b8 776ec546 ole32!CClassMoniker::GetMarshalSizeMax 776826bc 776ec589 ole32!CClassMoniker::MarshalInterface 776826c0 77702ca9 ole32!CClassMoniker::UnmarshalInterface 776826c4 776edbe1 ole32!CClassMoniker::ReleaseMarshalData 776826c8 776e5690 ole32!CDdeObject::COleItemContainerImpl::LockContainer 776826cc 7770313b ole32!CClassMoniker::QueryInterface 776826d0 7770314a ole32!CClassMoniker::AddRef 776826d4 776ec5a8 ole32!CClassMoniker::Release 776826d8 776ec4c6 ole32!CClassMoniker::GetComparisonData

 

 

2.1.5  检查程序资料的小例子

上边用一个小例子来演示怎么样具体地洞察程序中的数据结构。

率先在debug形式下编译并且按Ctrl+F5运作下边的代码:

 

 

struct innner { char arr[10]; }; class MyCls { private: char* str; innner inobj; public: void set(char* input) { str=input; strcpy(inobj.arr,str); } int output() { printf(str); return 1; } void hold() { getchar(); } };

void foo1() { MyCls *pcls=new MyCls(); void *rawptr=pcls; pcls->set("abcd"); pcls->output(); pcls->hold(); }; void foo2() { printf("in foo2\n"); foo1(); }; void foo3() { printf("in foo3\n"); foo2(); };

int _tmain(int argc, _TCHAR* argv[]) { foo3(); return 0; }

 

 

当console等待输入的时候,启动Windbg,然后用F6加载目的经过。

用~0s命令切换来主线程,查看callstack:

 

 

0:000> knL
# ChildEBP RetAddr  
00 0012f7a0 7c821c94 ntdll!KiFastSystemCallRet
01 0012f7a4 7c836066 ntdll!NtRequestWaitReplyPort+0xc
02 0012f7c4 77eaaba3 ntdll!CsrClientCallServer+0x8c
03 0012f8bc 77eaacb8 kernel32!ReadConsoleInternal+0x1b8
04 0012f944 77e41990 kernel32!ReadConsoleA+0x3b
05 0012f99c 10271754 kernel32!ReadFile+0x64
06 0012fa28 10271158 MSVCR80D!_read_nolock+0x584
07 0012fa74 10297791 MSVCR80D!_read+0x1a8
08 0012fa9c 102a029b MSVCR80D!_filbuf+0x111
09 0012faf0 102971ce MSVCR80D!getc+0x24b
0a 0012fafc 102971e8 MSVCR80D!_fgetchar+0xe 
0b 0012fb04 0041163b MSVCR80D!getchar+0x8
0c 0012fbe4 00413f82 exceptioninject!MyCls::hold+0x2b
0d 0012fcec 0041169a exceptioninject!foo1+0xa2
0e 0012fdc0 004114fa exceptioninject!foo2+0x3a
0f 0012fe94 004116d3 exceptioninject!foo3+0x3a
10 0012ff68 00412016 exceptioninject!wmain+0x23
11 0012ffb8 00411e5d exceptioninject!__tmainCRTStartup+0x1a6
12 0012ffc0 77e523cd exceptioninject!wmainCRTStartup+0xd
13 0012fff0 00000000 kernel32!BaseProcessStart+0x23

 

 

.frame 在栈中切换以便检查局部变量

下面callstack中每一行前边的序号叫做frame
number。通过.frame命令,可以切换来相应的函数中反省局部变量。比如exceptioninject!foo1
这些函数前边的 frame number是d,于是执行.frame d命令:

 

 

0:000> .frame d
0d 0012fcec 0041169a exceptioninject!foo1+0xa2 
[d:\xiongli\today\exceptioninject\exceptioninject\exceptioninject.cpp @ 72]

 

 

x命令突显当前frame的一部分变量。在foo1函数中,四个部分变量分别是pcls和rawptr:

 

 

0:000> x
0012fce4 pcls = 0x0039ba80
0012fcd8 rawptr = 0x0039ba80

 

 

dt 格式化显示资料

在符号文件加载的事态下,dt命令格式化显示变量的素材和布局:

 

 

0:000> dt pcls
Local var @ 0x12fce4 Type MyCls*
0x0039ba80 
+0x000 str              : 0x00416648  "abcd"
+0x004 inobj            : inner

 

 

地方的指令打印出pcls的序列是MyCls指针,指向的地点是0x0039ba80,其中的两个class成员的舞狮分别在+0和+4,对应的值在第2列呈现。加上-b
-r参数可以体现inner class和数组的信息:

 

 

0:000> dt pcls -b -r Local var @ 0x12fce4 Type MyCls* 0x0039ba80 +0x000 str : 0x00416648 "abcd" +0x004 inobj : innner +0x000 arr : "abcd" [00] 97 ‘a’ [01] 98 ‘b’ [02] 99 ‘c’ [03] 100 ‘d’ [04] 0 ” [05] 0 ” [06] 0 ” [07] 0 ” [08] 0 ” [09] 0 ”

 

 

对于随意的地点,也足以手动指定符号类型来格式化显示。比如把0x0039ba80地方上的多少用MyCls类型来显示:

 

 

0:000> dt 0x0039ba80 MyCls
+0x000 str              : 0x00416648  "abcd"
+0x004 inobj            : innner

 

 

2.1.6  用Windbg控制程序开展实时调试(Live Debug)

除外检查静态资料外,调试器仍可以够控制和着眼代码的实践。

  1. wt命令
    wt命令的效益是watch and trace data。
    它可以跟踪一个函数的所有执行过程,并且付诸总括信息。
  2. 设定断点
    Windbg里面可以设定灵活而强大的原则断点。比如可以透过标准断点实现如此的功力:当某个全局变量被修改100次将来,同时stack上的第2个参数是100,那么就停下来进入调试格局;假使第2个参数是200,那么就变更1个dump文件,否则就只打印出近年来的callstack,然后继续运行。

Wt 沃特ch and Trace, 跟踪执行的无敌命令

或者对于地点很是程序。

第一用bp (break point) 命令在foo3下边设断点:

 

 

0:001> bp exceptioninject!foo3 breakpoint 0 redefined

 

 

下一场用g命令让程序执行:

 

0:001> g

举办到foo3上的时候,调试器停下来了:

Breakpoint 0 hit eax=0000000a ebx=7ffd7000 ecx=0043780e edx=10310bd0 esi=0012fe9c edi=0012ff68 eip=004114c0 esp=0012fe98 ebp=0012ff68 iopl=0 nv up ei pl zr na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 exceptioninject!foo3: 004114c0 55 push ebp

 

 

用bd(breakpoint disable)命令撤销设定好的断点,以免打扰wt的施行:

 

0:000> bd 0

 

 

用wt命令监视foo3的实施,深度设定成2(-l2参数):

 

0:000> wt -l2 Tracing exceptioninject!foo3 to return address 0041186a 60 0 [ 0] exceptioninject!foo3 28 0 [ 1] MSVCR80D!printf 5 0 [ 2] MSVCR80D!__iob_func 32 5 [ 1] MSVCR80D!printf 12 0 [ 2] MSVCR80D!_lock_file2 35 17 [ 1] MSVCR80D!printf 5 0 [ 2] MSVCR80D!__iob_func 38 22 [ 1] MSVCR80D!printf 50 0 [ 2] MSVCR80D!_stbuf 46 72 [ 1] MSVCR80D!printf 5 0 [ 2] MSVCR80D!__iob_func 49 77 [ 1] MSVCR80D!printf 575 0 [ 2] MSVCR80D!_output_l 52 652 [ 1] MSVCR80D!printf 5 0 [ 2] MSVCR80D!__iob_func 57 657 [ 1] MSVCR80D!printf 33 0 [ 2] MSVCR80D!_ftbuf 60 690 [ 1] MSVCR80D!printf 7 0 [ 2] MSVCR80D!printf 71 697 [ 1] MSVCR80D!printf 63 768 [ 0] exceptioninject!foo3 1 0 [ 1] exceptioninject!ILT+380(__RTC_CheckEsp) 2 0 [ 1] exceptioninject!_RTC_CheckEsp 64 771 [ 0] exceptioninject!foo3 1 0 [ 1] exceptioninject!ILT+340(?foo2YAXXZ) 60 0 [ 1] exceptioninject!foo2 71 0 [ 2] MSVCR80D!printf 63 71 [ 1] exceptioninject!foo2 1 0 [ 2] exceptioninject!ILT+380(__RTC_CheckEsp) 2 0 [ 2] exceptioninject!_RTC_CheckEsp 64 74 [ 1] exceptioninject!foo2 1 0 [ 2] exceptioninject!ILT+215(?foo1YAXXZ) 108 0 [ 2] exceptioninject!foo1 70 183 [ 1] exceptioninject!foo2 1 0 [ 2] exceptioninject!ILT+380(__RTC_CheckEsp) 2 0 [ 2] exceptioninject!_RTC_CheckEsp 73 186 [ 1] exceptioninject!foo2 70 1031 [ 0] exceptioninject!foo3 1 0 [ 1] exceptioninject!ILT+380(__RTC_CheckEsp) 2 0 [ 1] exceptioninject!_RTC_CheckEsp 73 1034 [ 0] exceptioninject!foo3

1107 instructions were executed in 1106 events (0 from other threads)

Function Name Invocations MinInst MaxInst AvgInst MSVCR80D!__iob_func 4 5 5 5 MSVCR80D!_ftbuf 1 33 33 33 MSVCR80D!_lock_file2 1 12 12 12 MSVCR80D!_output_l 1 575 575 575 MSVCR80D!_stbuf 1 50 50 50 MSVCR80D!printf 3 7 71 49 exceptioninject!ILT+215(?foo1YAXXZ) 1 1 1 1 exceptioninject!ILT+340(?foo2YAXXZ) 1 1 1 1 exceptioninject!ILT+380(__RTC_CheckEsp) 4 1 1 1 exceptioninject!_RTC_CheckEsp 4 2 2 2 exceptioninject!foo1 1 108 108 108 exceptioninject!foo2 1 73 73 73 exceptioninject!foo3 1 73 73 73

0 system calls were executed

eax=00000073 ebx=7ffd7000 ecx=00437c7e edx=10310bd0 esi=0012fe9c edi=0012ff68 eip=0041186a esp=0012fe9c ebp=0012ff68 iopl=0 nv up ei pl zr na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 exceptioninject!wmain+0x4a: 0041186a ebe1 jmp exceptioninject!wmain+0x2d (0041184d)

 

 

地点wt命令平昔监视到foo3函数执行完截止。随着函数的实施,Windbg打印出foo3调用过的子函数。即便需要更详尽的音信,可以调整深度参数的值。

wt命令最终交给总计消息。无论是着眼函数执行过程和支行,或者是评估性能,wt命令都是很有援助的。

断点和准星断点 (condition breakpoint),高效地操纵观测目的

Windbg中的断点分为3种,命令格式和效能如下:

1.
bp+地址/函数名字可以在某个地方上设定断点。当程序运行到这个地点的时候断点触发。

  1. ba (break on
    access)用来设定访问断点,在某个地方被读/写的时候断点触发。
    3.
    Exception断点。当爆发某个Exception/Notification的时候断点触发。详情请参考Windbg匡助中的sx(Set
    Exception)小结。

第1种格式前面早已推行过。第2种格式的断点也很粗略。比如程序有一个全局变量,符号是testapp!g_Buffer。要想在先后修改这么些变量的时候停下来,可以行使下边的一声令下设定断点:

 

 

ba w4 testapp!g_Buffer

地点的w4表示需要检讨的档次和长短。W4中的W表示项目为写(Write),4意味长度为4字节。testapp!g_Buffer是标志的名字,调试器会活动转换成该符号所在的内存地址。所以该断点的效应是:监视一块内存地址区域,起源是testapp!g_Buffer所在的地址,长度为4字节。当有代码对该块地址任意地方有写操作爆发的时候,调试器就把程序断下来。

 

 

骨子里设置ba断点的法则很简短。在装置断点后,调试器通过API把所监视地址的页面属性改为不可访问。这样当有代码访问这块地点的时候,就会唤起访问相当。这样调试器就可以监视内存的读写操作,作出相应判断。

第3种命令用来监视很是。调试器能捕获程序中有所的这一个,可是并不是说其他卓殊发生的时候调试器就必将要把程序断下来。调试人士可以经过sx命令来指定特别暴发时候的照应操作。下边3条命令达到的功力是,当Access
Violation非凡爆发的时候,调试器就停下来。当DLL
Load事件爆发的时候,调试器就只是在屏幕上输出。当C++
exception发生的时候,调试器什么都不做。

 

 

Sxe av Sxn ld Sxd eh

 

 

至于丰裕的详尽表达,在前边的下结论有详尽介绍。

标准断点(condition
breakpoint)的是指在上头3种为主断点停下来后,执行一些自定义的判断。详细表达参考Windbg襄助中的Setting
a Conditional Breakpoint小结。

在基本断点命令后增长自定义调试命令,可以让调试器在断点触发停下来后,执行调试器命令。每个命令之间用分号分割。

下面这些命令,在exceptioninject!foo3上设断点,每一趟断下来后,先用k展现callstack,然后用.echo命令输出简单的字符串‘breaks’,最终g命令继续执行:

 

 

0:001> bp exceptioninject!foo3 "k;.echo ‘breaks’;g" breakpoint 0 redefined 0:001> g ChildEBP RetAddr 0012fe94 0041186a exceptioninject!foo3 0012ff68 00412016 exceptioninject!wmain+0x4a 0012ffb8 00411e5d exceptioninject!__tmainCRTStartup+0x1a6 0012ffc0 77e523cd exceptioninject!wmainCRTStartup+0xd 0012fff0 00000000 kernel32!BaseProcessStart+0x23 ‘breaks’ ChildEBP RetAddr 0012fe94 0041186a exceptioninject!foo3 0012ff68 00412016 exceptioninject!wmain+0x4a 0012ffb8 00411e5d exceptioninject!__tmainCRTStartup+0x1a6 0012ffc0 77e523cd exceptioninject!wmainCRTStartup+0xd 0012fff0 00000000 kernel32!BaseProcessStart+0x23 ‘breaks’

 

 

更扑朔迷离一点的例证是:

 

 

int i=0; int _tmain(int argc, _TCHAR* argv[]) {

while(1) { getchar(); i++; foo3(); } return 0; }

 

 

原则断点的命令是:

 

魔兽争霸, 

ba w4 exceptioninject!i "j (poi(exceptioninject!i)<0n40) '
.printf \"exceptioninject!i value is:%d\",
poi(exceptioninject!i);.echo;g';'.echo stop!'"

 

 

首先ba w4
exceptioninject!i表示在修改exceptioninject!i这么些全局变量的时候停下来。j(judge)命令的效率是对前边的表达式作标准判断,如若为true,执行第1个单引号里面的命令,否则执行第2个单引号里面的指令。
标准化表达式是(poi“exceptioninject!i”<0n40)。在Windbg中,exceptioninject!i符号表示符号所在的内存地址,而不是标志的数值,相当于C语言中的
&操作符的成效。Windbg命令poi的效能是取这多少个地方上的值,相当于C语言中的*操作符。所以这多少个条件的趣味就是判断exceptioninject!i的值,是否低于十进制(Windbg中十进制用0n当前缀)的40。
尽管为真,那么就实施第1个单引号:

 

 

printf \"exceptioninject!i value is:%d\",poi(exceptioninject!i);.echo;g

 

 

那多少个单引号里面有3个指令:.printf、.echo
和g,这里的printf语法跟C中printf函数语法一样。不过是因为这么些printf命令自己是在ba命令的双引号里面,所以需要用\来转义printf中的引号。转义的结果是:

 

 

printf “exceptioninject!i valus is %d”, poi(exceptioninject!i)

 

 

据此第1个单引号命令的职能是:

1)打印出当前exceptioninject!i的值;2).echo命令换行;3)g命令继续执行。
假如为假,那么就推行第2个单引号:.echo stop!
这么些命令就是彰显stop,由于前面没有g命令,所以windbg会停下。运行输出如下:

 

 

0:001> ba w4 exceptioninject!i "j (poi(exceptioninject!i)<0n40) '
.printf \"exceptioninject!i value is:%d\",poi(exceptioninject!i);.echo;g';'.echo stop!'"
breakpoint 0 redefined
0:001> g
exceptioninject!i value is:35
exceptioninject!i value is:36
exceptioninject!i value is:37
exceptioninject!i value is:38
exceptioninject!i value is:39
stop!
eax=00000028 ebx=7ffd5000 ecx=5e186b9c edx=10310bd0 esi=0012fe9c edi=0012ff68
eip=00411872 esp=0012fe9c ebp=0012ff68 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206
exceptioninject!wmain+0x52:
00411872 e856f8ffff       call exceptioninject!ILT+200(?foo3YAXXZ) (004110cd)

 

 

伪寄存器,协理保存调试的中游音信

考虑这样的事态,假诺要记录某一个函数被实施了略微次,应该怎么办?简单的做法就是修改代码,在相应的函数入口做笔录。可是,尽管要记录的函数是系统API呢?

上边的通令可以总结VirtualAllocEx被执行了略微次:

 

 

bp /1 /c @$csp @$ra;g

 

 

bp kernel32!VirtualAllocEx
“r $t0=@$t0+1;.printf \”function
executes: %d times \”,@$t0;.echo;g”

这里运用的$t0就是Windbg提供的伪寄存器。能够用来存储中间音信。这里用它来储存函数执行的次数。r命令可以用来查看,修改寄存器(CPU寄存器和Windbg的伪寄存器都灵验)的值。随便挑一个劳累的长河,用这个命令设定断点后观看:

 

0:009> bp kernel32!VirtualAllocEx "r $t0=@$t0+1;.printf 
\"function executes: %d times \",@$t0;.echo;g"
0:009> g
function executes: 1 times
function executes: 2 times 
function executes: 3 times 
function executes: 4 times
…

 

 

至于伪寄存器信息,可以参见帮忙文档中的Pseudo-Register Syntax小结。

Step Out的实现

Step Out的定义是“Target executes until the current function is
complete.”。Windbg中是什么样落实这些意义的吧?遵照这一个定义,可以简简单单地在当下函数的回来地址上设定bp
断点就可以了。当前函数的回到地址保存在函数入口时候的EBP+4上。但假若简单地在EBP+4下边设定断点有六个问题:

  1. 没辙区分递归调用和函数再次来到,甚至其他线程对该地方的调用。
  2. 先是次接触后不会活动清除端点,可能会反复触及。

比方观望windbg中step  out的落实,可以见见:

 

 

bp /1 /c @$csp @$ra;g

那里的/1参数使得断点在触发后自行清除,避免了第2个问题,/c
@$csp参数通过点名callstack
的细微深度防止了第1个问题。而$ra伪寄存器直接代表目前函数的回到地址。多方便
🙂

 

 

2.1.7  远程调试(Remote debug)

远程调试是让调节人士远程地操作调试器的一种手段。

在调节WinForm程序的时候,如若要保全目的程序一向全屏运行,就没办法在一如既往台机械上切换来调试器输入指令。使用remote
debug能够解决这样的情事,制止调试器对目的程序的困扰。

只要开发公司中的开发人士在五个都市,通过中远距离调试可以节省成立几个调剂环境的光阴。倘使某些问题不得不在稳住的机器上复发,远程调试让排错过程大概方便。

在Windbg中,一种办法使用.server命令在本土创设一个TCP端口或者经过named
pipe,使得远程的Windbg能够连续到地面调试。双方都可以输入指令,执行结果在二者的Windbg上都突显出来。具体介绍参考Windbg中关于.server命令的救助。

其余一种更加强大的模式是接纳DbgSrv。DbgSrv是一个调节服务。跟.server命令不同的地点在于,.server只是简单地因而重定向方便远程调试人士检查,而实际上的调节工作都暴发在对象机器上。DbgSrv则是让这一个必要的调试动作暴发在对象机器上,而次要的调剂功效,比如加载PDB显示符号等,暴发在调试人员的机器上。在DbgSrv出现此前,调试系统的中央服务,比如lsass.exe进程,需要同时整合用户态调试器和基础调试器,而且符号文件必须放在目的机器上。DbgSrv的面世让这些过程极为简化,请参见:

 

 

Debugging LSASS ... oh what fun, it is to ride..
http://blogs.msdn.com/spatdsg/archive/2005/12/27/507265.aspx

 

 

2.1.8  怎么样通过Windbg命令行让粤语魔兽争霸运行在英文系统上

买了中文版的魔兽争霸,但家里的Windows却是英文版。中文的魔兽争霸必须要运行到闽南语的操作系统上,否则就报告操作系统语言不匹配。

为了化解这一个题目,首先能体悟的就是到Windows的地面安装里面去把国家改为中国。尝试后发现题目仍旧。看来魔兽争霸判断的不要地点Local设置,而是操作系统的语言版本。如何做吧?重装系统?去网上找破解?其实Windbg就可以缓解问题。

收获系统语言版本的API是GetSystemDefaultUILanguage。所以可以在这些API上设定标准断点,然后观看魔兽争霸判断语言版本的逻辑是怎么着的。

用Windbg启动war3.exe,然后在GetSystemDefaultUILanguage上设定断点。API触发后,发现调用完这一个API后,war3.exe的下一条语句是一个cmp
eax,ChineselanID,判断当前是否是闽南语系统。假若不是中文,就退出程序。

这好,在cmp这条语句上设定断点,然后用上面的授命把eax修改成粤语语言符的
ID,就足以欺骗程序,让程序认为当前系统是中文:

 

 

r eax = 0n2052

eax被改动成了国文的言语符后,接下去的cmp执行结果就跟闽南语系统上的平等了,war3就足以正确运行了。每一回都要做这样的修改麻烦得很,为了简化这一个进程,创立内容为如下的script文件,在GetSystemDefaultUILanguage
API重回前把ax设定为0n2052:

 

 

 

 

bp kernel32!GetSystemDefaultUILanguage+0x2c "r ax=0n2052;g"

 

 

历次启动魔兽争霸都要手动设定断点是很费力的工作。假诺要简化整个经过,可以利用下边作品中介绍的格局,让war3.exe启动的时候自动启动Windbg,通过-cf参数自动执行我们的规则断点来达到诈骗war3的目标。

 

 

How to debug Windows services
http://support.microsoft.com/?kbid=824344

 

 

2.1.9  Dump文件

前边提到过,dump文件是经过的内存镜像。可以把程序的履行情状通过调试器保存到dump文件中。当在调试器中开辟dump文件时,使用前边介绍的授命检查,看到的结果跟用调试器检查过程是一致的。
在Windbg中得以通过.dump命令保存进程的dump文件。比如下边的命令把当下进程的镜像保存为c:\testdump.dmp文件:

 

 

.dump /ma C:\testdump.dmp

 

 

个中的/ma参数表示dump文件应当包含进程的完整信息,包括整个用户态的内存,这样dump文件尺寸会相比大,信息非凡周全。如若不行使/ma参数,保存下来的dump文件只含有了一部分要害资料,比如寄存器和线程栈空间,文件尺寸会比较小,不可能解析所有的数据。

在Windbg中,通过File→Open Crash
Dump菜单可以打开dump文件举办辨析。打开dump文件后,运行调节命令看到的音讯和情景,就是dump文件保留时经过的状况。通过dump文件可以有利于地保留暴发问题时经过的意况,方便事后分析。

2.1.10  CDB、NTSD和重定向到Kernel Debugging

除了Windbg,此外有两个调试器分别名叫CDB和NTSD。Windbg、CDB和NTSD三者使用的指令都统统一致。只是Windbg提供了窗口接口,剩下多少个是基于命令行的工具。NTSD位于system32索引下,不需要特地设置。
这3个工具其实都使用了同一的调节引擎dbgeng.dll。关于调试引擎的详细音讯,请参见:

 

 

Symbols and Crash Dumps
http://msdn.microsoft.com/msdnmag/issues/02/06/Bugslayer/

 

 

是因为CDB和NTSD选拔命令行标准输入输出,所以可以很有利地通过重定平素控制这两个工具。一个典型的用例就是可以把用户态的调试重定向到Kernel
Debugger。这样只需要一个Debugging
Session就足以同时控制要旨态和用户态的调节例程。详细消息请参见Windbg
匡助中的CDB and NTSD小结。

2.1.11  Debugger Extension,扩展Windbg的功能

Debugger
Extension相当于是用户自定义,可编程的Windbg插件。一个最有效的extension就是.NET
Framework
提供的sos.dll。它可以用来检查.NET程序中的内存、线程、callstack、appdomain、assembly等等音信。关于sos.dll后边会作详细讲解。关于什么开发自己的Debugger
Extension,可以参考:

 

 

 

Debug Tutorial Part 4: Writing WINDBG Extensions
http://www.codeproject.com/debug/cdbntsd4.asp

 

 

 

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*
*
Website