如何根据内存地址定位出错的源代码行?

相信很多人都遇到过内存错,可能是因为写一段只读的内存,或者是由于一个空指针,如果没有适当的进行异常处理,一个精心设计的软件可能就会因为一个小小的异常而很不体面地被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...哈哈

posted on 2007-12-28 15:04  王鹏翊  阅读(877)  评论(0编辑  收藏  举报