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

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

2012-08-13 17:18 281 查看
作为程序员,我们平时最担心见到的事情是什么?是内存泄漏?是界面不好看?……错啦!我相信我的看法是不会有人反对的——那就是,程序发生了崩溃!

“该程序执行了非法操作,即将关闭。请与你的软件供应商联系。”,呵呵,这句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中,我们要设置编译和连接参数,我通常是这样做的:

viewsource

print?

1.
rc%1.rc

2.
ml/c/coff/Zd%1.asm

3.
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,新建这样一个文件:

viewsource

print?

01.
01
//****************************************************************

02.
02
//程序名称:演示如何通过崩溃地址找出源代码的出错行

03.
03
//作者:罗聪

04.
04
//日期:2003-2-7

05.
05
//出处:http://www.luocong.com(老罗的缤纷天地)

06.
06
//本程序会产生“除0错误”,以至于会弹出“非法操作”对话框。

07.
07
//“除0错误”只会在Debug版本下产生,本程序为了演示而尽量简化。

08.
08
//注意事项:如欲转载,请保持本程序的完整,并注明:

09.
09
//转载自“老罗的缤纷天地”(http://www.luocong.com)

10.
10
//****************************************************************

11.
11

12.
12
void
Crash(
void
)

13.
13{

14.
14
int
i=1;

15.
15
int
j=0;

16.
16i/=j;

17.
17}

18.
18

19.
19
void
main(
void
)

20.
20{

21.
21Crash();

22.
22}


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

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

viewsource

print?

01.
CrashDemo

02.

03.
Timestampis3e430a76(FriFeb0709:23:022003)

04.

05.
Preferredloadaddressis00400000

06.

07.
StartLengthNameClass

08.
0001:000000000000de04H.textCODE

09.
0001:0000de040001000cH.textbssCODE

10.
0002:0000000000001346H.rdataDATA

11.
0002:0000134600000000H.edataDATA

12.
0003:0000000000000104H.CRT$XCADATA

13.
0003:0000010400000104H.CRT$XCZDATA

14.
0003:0000020800000104H.CRT$XIADATA

15.
0003:0000030c00000109H.CRT$XICDATA

16.
0003:0000041800000104H.CRT$XIZDATA

17.
0003:0000051c00000104H.CRT$XPADATA

18.
0003:0000062000000104H.CRT$XPXDATA

19.
0003:0000072400000104H.CRT$XPZDATA

20.
0003:0000082800000104H.CRT$XTADATA

21.
0003:0000092c00000104H.CRT$XTZDATA

22.
0003:00000a3000000b93H.dataDATA

23.
0003:000015c400001974H.bssDATA

24.
0004:0000000000000014H.idata$2DATA

25.
0004:0000001400000014H.idata$3DATA

26.
0004:0000002800000110H.idata$4DATA

27.
0004:0000013800000110H.idata$5DATA

28.
0004:00000248000004afH.idata$6DATA

29.

30.
AddressPublicsbyValueRva+BaseLib:Object

31.

32.
0001:00000020?Crash@@YAXXZ00401020fCrashDemo.obj

33.
0001:00000070_main00401070fCrashDemo.obj

34.
0004:00000000__IMPORT_DESCRIPTOR_KERNEL3200424000kernel32:KERNEL32.dll

35.
0004:00000014__NULL_IMPORT_DESCRIPTOR00424014kernel32:KERNEL32.dll

36.
0004:00000138__imp__GetCommandLineA@000424138kernel32:KERNEL32.dll

37.
0004:0000013c__imp__GetVersion@00042413ckernel32:KERNEL32.dll

38.
0004:00000140__imp__ExitProcess@400424140kernel32:KERNEL32.dll

39.
0004:00000144__imp__DebugBreak@000424144kernel32:KERNEL32.dll

40.
0004:00000148__imp__GetStdHandle@400424148kernel32:KERNEL32.dll

41.
0004:0000014c__imp__WriteFile@200042414ckernel32:KERNEL32.dll

42.
0004:00000150__imp__InterlockedDecrement@400424150kernel32:KERNEL32.dll

43.
0004:00000154__imp__OutputDebugStringA@400424154kernel32:KERNEL32.dll

44.
0004:00000158__imp__GetProcAddress@800424158kernel32:KERNEL32.dll

45.
0004:0000015c__imp__LoadLibraryA@40042415ckernel32:KERNEL32.dll

46.
0004:00000160__imp__InterlockedIncrement@400424160kernel32:KERNEL32.dll

47.
0004:00000164__imp__GetModuleFileNameA@1200424164kernel32:KERNEL32.dll

48.
0004:00000168__imp__TerminateProcess@800424168kernel32:KERNEL32.dll

49.
0004:0000016c__imp__GetCurrentProcess@00042416ckernel32:KERNEL32.dll

50.
0004:00000170__imp__UnhandledExceptionFilter@400424170kernel32:KERNEL32.dll

51.
0004:00000174__imp__FreeEnvironmentStringsA@400424174kernel32:KERNEL32.dll

52.
0004:00000178__imp__FreeEnvironmentStringsW@400424178kernel32:KERNEL32.dll

53.
0004:0000017c__imp__WideCharToMultiByte@320042417ckernel32:KERNEL32.dll

54.
0004:00000180__imp__GetEnvironmentStrings@000424180kernel32:KERNEL32.dll

55.
0004:00000184__imp__GetEnvironmentStringsW@000424184kernel32:KERNEL32.dll

56.
0004:00000188__imp__SetHandleCount@400424188kernel32:KERNEL32.dll

57.
0004:0000018c__imp__GetFileType@40042418ckernel32:KERNEL32.dll

58.
0004:00000190__imp__GetStartupInfoA@400424190kernel32:KERNEL32.dll

59.
0004:00000194__imp__HeapDestroy@400424194kernel32:KERNEL32.dll

60.
0004:00000198__imp__HeapCreate@1200424198kernel32:KERNEL32.dll

61.
0004:0000019c__imp__HeapFree@120042419ckernel32:KERNEL32.dll

62.
0004:000001a0__imp__VirtualFree@12004241a0kernel32:KERNEL32.dll

63.
0004:000001a4__imp__RtlUnwind@16004241a4kernel32:KERNEL32.dll

64.
0004:000001a8__imp__GetLastError@0004241a8kernel32:KERNEL32.dll

65.
0004:000001ac__imp__SetConsoleCtrlHandler@8004241ackernel32:KERNEL32.dll

66.
0004:000001b0__imp__IsBadWritePtr@8004241b0kernel32:KERNEL32.dll

67.
0004:000001b4__imp__IsBadReadPtr@8004241b4kernel32:KERNEL32.dll

68.
0004:000001b8__imp__HeapValidate@12004241b8kernel32:KERNEL32.dll

69.
0004:000001bc__imp__GetCPInfo@8004241bckernel32:KERNEL32.dll

70.
0004:000001c0__imp__GetACP@0004241c0kernel32:KERNEL32.dll

71.
0004:000001c4__imp__GetOEMCP@0004241c4kernel32:KERNEL32.dll

72.
0004:000001c8__imp__HeapAlloc@12004241c8kernel32:KERNEL32.dll

73.
0004:000001cc__imp__VirtualAlloc@16004241cckernel32:KERNEL32.dll

74.
0004:000001d0__imp__HeapReAlloc@16004241d0kernel32:KERNEL32.dll

75.
0004:000001d4__imp__MultiByteToWideChar@24004241d4kernel32:KERNEL32.dll

76.
0004:000001d8__imp__LCMapStringA@24004241d8kernel32:KERNEL32.dll

77.
0004:000001dc__imp__LCMapStringW@24004241dckernel32:KERNEL32.dll

78.
0004:000001e0__imp__GetStringTypeA@20004241e0kernel32:KERNEL32.dll

79.
0004:000001e4__imp__GetStringTypeW@16004241e4kernel32:KERNEL32.dll

80.
0004:000001e8__imp__SetFilePointer@16004241e8kernel32:KERNEL32.dll

81.
0004:000001ec__imp__SetStdHandle@8004241eckernel32:KERNEL32.dll

82.
0004:000001f0__imp__FlushFileBuffers@4004241f0kernel32:KERNEL32.dll

83.
0004:000001f4__imp__CloseHandle@4004241f4kernel32:KERNEL32.dll

84.
0004:000001f8\177KERNEL32_NULL_THUNK_DATA004241f8kernel32:KERNEL32.dll

85.

86.
entrypointat0001:000000f0

87.

88.

89.
Linenumbers
for
.\Debug\CrashDemo.obj(d:\msdev\myprojects\crashdemo\crashdemo.cpp)segment.text

90.

91.
130001:00000020140001:00000038150001:0000003f160001:00000046

92.
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$还是有意无意间提供了很多有价值的信息给我们的,只是我们往往不懂得怎么利用而已……相信这样一来,你就可以更为从容地面对“非法操作”提示了。你甚至可以要求用户提供崩溃的地址,然后就可以坐在家中舒舒服服地找到出错的那行,并进行修正。

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