您的位置:首页 > 编程语言

仅通过崩溃地址找出源代码的出错行

2013-05-15 11:41 316 查看
作为程序员,我们平时最担心见到的事情是什么?是内存泄漏?是界面不好看?……错啦!我相信我的看法是不会有人反对的——那就是,程序发生了崩溃!
“该程序执行了非法操作,即将关闭。请与你的软件供应商联系。”,呵呵,这句M$的“名言”,恐怕就是程序员最担心见到的东西了。有的时候,自己的程序在自己的机器上运行得好好的,但是到了别人的机器上就崩溃了;有时自己在编写和测试的过程中就莫名其妙地遇到了非法操作,但是却无法确定到底是源代码中的哪行引起的……是不是很痛苦呢?不要紧,本文可以帮助你走出这种困境,甚至你从此之后可以自豪地要求用户把崩溃地址告诉你,然后你就可以精确地定位到源代码中出错的那行了。(很神奇吧?呵呵。)

首先我必须强调的是,本方法可以在目前市面上任意一款编译器上面使用。但是我只熟悉M$的VC和MASM,因此后面的部分只介绍如何在这两个编译器中实现,请读者自行融会贯通,掌握在别的编译器上使用的方法。

Well,废话说完了,让我们开始!:)

首先必须生成程序的MAP文件。什么是MAP文件?简单地讲,MAP文件是程序的全局符号、源文件和代码行号信息的唯一的文本表示方法,它可以在任何地方、任何时候使用,不需要有额外的程序进行支持。而且,这是唯一能找出程序崩溃的地方的救星。

好吧,既然MAP文件如此神奇,那么我们应该如何生成它呢?在VC中,我们可以按下Alt+F7,打开“ProjectSettings”选项页,选择C/C++选项卡,并在最下面的ProjectOptions里面输入:/Zd,然后要选择Link选项卡,在最下面的ProjectOptions里面输入:/mapinfo:lines和/map:PROJECT_NAME.map。最后按下F7来编译生成EXE可执行文件和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,就可以在命令行输入makemfilename来编译生成EXE可执行文件和MAP文件了。

在此我先解释一下加入的参数的含义:

/Zd表示在编译的时候生成行信息

/map[:filename]表示生成MAP文件的路径和文件名

/mapinfo:lines表示生成MAP文件时,加入行信息

/mapinfo:exports表示生成MAP文件时,加入exportedfunctions(如果生成的是DLL文件,这个选项就要加上)

OK,通过上面的步骤,我们已经得到了MAP文件,那么我们该如何利用它呢?

让我们从简单的实例入手,请打开你的VC,新建这样一个文件:

01//****************************************************************
02//程序名称:演示如何通过崩溃地址找出源代码的出错行
03//作者:罗聪
04//日期:2003-2-7
05//出处:http://www.luocong.com(老罗的缤纷天地)
06//本程序会产生“除0错误”,以至于会弹出“非法操作”对话框。
07//“除0错误”只会在Debug版本下产生,本程序为了演示而尽量简化。
08//注意事项:如欲转载,请保持本程序的完整,并注明:
09//转载自“老罗的缤纷天地”(http://www.luocong.com)
10//****************************************************************
11
12voidCrash(void)
13{
14inti=1;
15intj=0;
16i/=j;
17}
18
19voidmain(void)
20{
21Crash();
22}


很显然本程序有“除0错误”,在Debug方式下编译的话,运行时肯定会产生“非法操作”。好,让我们运行它,果然,“非法操作”对话框出现了,这时我们点击“详细信息”按钮,记录下产生崩溃的地址——在我的机器上是0x0040104a。

再看看它的MAP文件:(由于文件内容太长,中间没用的部分我进行了省略)

CrashDemo

Timestampis3e430a76(FriFeb0709:23:022003)

Preferredloadaddressis00400000

StartLengthNameClass
0001:000000000000de04H.textCODE
0001:0000de040001000cH.textbssCODE
0002:0000000000001346H.rdataDATA
0002:0000134600000000H.edataDATA
0003:0000000000000104H.CRT$XCADATA
0003:0000010400000104H.CRT$XCZDATA
0003:0000020800000104H.CRT$XIADATA
0003:0000030c00000109H.CRT$XICDATA
0003:0000041800000104H.CRT$XIZDATA
0003:0000051c00000104H.CRT$XPADATA
0003:0000062000000104H.CRT$XPXDATA
0003:0000072400000104H.CRT$XPZDATA
0003:0000082800000104H.CRT$XTADATA
0003:0000092c00000104H.CRT$XTZDATA
0003:00000a3000000b93H.dataDATA
0003:000015c400001974H.bssDATA
0004:0000000000000014H.idata$2DATA
0004:0000001400000014H.idata$3DATA
0004:0000002800000110H.idata$4DATA
0004:0000013800000110H.idata$5DATA
0004:00000248000004afH.idata$6DATA

AddressPublicsbyValueRva+BaseLib:Object

0001:00000020?Crash@@YAXXZ00401020fCrashDemo.obj
0001:00000070_main00401070fCrashDemo.obj
0004:00000000__IMPORT_DESCRIPTOR_KERNEL3200424000kernel32:KERNEL32.dll
0004:00000014__NULL_IMPORT_DESCRIPTOR00424014kernel32:KERNEL32.dll
0004:00000138__imp__GetCommandLineA@000424138kernel32:KERNEL32.dll
0004:0000013c__imp__GetVersion@00042413ckernel32:KERNEL32.dll
0004:00000140__imp__ExitProcess@400424140kernel32:KERNEL32.dll
0004:00000144__imp__DebugBreak@000424144kernel32:KERNEL32.dll
0004:00000148__imp__GetStdHandle@400424148kernel32:KERNEL32.dll
0004:0000014c__imp__WriteFile@200042414ckernel32:KERNEL32.dll
0004:00000150__imp__InterlockedDecrement@400424150kernel32:KERNEL32.dll
0004:00000154__imp__OutputDebugStringA@400424154kernel32:KERNEL32.dll
0004:00000158__imp__GetProcAddress@800424158kernel32:KERNEL32.dll
0004:0000015c__imp__LoadLibraryA@40042415ckernel32:KERNEL32.dll
0004:00000160__imp__InterlockedIncrement@400424160kernel32:KERNEL32.dll
0004:00000164__imp__GetModuleFileNameA@1200424164kernel32:KERNEL32.dll
0004:00000168__imp__TerminateProcess@800424168kernel32:KERNEL32.dll
0004:0000016c__imp__GetCurrentProcess@00042416ckernel32:KERNEL32.dll
0004:00000170__imp__UnhandledExceptionFilter@400424170kernel32:KERNEL32.dll
0004:00000174__imp__FreeEnvironmentStringsA@400424174kernel32:KERNEL32.dll
0004:00000178__imp__FreeEnvironmentStringsW@400424178kernel32:KERNEL32.dll
0004:0000017c__imp__WideCharToMultiByte@320042417ckernel32:KERNEL32.dll
0004:00000180__imp__GetEnvironmentStrings@000424180kernel32:KERNEL32.dll
0004:00000184__imp__GetEnvironmentStringsW@000424184kernel32:KERNEL32.dll
0004:00000188__imp__SetHandleCount@400424188kernel32:KERNEL32.dll
0004:0000018c__imp__GetFileType@40042418ckernel32:KERNEL32.dll
0004:00000190__imp__GetStartupInfoA@400424190kernel32:KERNEL32.dll
0004:00000194__imp__HeapDestroy@400424194kernel32:KERNEL32.dll
0004:00000198__imp__HeapCreate@1200424198kernel32:KERNEL32.dll
0004:0000019c__imp__HeapFree@120042419ckernel32:KERNEL32.dll
0004:000001a0__imp__VirtualFree@12004241a0kernel32:KERNEL32.dll
0004:000001a4__imp__RtlUnwind@16004241a4kernel32:KERNEL32.dll
0004:000001a8__imp__GetLastError@0004241a8kernel32:KERNEL32.dll
0004:000001ac__imp__SetConsoleCtrlHandler@8004241ackernel32:KERNEL32.dll
0004:000001b0__imp__IsBadWritePtr@8004241b0kernel32:KERNEL32.dll
0004:000001b4__imp__IsBadReadPtr@8004241b4kernel32:KERNEL32.dll
0004:000001b8__imp__HeapValidate@12004241b8kernel32:KERNEL32.dll
0004:000001bc__imp__GetCPInfo@8004241bckernel32:KERNEL32.dll
0004:000001c0__imp__GetACP@0004241c0kernel32:KERNEL32.dll
0004:000001c4__imp__GetOEMCP@0004241c4kernel32:KERNEL32.dll
0004:000001c8__imp__HeapAlloc@12004241c8kernel32:KERNEL32.dll
0004:000001cc__imp__VirtualAlloc@16004241cckernel32:KERNEL32.dll
0004:000001d0__imp__HeapReAlloc@16004241d0kernel32:KERNEL32.dll
0004:000001d4__imp__MultiByteToWideChar@24004241d4kernel32:KERNEL32.dll
0004:000001d8__imp__LCMapStringA@24004241d8kernel32:KERNEL32.dll
0004:000001dc__imp__LCMapStringW@24004241dckernel32:KERNEL32.dll
0004:000001e0__imp__GetStringTypeA@20004241e0kernel32:KERNEL32.dll
0004:000001e4__imp__GetStringTypeW@16004241e4kernel32:KERNEL32.dll
0004:000001e8__imp__SetFilePointer@16004241e8kernel32:KERNEL32.dll
0004:000001ec__imp__SetStdHandle@8004241eckernel32:KERNEL32.dll
0004:000001f0__imp__FlushFileBuffers@4004241f0kernel32:KERNEL32.dll
0004:000001f4__imp__CloseHandle@4004241f4kernel32:KERNEL32.dll
0004:000001f8\177KERNEL32_NULL_THUNK_DATA004241f8kernel32:KERNEL32.dll

entrypointat0001:000000f0

Linenumbersfor.\Debug\CrashDemo.obj(d:\msdev\myprojects\crashdemo\crashdemo.cpp)segment.text

130001:00000020140001:00000038150001:0000003f160001:00000046
170001:00000050200001:00000070210001:00000088220001:0000008d


如果仔细浏览Rva+Base这栏,你会发现第一个比崩溃地址0x0040104a大的函数地址是0x00401070,所以在0x00401070这个地址之前的那个入口就是产生崩溃的函数,也就是这行:

0001:00000020?Crash@@YAXXZ00401020fCrashDemo.obj

因此,发生崩溃的函数就是?Crash@@YAXXZ,所有以问号开头的函数名称都是C++修饰的名称。在我们的源程序中,也就是Crash()这个子函数。

OK,现在我们轻而易举地便知道了发生崩溃的函数名称,你是不是很兴奋呢?呵呵,先别忙,接下来,更厉害的招数要出场了。

请注意MAP文件的最后部分——代码行信息(Linenumbersinformation),它是以这样的形式显示的:

130001:00000020

第一个数字代表在源代码中的代码行号,第二个数是该代码行在所属的代码段中的偏移量。

如果要查找代码行号,需要使用下面的公式做一些十六进制的减法运算:

崩溃行偏移=崩溃地址(CrashAddress)-基地址(ImageBaseAddress)-0x1000

为什么要这样做呢?细心的朋友可能会留意到Rva+Base这栏了,我们得到的崩溃地址都是由偏移地址(Rva)+基地址(Base)得来的,所以在计算行号的时候要把基地址减去,一般情况下,基地址的值是0x00400000。另外,由于一般的PE文件的代码段都是从0x1000偏移开始的,所以也必须减去0x1000。

好了,明白了这点,我们就可以来进行小学减法计算了:

崩溃行偏移=0x0040104a-0x00400000-0x1000=0x4a

如果浏览MAP文件的代码行信息,会看到不超过计算结果,但却最接近的数是CrashDemo.cpp文件中的:

160001:00000046

也就是在源代码中的第16行,让我们来看看源代码:

16i/=j;

哈!!!果然就是第16行啊!

兴奋吗?我也一样!:)

方法已经介绍完了,从今以后,我们就可以精确地定位到源代码中的崩溃行,而且只要编译器可以生成MAP文件(包括VC、MASM、VB、BCB、Delphi……),本方法都是适用的。我们时常抱怨M$的产品如何如何差,但其实M$还是有意无意间提供了很多有价值的信息给我们的,只是我们往往不懂得怎么利用而已……相信这样一来,你就可以更为从容地面对“非法操作”提示了。你甚至可以要求用户提供崩溃的地址,然后就可以坐在家中舒舒服服地找到出错的那行,并进行修正。

是不是很爽呢?:)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: