VC根据崩溃内存地址,查找异常代码位置

4 篇文章 0 订阅
4 篇文章 0 订阅

如何通过崩溃地址找到出错的代码行作为程序员,我们平时最担心见到的事情是什么?是内存泄漏?是界面不好看?

是,程序发生了崩溃!

经过下面四步,轻松解决上述难题:

第一:生成MAP文件;

第二:查找崩溃的内存地址;

第三:定位异常代码出错函数;

第四:定位出错行号

下面详细介绍具体步骤:

第一:生成MAP文件

首先必须生成程序的MAP文件。什么是MAP文件?

简单地讲, MAP文件是程序的全局符号、源文件和代码行号信息的唯一的文本表示方法,它可以在任何地方、任何时候使用,不需要有额外的程序进行支持。

如何生成MAP呢?

在VC6.0中,我们可以按下Alt+F7,打开“Project Settings”选项页,选择C/C++选项卡,并在最下面的 Project Options 里面输入:/Zd,然后要选择Link选项卡,在最下面的Project Options里面输入:/mapinfo:lines 和 /map:PROJECT_NAME.map。最后按下F7来编译生成EXE可执行文件和MAP文件。

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

/Zd            表示在编译的时候生成行信息
/map[:filename] 表示生成MAP文件的路径和文件名
/mapinfo:lines  表示生成MAP文件时,加入行信息
/mapinfo:exports表示生成MAP文件时,加入exported functions(如果生成的是DLL文件,就要加上)

生成的MAP文件在项目文件夹下,文件名为:PROJECT_NAME.map

第二:查找崩溃的内存地址;

OK,通过上面的步骤,我们已经得到了MAP文件,那么我们该如何利用它呢?让我们从简单的实例入手,请打开你的VC,新建这样一个例子文件:


2 void Crash(void)
3 {
4     int i = 1;
5       int j = 0;
6       i /= j;
7 }

9 void main(void)
10 {
11      Crash();
12 } 

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

如何查看异常“详细信息”?

在调试状态下,菜单栏:查看->调试窗口->Dissasembly。或则直接Alt+8。

第三:定位异常代码出错函数;

在MAP文件中:(由于文件内容太长,中间没用的部分我进行了省略),MAP文件可以用文本编辑器打开

addressError

Timestamp is 54abf59b(Tue Jan 06 22:47:55 2015)

 Preferred load address is 00400000

 Start        Length     Name                   Class

 0001:00000000 0001055cH .text                   CODE

 0002:00000000 00001380H .rdata                  DATA

 0002:00001380 00000000H .edata                  DATA

 0003:00000000 00000104H .CRT$XCA                DATA

 0003:00000104 00000104H .CRT$XCZ                DATA

 0003:00000208 00000104H .CRT$XIA                DATA

 0003:0000030c 00000109H .CRT$XIC                DATA

 0003:00000418 00000104H .CRT$XIZ                DATA

 0003:0000051c 00000104H .CRT$XPA                DATA

 0003:00000620 00000104H .CRT$XPX                DATA

 0003:00000724 00000104H .CRT$XPZ                DATA

 0003:00000828 00000104H .CRT$XTA                DATA

 0003:0000092c 00000104H .CRT$XTZ                DATA

 0003:00000a30 00003220H .data                   DATA

 0003:00003c50 000019a8H .bss                    DATA

 0004:00000000 00000014H .idata$2                DATA

 0004:00000014 00000014H .idata$3                DATA

 0004:00000028 00000120H .idata$4                DATA

 0004:00000148 00000120H .idata$5                DATA

 0004:00000268 000004f4H .idata$6                DATA

 

  Address         Publics by Value              Rva+Base     Lib:Object

 0001:00000020       ?Crash@@YAXXZ              00401020 f   addressError.obj

 0001:00000050       _main                      00401050 f   addressError.obj

 0001:00000080       __chkesp                   00401080 f   LIBCD:chkesp.obj

 0001:000000c0       _mainCRTStartup            004010c0 f   LIBCD:crt0.obj

 0001:000001f0       __amsg_exit                004011f0 f   LIBCD:crt0.obj

 0001:00000250       __CrtDbgBreak              00401250 f   LIBCD:dbgrpt.obj

 0001:00000260       __CrtSetReportMode         00401260 f   LIBCD:dbgrpt.obj

 0001:000002c0       __CrtSetReportFile         004012c0 f   LIBCD:dbgrpt.obj

 0001:00000340       __CrtSetReportHook         00401340 f   LIBCD:dbgrpt.obj

 0001:00000360       __CrtDbgReport             00401360 f   LIBCD:dbgrpt.obj

….

 

entrypoint at        0001:000000c0

 

Linenumbers for .\Debug\addressError.obj(E:\addressError\addressError.cpp) segment.text

  3 0001:00000020     4 0001:00000034     5 0001:0000003b     6 0001:00000042

  7 0001:0000004c    10 0001:00000050    110001:00000053    12 0001:00000058

 

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

0001:00000020       ?Crash@@YAXXZ              00401020 f   addressError.obj

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

第四:定位出错行号

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

3 0001:00000020

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

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

崩溃地址 = 偏移地址(Rva)+基地址(Base)

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

为什么要这样做呢?细心的朋友可能会留意到Rva+Base这栏了,我们得到的崩溃地址都是由偏移地址(Rva)+基地址(Base)得来的,所以在计算行号的时候要把基地址减去.

一般情况下,基地址的值是0x00400000(可由MAP文件的第五行得到:Preferred load address is00400000)。另外,由于一般的PE文件的代码段都是从0x1000(可由MAP文件的Line numbers information得到:3 0001:00000020)偏移开始的,所以也必须减去0x1000。

崩溃行偏移 = 0x00401046-0x00400000 - 0x1000 = 0x46

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

6 0001:00000042 也就是在源代码中的第6行,让我们来看看源代码:16 i /= j;果然就是第6行!

成功查找完毕。

注:VS2005等的查找方法类似,自己思考下就行,不懂的可以留言联系。


  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值