利用MAP文件精确定位代码中出错的代码行

    技术2024-12-03  18

     

    http://blog.csdn.net/Eric_Jo/archive/2009/04/14/4072699.aspx

     

    程序 ,我 最担心 到的事情是什 ?是内存泄漏?是界面不好看? …… 啦!我相信我的看法是不会有人反 —— 那就是,程序 生了崩 程序 行了非法操作,即将 关闭 与你的 件供 系。 ,呵呵,  M$  名言 ,恐怕就是程序 最担心 到的 西了。有的 候,自己的程序在自己的机器上运行得好好的,但是到了 人的机器上就崩 了;有 自己在 写和 测试 程中就莫名其妙地遇到了非法操作,但是却无法确定到底是源代 中的哪行引起的 …… 是不是很痛苦呢?不要 ,本文可以帮助你走出 这种 困境,甚至你从此之后可以自豪地要求用 把崩 地址告 你,然后你就可以精确地定位到源代 中出 的那行了。(很神奇吧?呵呵。) 首先我必 须强调 的是,本方法可以在目前市面上任意一款 编译 器上面使用。但是我只熟悉  M$   VC   MASM  ,因此后面的部分只介 如何在 两个 编译 器中 实现 请读 者自行融会 通,掌握在 编译 器上使用的方法。

     

     

    首先必 生成程序的  MAP  文件。什  MAP  文件? 简单  MAP  文件是程序的全局符号、源文件和代 行号信息的唯一的文本表示方法,它可以在任何地方、任何 候使用,不需要有 外的程序 行支持。而且, 是唯一能找出程序崩 的地方的救星。 们应该 如何生成 MAP 文件呢?在 VC 中,我 可以按下 Alt+F7 ,打 Project Settings” 选项页 选择 C/C++ 选项 卡,并在最下面的 Project Options 里面 入: /Zd ,然后要 选择 Link 选项 卡, 中“ Generate mapfile” 复选 框,并在最下面的 Project Options 里面 入: /mapinfo:lines ,表示生成 MAP 文件 ,加入行信息。最后按下 F7 编译 生成 EXE 行文件和 MAP 文件,此 可以在工程的 Debug 下找到 刚刚 生成的 MAP 文件,文件名 “工程名 .map”  MASM  中,我 编译 接参数,我通常是 这样 做的: rc %1.rc ml /c /coff /Zd %1.asm link /subsystem:windows /mapinfo:exports /mapinfo:lines /map:%1.map %1.obj %1.res  把它保存成  makem.bat  ,就可以在命令行  makem filename  编译 生成  EXE  行文件和  MAP  文件了。 在此我先解 一下加入的参数的含 /Zd               表示在 编译 候生成行信息 /map[:filename]   表示生成  MAP  文件的路径和文件名 /mapinfo:lines    表示生成  MAP  文件 ,加入行信息 /mapinfo:exports  表示生成  MAP  文件 ,加入  exported functions  (如果生成的是  DLL  文件, 选项 就要加上)   OK ,通 上面的 步骤 ,我 得到了  MAP  文件,那 们该 如何利用它呢? 简单 例入手, 你的 VC  ,新建 这样 一个文件: 01  //**************************************************************** 02  // 程序名称:演示如何通 地址找出源代 的出 03  // 作者:Eric 04  // 日期:2003-2-7 05  // :Eric的空间

    06  // 本程序会 0 错误 ,以至于会 非法操作 对话 框。 07  //“ 0 错误 只会在  Debug  版本下 生,本程序 了演示而尽量 化。 08  // 注意事 :如欲 转载 保持本程序的完整,并注明: 09  // 转载 “亿人博览 10  //**************************************************************** 11   12  void Crash(void) 13  { 14      int i = 1; 15      int j = 0; 16      i /= j; 17  } 18   19  void main(void) 20  { 21      Crash(); 22  }  然本程序有0 错误 ,在 Debug  方式下 编译 ,运行 肯定会 非法操作 好, 运行它,果然, 非法操作 对话 框出 了, 这时 详细 信息 记录 生崩 的地址 —— 在我的机器上是  0x0040104a  再看看它的 MAP  文件:(由于文件内容太 ,中 没用的部分我 行了省略) CrashDemo Timestamp is 3e430a76 (Fri Feb 07 09:23:02 2003) Preferred load address is 00400000 Start         Length     Name                   Class 0001:00000000 0000de04H .text                   CODE 0001:0000de04 0001000cH .textbss                CODE 0002:00000000 00001346H .rdata                  DATA 0002:00001346 00000000H .edata                  DATA 0003:00000000 00000104H .CRT$XCA                DATA 0003:00000104 00000104H .CRT$XCZ                DATA 0003:00000208 00000104H .CRT$XIA                DATA 0003:0000030c 00000109H .CRT$XIC                DATA 0003:00000418 00000104H .CRT$XIZ                DATA 0003:0000051c 00000104H .CRT$XPA                DATA 0003:00000620 00000104H .CRT$XPX                DATA 0003:00000724 00000104H .CRT$XPZ                DATA 0003:00000828 00000104H .CRT$XTA                DATA 0003:0000092c 00000104H .CRT$XTZ                DATA 0003:00000a30 00000b93H .data                   DATA 0003:000015c4 00001974H .bss                    DATA 0004:00000000 00000014H .idata$2                DATA 0004:00000014 00000014H .idata$3                DATA 0004:00000028 00000110H .idata$4                DATA 0004:00000138 00000110H .idata$5                DATA 0004:00000248 000004afH .idata$6                DATA   Address         Publics by Value              Rva+Base     Lib:Object 0001:00000020       ?Crash@@YAXXZ              00401020 f   CrashDemo.obj 0001:00000070       _main                      00401070 f   CrashDemo.obj 0004:00000000       __IMPORT_DESCRIPTOR_KERNEL32 00424000     kernel32:KERNEL32.dll 0004:00000014       __NULL_IMPORT_DESCRIPTOR   00424014     kernel32:KERNEL32.dll 0004:00000138       __imp__GetCommandLineA@0   00424138     kernel32:KERNEL32.dll 0004:0000013c       __imp__GetVersion@0        0042413c     kernel32:KERNEL32.dll 0004:00000140       __imp__ExitProcess@4       00424140     kernel32:KERNEL32.dll 0004:00000144       __imp__DebugBreak@0        00424144     kernel32:KERNEL32.dll 0004:00000148       __imp__GetStdHandle@4      00424148     kernel32:KERNEL32.dll 0004:0000014c       __imp__WriteFile@20        0042414c     kernel32:KERNEL32.dll 0004:00000150       __imp__InterlockedDecrement@4 00424150     kernel32:KERNEL32.dll 0004:00000154       __imp__OutputDebugStringA@4 00424154     kernel32:KERNEL32.dll 0004:00000158       __imp__GetProcAddress@8    00424158     kernel32:KERNEL32.dll 0004:0000015c       __imp__LoadLibraryA@4      0042415c     kernel32:KERNEL32.dll 0004:00000160       __imp__InterlockedIncrement@4 00424160     kernel32:KERNEL32.dll 0004:00000164       __imp__GetModuleFileNameA@12 00424164     kernel32:KERNEL32.dll 0004:00000168       __imp__TerminateProcess@8  00424168     kernel32:KERNEL32.dll 0004:0000016c       __imp__GetCurrentProcess@0 0042416c     kernel32:KERNEL32.dll 0004:00000170       __imp__UnhandledExceptionFilter@4 00424170     kernel32:KERNEL32.dll 0004:00000174       __imp__FreeEnvironmentStringsA@4 00424174     kernel32:KERNEL32.dll 0004:00000178       __imp__FreeEnvironmentStringsW@4 00424178     kernel32:KERNEL32.dll 0004:0000017c       __imp__WideCharToMultiByte@32 0042417c     kernel32:KERNEL32.dll 0004:00000180       __imp__GetEnvironmentStrings@0 00424180     kernel32:KERNEL32.dll 0004:00000184       __imp__GetEnvironmentStringsW@0 00424184     kernel32:KERNEL32.dll 0004:00000188       __imp__SetHandleCount@4    00424188     kernel32:KERNEL32.dll 0004:0000018c       __imp__GetFileType@4       0042418c     kernel32:KERNEL32.dll 0004:00000190       __imp__GetStartupInfoA@4   00424190     kernel32:KERNEL32.dll 0004:00000194       __imp__HeapDestroy@4       00424194     kernel32:KERNEL32.dll 0004:00000198       __imp__HeapCreate@12       00424198     kernel32:KERNEL32.dll 0004:0000019c       __imp__HeapFree@12         0042419c     kernel32:KERNEL32.dll 0004:000001a0       __imp__VirtualFree@12      004241a0     kernel32:KERNEL32.dll 0004:000001a4       __imp__RtlUnwind@16        004241a4     kernel32:KERNEL32.dll 0004:000001a8       __imp__GetLastError@0      004241a8     kernel32:KERNEL32.dll 0004:000001ac       __imp__SetConsoleCtrlHandler@8 004241ac     kernel32:KERNEL32.dll 0004:000001b0       __imp__IsBadWritePtr@8     004241b0     kernel32:KERNEL32.dll 0004:000001b4       __imp__IsBadReadPtr@8      004241b4     kernel32:KERNEL32.dll 0004:000001b8       __imp__HeapValidate@12     004241b8     kernel32:KERNEL32.dll 0004:000001bc       __imp__GetCPInfo@8         004241bc     kernel32:KERNEL32.dll 0004:000001c0       __imp__GetACP@0            004241c0     kernel32:KERNEL32.dll 0004:000001c4       __imp__GetOEMCP@0          004241c4     kernel32:KERNEL32.dll 0004:000001c8       __imp__HeapAlloc@12        004241c8     kernel32:KERNEL32.dll 0004:000001cc       __imp__VirtualAlloc@16     004241cc     kernel32:KERNEL32.dll 0004:000001d0       __imp__HeapReAlloc@16      004241d0     kernel32:KERNEL32.dll 0004:000001d4       __imp__MultiByteToWideChar@24 004241d4     kernel32:KERNEL32.dll 0004:000001d8       __imp__LCMapStringA@24     004241d8     kernel32:KERNEL32.dll 0004:000001dc       __imp__LCMapStringW@24     004241dc     kernel32:KERNEL32.dll 0004:000001e0       __imp__GetStringTypeA@20   004241e0     kernel32:KERNEL32.dll 0004:000001e4       __imp__GetStringTypeW@16   004241e4     kernel32:KERNEL32.dll 0004:000001e8       __imp__SetFilePointer@16   004241e8     kernel32:KERNEL32.dll 0004:000001ec       __imp__SetStdHandle@8      004241ec     kernel32:KERNEL32.dll 0004:000001f0       __imp__FlushFileBuffers@4  004241f0     kernel32:KERNEL32.dll 0004:000001f4       __imp__CloseHandle@4       004241f4     kernel32:KERNEL32.dll 0004:000001f8       /177KERNEL32_NULL_THUNK_DATA 004241f8     kernel32:KERNEL32.dll entry point at        0001:000000f0 Line numbers for ./Debug/CrashDemo.obj(d:/msdev/myprojects/crashdemo/crashdemo.cpp) segment .text     13 0001:00000020    14 0001:00000038    15 0001:0000003f    16 0001:00000046     17 0001:00000050    20 0001:00000070    21 0001:00000088    22 0001:0000008d  如果仔 细浏览  Rva+Base  这栏 ,你会 发现 第一个比崩 地址 0x0040104a  大的函数地址是 0x00401070  ,所以在 0x00401070  个地址之前的那个入口就是 生崩 的函数,也就是 行: 0001:00000020       ?Crash@@YAXXZ              00401020 f   CrashDemo.obj  因此, 生崩 的函数就是 ?Crash@@YAXXZ  ,所有以 开头 的函数名称都是 C++  的名称。 在我 的源程序中,也就是  Crash()  个子函数。 OK 在我 们轻 而易 地便知道了 生崩 的函数名称,你是不是很 兴奋 呢?呵呵,先 忙,接下来,更 害的招数要出 了。 注意  MAP  文件的最后部分 —— 行信息( Line numbers information ),它是以 这样 的形式 示的: 13 0001:00000020  第一个数字代表在源代 中的代 行号,第二个数是 行在所属的代 段中的偏移量。 如果要 找代 行号,需要使用下面的公式做一些十六 制的减法运算: 行偏移  =  地址( Crash Address  -  基地址( ImageBase Address  - 0x1000  这样 做呢? 心的朋友可能会留意到  Rva+Base  这栏 了,我 得到的崩 地址都是由   偏移地址( Rva 基地址( Base   得来的,所以在 算行号的 候要把基地址减去,一般情况下,基地址的  0x00400000  。另外,由于一般的  PE  文件的代 段都是从  0x1000  偏移 始的,所以也必 减去  0x1000  好了,明白了 点,我 就可以来 行小学减法 算了: 行偏移  = 0x0040104a - 0x00400000 - 0x1000 = 0x4a  如果 浏览  MAP  文件的代 行信息,会看到不超 过计 果,但却最接近的数是  CrashDemo.cpp  文件中的: 16 0001:00000046  也就是在源代 中的第  16  行, 来看看源代 : 16      i /= j;  哈!!!果然就是第  16  行啊! 兴奋吗 ?我也一   :) 方法已 完了,从今以后,我 就可以精确地定位到源代 中的崩 行,而且只要 编译 器可以生成  MAP  文件(包括  VC MASM VB BCB Delphi…… ),本方法都是适用的。我 们时 常抱怨  M$  品如何如何差,但其  M$  是有意无意 提供了很多有价 的信息 的,只是我 往往不懂得怎 利用而已 …… 相信 这样 一来,你就可以更 从容地面 非法操作 提示了。你甚至可以要求用 提供崩 的地址,然后就可以坐在家中舒舒服服地找到出 的那行,并 行修正。 是不是很爽呢?   :) 方法已经介绍完了,从今以后,我们就可以精确地定位到源代码中的崩溃行,而且只要编译器可以生成  MAP  文件,无论在 WIN 平台还是 UNIX 平台,本方法都是适用的。   本文我们只是列举了一个非常简单的 0 异常 例子,使用 MAP 文件的效力或许还不十分明显。但相信在我们的大型应用系统调试中,使用 MAP 文件的辅助方法来快速定位发生程序崩溃的函数以及代码行,将会为我们的程序调试工作节省大量时间和精力,提高我们的调试质量。我们甚至可以要求远地用户直接提供程序崩溃的地址,然后就可以在自己机器上利用 MAP 文件静态地找到出错的那行,并在程序中进行相应修正了。

    最新回复(0)