windbg入门之旅:(2)一个简单的integer divide-by-zero exception分析案例
假设该程序名为mydebug,并且使用
cscript.exe adplus.vbs -crash -pn mydebug.exe -o c:\test\crashdump -quiet ,捕获了程序crash时刻的dump文件。下载该程序的crash dump,请点击此处。
step1:打开该crash dump之后,立刻可以看到捕获的exception
Loading Dump File [D:\study\mydebug\crashdump\Crash_Mode__Date_11-12-2008__Time_15-30-1818\PID-3512__MYDEBUG.EXE__2nd_chance_IntegerDivide__full_0474_2008-11-12_15-30-35-715_0db8.dmp]
User Mini Dump File with Full Memory: Only application data is available
Comment: '2nd_chance_IntegerDivide_exception_in_MYDEBUG.EXE_running_on_T-RENHE-03'
Symbol search path is: srv*d:\symbolslocal*http://msdl.microsoft.com/download/symbols;D:\symbolslocal
Executable search path is:
Windows Server 2003 Version 3790 (Service Pack 2) UP Free x86 compatible
Product: Server, suite: Enterprise TerminalServer SingleUserTS
Debug session time: Wed Nov 12 15:30:35.000 2008 (GMT+8)
System Uptime: 0 days 6:30:46.419
Process Uptime: 0 days 0:02:46.000
...
This dump file has an exception of interest stored in it.
The stored exception information can be accessed via .ecxr.
(db8.1678): Integer divide-by-zero - code c0000094 (first/second chance not available)
eax=00000004 ebx=7ffdd000 ecx=00434e40 edx=00000000 esi=00000000 edi=0012fe60
eip=00401490 esp=0012fe04 ebp=0012fe60 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
*** WARNING: Unable to verify checksum for mydebug.exe
mydebug!CallFast+0x60:
00401490 f77df4 idiv eax,dword ptr [ebp-0Ch] ss:0023:0012fe54=00000000
从以上这段信息可以看到,在指令寄存器eip所保存的00401490处的指令出现exception,并且产生异常的instruction和exception information已经列出。
一眼即可以看出,这个程序是因为除零而产生的exception,究竟这个exception如何产生的呢?
下面列出完整debug过程。
0:000> .logopen c:\crahdebug1.log //打开crashdebug1.log开始写入debug log
Opened log file 'c:\crashdebugl.log'
0:000> kb
/*
该comand显示call stack内容,call stack保存的是exception发生时的瞬时状态,而stack top是出现exception的焦点所在,从以下的callstack可以看出,其调用嵌套过程如下:main-> CallWidhCDel-> CallWithStd ->CallFast (->表示调用关系),当在调用CallFast函数时,运行到00401490处的指令时发生exception.因此,下面需要step into至CallFast函数中,查看其指令。
*/
ChildEBP RetAddr Args to Child
0012fe60 00401402 00000006 0012ff18 00000000 mydebug!CallFast+0x60 [D:\study\mydebug\mydebug.cpp @ 58]
0012feb8 00401393 004310d8 00000004 00000006 mydebug!CallwithStd+0x42 [D:\study\mydebug\mydebug.cpp @ 44]
0012ff18 0040130a 0043101c 00000004 00000006 mydebug!CallWithCDecl+0x43 [D:\study\mydebug\mydebug.cpp @ 37]
0012ff80 00401969 00000001 00440e90 00440dc0 mydebug!main+0x9a [D:\study\mydebug\mydebug.cpp @ 28]
0012ffc0 77e6f23b 00000000 00000000 7ffdd000 mydebug!mainCRTStartup+0xe9 [crt0.c @ 206]
0012fff0 00000000 00401880 00000000 78746341 kernel32!BaseProcessStart+0x23
/*
从call stack我们可以清晰的看出函数调用链,在完成所有初始化process之后主函数调用CallWithCDecl,然后CallWithCDecl嵌套调用CallWithStd,然后CallWithStd又嵌套调用CallFast。每次嵌套调用的同时都伴随着一系列寄存器和,各参数的push stack。因此我们才可以顺着call stack看出清晰的调用链:)
下面对call stack中返回的值作简要说明:
ChildEBP:
该值保存的是对应的函数被调用时的EBP寄存器的值e.g.
0:000> dd 0012feb8 L4
0012feb8 0012ff18 00401393 004310d8 00000004
0:000> dd 0012ff80 L4
0012ff80 0012ffc0 00401969 00000001 00440e90
*/
0:000> kn
# ChildEBP RetAddr
00 0012fe60 00401402 mydebug!CallFast+0x60 [D:\study\mydebug\mydebug.cpp @ 58]
01 0012feb8 00401393 mydebug!CallwithStd+0x42 [D:\study\mydebug\mydebug.cpp @ 44]
02 0012ff18 0040130a mydebug!CallWithCDecl+0x43 [D:\study\mydebug\mydebug.cpp @ 37]
03 0012ff80 00401969 mydebug!main+0x9a [D:\study\mydebug\mydebug.cpp @ 28]
04 0012ffc0 77e6f23b mydebug!mainCRTStartup+0xe9 [crt0.c @ 206]
05 0012fff0 00000000 kernel32!BaseProcessStart+0x23
0:000> .frame 0 //选择stack top的frame
00 0012fe60 00401402 mydebug!CallFast+0x60 [D:\study\mydebug\mydebug.cpp @ 58]
0:000> dv //然后查看其参数情况
szMessage = 0x004310d8 "Now in the callwithstd function, parameters are :"
a = 4
b = 6
iDivider = 0 //从名字意义上判断不符合business logic.
iResult = 0
0:000> r
eax=00000004 ebx=7ffdd000 ecx=00434e40 edx=00000000 esi=00000000 edi=0012fe60
eip=00401490 esp=0012fe04 ebp=0012fe60 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
mydebug!CallFast+0x60:
00401490 f77df4 idiv eax,dword ptr [ebp-0Ch] ss:0023:0012fe54=00000000
0:000> u mydebug!CallFast //查看该function的指令 默认显示10行
mydebug!CallFast [D:\study\mydebug\mydebug.cpp @ 48]:
00401430 55 push ebp
00401431 8bec mov ebp,esp
00401433 83ec50 sub esp,50h
00401436 53 push ebx
00401437 56 push esi
00401438 57 push edi
00401439 51 push ecx
0040143a 8d7db0 lea edi,[ebp-50h]
0:000> u mydebug!CallFast L40 //显示40个单元块的指令集合
/*
00401490处的指令肯定包含其中
*/
mydebug!CallFast [D:\study\mydebug\mydebug.cpp @ 48]:
00401430 55 push ebp
00401431 8bec mov ebp,esp
00401433 83ec50 sub esp,50h
00401436 53 push ebx
00401437 56 push esi
00401438 57 push edi
00401439 51 push ecx
0040143a 8d7db0 lea edi,[ebp-50h]
0040143d b914000000 mov ecx,14h
00401442 b8cccccccc mov eax,0CCCCCCCCh
00401447 f3ab rep stos dword ptr es:[edi]
00401449 59 pop ecx
0040144a 8955f8 mov dword ptr [ebp-8],edx
0040144d 894dfc mov dword ptr [ebp-4],ecx
00401450 8b4508 mov eax,dword ptr [ebp+8]
00401453 50 push eax
00401454 8b4df8 mov ecx,dword ptr [ebp-8]
00401457 51 push ecx
00401458 8b55fc mov edx,dword ptr [ebp-4]
0040145b 52 push edx
0040145c 6814114300 push offset mydebug!`string' (00431114)
00401461 e89a030000 call mydebug!printf (00401800)
00401466 83c410 add esp,10h
00401469 c745f400000000 mov dword ptr [ebp-0Ch],0
00401470 c745f000000000 mov dword ptr [ebp-10h],0
00401477 837d0806 cmp dword ptr [ebp+8],6
0040147b 7509 jne mydebug!CallFast+0x56 (00401486)
0040147d c745f400000000 mov dword ptr [ebp-0Ch],0
00401484 eb06 jmp mydebug!CallFast+0x5c (0040148c)
00401486 8b4508 mov eax,dword ptr [ebp+8]
00401489 8945f4 mov dword ptr [ebp-0Ch],eax
0040148c 8b45f8 mov eax,dword ptr [ebp-8]
0040148f 99 cdq
00401490 f77df4 idiv eax,dword ptr [ebp-0Ch] //这就是罪魁祸首
00401493 8945f0 mov dword ptr [ebp-10h],eax
00401496 8b4df0 mov ecx,dword ptr [ebp-10h]
00401499 51 push ecx
0040149a 8b55f4 mov edx,dword ptr [ebp-0Ch]
0040149d 52 push edx
0040149e 8b45f8 mov eax,dword ptr [ebp-8]
004014a1 50 push eax
004014a2 6824114300 push offset mydebug!`string' (00431124)
004014a7 e854030000 call mydebug!printf (00401800)
004014ac 83c410 add esp,10h
004014af b803000000 mov eax,3
004014b4 5f pop edi
004014b5 5e pop esi
004014b6 5b pop ebx
004014b7 83c450 add esp,50h
004014ba 3bec cmp ebp,esp
004014bc e87f010000 call mydebug!_chkesp (00401640)
004014c1 8be5 mov esp,ebp
004014c3 5d pop ebp
004014c4 c20400 ret 4
004014c7 cc int 3
004014c8 cc int 3
004014c9 cc int 3
004014ca cc int 3
004014cb cc int 3
004014cc cc int 3
004014cd cc int 3
004014ce cc int 3
004014cf cc int 3
004014d0 cc int 3
0:000> dd ebp-0Ch L1 //看看指定的内存单元地址表示的被除数的值,当然为0了
0012fe54 00000000
0:000> .logclose
Closing open log file c:\crashdebugl.log
至于这个0是怎么来的,你可以联系你的vendor,告诉他当前实参为dv中所指定的值时,在CallFast函数中会抛出divide by zero exception,然后让他们去进行修改business logic了,vendor在有源代码的情况下debug就轻松多了。
当然如果你有兴趣,可以去研究以上列出的出现exception的instruction set所表明的business logic。