如何通过崩溃地址找到出错的代码行作为程序员,我们平时最担心见到的事情是什么?是内存泄漏?是界面不好看?
是,程序发生了崩溃!
经过下面四步,轻松解决上述难题:
第一:生成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 }
8
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等的查找方法类似,自己思考下就行,不懂的可以留言联系。