读了老罗的“仅通过崩溃地址找出源代码的出错行”(下称"罗文")一文后,感觉该文还是 可以学到不少东西的。不过文中尚存在有些说法不妥,以及有些操作太繁琐的地方 。 为此,本人在
学习了此文后,在多次实验实践基础上,把该文中的一些内容进行补充与 改进,希望对大家调试程序,尤其是 release 版本的程序有帮助 。欢迎各位朋友批评 指正。
一、该方法适用的范围 在 windows 程序中造成程序崩溃的原因很多, 而文中所述的方法仅适用与:由一条 语句当即引起的程序崩溃。如原文中举的除数为零的崩溃例子。而笔者在实际
工作中碰 到更多的情况是:指针指向一非法地址 ,然后对指针的内容进行了,读或写的操作。例 如:
void Crash1(){ char * p =(char*)100; *p=100;}
这些原因造成的崩溃,无论是 debug 版本,还是 release 版本的程序,使用该方 法都可找到造成崩溃的函数或子程序中的语句行,具体方法的下面还会补充说明。 另 外,实践中另一种常见的造成程序崩溃的原因:函数或子程序中局部变量数组越界付值, 造成函数或子程序返回地址遭覆盖,从而造成函数或子
程序返回时崩溃。例如:
#include void Crash2();int main(int argc,char* argv[]){ return 0;}void Crash2(){ char p[1]; strcpy(p,"0123456789");}
在 vc 中编译运行此程序的 release 版本,会跳出如下的出错提示框。
Crash2();
图一 上面例子运行结果 这里显示的崩溃地址为:0x34333231。这种由前面语句造成的崩溃根源,在后续 程序中方才显现出来的情况,显然用该文所述的方法就无能为力了。不过在此例中多少 还有些蛛丝马迹可寻找到崩溃的原因:函数 Crash2 中的局部数组 p 只有一个字节大 小 , 显然拷贝"0123456789"这个字符串会把超出长度的字符串拷贝到数组 p 的后面, 即*(p+1)=''1'',*(p+2)=''2'',*(p+3)=''3'',*(p+4)=4。。。。。。而字符''1'' 的 ASC 码的值为 0x31,''2''为 0x32,''3''为 0x33,''4''为 0x34。。。。。,由于 intel 的 cpu 中 int 型数据是低字节保存在低地址中 , 所以保存字符串''1234''的内存, 显示为一个 4 字节的 int 型数时就是 0x34333231。显然拷贝"0123456789"这个字 符串时,"1234"这几个字符把函数 Crash2 的返回地址给覆盖 ,从而造成程序崩溃。 对于类似的这种造成程序崩溃的错误朋友们还有其他方法排错的话,欢迎一起交流讨 论。
二、设置编译产生 map 文件的方法 该文中产生 map 文件的方法是手工添加编译参数来产生 map 文件。其实在 vc6 的 IDE 中有产生 map 文件的配置选项的。操作如下:先点击菜单 "Project"->"Settings。。。",弹出的属性页中选中"Link"页 ,确保在"category" 中选中"General",最后选中"Generate mapfile"的可选项
。若要在在 map 文件中显 示 Line numbers 的信息的话 ,还需在 project options 中加入/mapinfo:lines 。 Line numbers 信息对于"罗文"所用的方法来定位出错
源代码行很重要 ,但笔者后面 会介绍更加好的方法来定位出错代码行,那种方法不需要 Line numbers 信息。
图二 设置产生 MAP 文件
三、定位崩溃语句位置的方法 定位崩溃语句位置的方法 "罗文"所述的定位方法中,找到产生崩溃的函数位置的方法是正确的,即在 map 文件列出的每个函数的起始地址中, 最近的且不大于崩溃地址的地址即为包含崩溃语句 的函数的地址 。但之后的再进一步的定位出错语句行的方法不是最妥当,因为那种方 法前提是,假设基地址的值是 0x00400000 ,以及一般的 PE 文件的代码段都是从 0x1000 偏移开始的 。 虽然这种情况很普遍, 但在 vc 中还是可以基地址设置为其他数, 比如设置为 0x00500000,这时仍旧套用
崩溃行偏移 = 崩溃地址 - 0x00400000 - 0x1000
的公式显然无法找到崩溃行偏移。 其实上述公式若改为
崩溃行偏移 = 崩溃地址 - 崩溃函数绝对地址 + 函数相对