windbg入门之旅:(2)一个简单的integer dividebyzero 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。

原文地址:https://www.cnblogs.com/Winston/p/1332888.html