如何根据内存地址定位出错的源代码行?
相信很多人都遇到过内存错,可能是因为写一段只读的内存,或者是由于一个空指针,如果没有适当的进行异常处理,一个精心设计的软件可能就会因为一个小小的异常而很不体面地被windows终结,得到类似下面的一个对话框:
一旦出现上面的对话框,特别是在给用户演示的时候,可不仅仅是颜面扫地的问题,可能就因此丢了一个大单子。作为一个技术人员,在懊恼之余,还得修改这个bug的,仅仅从上面的对话框中,我们除了知道是一个空指针引起的问题外,怎么知道源代码中那一行代码出错了呢?
下面以一个小例子演示如何解决这个问题。启动Delphi(2007),建立一个VCL Form Applcation,引发异常的代码如下:
大家应该一眼就看出哪一行代码出问题了吧?
现在到了关键部分,在项目选项对话框中把“Map file”置为“Detailed”,这将导致linker为我们生成map文件(map文件是什么?自己baidu去o(∩_∩)o),本文的核心就是从map文件中找到我们需要的信息。
首先从出错对话框中记下出错的内存地址“00454e07”,打开map文件(editplus,ue,vi,notepad……),进入视野的是如下内容:
Start Length Name Class
0001:00401000 00054018H .text CODE
0002:00456000 00000760H .itext ICODE
0003:00457000 00001AB0H .data DATA
0004:00459000 00004CC8H .bss BSS
0005:00000000 00000034H .tls TLS
从上面可以看出,出错的代码位于“.text”段中,因为00401000 < 00454e07 << 00455018 (00401000 + 00054018),并且我们得到00454e07在.text段中的偏移 00053e07 。
接着往下看是“Detailed map of segments”,我们找比偏移量略大那行的前一行,显然就是下文中红色的那一行,从这一行中我们可以知道我们的出错行位于MainFrm单元中。
...
0001:00044BF4 0000F03C C=CODE S=.text G=(none) M=Forms ACBP=A9
0001:00053C30 00000210 C=CODE S=.text G=(none) M=MainFrm ACBP=A9
0001:00053E40 000001D8 C=CODE S=.text G=(none) M=CrashTest ACBP=A9
0002:00000000 00000095 C=ICODE S=.itext G=(none) M=System ACBP=A9
0002:00000098 00000011 C=ICODE S=.itext G=(none) M=Windows ACBP=A9
...
当然,我们不能仅仅满足于定位到哪个单元,能不能知道是那个函数出了问题呢?没有问题,往下拖动直到定位到这样一行“Address Publics by Value”,同上,我们找到比我们偏移量小的那个地址:
...
0001:00053BC4 Forms.Finalization
0001:00053C30 MainFrm..TMainForm
0001:00053DE8 MainFrm.TMainForm.btnTestCrashClick
0001:00053E40 CrashTest.Finalization
0002:00000000 System.System
...
哦,原来是MainFrm单元中TMainForm那个类的btnTestCrashClick方法出了问题。
你也许会问,这个函数几百行(注:把写这个函数的人拖出去,txjjtds),我调试起来还是耗时间,能不能告诉我那一行代码错了啊?行,没问题!把map文件拖到最后,我们可以得到行号信息:
Line numbers for MainFrm(MainFrm.pas) segment .text
29 0001:00053DE8 30 0001:00053E00 31 0001:00053E02 32 0001:00053E09
33 0001:00053E1D 35 0001:00053E6E
Line numbers for CrashTest(D:\Data\My Documents\RAD Studio\Projects\CrashTest\CrashTest.dpr) segment .itext
9 0002:0000070C 10 0002:0000071C 11 0002:00000728 12 0002:00000736
13 0002:0000074E 14 0002:0000075A
吼吼,还是同样的方法,我们得知原来第“31”行报了那个可恶的异常,大家可以通过上面的源码(图)验证(注:当然,更可恶的是那个创造异常的人,这里是我o(∩_∩)o...哈哈),至此,大功告成!!!
注意,如果“Debug Information”那个选项被勾掉,那么产生的map文件就不包含行信息,那么最多能定位到函数,定位到行就不行了。
利用map文件,当面对内存错的时候,我们不再束手无策。
注:这个方法我很早之前在哪里看过(忘了在哪里了),是c++版本的,突然想起来,把它改成delphi版本,o(∩_∩)o...哈哈