65.9K
CodeProject 正在变化。 阅读更多。
Home

如何使用内存转储来跟踪 bug

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (46投票s)

2003年9月10日

6分钟阅读

viewsIcon

275943

如何使用内存转储来跟踪 bug

引言

我们的软件刚刚发布了1.0版本。我的客户经常遇到异常和蓝屏导致程序终止。大多数时候,我们在自己的系统上无法重现该 bug。因此,捕获和分析内存转储文件非常重要。本文将教您如何使用实际操作工具来捕获和读取内存转储文件。

实际操作的工具包括:

  • Dr. Watson,几乎所有 Windows 系统都包含它。
  • VC6 和 VC7 编译器,我们需要重新编译项目以生成 Map 文件和 COM 文件用于跟踪 bug。
  • Dumpchk 可以在您发送转储文件之前对其进行检查。
  • Windbg 和 kd 可以提供异常发生时点的详细信息。符号文件对于调试转储文件总是必需的。

详细说明

在重现 bug 之前,必须安装并运行 Dr. Watson 以创建 srwtsn32.log。我们还需要配置项目以生成调试信息、MAPFILE 和 COD 文件,然后重新编译项目。

配置 Dr. Watson

要启动 Dr. Watson 并将其设置为默认调试器,请转到命令行,键入

drwtsn32 –I 

如果您想更改设置,可以随时在命令行运行 drwtsn32。默认设置是可以的。然后,如果程序崩溃,Dr. Watson 会写入 drwtsn32.log 并将内存转储到 user.dmp

在 Visual C++ 环境中重新配置项目设置

您可以准备好源代码,以便 Dr. Watson 和内存转储可以使用。您需要按照以下三个简单步骤进行操作。

  1. 您需要为 .EXE 和/或 .DLL 文件的 Win32 Release 版本生成调试符号,在项目的设置配置中,对于链接器,打开“项目设置”窗口(在项目菜单中单击设置项),选择链接选项卡,勾选生成 Map 文件,并勾选生成调试信息。对于编译器,在 C/C++ 选项卡下的常规选项中,勾选调试信息:程序数据库。
  2. 创建并保留应用程序的 .MAP 和 .COD 中间文件的副本以供将来参考。在链接选项卡的项目选项编辑框中,添加开关“/mapinfo:exports”(开关 /mapinfo:exports 会使链接器描述二进制文件的导出函数。)添加开关“/mapinfo:lines”(开关 /mapinfo:lines 会在 mapfile 中添加行号)。.COD 文件包含源代码、汇编代码和内存地址的混合体,它将帮助您通过 .MAP 文件识别出的函数中的出错代码行。在 C/C++ 选项卡常规部分,在项目选项中添加开关“/FAcs”。
  3. 为您的 .EXE 和/或 .DLL 文件指定固定的加载基址十六进制地址。在链接选项卡的输出下,在基址中输入一个十六进制地址,例如 0x602f0000。

示例

使用 Visual C++ 应用程序向导创建一个名为“MapDemo”的 MFC Win32 可执行文件。在 CMapDemoApp 类的 InitInstance 函数中添加以下行。

 char * pch1 = 0; 
 char * pch2 = 0; 
 pch1 = (char *)malloc( 20 ); 
 pch2 = (char *)malloc( 20 ); 
 pch1 = pch2; free( pch1 ); 
 free( pch2 ); 

此代码最终会失败,因为应用程序将尝试两次释放同一块内存。运行程序,它会崩溃。打开文件 drwtsn32.log

*----> Module List <----*
(0000000000400000 - 000000000040b000: 
    C:\WORK\CODEPROJ\MapDemo\Debug\MapDemo.exe
(0000000010200000 - 000000001026c000: C:\WINDOWS\System32\MSVCRTD.dll
(000000005f400000 - 000000005f4e5000: C:\WINDOWS\System32\MFC42D.DLL
(00000000605d0000 - 00000000605d8000: C:\WINDOWS\System32\mslbui.dll
(0000000070a70000 - 0000000070ad4000: C:\WINDOWS\system32\SHLWAPI.dll
(0000000074720000 - 0000000074764000: C:\WINDOWS\System32\MSCTF.dll
(0000000077120000 - 00000000771ab000: C:\WINDOWS\system32\OLEAUT32.DLL
(00000000771b0000 - 00000000772d1000: C:\WINDOWS\system32\OLE32.DLL
(0000000077c00000 - 0000000077c07000: C:\WINDOWS\system32\VERSION.dll
(0000000077c10000 - 0000000077c63000: C:\WINDOWS\system32\msvcrt.dll
(0000000077c70000 - 0000000077cb0000: C:\WINDOWS\system32\GDI32.dll
(0000000077d40000 - 0000000077dc6000: C:\WINDOWS\system32\USER32.dll
(0000000077dd0000 - 0000000077e5d000: C:\WINDOWS\system32\ADVAPI32.dll
(0000000077e60000 - 0000000077f46000: C:\WINDOWS\system32\kernel32.dll
(0000000077f50000 - 0000000077ff7000: C:\WINDOWS\System32\ntdll.dll
(0000000078000000 - 000000007807f000: C:\WINDOWS\system32\RPCRT4.dll 
*----> State Dump for Thread Id 0x3274 <----* 
eax=00000001 ebx=7ffdf000 ecx=10261558 edx=00010000 
esi=0012febc edi=7ffdf000
eip=10213638 esp=0012fe5c ebp=0012fe6c iopl=0         
nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000            
efl=00000246 
function: MSVCRTD!_free_dbg_lk
        10213616 7521             jnz     MSVCRTD!_free_dbg_lk+0xc9 
                                          (10213639)
        10213618 6814732510       push    0x10257314
        1021361d 6a00             push    0x0
        1021361f 6814040000       push    0x414
        10213624 68f4712510       push    0x102571f4
        10213629 6a02             push    0x2
        1021362b e810180000       call    MSVCRTD!_CrtDbgReport (10214e40)
        10213630 83c414           add     esp,0x14
        10213633 83f801           cmp     eax,0x1
        10213636 7501             jnz     MSVCRTD!_free_dbg_lk+0xc9 
                                           (10213639)
FAULT ->;10213638 cc               int     3
        10213639 33c0             xor     eax,eax
        1021363b 85c0             test    eax,eax
        1021363d 75c9             jnz     MSVCRTD!_free_dbg_lk+0x98 
                                           (10213608)
        1021363f 8b4d08           mov     ecx,[ebp+0x8]
        10213642 83e920           sub     ecx,0x20
        10213645 894dfc           mov     [ebp-0x4],ecx
        10213648 8b55fc           mov     edx,[ebp-0x4]
        1021364b 8b4214           mov     eax,[edx+0x14]
        1021364e 25ffff0000       and     eax,0xffff
        10213653 83f804           cmp     eax,0x4 
*----> Stack Back Trace <----*
*** WARNING: Unable to verify checksum for 
  C:\WORK\CODEPROJ\MapDemo\Debug\MapDemo.exe
*** ERROR: Symbol file could not be found.  Defaulted to 
  export symbols for C:\WINDOWS\system32\kernel32.dll - 
WARNING: Stack unwind information not available. Following 
  frames may be wrong.
ChildEBP RetAddr  Args to Child              
0012fe6c 10213541 00324708 00000001 7ffdf000 MSVCRTD!_free_dbg_lk+0xc8
0012fea0 102134ce 00324708 00000001 0012fee8 MSVCRTD!_free_dbg+0x41
0012feb0 00401360 00324708 7ffde000 00324238 MSVCRTD!free+0xe
0012fee8 5f4335c3 7ffdf000 7ffde000 7ffdf000 
    MapDemo!CMapDemoApp__InitInstance+0x132
0012ff08 00402248 00400000 00000000 001423ac MFC42D!AfxWinMain+0x83
0012ff20 00402153 00400000 00000000 001423ac MapDemo!WinMain+0x18
0012ffc0 77e814c7 7ffdf000 7ffde000 7ffdf000 
    MapDemo!WinMainCRTStartup+0x1b3
0012fff0 00000000 00401fa0 00000000 78746341 
    kernel32!GetCurrentDirectoryW+0x44 

由于代码在内存释放函数“_free_dbg_lk”中出错,该函数由 Initinstance() 函数调用,因此我们搜索堆栈跟踪部分,在第四行,它表明子函数是由 InitInstance 函数调用,偏移量为 0x132。

因此,查看 .COD 中间文件“MapDemo.COD”,搜索模块“InitInstance()”,模块开头的偏移地址为 0x00ae,加上地址(0x132+0xae = 0x01e0),我们就知道程序在源文件“MapDemo.cpp”的第 79 行崩溃了。


另一个示例 - 如何跟踪 DLL 文件中的故障(进程内和进程外 DLL 文件)。

使用 Visual Studio 中的应用程序向导生成一个名为 MapDemoDll 的 DLL 库项目,创建一个导出函数 DllSquareRoot(),并在该函数中放置一个故障代码:

 char* pChar = NULL;
 *pChar = 'a'; // This is fault line 70 

程序将抛出访问无效内存地址(0x0000)的异常。使用应用程序向导生成一个调用 MapDemoDll.dllDllSquareRoot 函数的客户端程序。在运行带有 DLL 库的可执行程序之前,我们需要设置 MapDemoDll 项目的设置。生成 MAP、COD 文件,指定加载基址为 0x60f20000。编译项目并运行程序。

程序崩溃,屏幕显示类似“DemoDllClient.exe 已生成错误,将被 Windows 关闭。您需要重新启动程序。”的信息。以下是 Dr. Watson 生成的 drwsnt32.log

State Dump for Thread Id 0x7b0 
eax=00000000 ebx=005e4b68 ecx=00135dd0 edx=60f35918 
esi=0012f27c edi=0012f26c
eip=60f21320 esp=0012f204 ebp=0012f26c iopl=0         
nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000           
efl=00000246 
function: DllSquareRoot
        60f212f9 8d7da4           lea     edi,[ebp+0xa4]         
                ss:00c8c83e=????????
        60f212fc b917000000       mov     ecx,0x17
        60f21301 b8cccccccc       mov     eax,0xcccccccc
        60f21306 f3ab             rep     stosd                  
                es:0012f26c=0012f2cc
        60f21308 e8f3020000       call    AfxGetStaticModuleState 
                (60f21600)
        60f2130d 50               push    eax
        60f2130e 8d4df8           lea     ecx,[ebp+0xf8]         
                ss:00c8c83e=????????
        60f21311 e8a4010000  call 
                AFX_MAINTAIN_STATE2::AFX_MAINTAIN_STATE2 (60f214ba)
        60f21316 c745f400000000   mov   dword ptr [ebp+0xf4],0x0 
                ss:00c8c83e=????????
        60f2131d 8b45f4           mov     eax,[ebp+0xf4]         
                ss:00c8c83e=????????
FAULT ->;60f21320 c60078           mov     byte ptr [eax],0x78          
                ds:00000000=??
        60f21323 dd4508          fld qword ptr [ebp+0x8] 
                ss:00c8c83e=????????????????
        60f21326 dc1d4841f360                            
                ds:60f34148=0000000000000000
                                  fcomp   qword ptr [_real (60f34148)]
        60f2132c dfe0             fstsw
        60f2132e f6c401           test    ah,0x1
        60f21331 7520             jnz     _enc$textbss$begin+0x5f43 
               (60f29e53)
        60f21333 8b4d0c           mov     ecx,[ebp+0xc]          
               ss:00c8c83e=????????
        60f21336 51               push    ecx
        60f21337 8b5508           mov     edx,[ebp+0x8]          
               ss:00c8c83e=????????
        60f2133a 52               push    edx
        60f2133b e848060000       call    sqrt (60f21988)
        60f21340 83c408           add     esp,0x8 

程序在地址 0x60f21230 处崩溃,这是 mapfile 的一部分:

Address Publics by Value Rva+Base Lib:Object 
 0001:00000050 ?_GetBaseMessageMap@CMapDemoDllApp@@KGPBUAFX_MSGMAP@@XZ 
        60f21050 f MapDemoDll.obj
 0001:00000080 ?GetMessageMap@CMapDemoDllApp@@MBEPBUAFX_MSGMAP@@XZ 
        60f21080 f MapDemoDll.obj
 0001:000000c0 ??0CMapDemoDllApp@@QAE@XZ 60f210c0 f MapDemoDll.obj
 0001:00000120 ??_GCMapDemoDllApp@@UAEPAXI@Z 60f21120 f i MapDemoDll.obj
 0001:00000120 ??_ECMapDemoDllApp@@UAEPAXI@Z 60f21120 f i MapDemoDll.obj
 0001:00000190 ??1CMapDemoDllApp@@UAE@XZ 60f21190 f i MapDemoDll.obj
 0001:000002f0 _DllSquareRoot 60f212f0 f MapDemoDll.obj
 0001:000003b2 ?WinHelpA@CWinApp@@UAEXKI@Z 60f213b2 
      f mfc42d:MFC42D.DLL
 0001:000003b8 ?OnDDECommand@CWinApp@@UAEHPAD@Z 60f213b8 
      f mfc42d:MFC42D.DLL
 0001:000003be ?DoWaitCursor@CWinApp@@UAEXH@Z 60f213be 
      f mfc42d:MFC42D.DLL
 0001:000003c4 ?DoMessageBox@CWinApp@@UAEHPBDII@Z 60f213c4 
      f mfc42d:MFC42D.DLL
 0001:000003ca ?SaveAllModified@CWinApp@@UAEHXZ 60f213ca 
      f mfc42d:MFC42D.DLL
 0001:000003d0 ?InitApplication@CWinApp@@UAEHXZ 60f213d0 
      f mfc42d:MFC42D.DLL 

小于崩溃地址 0x60f21320 的最大函数地址是 0x60f212f0,它属于 MapDemoDll 对象中的 DllSquareRoot 函数。我们也可以通过以下计算:crash_address - preferred_load_address - 0x1000。为什么我们需要减去 0x1000,因为二进制文件的第一部分是可执行文件(PE),长度为 0x1000 字节。因此,计算为:0x60f21320 – 0x60f20000 – 0x1000 = 0x320。

Line numbers for .\Debug\MapDemoDll.obj 
(D:\work\MapDemoDll\MapDemoDll.cpp) segment .text 
    44 0001:00000050    44 0001:00000080    
    55 0001:000000c0    58 0001:000000f0
    63 0001:00000220    66 0001:000002f0    
    67 0001:00000308    69 0001:00000316
    70 0001:0000031d    72 0001:00000323    
    73 0001:00000333    74 0001:00000353
    75 0001:00000361    76 0001:0000037a 

以上是 MAPFILE 的信息部分,例如:44 0001:00000050,第一个数字是行号,第二个数字是从该行发生的代码段开始的偏移量。因此,我们将知道程序在 MapDemoDll.cpp 的第 70 行崩溃。该行试图将一个数字写入无效地址。

如何读取和调试内存转储文件

  1. 获取符号表或符号服务器地址。为了准备调试转储文件,您应该获取 Microsoft 符号服务器地址或从 Microsoft 下载符号(http://www.microsoft.com/whdc/ddk/debugging/symbolpkg.mspx#Windows%20symbol%20packages)。
  2. 下载 Windows 调试工具。这些调试工具可以读取 Windows 内存转储并模拟原始内存环境。
  3. 需要从原始安装 CD 安装支持工具。在使用内存转储进行调试之前,我们需要使用 dumpchk.exe 检查转储文件是否损坏,该工具位于 Windows 安装 CD 中。转到命令行并输入:Dumpchk -a [dump file]
  4. 如果内存转储文件通过检查,现在我们可以使用 windbg 或 kd 来调试内存转储。安装完成后,启动命令提示符,切换到安装了调试工具的路径。使用以下命令之一将转储文件加载到调试器中。Windbg.exe 是一个基于 Windows 的调试器,kd.exe 是一个命令行调试器。
windbg -y SymbolPath -i ImagePath -z DumpFilePath
kd -y SymbolPath -i ImagePath -z DumpFilePath

SymbolPath 是符号的路径,它是下载了符号文件的本地路径,或者是服务器路径(URL)。ImagePath 是 Windows 映像文件的路径,这些文件包含在 Windows 安装 CD-ROM 的 /I386 文件夹中。DumpPath 是您正在检查的转储文件的路径和文件名。

示例

kd -y SRV*f:\symbols*http://msdl.microsoft.com/download/symbols 
   -i e:\i386 -z f:\dumps\minidump.dmp
kd -y f:\symbols -i e:\i386 -z "f:\my debug files\minidump.dmp"
windbg -y f:\symbols -i e:\i386 -z "f:\dumps\minidump.dmp"

这两个调试器都接受用于从转储文件中收集信息的命令,例如:

The !analyze -show command displays the Stop error code 
    (also known as the bug check code) and its parameters. 
The !analyze -v command displays verbose output. 
The !drivers command displays a list of the drivers that 
    were loaded on the computer when the problem occurred.
© . All rights reserved.