您的位置:首页 > 其它

【转帖】使用WinDbg和虚拟机调试Windows驱动程序

2008-11-01 15:07 453 查看
 使用WinDbg和虚拟机调试Windows驱动程序
2008-10-08 09:40
本文来自:http://blog.163.com/zhg_tao http://www.cnblogs.com/suiyingjie/ http://blog.163.com/zhg_tao/blog/static/84116744200862424215/ http://www.cnblogs.com/suiyingjie/archive/2008/01/25/1053391.html http://silverstr.ufies.org/lotr0/windbg-vmware.html http://www.vmware.com/support/ws3/doc/ws32_devices3.html
驱动调试攻略(WinDbg)

驱动调试是一个系统级调试方式,所以调试工具用WinDbg是最佳选择。驱动调试一般情况下,需要两台电脑进行。一台作为主机进行驱动调试,另一台作为目标机进行驱动安装。当然如果没有多余的电脑,我们可以安装VMware模拟系统调试(虚拟机)。只是对电脑的配置要求比较高。本文介绍使用WinDbg及WMware进行调试的具体设置及方法。这些资料在网络很难找到,笔者也是吸取了他人很多宝贵意见才完成的,供大家学习讨论。
VMware Support 中说,自 4.0.18.0
版本之后的 WinDbg 都支持了通过 pipe
来进行调试,不过微软对此并没有任何说明。所以,在VMware中虚拟被调试的系统,然后通过VMware虚拟一个com端口。使用这个虚拟的端口,就可以用
WinDbg 进行调试了。
具体步骤如下:

1.
安装和设置WinDbg,WMware

首先,请到微软官方网站
http://www.microsoft.com/whdc/devtools/debugging/default.mspx,下载最新版本的WinDbg工具,建议使用6.0以上版本的WinDbg做为调试工具。WMware也可以在网站上找到,当然使用高版本比较好。
安装过程比较简单,就不在此多做介绍。唯一要注意的是,安装完虚拟机后,请在VMware主菜单“VM”中选择“Install
VM Tools…”,完成系统驱动文件的安装。
安装完这两个软件后,请在VMware中安装跟你的电脑版本相同的操作系统。例如:你的电脑安装的是WindowsXP
Professional,那么最好在Vmware中安装相同版本。至少是相同的系统,Windows
XPßà Windows XP、Windows 2000ßà Windows
2000。这样会在驱动调试过程中避免不必要的麻烦。笔者使用的操作系统为Windows XP
Professional。
我们将使用电脑的COM1口最为调试口。1.1设置
VMware 的虚拟com

1.     运行 VMware ,点击 "Edit virtual machine
settings"

2.     点击 "Add..." 来运行 VMware 的 Hardware Wizard

3.    
选择 "Serial Port",点 "下一步"

4.     选择 "Output to named pipe",点
"下一步"

5.     第一框里保持默认的 "//./pipe/com_1"
第二框里选"This end is the
server."
第三框里选"The other end is an application."
选中 "Connect at power
on"
然后点击 "Advanced>>"

6.     选中 "Yield CPU on poll"(VMware
Support 中提到了这一点),然后点完成。

7.     这样就完成了虚拟com的设置。

8.    
重新启动一下。
1.2设置 VMware 虚拟出来的 guest os

9.    
编辑虚拟机中C盘根目录下的boot.ini文件。

在c:/下,可以找到boot.ini,可以用记事本打开它,或者命令行方式

C:/>attrib -s -h -r
boot.ini
C:/>notepad boot.ini
我们需要在
guest os
的启动项上加些参数,才能够使用WinDbg调试它。我们可以在现有的行后面直接加参数,不过强烈推荐复制一个新行,在新行的后面加参数。这样在调试启动有问题的时候,我们可以方便的换回原来的启动方式。下面就是我改好的boot.ini。
在boot.ini文件中添加以下代码:
multi(0) disk(0) rdisk(0)
partition(1) /WINDOWS="Microsoft Windows XP Professional-Debug" /fastdetect
/debugport=COM1 /baudrate=115200
10.
在 guest os
的设备管理器中把com1端口的速度也就是"每秒位数"项,设为和上面一样的115200。
打开vmware中winxp的设备管理器,选择端口(com1), 双击这个"com1",在弹出的对话框中。

1.3设置真实系统(主机)com

11.
回到我们的真实系统(主机)中,重复第10步。

1.4设置真实系统(主机)WinDbg

12.
设置WinDbg的快捷方式,及其运行参数:
我们需要告诉WinDbg通过pipe进行连接和连接的速度。
可以在命令提示符(cmd.exe)下加参数
-k com:port=//./pipe/com_1,baud=11520,pipe
运行WinDbg(VMware
Support 中没有提到 baud=11520
这个参数,其实这是个比较重要的参数)。
更方便的方法是在桌面建立一个WinDbg的快捷方式,在该快捷方式的属性,"目标"框中,加上参数
-k com:port=//./pipe/com_1,baud=11520,pipe
。这样运行这个快捷方式启动的WinDbg就完成了设置。参数的具体作用,可以参考
WinDbg 的帮助文件。
在快捷方式的属性中,将目标(Target)改为:
"C:/Program Files/Debugging
Tools for Windows/windbg.exe" -b -k
com:pipe,port=//./pipe/com_1,resets=0
命令参数的意义:
-b:一旦主机目标机之间建立起连接,立刻中断目标机
-k:内核调试
Com:设置连接目标机的通信端口(此处为命名管道)和波特率(此处为115200)
-y:设置符号文件路径
13.
设置WinDbg参数
现在我们假设你的驱动代码放在D:/mydriver目录中;系统级Symbol文件存放在C:/WINDOWS/Symbols目录中,这些Symbol文件是从微软的网站上http://msdl.microsoft.com/download/symbols下载而来,这个网址是不可以用IE直接打开的,WinDbgà
Fileà Symbol File
Path...界面中选择Reload,WinDbg会自动帮你下载;驱动生成的Symbol文件存放在D:/mydriver/objchk_wxp_x86/i386目录中。
a)    
设置驱动Symbol路径,WinDbg->File->
Symbol File Path...为
SRV*C:/WINDOWS/Symbols*http://msdl.microsoft.com/download/symbols;
D:/mydriver/objchk_wxp_x86/i386
b)    
设置驱动源文件路径,WinDbg->File-> Source
File Path…为D:/mydriver
c)    
为了保险起见,我们同时设置系统变量。
My Computer ->
Properties-> Advanced Tab-> Enviroment Variables->
Add._NT_DEBUG_BAUD_RATE=115200
_NT_SYMBOL_PATH=SRV*C:/WINDOWS/Symbols*http://msdl.microsoft.com/download/symbols
_NT_ALT_SYMBOL_PATH =
D:/mydriver/objchk_wxp_x86/i386
_NT_SOURCE_PATH =
D:/mydriver
14.
启动VMware,将主机目录C:/WINDOWS/Symbols下的拷贝到虚拟机的相同位置。同时请把驱动编译后生成在D:/mydriver/objchk_wxp_x86/i386目录下的*.pdb、*.sys、*.map、*.exp、*.lib文件一并拷贝到两个系统的C:/WINDOWS/Symbols目录中。
15.
重新启动VMware,并选择如图所示。这个时候先不敲回车键。
16.
在主机中运行之前设置好的WinDbg快捷方式。直到在WinDbg命令窗口中出现“Waiting
to
reconnect...”,此时打开VMware,敲回车键进入虚拟机。到这里所有的设置工作就全部完成了。

2.驱动安装及调试

这里其大家注意,由于我们目前用的是虚拟机,所以安装驱动程序的时候一定要把VMware设置到全屏状态。
17.
编辑你要调试的源代码,在你需要调试的代码中添加硬断点。如果不添加硬断点,将没有办法进入所调试的程序中。具体方法如下:
在你需要调试的程序中加入以下代码:
extern "C"
{
#include
<wdm.h>
//或者是#include
<winddk.h>
}//放在文件开头
DbgBreakPoint();//放在需要调试的地方
重新编译驱动程序,记住一定要更新把重新生成的*.pdb、*.sys、*.map、*.exp、*.lib文件一并拷贝到两个系统的C:/WINDOWS/Symbols目录中。18.
打开VMware,Ctrl+Alt+Enter进入虚拟机全屏模式。插入需要调试设备的硬件,笔者调试的目标设备为USB。当USB插入主机后,直接由VMware识别到这个USB设备,指定驱动位置,系统将会在你设置硬断点的地方停下来。这个时候你就可以回到WinDbg,进行单步调试了。你还可以在程序中添加软断点,“F9”或者“bp
MyApp!MyFunction”。
3.两台电脑调试说明

两台电脑调试跟借助VMware调试有所不同,不同之处有以下几个方面。

(1)      
需要自己制作一条Null Modem Cable. 请参考以下电路。将两台电脑连接起来。

管脚信息说明:

9-Pin NULL
Modem Cabling

3 2 Transmit Data

2 3 Receive Data

7 8
Request to Send

8 7 Clear to Send

6,1 4 Data Set Ready and Carrier
Detect

5 5 Signal Ground

4 6,1 Data Terminal
Ready

25-Pin NULL Modem Cabling

2 3 Transmit Data

3
2 Receive Data

4 5 Request to Send

5 4 Clear to Send

6 20
Data Set Ready and Carrier Detect

7 7 Signal Ground

20 6 Data
Terminal Ready

(2)WinDbg快捷方式参数设置
"C:/Program Files/Debugging Tools
for Windows/windbg.exe" -b -k com:port=//./ com1,resets=0
//////////////////////////////////////////////////////////////////////////////////////
Windbg简明教程
2008-10-08 09:10
Windbg是Microsoft公司免费调试器调试集合中的GUI的调试器,支持Source和Assembly两种模
式的调试。Windbg不仅可以调试应用程序,还可以进行Kernel Debug(新版本对于XP+操作系统支持Live kernel
debug),同时结合Microsoft的Symbol
Server调试应用程序和Kernel非常得利。Windbg支持x86,IA64,AMD64。
Windbg下载地址:http://www.microsoft.com/whdc/devtools/debugging/default.mspx
下面我来说一下Windbg的基本用法:
基本设置
由于Windbg是GUI调试器,所以在设置方面可以通过菜单,也可以通过命令。我只说明最方便的办法:),那请先打开Windbg吧

号路径设置,Ctrl+S在弹出的窗口中输入你的符号路径,路径的格式只要符合Windows操作系统路径格式即可,路径可以多个,中间以分号间隔,如:
d:/symbols/win2k3_en;个人感觉Windbg的强大功能之一在于Windbg会自动到Microsoft的服务器上下载符号表文件
(.dbg或.pdb,有时DLL和EXE也会下载),只要在符合表路径里做如下设置:srv*d:/symbolslocal*http:
//msdl.microsoft.com/download/symbols,这样如果相关符号表在d:/symbolslocal目录没有找到的话,
Windbg会自动在Microsoft的Symbol
Servers上下载。如果你是调试自己的应用程序的话,建议你将自己应用程序的*.pdb文件的路径放在前面这样对Windbg来说查找起来比较快。
源文件路径设置:Ctrl+P在弹出的窗口里指定你的源代码文件的路径,路径格式只要符合Windows操作系统的格式即可,可以指定多个,中间以分号间隔。
可以将你的设置进行保存,File->Save
Workspace。
开始调试
可以创建一个子进程进行调试,也可以对正在运行的程序进行调试,方法如下:Ctrl+E打开一个应用程序并可指定运行参数进行调试;F6从对话框中选择当前正在运行的进程调试。
对于调试子进程,通常Windbg会在应用程序运行之前中断,此时你可下一些想要的断点:
以Notepad.exe
为例,如果你还没有符号表文件的而又设置了MicrosoftSymbol
Servers的话,你得先等一下,因为Windbg会自己自动到Microsoft上下载相关的符号表,在命令提示符的位置如果没有出现0:
000>这个标记表示Windbg正在忙。
表达式格式:
无论在那条指定里都会涉及这个问题:数制,语法。Windbg支持C++和
MASM两个表达式格式,@@用于即时改变语法格式,即在MASM下使用@@指令来使用C++的语法解释器,反之亦然。Windbg数制的表示0x表示
16进制,0n表示10进制,0t表示8进制,0y表示2进制;Windbg默认数制为16进制,n指认用于设置数制;默认表达式语法是MASM,. expr
指令用于设置表达式语法解释器。本文均使用MASM为默认语法解释器。大小问题,通常如果不特别说明,Windbg对指令不区分大小
断点指令:BP,BM,BA,BL,BC,BD,BE
BP
在指定的地址设置断点
bp notepad!WinMain,在Notepad的WinMain函数处下断点。

点的位置可以用符号表示,也以直接使用地址及Windbg的Pseudo-Register(虚拟寄存器),如$exentry表示进程的入口点,可以使 用bp
@$exentry在进程的入口点设置断点,对于Notepad当前入口点为01006420,也可以直接 bp 01006420,等效于bp
notepad!WinMainCRTStartup.
BM 使用模式匹配设置断点,需要符号表支持
bm
值一提,在符号表合法的情况下(符号表中包含私有符号的时候),bm可能通过模式一次下多个断点,bm
mydriver!FastIo*指定可以将所有与FastIo*模式相匹配的函数下断点,如FastIoRead,FastIoWrite等。但是bm 需要full
or export symbols支持,Microsoft的提供的符号表不是都支持的,通常我们自己编译的程序的符号表(Windbg显示为private pdb
symbols)默认是支持的。
BA (Break on Access)
顾名思义,对内存访问下断点。对于在多核或多处理器调试的时候
很有用,对于调试多线程也很有用,应该说用处很多,比如对一个全局变量设置断点,ba
mydriver!gMonitoredDevices,如果如果你认为这个变量的值被莫名的修改了,相信通过BA设置的断点,你很快就能找到是谁修改
的。
BL(List),BC(Clear),BE(Enable),BD(Disable)
  
这四个指令是分别用于列表,清除,开启和禁用断点,也是使用非常频繁的指令。
条件断点
  
以上所提到的断点指令通过与J指令很容易形成条件断点。比如:
bp USER32!GetMessageW "r $t1=poi(esp+4);r
$t2=poi(@$t1+4); j(@$t2 = 0x102 ) 'du @$t1+8
L2;gc';'gc'"
这个条件断点,截取WM_CHAR消息,并将字符(包括中文)显示出来。
条件断点的最简形式:bp Address "j
(Condition) 'OptionalCommands'; 'gc'
"
Address是指令的地址,Condition是一个条件表达式,如果@eax=1,'OptionalCommands'是在断点被击中并且表达式成立时要执行的指令;gc指定是从一个条件断点返回,是不可少的一部分。
数据查看指令
d{a|b|c|d|D|f|p|q|u|w|W}
d{b|c|d|D|f|p|q}分别是显示:
byte&ASCII,
double-word&ASCII,double-word,double-precision,float,pointer-sized,quad-word数据;
DA用于显示ASCII,DU用于显示UNICODE;
BYB,BYD,显示binary和Byte及binary和DWORD
补充一个DV,用于查看本地变量用的
这些指令区分大小。
栈指令k[b|p|P|v]

四条指令显示的内容类似,但是每个指令都有特色,KB显示三个参数,Kp显示所有的参数,但需要Full Symbols或Private
PDBSymbols支持。KP与Kp相似,只是KP将参数换行显示了。Kv用于显示FPO和调用约定,KD,用于显示Stack的Dump,在跟踪栈时
比较有用。
这些指令区分大小。
KD显示的内容:
0012fbd0   0012fbf0
0012fbd4   77e2158f
USER32!UserCallWinProc+0x18
0012fbd8   0016011e
0012fbdc  
00000030
0012fbe0   750a0c3f
0012fbe4   00000000
0012fbe8  
00000000
0012fbec   dcbaabcd
0012fbf0   0012fc2c
0012fbf4   77e1279c
USER32!DefDlgProcWorker+0xbf
0012fbf8   004018e0 DGGuarder!MainDLGproc
[j:/mydriver/dgguarder2/dgguarder.c @ 350]
0012fbfc   0016011e
0012fc00  
00000030
0012fc04   750a0c3f
0012fc08   00000000
0012fc0c  
00629d08
0012fc10   00000030
0012fc14   00619828
0012fc18   77e0f626
USER32!__ClientLoadMenu+0x38
0012fc1c   77e0f635
USER32!__ClientLoadMenu+0x47
   KP显示的内容:
ChildEBP RetAddr  
0012fbd0
77e2158f DGGuarder!MainDLGproc(
struct HWND__ * hwnd = 0x0016011e,

unsigned int message = 0x30,
unsigned int wParam = 0x750a0c3f,
long
lParam = 0)+0x227 [j:/mydriver/dgguarder2/dgguarder.c @ 415]
0012fbf0
77e1279c USER32!UserCallWinProc+0x18
0012fc5c 77e0b981
USER32!DefDlgProcWorker+0xbf
0012fd14 77e140bb
USER32!InternalCreateDialog+0x695
0012fd44 77e1410f
USER32!InternalDialogBox+0xaa
0012fd64 77df41ec
USER32!DialogBoxIndirectParamAorW+0x34
0012fd90 00401cdc
USER32!DialogBoxParamA+0x4a
0012fe88 00408684 DGGuarder!WinMain(
struct
HINSTANCE__ * hInstance = 0x00400000,
struct HINSTANCE__ * hPrevInstance =
0x00000000,
char * lpCmdLine = 0x00132902 "",
int nCmdShow = 10)+0x5c
[j:/mydriver/dgguarder2/dgguarder.c @ 469]
0012ffc0 77e88989
DGGuarder!WinMainCRTStartup(void)+0x194
[f:/vs70builds/3077/vc/crtbld/crt/src/crt0.c @ 251]
0012fff0 00000000
KERNEL32!BaseProcessStart+0x3d
数据修改指令e{b|d|D|f|p|q|w}
  
请参见文档吧:)
反汇编指令u,uf
u @$exentry L10
0:001> u @$exentry
L10
notepad!WinMainCRTStartup:
01006420 55             push
ebp
01006421 8bec          mov     ebp,esp
01006423 6aff          push
0xff
01006425 6888180001    push 0x1001888
0100642a 68d0650001    push
0x10065d0
0100642f 64a100000000     mov     eax,fs:[00000000]
01006435 50
            push eax
01006436 64892500000000 mov    
fs:[00000000],esp
0100643d 83c498           add    
esp,0xffffff98
01006440 53             push ebx
01006441 56            
push esi
01006442 57             push edi
01006443 8965e8           mov
    [ebp-0x18],esp
01006446 c745fc00000000 mov     dword ptr
[ebp-0x4],0x0
0100644d 6a02          push 0x2
0100644f ff1560110001 call
dword ptr [notepad!_imp____set_app_type (01001160)]
uf (Unassemble
Function)指令对整个函数进行反汇编
uf GetLanguageCount
0:000> uf
GetLanguageCount
DGGuarder!GetLanguageCount
[j:/mydriver/dgguarder2/language.c @ 54]:
54 00403c00 55             push
ebp
54 00403c01 8bec          mov     ebp,esp
54 00403c03 81ecc0000000    
sub     esp,0xc0
54 00403c09 53             push ebx
54 00403c0a 56      
      push esi
54 00403c0b 57             push edi
54 00403c0c
8dbd40ffffff     lea     edi,[ebp-0xc0]
54 00403c12 b930000000    mov    
ecx,0x30
54 00403c17 b8cccccccc    mov     eax,0xcccccccc
54 00403c1c f3ab
         rep     stosd
56 00403c1e 6a00          push 0x0
56 00403c20
6828b44100    push 0x41b428
56 00403c25 6820b44100    push 0x41b420
56
00403c2a e8b1edffff    call DGGuarder!ReadInt (004029e0)
56 00403c2f 83c40c
          add     esp,0xc
58 00403c32 5f             pop     edi
58
00403c33 5e             pop     esi
58 00403c34 5b             pop    
ebx
58 00403c35 81c4c0000000     add     esp,0xc0
58 00403c3b 3bec      
   cmp     ebp,esp
58 00403c3d e85e0f0000    call DGGuarder!_RTC_CheckEsp
(00404ba0)
58 00403c42 8be5          mov     esp,ebp
58 00403c44 5d      
      pop     ebp
58 00403c45 c3            
ret
跟踪指令T,TA,TB,TC,WT,P,PA,PC
  
T指令单步执行,在源码调试状态下,可指源码的一行,根据不同的选项也可以为一行ASM指令;
TA单步跟踪到指定地址,如果没有参数将运行到断点处。
TB执行到分支指令,分支指令包括calls,
returns, jumps, counted loops, and while loops
TC执行到Call指令
WT Trace and
Watch Data,一条强大指令,对执行流程做Profile,执行一下看看结果吧
P,PA,PC相信不用多做解释,大家也都明白了
源代码操作指令
.,lsf,lsc,ls,l,lsp
.指令打一个源文件,可以打开一个全路径的文件,也可以通过函数地址来打开并定位到源文件中函数的位置,如. –a
myapp!main,.
j:/mydriver/mydriver.c
lsf指定一个源文件为当前源文件,使用lsc可显示当前指定的源文件ls可显示源文件的代码。Lsf可以使用全路径,如果源路径已经设置,也可以直接指定源文件名称。如lsf
mydriver.c,lsf j:/mydriver/mydriver.c
lsc显示当前源文件
ls显示当前源文件的代码,如ls
200显示第200行
l 用于设置源文件选项
lsp 设置源文件行在调试时显示范围比如,显示当前行的前50,后50,lsp
100
但通常使用Windbg时,可以直接用Ctrl+O来打开并查看源文件
寄存器指令 r

信大家对这个指令都很熟悉,在Windbg中r指令除了可以显示修改CPU寄存器之外,Pseudo-Register可使用这个命令来修改。对eax 操作,r eax
显示其值,r eax=2,修改其值;r $t2=10,修改Pseudo-Register的值,r @$t2显示其值。
Search 指令 s,#
S
指令对内存区别进行查找,可用于查找数字,字符串,但不支持模式查找。s -d @esp L100 8187bc40,从esp指向的内存地址0x100个字节内查找
DWORD   8187bc40。查找字符串 s -a 0012ff40 L20 "Hello" 。s -sa 和 s -su
显示内存可打印的ASCII和Unicode字符串。
#指令可以查询汇编指令模式, # "call[ ]+esp" kernel32 L1000查找call
esp 指令。
其他常用指令 lm,!peb,x,dt
lm 查看当前载入的模块
!peb 查看当前进程环境块(PEB)
x
查看模块的符号,如x mydriver!*FastIo*,显示所有与*FastIo*匹配的符号列表
dt 查看类型数据,还可用于查看模块类型的符号列表,如
dt dgguarder!_IMAGE_DOS_HEADER
00400000
从00400000处查看_IMAGE_DOS_HEADER类型的数据
上下文的概念
  
Windbg下上下文的概念很重要,根据文档中说明有多种上下文概念,
Session Context,
Process
Context
Register Context(其实也就是线程上下文)
Local
Context(这个关系的本地如果解析本地变量的问题)
调 试Win32应用程序,Session Context和Process
Context是确定的,主要是Register Context,也即Thread Context,可以使用~指令来查看,改变当前的Thread Context
。~*显示所有线程列表,~xs用于切换上下文(x是数字),如:~1s,将上下文切换到1号线程。
.frame用来设置Local
Context。

PAUSE
好了,基本的指令都已经列出了来了,Kernel
Debug现在先不写了,其实跟Win32也差不多。如果再有时间再写吧。本文对指令没有说的太细,详细说明见Windbg文档,希望见谅。
//////////////////////////////////////////////////////////////
WINDBG Script简易教程
2008-10-08 08:56
【文章标题】: WINDBG Script简易教程
【文章作者】:
笨笨雄
【【工具名称】: WINDBG
【下载地址】: http://www.microsoft.com/whdc/devtools/debugging/installx86.mspx 【作者声明】:
希望更多人使用WINDBG,然后大概就能看到debugging tools for windows的帮助文件的中文翻译了吧。花了
几天时间才在翻译软件的帮助下看完debugger commands部分,痛苦死了。也找不到WINDBG的插件,还是WINDBG没插件功能?为了简
化调试过程,只有学习使用SCRIPT了,现在把这几天的经验跟大家分享。附件中所有代码经NOTEPAD,REGEDIT等调试,花了几小时,基本通
过。

    
废话多了,现在是正文。WINDBG的指令比较多,还是英文的,所以我只挑了一部分经常会用到的,并通过实例去告诉大家那些指令的作用和格式。正如大多数高级语言教程一样,我们先来看看如何写一个HELLO
WORLD的程序。

     如果使用.echo "HELLO
WORLD"作为例程就太简单了,我希望介绍更多的指令。所以我在OD直接用汇编写了个程序:

PUSH 0
PUSH 12345678
;TITLE跟显示内容都在这个跟下一个PUSH
PUSH 12345678 ;我比较懒。。。就用一样的字符了
PUSH 0
MOV
EAX,OFFSET MESSAGEBOXW
CALL
EAX

先用EAX保存MESSAGEBOXW的指针,然后再CALL。这是为了你在任何一个程序下都能用使用这个SCRIPT。如果直接 CALL
MESSAGBOXW的指针,翻译成机器码是相对于当前位置的偏移,这样写出来的SCRIPT文件在这个程序能用,别的程序就不能用了。

机器码 6A
00 68 78 56 34 12 68 78 56 34 12 6A 00 b8 68 3d e2 77 ff d0

我虚拟机使用的是WIN
2000 连SP1都不是。。所以我不保证你的机器仍然能正常运行这个程序。为了能正常使用,你可以随便找一个程序,然后 BP
MESSAGEBOXW,中断之后当前的EIP就是了,把ff d0前面的68 3d e2 77换掉了就可以了。我的目的并不是介绍如何写一个兼
容性差的程序,重点是学会如何写SCRIPT。

准备工作做好了,在看代码之前,先解释一些指令:

$exentry伪寄存器,数值上等于EP

$t0-$t19,WINDBG为我们提供了20个自定义的伪寄存器

R指令能改变几乎所有寄存器的值,包括EAX等

.dvalloc
[/b] size 申请内存空间,带/b
地址,可在指定地址申请空间,不带则自动分配,指定地址时不一定成功,暂时的经验指定地址越大越容易成功。

e* 地址 在指定内存中写入数据,EW
写入WORD,EB写入BYTE,ED写入DWORD

注意: EW 00400000 12345会产生溢出错误,同理EB 00400000
123也是错的,正确的例子可见后面的代码

f 地址 L长度 BYTE
在长度的地址写入数据,你可以在示例中看到效果。同样BYTE的位置只能是BYTE,多于8位的数据都会造成溢出错误。

m 源地址 L源地址长度
目的地址 复制内存区域

d* 地址 显示地址中的数据,其中db的效果可在示例中看到。

.dvfree /d 地址 size
释放指定地址的内存,这里指定地址用的是/d要与
.dvalloc的/b相区别。

附件中helloworld.txt的代码:

---------------------------------------helloworld.txt--------------------------------

g
$exentry

r $t0=00ff0000;      $$ $t0:源内存基址

r $t2=@$exentry;     $$
$t2:目的内存基址

r $t1=@$t0;        $$ $t1:当前指针

.dvalloc /b $t0
1000;     $$在00b90000申请1000BYTE的内存空间

ew $t1 006A 0068

db
$t0

.echo "ew指令的效果"

r $t3 = @$t1 + 3;      $$PUSH DWORD的机器码=68
DWORD
         $$这里应该输入字符串的首址
r $t1 = @$t1 + 7;     
$$懒得计算,所以用$t3存起DWORD的指针
         $$输入字符串的时候一起搞定
eb $t1 68 12 34

db
$t0

.echo "eb指令的效果"

r $t4 = @$t1 + 1;      $$同上

r $t1 =
@$t1 + 5

f $t1 l20 6A 00 b8 68 3d e2 77 ff d0

db $t0

.echo
"f指令的效果"

r $t1 = @$t1 + 9

r $t5 =$t1 - $t0 + $t2

ed $t3
$t5;        $$ 添加字符串的指针回去

ed $t4 $t5

f $t1 l20 'h' 'e' 'l' 'l' 'o'
' ' 'w' 'o' 'r' 'l' 'd' '!' 00 00
         $$ 把字符串写进内存中
m $t0 l30
$t2

r $ip=$t2;       $$ 修改EIP

.dvfree /d $t0 1000;     $$
释放内存

g

---------------------------------------helloworld.txt完结的分割线--------------------------------

为了你的速度,请保证symbol
path为空,只有在你有源代码或者系统核心的时候它的存在才有意义,否则你会发现它会非常费时且毫无意义,尤其是你得连上网络下载symbol资源的时候。

使用SCRIPT文件的命令有4个"$<","$$<","$><","$$><",他们的区别就是有没有空格或者
换行符的限制。使用$$><没有任何限制,这样可使代码更具可读性。要使用附件中的SCRIPT请使用$$><指令。

例如你可以用下面指令访问在D盘下的helloworld.txt。

$$><d:/helloworld.txt

如果你把helloworld.txt放在WINDBG的安装目录,那么你可以使用下面指令:
$$><helloworld.txt

运行完helloworld.txt后你会发现报错了,因为我的代码覆盖了EP。通过上面的例子,我们能用SCRIPT做什么呢?在合适的时机,把没加密的IAT或者其他什么的,暂存到内存中,在脱壳完毕的时候再自动用正确的部分把加密部分覆盖掉。

也许在未来,也会遇到这样的需要,程序运行到某部分的时候,中断,然后运行我们自己的代码,运行完毕之后,我们需要返回到程序原来的流程。为此我把上面的SCRIPT修改了一下,写成BACKTOCODE.TXT。

--------------------------------------------backtocode.txt--------------------------------------

r
$t0=00ff0000                

r $t1=@$t0

r
$t18=$ip                     ;$$ 用$t18暂存当前EIP,显然$ip=EIP

.dvalloc /b $t0
1000

ew $t1 006A 0068

r $t3 = @$t1 + 3

r $t1 = @$t1 +
7

eb $t1 68 12 34

r $t4 = @$t1 + 1

r $t1 = @$t1 +
5

f $t1 l20 6A 00 b8 68 3d e2 77 ff d0

r $t1 = @$t1 + 9

ed
$t3 $t1

ed $t4 $t1

ba e1 $t1                     ;$$
内存运行断点,E代表运行1是长度,在E后面通常是1,断在最后一个指令后的第一个地址

f $t1 l20 'h' 'e' 'l' 'l' 'o' '
' 'w' 'o' 'r' 'l' 'd' '!' 00 00

r $ip=$t0                     ;$$
在分配的内存中直接运行我们自己的代码

g

.dvfree /d $t0 1000

r
$ip=$t18                     ;$$
把EIP设置为原来的

.cls                           ;$$
当前命令行窗口清屏

p                              ;$$
单步步过

-------------------------------------------backtocode.txt结束-----------------------------------

为了anti
anti
debug我们需要隐藏标题,虽然WINDBG本身有.wtitle指令,可是那个指令会把它后面的所有内容当作字符串输入,而且windbg+版本号是无论怎么改都会被默认添加到最后的。这样上面那个SCRIPT就有用了,就差CODE了。

为了anti
anti
debug,每次DEBUG的时候,我们都要做一些准备工作,用SCRIPT文件,我们可以自动完成这些操作,下面是我DEBUG之前都会用到的SCRIPT文件

-----------------------------------------------start.txt----------------------------------------

r
$t0 = 00
eb 7FFDF002 $t0                 ;$$去除DEBUG标志
.pcmd -s
".if(eax<70000000 and eax>00120000){da eax;du eax}; .if(edx<70000000
and edx>00120000){da edx;du edx}"
g $exentry                     
;$$入口点

-------------------------------------------start.txt结束----------------------------------------

如你所见的,这有点少。没办法水平有限,而且写这篇文章的时候我才勉强说是学会用,还是那句重点是教会大家用WINDBG。WINDBG的初始断点并不是
入口点所以得自己用指令让它自动停在入口点,有的程序是有TLS表的,对着PE格式的介绍文章,写一个SCRIPT在有TLSCALLBACK的情况下自
动停在TLSCALLBACK入口是有可能的,你会在文章的最后部分得到相关指令的介绍。现在来说说START.TXT中没有注释的指令。


分号,多条命令的分隔符。从左到右运行。

下面例子中,对MESSAGEBOXW下断后运行,中断之后便会运行r
$t0=esp+8指令

bp messageboxw;g;r
$t0=esp+8

注意:如果你使用CRTL+BREAK快捷键在中断之前暂停调试也会导致r
$t0=esp+8的运行。

.if(条件表达式){命令} 跟C语言中的用法一样。

.pcmd
不带参数则显示每条指令之后自动使用的指令。-s "命令" 设置命令。-c 清除命令。

da
以ASCII显示内存地址,du以UNICODE显示内存地址

在示例中,整条指令的效果表现为,每单步一个指令,便会当EAX,EDX指向的是一个合法地址的时候,便以ASCII和UNICODE的方式分别显示它的值,就象OD那样。如果熟悉ASCII和UNICODE字符集的范围还能设置仅当有效字符时才显示结果。
上传的附件

11.rar
在调试的过程中,有时我们希望自动化解决一些问题。例如调试使用了UnhandledExceptionFilter的SEH,我们需要自动修改
ZwQueryInformationProcess的返回值。或者对于某些API的ANTI DEBUG,如果我们修改了输入参数,同样不能返回应该返
回的值。学破解不久,一下子要找用了UnhandledExceptionFilter的软件还真不容易,用别的API代替了。我用OD把NOTEPAD
修改一下,改名为TEST放在附件中。

流程MESSAGEBOXW,GETCOMMANDLINEW,MESSAGEBOXW输出COMMANDLINE,最后EXITPROCESS。

现在我要做的是改变GETCOMMANDLINEW的输出,和第二个MESSAGEBOXW的输入。现在让我们看看test2.txt

-------------------------------------------test2.txt-----------------------------------------------

g
$exentry
r $t0=0
bp messageboxw "r $t0=$t0+1;j($t0=2)'r $t1=poi(esp+8);f
$t1 l4 45;g';g"
bp getcommandlinew "g poi(esp);r $t1=eax+5;f $t1 l4
55;g"
g

------------------------------------------test2.txt完结---------------------------------------------

首先对相关指令作一些介绍

BP
地址或者函数名 "命令" 命令参数是可选的,存在的情况下,中断的同时会先运行那些命令。

J(条件表达式)'命令1';命令2  
相当于.if但是又有点不同命令2只能是1个,后面所有命令会被忽略。

POI() 返回指针的指向位置的内容。

!=  
不等于

这里用了条件中断的方法实现,第一个条件中断指令用$t0作为计数器,第二次中断的时候变修改堆栈中指针指向位置的内存区域。注意到调用API的返回地址在ESP中,直接跳出去,然后修改EAX就可以达到修改函数输出参数的效果了。

这里提供第二种可行的方法,并且更有可扩展性,现在看看test.txt中的代码。

-------------------------------------------test.txt------------------------------------------------

g
$exentry
r $t0=0
bp messageboxw
bp getcommandlinew
bp
exitprocess

.while (eip!=77e7b0bb){
g
.if($ip=77e116cc){
r
$t0=$t0+1
.if($t0=2){
r $t1=poi(esp+8)
f $t1 l4 45
      
}
       }
.if($ip=77e7c693){
g poi(esp)
r $t1=eax+5
f $t1 l4
55
       }
.elsif($ip=77e7b0bb){
.break
      
}
}

g

------------------------------------------test.txt完结----------------------------------------------

仍然先介绍一些指令:
.while(条件表达式){}
跟C语言中的一样,循环结构,直到条件表示式为真
.elsif(){}
跟前面的.if用法一样,它的作用如字面上意思,只是小心别拼错为ELSEIF
.break
跟C语言中的一样,跳出循环。

如果在条件为真的时候不用.break跳出循环就会出错,这点要注意。

这里构造了一个循环结构,并且通过对比EIP的方法来识别函数,同样地因为我的虚拟机是WIN
2000 连SP1都不是,所以我不肯定该地址在你的机器
中仍然可用。不过这里提供了一个思路,你可以用这个方法构造一个SCRIPT来加强WINDBG的功能,例如象OD一样中断的时候自动显示所有参数,并且
带上英文提示那是什么参数。同样地,我们可以做一个自动化分析SCRIPT,分析每个CALL中包含了什么API,并且列出输入和输出参数,CALL的深
度还指令数,并且自动生成报告文件,假如有人开发出这样一个SCRIPT,调试分析将会变得容易。WINDBG里面有个相似功能的指令。

WT
自动跟踪并生成报告,几乎跟我上面说的一样。带/l参数的时候可以设置深度,不过很多时候,我们看到一个CALL并不知道里面究竟有多深,但是我们希望得到一些关于那个CALL的详细信息来判断是否值得跟进。这里有两个问题:

1
递归,那这个指令不知道运行多久。
2 大量NATIVE
API调用,显然大多数情况下,我们并不关心。

比起1,2更加常见,/i参数是用来避开指定模块的,不会用,帮助文件里也没提。。。。希望有大大能答我这个问题

WINDBG提供了下面3个指令用于保存分析过程进文件,通过适当的开关可以过滤一些无意义的信息,使分析过程易于观看。

.logopen  
文件路径    带/U参数则以UNICODE方式输入文本。重写整个文件,并记录当前命令窗口在使用该指令之后的所有内容。
.logclose   
文件路径    停止记录并关闭文件。
.logappend    文件路径   
带/U参数则以UNICODE方式写文件。记录当前命令窗口在使用该指令之后的所有内容,并添加进文件。

提到了功能强化,大家都知道OD里面有个命令是运行到RET处吧,在WINDBG中似乎没有这样的指令,类似的有PC,即运行到CALL。我写了一个SCRIPT来模拟OD中的那个指令。现在我们来看看goret.txt

-------------------------------------------goret.txt------------------------------------------------

r
$t0=0
.while(@$t0!=c3){
p
r
$t0=by(eip)
.if(@$t0=c3){
.break
}
}

-----------------------------------------goret.txt完结----------------------------------------------

这里是最后一个示例分析,所以除了解释上面的指令之外也给出一些有价值的指令
not
非                                     and或者&   与
hi()  
取高16位                             or或者^    或
low()  
取低16位                            xor或者|   异或
by()   
取低8位                             gu   步出,不知道具体原理,有时会出错
wo()   
取低16位                            t    步入
mod或者%  
模运算

这个SCRIPT使用了一个循环,通过EIP取得当前指令的机器码,低8位既为指令,然后把指令存进$t0作比较。C3是RET的机器码,等于则跳出循环,否则一直步过。

这个示例表明,我们可以在SCRIPT里分析每一条指令。我们可以在WINDBG中进行2次开发,动态将那些简单使用JMP+内存指针或者寄存器作为跳转
的乱序的程序重新排序,使花指令失效,并且实现自动清除垃圾指令,最后生成优化后的汇编代码文件。本论坛翻译区里的变形多态中的关于收缩器的理论已经为我
们奠定了理论基础。

你可能会需要用到反汇编指令

u 起始地址 l长度  
L代表的不是地址长度而是指令的个数

---------------------------------后续讨论,用SCRIPT把WINDBG变成脱壳机------------------------------

在准备写这篇文章的时候,我又把DEBUGGER
COMMANDS看了一次,发现了这个指令

.writemen filename range 将目标内存区域写进文件。RANGE的格式为 地址
l长度

我没试过L后面是否接受寄存器作为参数。也没实际测试过这个指令的具体操作是怎么样的,无论如何,有这个可能存在。当然我们也可以申请内存区域以程序的方式来完成这个工作,不过我希望它仅用SCRIPT完成。

假如这的确可行,可以通过下面指令组合来自动寻找文件头,当然也能确定文件大小。

$p
伪寄存器,它将返回前一次用d*指令所显示的内存的内容。

假设00100000   01 02 03 04 05 06 07
08

我使用dd 00100000,那么$p = 04030201

显然我们可以通过这个方法来访问内存。

dw
取一个WORD; dd取DWORD;
dw取qword

能访问内存也代表说我们在调试程序中插入的代码也能跟SCRIPT通信,并且把一些SCRIPT无法完成的工作交给程序执行,然后把结果返回给SCRIPT。

假如l的参数无法通过寄存器来传递,只能依靠用户按照提示进行操作,那么我们有更简单的方法

.imgscan  
它将返回所有模块MZ的地址和它的SIZE

----------------------------------这里给出一些可能的疑问和解答--------------------------------------
Q:在调试SCRIPT文件的时候,我该如何知道寄存器跟内存的变化?

A:我们可以用下面的指令来观察寄存器跟内存的变化

d*
用于显示内存,之前已经提到就不详细说明了

? 寄存器 显示寄存器的值,例如

? poi(esp); ?
$t0

这将先显示ESP指向的值,然后显示$t0的值

除了可以使用.echo命令对显示参数作说明之前,也可以使用.printf作格式化输出,它的用法跟C语言中的printf是一样的

Q:我写的SCRIPT文件出错了,语法跟参数都没错,为什么我找不到出错原因?

A:有的指令要注意的,BA只能在进入程序区域之后才能用。.dvalloc申请过的内存,即使用.dvfree释放了,也无法在同样的位置再申请,可能是BUG。

*是一个注释命令,它后面所有的内容都会被当作字符
$$则是以分号为结束

.restart指令跟.wtitle指令,不知道为什么不能放在SCRIPT中使用。

还有就是@这个标记,这个标记是告诉WINDBG后面的是一个伪寄存器而不是程序里的某一个变量的符号。有的指令在没有@标记的时候会报错,例如.
while括号里的条件表达式,如果你用了伪寄存器,一定要在前面加上@否则一定报错。此外用帮助文件里的话来说,使用@,可以让SCRIPT文件运行得
更快,因为在解读这个代码的时候不需要先搜索一次SYMBOL记录。

Q:我能把功能模块化然后在其他SCRIPT文件中使用吗?

A:我已经测试过$$><指令也能在SCRIPT里面使用

Q:我写的SCRIPT
FILE能在64位系统中用吗?

A:如果你仅使用SCRIPT来实现功能,那么很可能与64位兼容。尽量使用伪寄存器。

$ip,$retreg,$csp在32位系统中分别表示EIP,EAX,ESP,而在64位系统中则表示RIP,RAX,RSP也分别对应Itanuim处理器中的相关寄存器。

通过函数名获取不同版本下的地址,可以通过下面代码:

bp
messageboxw ;     $$第一个断点,断点ID为0
bp getcommandlinew; $$第二个断点,断点ID为1
r $t10
= $bp0;              $$将第一个断点的地址转存$t10
r $t11 = $bp1;             
$$将第二个断点的地址转存$t11
bc *;               
$$清除所有断点。

这段代码运行之后MESSAGEBOXW的地址便存于$t10中,而$t11里面的则是getcommandlinew的地址。这里要说明,断点用完要释放,否则不好估计断点的ID,此外在内核模式中,最多只允许32个断点。

$peb和$teb返回当前进程的PEB和TEB地址,这里的翻译区有介绍如何仅通过PEB或者TEB判断当前操作系统类型

为不同系统准备不同代码

总算写完了。。。。再废话几句

WINDBG看起来很难用,是因为用的人不多。

即使没有插件功能,WINDBG
SCRIPT的功能也已经很强大了。

如果大家都来做SCRIPT,调试分析难度会降低很多,新手也可以通过阅读SCRIPT
FILE来学习。搜索引擎使用得好的确可以学到很多,可惜这跟作
者的表达和使用引擎者的表达有关,很可能相同的内容,因为表达方式不同就查不到了。就象我学校的图书馆,在电脑搜索逆向工程是什么都找不到的,但是搜索加
密解密,却看到好几本书。

OD虽然好,始终是RING3的,SOFTICE似乎也已经停止开发了。希望大家都能加入WINDBG的行列
///////////////////////////////////////////////////////
WinDbg入门教程(1)-调试器的基础知识
2008-10-08 09:22

WinDbg 入门教程

介绍

在我的职业生涯中,我看到我们大多数都是使用Visual
Studio来进行调试,而不是用其它许多免费的调试器。你可能有许多理由来使用这样的调试器,比如,在你家里的机器上没装开发环境,但是一个程序一次次的崩溃。其实根据堆栈的dump就可以判断出IE的崩溃是否是由于一个第三方的插件。
对于WinDbg,我目前为止还没有发现很好的快速入门的教程。这篇文章结合实例讨论了WinDbg的使用。我首先假设你熟悉调试的基本概念:stepping
in, stepping out,断点以及远程调试的基本概念。
注意,这本来是座位一个入门的文档,你可以阅读并且开始使用WinDbg.
如果对于特定的命令有疑问,请查阅WinDbg的文档。你可以在任何微软提供的调试器中使用这篇文章中提到的命令,比如在VS的命令行窗口中。
这篇文章是基于WinDbg 6.3.
这仅仅是一系列关于调试技术的文章中的第一篇。在下一篇文章中,我会解释如何针对调试器编写扩展DLL.

调试器一览

下面大概介绍了你可以从微软网站上下载到的调试器:
·        
KD-内核调试器。你可以用它来调试蓝屏一类的系统问题。如果是开发设备驱动程序是少不了它的。
·        
CDB-命令行调试器。这是一个命令行程序
·        
NTSD-NT调试器。这是一个用户模式调试器,可以用来调试用户模式应用程序。它实际上是一个CDB的windows
UI增强。
·        
WinDbg-用一个漂亮的UI包装了KD和NTSD。WinDbg即可以调试内核模式,也可以调试用户模式程序。
·        
VS,
VS.net-使用同KD和NTSD相同的调试引擎,并且相比于同样用于调试目的的WinDbg,提供了功能更丰富的界面。

调试器之间的比较

功能KDNTSDWinDbgVisual Studio .NET
内核模式调试YNYN
用户模式调试YYY
非托管调试YYYY
托管调试YYY
远程调试YYYY
附加到进程YYYY
从进程分离YYYY
SQL调试NNNY

WinDbg

WinDbg实际上包装了NTSD和KD并且提供了一个更好用的用户界面。它也提供了命令行开关,比如最小化启动(-m),附加到一PID指定的进程(-p)以及自动打开崩溃文件(-z)。它支持三种类型的命令。
·        
Regular commands(比如: k)
用来调试进程
·        
Dot
commands(比如:.sympath)用来控制调试器
·        
Extension commands(比如:
!handle)-这些命令属于可以用来添加到WinDbg的自定义命令;它们用扩展DLL的输出函数来实现。

PDB文件

PDB文件指的是链接器生成程序数据库文件(Program database
files)。私有的PDB文件包括私有以及公有符号,源代码行号,类型,局部以及全局变量。公有的PDB文件不包含类型,局部变量以及源代码行号信息。
WinDbg入门教程(2)-各种调试场景介绍
2008-10-08 09:26

调试场景

远程调试

使用WinDbg进行远程调试是很容易的,而且有很多种可行的方法。在下文中,’调试服务器’指的是运行在你所要调试的远程机器上的调试器。’调试客户端’指的是控制当前会话的调试器。
·        
使用调试器:你需要CDB,
NTSD或者WinDbg已经安装在远程机器上。WinDbg客户端可以连接到CDB,
NTSD或者WinDbg中的任何一个作为服务器,反之亦然。在客户端和服务器直接可以选择TCP或者命名管道作为通讯协议。
o  
在服务器端的启动过程:
§
WinDbg
–server npipe:pipe=pipename(注:可以允许多个客户端连或
§
从WinDbg内部: .server
npipe:pipe=pipename(注,连接单个客户端)
你可以用多种协议开启不同的服务会话。并且可用密码来保护一个会话。
o  
从客户端连接:
§
WinDbg
-remote npipe:server=Server, pipe=PipeName[,password=Password]
§
从WinDbg内部: File->Connect to Remote Session:
for connection string, enter npipe:server=Server, pipe=PipeName
[,password=Password]
·        
使用Remote.exe:
Remote.exe使用命名管道作为通讯的方式。如果你使用的是一个命令行接口的程序,比如KD,CDB或者NTSD。你可以使用remote.exe来远程调试。注意:使用@q(不是q)来退出客户端,不用关掉服务端。
o  
要启动一个服务端:
§
Remote.exe /s “cdp –p <pid>” test1
o  
从客户端连接:
§
Remote.exe /c <machinename> test1
上面的test1是我们所选择的命名管道的名字。
服务端会显示那个客户端从那个服务器连接以及执行过的命令。你可以使用‘qq’命令来退出服务端;或者使用File->Exit来退出客户端。另外,如果要进行远程调试,你必须属于远程机器的”Debugger
User”组并且服务器必须允许远程连接。

即时调试

在WinDbg的文档的”Enabling Postmorten
Debugging”部分对此有很详细的讨论。简而言之,你可以把WinDbg设置成默认的即时调试器,命令就是:Windbg
–I。这个命令实际上是把注册表中 HKLM/Software/Microsoft/Windows
NT/CurrentVersion/AeDebug的键值设置成WinDbg。如果要把WinDbg设置成为默认的托管调试器,你需要显示设置如下的注册表键值:

HKLM/Software/Microsoft/.NETFramework/DbgJITDebugLaunchSetting 设置成 2

HKLM/Software/Microsoft/.NETFramework/DbgManagedDebugger 设置成Windbg.(注意其中的启动参数设置)

通过JIT的设置,当一个应用程序在不是调试的状态下抛出了未处理的异常之时,WinDbg就会被启动。

64位调试

所有这些调试器均支持在AMD64和IA64上的64位调试环境。

托管应用程序的调试

WinDbg
6.3以后的版本支持在Widbey(VS2005和.net 2.0的内部开发代号)
.net
CLR托管调试。在文档中针对托管调试有很好的讨论。需要注意的是,对于托管程序来说,没有刚才所说的PDB(译注:托管代码实际上也是有PDB的,但是这个PDB实际上记录了C#代码和IL代码的对应关系以及相关的一些信息)的概念,因为所有的程序都是编译成为ILASM。调试器通过CLR来查询所需的附加信息。
有几点需要注意:
你只能在托段函数的代码被执行过至少一次之后才能设置断点。只有这样它才能被编译成汇编代码。记住以下的几点:
·        
关于函数的地址的复杂化以及对应的断点设置:
o  
CLR有可能丢弃已经编译好的代码,所以函数的入口地址有可能改变。
o  
同样的代码有可能被多次编译,如果多个应用程序域没有共享这段代码的话。如果你设置了一个断点,它就会被设置在当前线程(译注:CLR的逻辑线程)所在的应用程序域内。
o  
泛型的特殊实例可能导致同一个函数有不同的地址。.
·        
数据存储布局的复杂化以及对应的数据检查:

CLR可能会在运行的时候任意改变数据的存储布局,所以一个结构体成员的偏移量可能会被改变掉.
(译注:实际上是在一个类型被加载的时候决定的数据布局,之后是不会改变的。)

一个类型的信息是在第一次使用的时候被加载,所以你可能不能够查看一个数据成员如果它还没有被使用过.

·        
调试器命令的复杂化
o  
当跟踪托管代码的时候,你会需要穿越大段的CLR自己的代码比如JIT编译器的代码,原因可能是你第一次进入一个函数,或者是你在托管和非托管代码之间进行切换。

调试Windows服务

使用WinDbg,你可以像调试其它应用程序那样调试Windows服务程序。即可以通过附加进程的方法启动Windows服务,也可以把WinDbg当作一个即时调试器,并且在代码中调用
DbgBreakPoint

或者
DebugBreak
,或者在
x86
机器上加入一条
int 3
汇编指令。

调试异常

一个调试器会得到两次的异常通知-第一次在应用程序有机会处理异常之前(‘first
chance exception’);如果应用程序没有处理这个异常,这时候调试器就会有机会来处理异常(‘second-chance
exception’)。如果调试器没有处理二次机会的异常,应用程序就会退出。
.lastevent或者,!analyze
–v命令会给你显示异常的记录以及异常抛出所在函数的堆栈跟踪信息。
你也可以使用 .exr,
.cxr以及
.ecxr命令来显示异常和上下文记录。同时需要注意的是,你也可以改变first-chance的处理选项。对应的命令就是:
sxe, sxd, sxn和sxi。

能否请教一个问题:
我的Windbg不能设置断点,重新加载Symbols文件的时候也总是有几个加载失败,Symbols
file
path的设置是:
SRV*c:/symbols*http://msdl.microsoft.com/download/symbols。
类似的环境变量也设置了,可是怎么弄都不行,又没有什么好的建议?
用;将Symbols file
path分开,例如:
d:/dev/src;SRV*c:/symbols*http://msdl.microsoft.com/download/symbols

WinDbg 入门指导(3)-WinDbg中常用的功能以及设置
2008-10-08 09:34

WinDbg的功能

调试器扩展DLL

所谓的扩展指的是一些DLL,你可以用在调试器内调用并且执行一些自定义的命令。这些DLL必须实现一些特定的函数,并且要满足一些需求,这样才能被认为是一个扩展DLL。在下一篇文章内,我们将会了解到怎样写出一个扩展DLL。所谓的bang(!)命令就是从你的扩展DLL内执行的命令。注意这些DLL是被加载到调试器的进程空间内。

内存转储文件

你可以使用转储功能来取得一个进程的快照信息。一个mini-dump通常比较小,除非你使用了全内存的minidump(.dump
/mf)。通常能够转储句柄信息也是很有用的,命令是
.dump/mfh。一个小型转储通常包含了所有的线程的堆栈以及一个已被加载的模块的列表。一个全转储包含了更多的信息,比如进程的堆。

崩溃转储分析

如果你的windows系统当机,那么它就会在一个文件中转储物理内存中的数据,以及所有的进程信息。可以通过Control
Panel ->System->Advanced->’Startup and
Recovery’来配置。你也可以首先把WinDbg配置成为一个即时调试器,然后就可以取得任意一个非正常终止的进程的转储(.dump)。注意,从转储文件中分析出代码中的bug往往是一个复杂费力的过程。
使用以下的步骤来分析一个转储文件:
1)     
在WinDbg内,通过 File->’Open Crash
Dump’, 指向转储文件。
2)     
WinDgb会给你显示应用程序崩溃之时所执行的指令。
3)     
正确设置你的符号文件目录和源代码目录。如果你不能够匹配正确的符号文件,想要弄清楚程序的逻辑是非常困难的。如果你能够把符号文件匹配到正确版本的源代码,这是就应该很容易分析出Bug原因。注意,私有符号文件含有行号信息并且会盲目的显示你源代码中的对应行而不进行任何的检查;如果你的源码版本不对,那么你就不能够看到匹配汇编代码的正确源码。如果你仅仅有公有的符号文件,你会看到最后一个被调用的函数(栈上的)。
注意调试驱动或者托管代码是与此有很大不同的。参考《The Windows 2000 Device
Driver Book》来获得调试设备驱动的技术。

WinDbg的常用设置

符号文件与文件夹

如果想更有效的调试,你需要符号文件。符号文件可以是老式的COFF格式或者就是PDB格式。PDB就是程序数据库文件并且包含了公有符号。这些调试器内,你可以使用一系列的地址来让调试器寻找已经加载的二进制文件的符号。
操作系统的符号文件一般存储在%SYSTEMDIR%Symbols目录。驱动程序的符号文件(.DBG或.PDB)一般存储在和驱动文件(.sys
文件)相同的目录下。私有符号文件包含的信息包括:函数,局部以及全局变量,以及用来把汇编代码和源代码关联起来的行号信息;对于客户来说,符号文件一半是公有的-这些文件仅仅包括公有成员。
你可以通过File-Symbol File
Path来设置符号文件目录,或者使用
.sympath命令。如果想要添加到网络上符号文件的引用,添加以下的内容到你的
.sympath
SRV*downstream_store*http://msdl.microsoft.com/download/symbols
使用的命令就是:
.sympath+
SRV*c:/tmp*http://msdl.microsoft.com/download/symbols


C:/tmp就是download_store,所需要的符号文件会被下载存储至此。注意这个符号服务器仅仅开放了公有的符号文件。
当调试器把一个二进制文件(DLL或exe)的时候,他会检查比如文件名,时间戳以及校验值。如果你有符号信息,你就可以在调用栈上看到函数名和他们的参数。如果二进制文件和PDB文件都来自于你自己的应用程序,你就可以看到比如私有函数,局部变量以及类型这类额外的信息。

源代码路径

你可以通过File->Source File
Path来设置源码路径,或者使用.srcpath命令。如果你设置了代码的路径,当你调试的时候,调试器会通过PDB文件的行号信息来显示相匹配的源代码。

断点,跟踪

·        
通过bp命令或者工具栏上的断点图片来设置软断点。
·        
通过代码比如DbgBreakPoint() 或者
KdBreakPoint()来设置硬断点。
·        
在扩展DLL中使用跟踪函数DbgPrint, KdPrint,
OutputDebugString 来把输出显示在WinDbg的输出窗口中。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: