使用MAP文件定位程序Crash

介绍


开发优秀的程序是一个重要事。但是当一个用户向你报告你的程序有一个Crash,你知道最好的在新增其它Feature之前先修改这个问题。如果你足够的幸运的话,同时用户提供了crash地址信息。解决这个问题有很多的方法。但是,你如何使用这个crash地址来定位程序什么地方出错了。


创建MAP文件
第一步,你需要一个MAP文件。假如你没有,这几乎不可能使用这个crash地址找到应用程序crash位置。首先,我将用一个例子来描述如何创建一个有用的MAP文件。为了这个,我创建了一个新工程叫MAPFILE。你可以用同样的方法来设置你的自己的工程。我用VC6创建了一个Win32应用程序,为了解释MAP文件,选择了“Hello World!”程序。
一旦创建工程,我们先要更改release版本工程设置。在C/C++标题中选择"Line Number Only“。

1142669550414_6855.jpg

为了生成调试信息。很多人忘记这个,但是你假如需要一个好的MAP文件就需要设置这个选项。这不会影响你的release版本。不一步是修改LINK标题中的选项,在这里你需要设置"Generate mapfile"选项。同时,在Project选项中输入/MAPINFO:LINES和/MAPINFO:EXPORTS。

1142669601160_2856.jpg

现在,你可以编译和连接你的程序了。编译完成后,你会在你的intermediate目录找到一个MAP文件(就是EXE目录)


分析MAP文件
做完这个无趣的工作,现在到了关键地步:如何分析这个MAP文件。我们将用这个做一个crash例子。首先要做的就是:如何把你的应用程序crash掉。我在InitInstance()函数结尾地方加入两行代码。


char* pEmpty = NULL;
*pEmpty = 'x';
 // 这里是第119行


我相信你还有很多方法可以使你的程序crash掉。重新编译你的程序。运行程序,crash同时会弹出一个消息对话框:'The instruction at "0x004011a1" referenced memory at "0x00000000". The memory could not be "Written".' 。
现在,你用记事本或其它工具打开这个MAP文件。你的MAP文件看起来象下面这样:
MAP文件的开头包含模块的名字,工程连接的时间,和首选装载的地址(大概是0x00400000除非你正在使用DLL)。接下来就是片段信息,这些信息显示不同OBJ和LIB文件的连接信息。

MAPFILE

 Timestamp is 3df6394d (Tue Dec 10 19:58:21 2002)

 Preferred load address is 00400000

 Start         Length     Name                   Class
 0001:00000000 000038feH .text                   CODE
 0002:00000000 000000f4H .idata$5                DATA
 0002:000000f8 00000394H .rdata                  DATA
 0002:0000048c 00000028H .idata$2                DATA
 0002:000004b4 00000014H .idata$3                DATA
 0002:000004c8 000000f4H .idata$4                DATA
 0002:000005bc 0000040aH .idata$6                DATA
 0002:000009c6 00000000H .edata                  DATA
 0003:00000000 00000004H .CRT$XCA                DATA
 0003:00000004 00000004H .CRT$XCZ                DATA
 0003:00000008 00000004H .CRT$XIA                DATA
 0003:0000000c 00000004H .CRT$XIC                DATA
 0003:00000010 00000004H .CRT$XIZ                DATA
 0003:00000014 00000004H .CRT$XPA                DATA
 0003:00000018 00000004H .CRT$XPZ                DATA
 0003:0000001c 00000004H .CRT$XTA                DATA
 0003:00000020 00000004H .CRT$XTZ                DATA
 0003:00000030 00002490H .data                   DATA
 0003:000024c0 000005fcH .bss                    DATA
 0004:00000000 00000250H .rsrc$01                DATA
 0004:00000250 00000720H .rsrc$02                DATA


在片段信息后面是一些公共函数信息。注意这个”public“部分。假如你有以static声名的C函数,它们不会显示在这个MAP文件中。幸运的是,这个行数仍然反映这些static函数。公共函数信息最重要的部分是函数名和Rva+Base栏上的信息。Rva+Base栏表明了函数的起始地址。

Address         Publics by Value              Rva+Base     Lib:Object

 0001:00000000       _WinMain@16                00401000 f   MAPFILE.obj
 0001:000000c0      
?MyRegisterClass@@YAGPAUHINSTANCE__@@@Z 004010c0 f   MAPFILE.obj
 0001:00000150      
?InitInstance@@YAHPAUHINSTANCE__@@H@Z 00401150 f   MAPFILE.obj
 0001:000001b0      
?WndProc@@YGJPAUHWND__@@IIJ@Z 004011b0 f   MAPFILE.obj
 0001:00000310      
?About@@YGJPAUHWND__@@IIJ@Z 00401310 f   MAPFILE.obj
 0001:00000350       _WinMainCRTStartup         00401350 f   LIBC:wincrt0.obj
 0001:00000446       __amsg_exit                00401446 f   LIBC:wincrt0.obj
 0001:0000048f       __cinit                    0040148f f   LIBC:crt0dat.obj
 0001:000004bc       _exit                      004014bc f   LIBC:crt0dat.obj
 0001:000004cd       __exit                     004014cd f   LIBC:crt0dat.obj
 0001:00000591       __XcptFilter               00401591 f   LIBC:winxfltr.obj
 0001:00000715       __wincmdln                 00401715 f   LIBC:wincmdln.obj
 //SNIPPED FOR BETTER READING
 0003:00002ab4       __FPinit                   00408ab4     <common>
 0003:00002ab8       __acmdln                   00408ab8     <common>

 entry point at        0001:00000350

 Static symbols

 0001:000035d0       LeadUp1                    004045d0 f   LIBC:memmove.obj
 0001:000035fc       LeadUp2                    004045fc f   LIBC:memmove.obj
  //SNIPPED FOR BETTER READING
 0001:00000577       __initterm                 00401577 f   LIBC:crt0dat.obj
 0001:0000046b       _fast_error_exit           0040146b f   LIBC:wincrt0.obj


接着公共函数信息后面的是行信息(你能够看到这个就是因为你在link标题中使用/MAPINFO:LINES,并且在C/C++标题中选择了"Line numbers")。在这个之后,是你工程的导出函数信息如何你导出了一些函数(如果你在Link标题中包含了/MAPINFO:EXPORTS)

Line numbers for ./Release/MAPFILE.obj(F:/MAPFILE/MAPFILE.cpp) segment .text

    24 0001:00000000    30 0001:00000004    31 0001:0000001b    32 0001:00000027
    35 0001:0000002d    53 0001:00000041    40 0001:00000047    43 0001:00000050
    45 0001:00000077    47 0001:00000088    48 0001:0000008f    52 0001:000000ad
    53 0001:000000b3    71 0001:000000c0    80 0001:000000c3    81 0001:000000c8
    82 0001:000000ff    86 0001:00000114    88 0001:00000135    89 0001:00000145
   102 0001:00000150   108 0001:00000155   110 0001:00000188   122 0001:0000018d
   115 0001:0000018e   116 0001:0000019a   119 0001:000001a1   121 0001:000001a8
   122 0001:000001ae   135 0001:000001b0   143 0001:000001cc   172 0001:000001ee
   175 0001:0000020d   149 0001:00000216   157 0001:0000022c   175 0001:00000248
   154 0001:00000251   174 0001:0000025f   175 0001:00000261   151 0001:0000026a
   174 0001:00000287   175 0001:00000289   161 0001:00000294   164 0001:000002a8
   165 0001:000002b6   166 0001:000002d8   174 0001:000002e7   175 0001:000002e9
   169 0001:000002f2   174 0001:000002fa   175 0001:000002fc   179 0001:00000310
   186 0001:0000031e   193 0001:0000032e   194 0001:00000330   188 0001:00000333
   183 0001:00000344   194 0001:00000349

 

现在我们来查找crash的位置。首先,我们先找到引起crash的函数。查看"Rva+Base" 栏,并找到比crash地址大的函数。MAP文件中在这个函数前面的入口就是发生crash的函数。在我们例子中,crash地址是0x004011a1。在0x00401150和0x004011b0之间,因此我们知道发生crash函数是?InitInstance@@YAHPAUHINSTANCE__@@。H@Z函数名开头是一个C++修饰名“?”号。如果要转化这个名字,把这个名字做为命令行参数传给Platform SDK的UNDNAME.EXE (在bin目录)。很多时候你并不需要这个,一看就可以指出这个函数正确的名字(在这里是:InitInstance()函数)。
最重要部分是问题的定位。但是我们可以得到更精确的位置:我们能够找到是那一行代码发生crash问题!在这里我们需要一些基本的十六进制算术知识,在这个世界上人们还不能离开数学:现在是时候用它了。

第一步采用下面的计算公式:crash_address - preferred_load_address - 0x1000这是代码段开头的偏移地址,因此我们需要做这个计算。得到的值就是逻辑首选的装载地址,但是为什么我们需要减去这个0x1000?由于这个crash地址是从代码段开始的偏移量,但是生成的二进制文件的第一部分并不是代码段!第一部分的二进制是Portable Executable (PE)信息头,大小就是0x1000字节长。神秘的解决方案。在我们的例子中,它结果就是:0x004011a1 - 0x00400000 - 0x1000 = 0x1a1

现在是时候研究MAP文件中的行信息部分。这些行象这个: 30 0001:00000004。第一个数字是行数,第二个数字是一个偏移量,这个偏移量就是当前代码行到代码段开始的偏移量。假如我们需要我们的行数,我们必须对这个函数做同样的事情:确定我们刚才计算更大的偏移量。这个crash发生在前面的入口。在我们的例子中:0x1a1比0x1a8少。因此我们crash发生在MAPFILE.CPP中第119行。

深入分析MAP文件
每个release版本工程都有自己的MAP文件。在发布的EXE文件包含MAP文件并不是一件坏主意。这样,你就能够拥有这个EXE正确版本的MAP文件。你能够在你的机器保留每个EXE文件的MAP文件,但是我们也知道这样做可能在后来带来一些问题。这个MAP文件没有包含任何信息你不想用户去看(除非可能的类和函数名字?)。一个用户可能对它没有任何用,但是至少你能要求有这个MAP文件,假如你没有这个文件。

原文地址:Finding crash information using the MAP file
http://www.codeproject.com/debug/mapfile.asp

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值