最近的工程在用vs2005进行调试, 现在可以Attach到Release版带调试符号的目标程序进行调试, 但是在非开发机上就没这条件了.
看了以下几篇资料, 做个实验.
http://www.codeproject.com/KB/debug/mapfile.aspx?msg=1315494
Finding crash information using the MAP file <vc6版好使>
http://www.cppblog.com/Walker/articles/146153.html
调试Release发布版程序的Crash错误 (转)<这个资料说全了, 可以在vs2005中做事后调试>
自己写了一个demo, 进行了验证, 可行. 但是没有实战性, 准备找个实际的程序来验证.
我在CodeProject 找了一个扫雷程序:
http://www.codeproject.com/KB/cpp/Mine4Edu.aspx
<<Adapted WinMine Source for Teaching Win32 API Programming>>
本来想搞个数组越界的错误,人家的程序写的太坚固了,弄了数组越界操作,也不报错。
只好弄了一个除零错~
编译选项是Release版不优化带调试符号,为了根据转储信息定位到代码中的行, 设置了MAPInfo和COD文件.
在Win7上程序报错是有崩溃对话框的, 但是我的WinXp上崩溃对话框没出来,只有一个报错对话框, 没有转储信息.
安装系统自带的drwtsn32.exe作为调试工具.
http://support.microsoft.com/kb/188296/zh-cn
<<如何禁用或启用 Windows 的 Dr. Watson>>
默认调试器的注册表备份
Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug] "Auto"="1" "Debugger"="drwtsn32 -p %ld -e %ld -g"
设置华生医生转储选项
再运行程序时,程序报错后,直接崩溃消失了。到那时,就可以从非开发机(大部分情况是客户现场)拿到转储信息转到开发机进行分析.
如果drwtsn32.exe设置了"视觉通知", 当程序报错时,会有转储提示对话框弹出.如果错误不是每次必现或在客户现场运行不用设置"视觉通知"选项.
如果不设置 "视觉通知", 程序报错后,直接退出了.这时可以去取程序报错时的转储信息。
将转储对话框中的文本全选,复制弄回来.
发生应用程序意外错误: 应用程序: D:\LsPrj\subjectResearch\DebugAcrossMap\WinMine4Edu\Unicode Release\WinMine.exe (pid=3528) 时间: 2011-12-26 @ 00:51:45.265 意外情况编号: c0000094 (除以零) *----> 系统信息 <----* 计算机名: 20110616-2328 用户名: Administrator 终端会话 Id: 0 处理器数量: 2 处理器类型: x86 Family 6 Model 15 Stepping 6 Windows 版本: 5.1 当前内部版本号: 2600 Service Pack: 3 当前类型: Multiprocessor Free 注册的单位: 微软中国 注册的所有者: 微软用户 *----> 任务列表 <----* 0 System Process 4 System 840 smss.exe 908 csrss.exe 932 winlogon.exe 976 services.exe 988 lsass.exe 1192 nvsvc32.exe 1252 svchost.exe 1300 svchost.exe 252 svchost.exe 484 svchost.exe 584 svchost.exe 620 zhudongfangyu.exe 1620 spoolsv.exe 1852 Explorer.EXE 1932 RTHDCPL.EXE 1948 360Tray.exe 2000 bjcacertd_ft11.exe 2016 vmware-tray.exe 2024 ctfmon.exe 128 360sd.exe 184 263em.exe 288 Snagit32.exe 1728 TSCHelp.exe 368 SnagPriv.exe 2004 snagiteditor.exe 1336 GSvr.exe 1384 wdfmgr.exe 1432 vmware-usbarbitrator.exe 364 vmnat.exe 700 vmnetdhcp.exe 1468 vmware-authd.exe 2168 wmiprvse.exe 2224 vmware-hostd.exe 2568 alg.exe 4000 360rp.exe 4072 firefox.exe 3452 NOTEPAD.EXE 3652 drwtsn32.exe 4080 drwtsn32.exe 3528 WinMine.exe 3036 drwtsn32.exe *----> 模块清单 <----* (0000000000400000 - 0000000000416000: D:\LsPrj\subjectResearch\DebugAcrossMap\WinMine4Edu\Unicode Release\WinMine.exe (0000000000920000 - 000000000098d000: C:\Program Files\360\360Safe\safemon\safemon.dll (000000005d170000 - 000000005d20a000: C:\WINDOWS\system32\comctl32.dll (0000000062c20000 - 0000000062c29000: C:\WINDOWS\system32\LPK.DLL (0000000071a10000 - 0000000071a18000: C:\WINDOWS\system32\WS2HELP.dll (0000000071a20000 - 0000000071a37000: C:\WINDOWS\system32\WS2_32.dll (0000000073640000 - 000000007366e000: C:\WINDOWS\system32\msctfime.ime (0000000073fa0000 - 000000007400b000: C:\WINDOWS\system32\USP10.dll (0000000074680000 - 00000000746cc000: C:\WINDOWS\system32\MSCTF.dll (0000000075ff0000 - 0000000076055000: C:\WINDOWS\system32\MSVCP60.dll (0000000076300000 - 000000007631d000: C:\WINDOWS\system32\IMM32.DLL (00000000765e0000 - 0000000076673000: C:\WINDOWS\system32\CRYPT32.dll (0000000076680000 - 0000000076726000: C:\WINDOWS\system32\WININET.dll (0000000076990000 - 0000000076ace000: C:\WINDOWS\system32\ole32.dll (0000000076bc0000 - 0000000076bcb000: C:\WINDOWS\system32\PSAPI.DLL (0000000076d70000 - 0000000076d92000: C:\WINDOWS\system32\Apphelp.dll (0000000076db0000 - 0000000076dc2000: C:\WINDOWS\system32\MSASN1.dll (00000000770f0000 - 000000007717b000: C:\WINDOWS\system32\OLEAUT32.dll (0000000077180000 - 0000000077283000: C:\WINDOWS\WinSxS\x86_Microsoft.Windows.Common-Controls_6595b64144ccf1df_6.0.2600.6028_x-ww_61e65202\comctl32.dll (0000000077bd0000 - 0000000077bd8000: C:\WINDOWS\system32\VERSION.dll (0000000077be0000 - 0000000077c38000: C:\WINDOWS\system32\msvcrt.dll (0000000077d10000 - 0000000077da0000: C:\WINDOWS\system32\USER32.dll (0000000077da0000 - 0000000077e49000: C:\WINDOWS\system32\ADVAPI32.dll (0000000077e50000 - 0000000077ee3000: C:\WINDOWS\system32\RPCRT4.dll (0000000077ef0000 - 0000000077f39000: C:\WINDOWS\system32\GDI32.dll (0000000077f40000 - 0000000077fb6000: C:\WINDOWS\system32\SHLWAPI.dll (0000000077fc0000 - 0000000077fd1000: C:\WINDOWS\system32\Secur32.dll (000000007c800000 - 000000007c91e000: C:\WINDOWS\system32\kernel32.dll (000000007c920000 - 000000007c9b6000: C:\WINDOWS\system32\ntdll.dll (000000007d590000 - 000000007dd84000: C:\WINDOWS\system32\SHELL32.dll *----> 线程 ID 0xcc0 的状态转储 <----* eax=00000006 ebx=00000000 ecx=0012fd10 edx=00000000 esi=00401fd0 edi=0012fe3c eip=00401761 esp=0012fd48 ebp=0012fd54 iopl=0 nv up ei pl nz ac po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000216 *** ERROR: Module load completed but symbols could not be loaded for D:\LsPrj\subjectResearch\DebugAcrossMap\WinMine4Edu\Unicode Release\WinMine.exe 函数: WinMine 00401749 7423 jz WinMine+0x176e (0040176e) 0040174b 6a03 push 0x3 0040174d 8b450c mov eax,[ebp+0xc] 00401750 50 push eax 00401751 8b4d08 mov ecx,[ebp+0x8] 00401754 51 push ecx 00401755 e8b6060000 call WinMine+0x1e10 (00401e10) 0040175a 83c40c add esp,0xc 0040175d 8b4508 mov eax,[ebp+0x8] 00401760 99 cdq 错误 ->00401761 f77d0c idiv dword ptr [ebp+0xc] ss:0023:0012fd60=00000000 00401764 894508 mov [ebp+0x8],eax 00401767 33c0 xor eax,eax 00401769 e9be000000 jmp WinMine+0x182c (0040182c) 0040176e 8b15e8c84000 mov edx,[WinMine+0xc8e8 (0040c8e8)] 00401774 83c201 add edx,0x1 00401777 8915e8c84000 mov [WinMine+0xc8e8 (0040c8e8)],edx 0040177d 8b450c mov eax,[ebp+0xc] 00401780 50 push eax 00401781 8b4d08 mov ecx,[ebp+0x8] 00401784 51 push ecx *----> 堆栈反向跟踪 <---* WARNING: Stack unwind information not available. Following frames may be wrong. *** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\WINDOWS\system32\USER32.dll - *** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\WINDOWS\system32\kernel32.dll - ChildEBP RetAddr Args to Child 0012fd54 0040118f 00000006 00000000 00000006 WinMine+0x1761 0012fd74 004020d7 00000202 00a30012 000000ef WinMine+0x118f 0012fdd4 77d18734 00060648 00000202 00000000 WinMine+0x20d7 0012fe00 77d18816 00401fd0 00060648 00000202 USER32!GetDC+0x6d 0012fe68 77d189cd 00000000 00401fd0 00060648 USER32!GetDC+0x14f 0012fec8 77d18a10 0012ff0c 00000000 0012ff28 USER32!GetWindowLongW+0x127 0012fed8 00401fb4 0012ff0c 00000000 00401fd0 USER32!DispatchMessageW+0xf 0012ff28 00402e78 00400000 00000000 000207e0 WinMine+0x1fb4 0012ffc0 7c817077 0065002e 00650078 7ffdf000 WinMine+0x2e78 0012fff0 00000000 00402ee1 00000000 78746341 kernel32!RegisterWaitForInputIdle+0x49 *----> 原始堆栈转储 <----* 000000000012fd48 bc 13 40 00 06 00 00 00 - 00 00 00 00 74 fd 12 00 [email protected]... 000000000012fd58 8f 11 40 00 06 00 00 00 - 00 00 00 00 06 00 00 00 ..@............. 000000000012fd68 12 00 00 00 a3 00 00 00 - 00 00 00 00 d4 fd 12 00 ................ 000000000012fd78 d7 20 40 00 02 02 00 00 - 12 00 a3 00 ef 00 00 00 . @............. 000000000012fd88 68 01 00 00 30 02 00 00 - 48 06 06 00 01 00 00 00 h...0...H....... 000000000012fd98 b0 fd 12 00 01 b4 d2 77 - 58 03 62 00 00 00 00 00 .......wX.b..... 000000000012fda8 00 00 00 00 01 00 00 00 - f4 fd 12 00 d4 13 69 74 ..............it 000000000012fdb8 ee 00 03 00 00 00 00 00 - 01 00 00 00 d9 13 69 74 ..............it 000000000012fdc8 00 00 00 00 00 e0 fd 7f - ac 98 96 db 00 fe 12 00 ................ 000000000012fdd8 34 87 d1 77 48 06 06 00 - 02 02 00 00 00 00 00 00 4..wH........... 000000000012fde8 12 00 a3 00 d0 1f 40 00 - cd ab ba dc 00 00 00 00 ......@......... 000000000012fdf8 3c fe 12 00 d0 1f 40 00 - 68 fe 12 00 16 88 d1 77 <[email protected] 000000000012fe08 d0 1f 40 00 48 06 06 00 - 02 02 00 00 00 00 00 00 [email protected]........... 000000000012fe18 12 00 a3 00 14 ff 12 00 - 0c ff 12 00 a8 64 67 00 .............dg. 000000000012fe28 14 00 00 00 01 00 00 00 - 00 00 00 00 00 00 00 00 ................ 000000000012fe38 10 00 00 00 00 00 00 00 - 8f 04 d4 77 00 00 00 00 ...........w.... 000000000012fe48 00 00 00 00 00 00 00 00 - 1c fe 12 00 74 f9 12 00 ............t... 000000000012fe58 b8 fe 12 00 8f 04 d4 77 - 30 88 d1 77 00 00 00 00 .......w0..w.... 000000000012fe68 c8 fe 12 00 cd 89 d1 77 - 00 00 00 00 d0 1f 40 00 .......w......@. 000000000012fe78 48 06 06 00 02 02 00 00 - 00 00 00 00 12 00 a3 00 H............... *----> 符号表 <----* D:\LsPrj\subjectResearch\DebugAcrossMap\WinMine4Edu\Unicode Release\WinMine.exe
在转储信息中,可以看到报错的地址.
错误 ->00401761 f77d0c idiv dword ptr [ebp+0xc] ss:0023:0012fd60=00000000将编译这个程序时产生的.map文件,.cod文件,转储信息文件放在一起,都打开,分析代码错误行.
map文件中看到程序的基址 = 0x400000
Preferred load address is 00400000
Address Publics by Value Rva+Base Lib:Object
0001:000004c0 _GameWon 004014c0 f MineGame.obj 0001:00000570 _GameLost 00401570 f MineGame.obj 0001:00000650 _CountMines 00401650 f MineGame.obj 0001:000006f0 _StepBox 004016f0 f MineGame.obj 0001:00000830 _FillRectStockBrush 00401830 f MinePaint.obj 0001:00000870 _DrawMargin 00401870 f MinePaint.obj 0001:000008b0 _DrawBorder 004018b0 f MinePaint.obj计算包含0x401761地址的函数
_StepBox 0x400000 + 0x1000 + 0x6f0 = 0x4016f0
0x401761 报错的代码行
_FillRectStockBrush 0x400000 + 0x1000 + 0x830 = 0x401830
可以看到 0x401761地址在函数_StepBox和_FillRectStockBrush之间, 说明代码中出错的位置在_StepBox函数中~
从转储信息中计算出错位置离函数入口的偏移:
*** ERROR: Module load completed but symbols could not be loaded for D:\LsPrj\subjectResearch\DebugAcrossMap\WinMine4Edu\Unicode Release\WinMine.exe 函数: WinMine 00401749 7423 jz WinMine+0x176e (0040176e) 0040174b 6a03 push 0x3 0040174d 8b450c mov eax,[ebp+0xc] 00401750 50 push eax 00401751 8b4d08 mov ecx,[ebp+0x8] 00401754 51 push ecx 00401755 e8b6060000 call WinMine+0x1e10 (00401e10) 0040175a 83c40c add esp,0xc 0040175d 8b4508 mov eax,[ebp+0x8] 00401760 99 cdq 错误 ->00401761 f77d0c idiv dword ptr [ebp+0xc] ss:0023:0012fd60=00000000 00401764 894508 mov [ebp+0x8],eax
从包含_StepBox函数的MineGame.cod中找到离_StepBox入口偏移为0x71的代码行:
_TranslateMouseMsg ENDP _TEXT ENDS PUBLIC _GameLost PUBLIC _GameWon PUBLIC _StepBox
; COMDAT _StepBox _TEXT SEGMENT _r$78102 = -12 ; size = 4 _c$78103 = -8 ; size = 4 _cMinesSurround$ = -4 ; size = 4 _row$ = 8 ; size = 4 _col$ = 12 ; size = 4 _StepBox PROC ; COMDAT ; 282 : { 00000 55 push ebp 00001 8b ec mov ebp, esp 00003 83 ec 0c sub esp, 12 ; 0000000cH ; 283 : UINT cMinesSurround; ; 284 : ; 285 : if (board.Box[row][col].State != BS_INITIAL && ; 286 : board.Box[row][col].State != BS_DICEY) 00006 8b 45 08 mov eax, DWORD PTR _row$[ebp] 00009 6b c0 78 imul eax, 120 ; 00000078H 0000c 8b 4d 0c mov ecx, DWORD PTR _col$[ebp] 0000f 8b 94 88 b8 00 00 00 mov edx, DWORD PTR _board[eax+ecx*4+184] 00016 c1 e2 1a shl edx, 26 ; 0000001aH 00019 c1 fa 1b sar edx, 27 ; 0000001bH 0001c 74 25 je SHORT $LN11@StepBox 0001e 8b 45 08 mov eax, DWORD PTR _row$[ebp] 00021 6b c0 78 imul eax, 120 ; 00000078H 00024 8b 4d 0c mov ecx, DWORD PTR _col$[ebp] 00027 8b 94 88 b8 00 00 00 mov edx, DWORD PTR _board[eax+ecx*4+184] 0002e c1 e2 1a shl edx, 26 ; 0000001aH 00031 c1 fa 1b sar edx, 27 ; 0000001bH 00034 83 fa 02 cmp edx, 2 00037 74 0a je SHORT $LN11@StepBox ; 287 : { ; 288 : // previously stepped, must be safe, and no need to step second time ; 289 : return TRUE; 00039 b8 01 00 00 00 mov eax, 1 0003e e9 f9 00 00 00 jmp $LN12@StepBox $LN11@StepBox: ; 290 : } ; 291 : ; 292 : if (board.Box[row][col].fMine) 00043 8b 45 08 mov eax, DWORD PTR _row$[ebp] 00046 6b c0 78 imul eax, 120 ; 00000078H 00049 8b 4d 0c mov ecx, DWORD PTR _col$[ebp] 0004c 8b 94 88 b8 00 00 00 mov edx, DWORD PTR _board[eax+ecx*4+184] 00053 c1 e2 1f shl edx, 31 ; 0000001fH 00056 c1 fa 1f sar edx, 31 ; 0000001fH 00059 74 23 je SHORT $LN10@StepBox ; 293 : { ; 294 : // stepped on a mine! ; 295 : SetAndDispBoxState(row, col, BS_BLAST); 0005b 6a 03 push 3 0005d 8b 45 0c mov eax, DWORD PTR _col$[ebp] 00060 50 push eax 00061 8b 4d 08 mov ecx, DWORD PTR _row$[ebp] 00064 51 push ecx 00065 e8 00 00 00 00 call _SetAndDispBoxState 0006a 83 c4 0c add esp, 12 ; 0000000cH ; 296 : ; 297 : /** 制造一个BUG, 踩了雷程序就报错, 数组越界 ; 298 : * 程序编译选项不选优化, 否则这里制造的BUG就被干掉了 ; 299 : */ ; 300 : row = row / col;/**< 零列出现雷的时候报除零错 */ 0006d 8b 45 08 mov eax, DWORD PTR _row$[ebp] 00070 99 cdq 00071 f7 7d 0c idiv DWORD PTR _col$[ebp] 00074 89 45 08 mov DWORD PTR _row$[ebp], eax ; 301 : ; 302 : return FALSE; 00077 33 c0 xor eax, eax 00079 e9 be 00 00 00 jmp $LN12@StepBox $LN10@StepBox: ; 303 : } ; 304 :
离入口偏移为0x71的行找到了,果真是一句除零~.
00071 f7 7d 0c idiv DWORD PTR _col$[ebp]这句汇编对应的代码上就在他的上面: row = row / col;/**< 零列出现雷的时候报除零错 */
; 300 : row = row / col;/**< 零列出现雷的时候报除零错 */ 0006d 8b 45 08 mov eax, DWORD PTR _row$[ebp] 00070 99 cdq 00071 f7 7d 0c idiv DWORD PTR _col$[ebp] 00074 89 45 08 mov DWORD PTR _row$[ebp], eax
好, 已经找到报错的代码行了。
如果是在开发机上, 就直接可以在Release版不优化带调试符号的工程上单步了.
如果没有条件单步,在报错的代码上上,用调试日志记录关心的上下文,问题就能解决了。
这个扫雷游戏做的太坚固了,还没想到弄个别的非除零错之外的不明显的BUG来找找看.
以后在实际工程中再验证,那时就不会让drwtsn32这么容易的判断是什么具体错误~.
如果有崩溃对话框弹出, 连华生医生也不用了,那时也有转储信息. 可以用来了解客户那的操作系统信息, 当时运行的程序(有没有杀毒软件之类的程序).