为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

如何使用MAP文件找到程序崩溃的原因

2018-01-07 8页 doc 66KB 14阅读

用户头像

is_511210

暂无简介

举报
如何使用MAP文件找到程序崩溃的原因如何使用MAP文件找到程序崩溃的原因 作者 Wouter Dhondt 翻译 冯亦成(fengyc@pset.suntec.net) [译者] 在我们调试程序的时候,习惯于不停的Step in, Step in...可是如果我们发现Debug版的exe可以完全正常运行,而Release版却经常莫名其妙Crash。那该怎么办,,没有关系,这篇文章就是帮你解决这个问题的:) 当然,你如果希望全面提高你的Debug能力,不妨去读一下John Robbins 的"Debugging Applications"一书,不过你要读英文版...
如何使用MAP文件找到程序崩溃的原因
如何使用MAP文件找到程序崩溃的原因 作者 Wouter Dhondt 翻译 冯亦成(fengyc@pset.suntec.net) [译者] 在我们调试程序的时候,习惯于不停的Step in, Step in...可是如果我们发现Debug版的exe可以完全正常运行,而Release版却经常莫名其妙Crash。那该怎么办,,没有关系,这篇文章就是帮你解决这个问题的:) 当然,你如果希望全面提高你的Debug能力,不妨去读一下John Robbins 的"Debugging Applications"一书,不过你要读英文版,中文版翻译的太烂了! 导言 编写整洁的程序是一回事。而当用户通知你的程序崩溃了,你知道在增加程序新属性之前最好先修正这些错误,如果你足够幸运的话,用户会给你提供一个崩溃地址,要解决这个问题还是要有很长的路要走。有了崩溃地址,你怎么确定到底是在什么出了错呢, 创建MAP文件 首先,你需要MAP文件。如果你没有MAP文件,那你几乎是不可能通过崩溃地址找到你程序出错的具体代码行。那么先让我教你怎么创建合适的MAP文件。为此我们创建了一个新工程(MAPFILE):我在VC++6.0中创建了应用Win32 Application选项的新工程,并且选择'typical "Hello Word!" application',这样可以使得生成的MAP文件能够满足我下边解说的需要。 当生成新工程后,我们调整release版的工程设置信息。在C/C++属性页,设置Debug Info的值为"Line Numbers Only"。 很多人都忘了这一步,但是如果你想得到合适的MAP文件,你就需要设置这个选项,这不会对你的release程序造成任何影响。下一步是Link属性页,你需要选择"Generate mapfile"选项。在Project Options编辑框中输入/MAPINFO:LINES和/MAPINFO:EXPORTS开关。 现在你可以编译和链接你的工程了,链接之后,你可以在你的中间目录中找到.map文件(和exe文件在一起)。 阅读MAP文件 在上面这些无趣的工作之后,接下去就是很有趣的部分:怎么读MAP文件。我们通过一个崩溃实例来介绍怎么读MAP文件。那么我们先得让程序崩溃,于是我在InitInstance()函数的最后增加了下边的两行代码: char* pEmpty = NULL; *pEmpty = 'x'; // 第119行 我相信你能够找到其它代码使得你的程序崩溃。现在重新编译且链接工程。如果你运行你的程序,程序将崩溃,并且得到怎样的消息:'The instruction at "0x004011a1" referenced memory at "0x00000000"。0x00000000内存不能写。 现在,可以用Notepad或者类似的编辑工具打开MAP文件。MAP文件如下所示: 在MAP文件的头部包含了模块名称,表示工程链接时刻的时间戳,以及首选加载地址(一般是0x00400000,除非是dll)。文件头之后就是一些section信息,是由链接程序把各种OBJ和LIB文件的section信息组织起来的。 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 在section信息之后,你看到的时公共函数信息。注意下边的"public"部分,如果你有静态C函数, 那它不会在"public"部分出现。幸运的是,在line numbers部分仍会反映静态函数的信息。"public" 函数信息的最重要的部分是函数名称和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 0003:00002ab8 __acmdln 00408ab8 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 Public函数部分之后是line信息部分(你设置了Link属性页中使用了/MAPINFO:LINES并且在 C/C++属性页中选择"Line numbers")。在这之后就是export信息了,只要你的程序有输出函数并 且在link属性页包含了/MAPINFO:EXPORTS,你就可得到export信息。 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 86 0001:00000114 88 0001:00000135 89 0001:00000145 82 0001:000000ff 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 现在我们来定位代码中哪里发生崩溃。首先我们先要确定是哪个函数包含了崩溃地址。浏览 "Rva+Base"栏,查找到第一个地址比崩溃地址大的函数,那么该函数的上一个函数就是发生崩溃的 函数了。在我们的例子中,崩溃地址是0x004011a1,这个地址位于0x00401150 和 0x004011b0 之间,这样我们就知道崩溃函数是?InitInstance@@YAHPAUHINSTANCE__@@H@Z。任何以问 号开头的函数名都是C++ decorated name。你可以把C++ decorated name作为命令行参数传 递给Platform SDK的UNDNAME.EXE程序,就可以得到原始函数名称了。在大多数情况下你不需 要这样做,通过观察C++ decorated name我们就可以知道原始函数名称(这里,函数名称就是 InitInstance()) 以上是bug跟踪的重要一步。但是我们可以做得更好:我们可以找到是哪一行代码导致崩溃~我们 需要做一些基本的十六进制计算,因此需要一个计算器。首先计算下边的值:崩溃地址 – 首选加载地址 - 0x1000。 地址就是相对于第一个code section的偏移量,因此需要做这样的计算。减去首选加载地址可以得到相对于文件起始位置的偏移量(逻辑上),但是为什么还要减去0x1000? 由于Line numbers中的地址是相对于code section的起始位置的偏移量。二进制代码的第一部分是Portable Executable (PE),这部分有0x1000字节长度。因此,在我们的例子中,崩溃地址(即相对于code section的偏移量)应该为0x004011a1 - 0x00400000 - 0x1000 = 0x1a1 现在我们查看MAP文件的line information section部分,每一行都是像30 0001:00000004这个样子。第一个数字是行数,第二个数字是这一行代码相对于code section的偏移值。如果我们要找崩溃代码的行数,我们只要使用与刚才定位崩溃函数相同的方式:找到第一个比我们计算得到的崩溃地址大的偏移量,那么发生崩溃的就是上一个偏移。在我们的例子中,0x1a1 在0x1a8 之前,那我们就可以确定崩溃发生在MAPFILE.CPP文件的119行。 保持对MAP文件的跟踪 每个发布版本都有自己相应的MAP文件。在发布exe文件时包含MAP文件不是一个坏主意。这样,你可以确保当前exe拥有合适的MAP文件。你可以让系统里的每个exe都有相应的MAP文件,但是我们都知道这样最后可能导致一些问题,MAP文件中不包含任何你要让用户知道的信息,对用户来说一点用处都没有。不过,当发生程序崩溃后,如果你没有了MAP文件,你至少可以让用户提供MAP文件。
/
本文档为【如何使用MAP文件找到程序崩溃的原因】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索