分享
 
 
 

对“仅通过崩溃地址找出源代码的出错行”一文的补充与改进

王朝vc·作者佚名  2006-01-17
窄屏简体版  字體: |||超大  

对“仅通过崩溃地址找出源代码的出错行”一文的补充与改进

作者:上海伟功通信 roc

下载源代码

读了老罗的“仅通过崩溃地址找出源代码的出错行”(下称"罗文")一文后,感觉该文还是可以学到不少东西的。不过文中尚存在有些说法不妥,以及有些操作太繁琐的地方

。为此,本人在学习了此文后,在多次实验实践基础上,把该文中的一些内容进行补充与改进,希望对大家调试程序,尤其是release版本的程序有帮助

。欢迎各位朋友批评指正。

一、该方法适用的范围

在windows程序中造成程序崩溃的原因很多,而文中所述的方法仅适用与:由一条语句当即引起的程序崩溃。如原文中举的除数为零的崩溃例子。而笔者在实际工作中碰到更多的情况是:指针指向一非法地址

,然后对指针的内容进行了,读或写的操作。例如:

void Crash1()

{

char * p =(char*)100;

*p=100;

}

这些原因造成的崩溃,无论是debug版本,还是release版本的程序,使用该方法都可找到造成崩溃的函数或子程序中的语句行,具体方法的下面还会补充说明。

另外,实践中另一种常见的造成程序崩溃的原因:函数或子程序中局部变量数组越界付值,造成函数或子程序返回地址遭覆盖,从而造成函数或子程序返回时崩溃。例如:

#include

void Crash2();

int main(int argc,char* argv[])

{

Crash2();

return 0;

}

void Crash2()

{

char p[1];

strcpy(p,"0123456789");

}

在vc中编译运行此程序的release版本,会跳出如下的出错提示框。

图一 上面例子运行结果

这里显示的崩溃地址为: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 的公式显然无法找到崩溃行偏移。 其实上述公式若改为

崩溃行偏移 = 崩溃地址 - 崩溃函数绝对地址 + 函数相对偏移即可通用了。仍以"罗文"中的例子为例:"罗文"中提到的在其崩溃程序的对应map文件中,崩溃函数的编译结果为

0001:00000020 ?Crash@@YAXXZ 00401020 f CrashDemo。obj 对与上述结果,在使用我的公式时

,"崩溃函数绝对地址"指00401020, 函数相对偏移指 00000020,

当崩溃地址= 0x0040104a时, 则 崩溃行偏移 = 崩溃地址 - 崩溃函数起始地址+ 函数相对偏移 = 0x0040104a - 0x00401020 + 0x00000020= 0x4a,结果与"罗文"计算结果相同

。但这个公式更通用。

四、更好的定位崩溃语句位置的方法。

其实除了依靠map文件中的Line numbers信息最终定位出错语句行外,在vc6中我们还可以通过编译程序产生的对应的汇编语句,二进制码,以及对应c/c++语句为一体的"cod"文件来定位出错语句行

。先介绍一下产生这种包含了三种信息的"cod"文件的设置方法:先点击菜单"Project"-"Settings。。。",弹出的属性页中选中"C/C++"页

,然后在"Category"中选则"Listing Files",再在"Listing file type"的组合框中选择"Assembly,Machine code, and source"。接下去再通过一个具体的例子来说明这种方法的具体操作。

图三 设置产生"cod"文件

准备步骤1)产生崩溃的程序如下:

01 //****************************************************************

02 //文件名称:crash。cpp

03 //作用: 演示通过崩溃地址找出源代码的出错行新方法

04 //作者: 伟功通信 roc

05 //日期: 2005-5-16

06//****************************************************************

07 void Crash1();

08 int main(int argc,char* argv[])

09 {

10Crash1();

11return 0;

12 }

13

14 void Crash1()

15 {

16 char * p =(char*)100;

17 *p=100;

18 }

准备步骤2)按本文所述设置产生map文件(不需要产生Line numbers信息)。

准备步骤3)按本文所述设置产生cod文件。

准备步骤4)编译。这里以debug版本为例(若是release版本需要将编译选项改为不进行任何优化的选项,否则上述代码会因为优化时看作废代码而不被编译,从而看不到崩溃的结果),编译后产生一个"exe"文件

,一个"map"文件,一个"cod"文件。

运行此程序,产生如下如下崩溃提示:

图四 上面例子运行结果

排错步骤1)定位崩溃函数。可以查询map文件获得。我的机器编译产生的map文件的部分如下:

Crash

Timestamp is 42881a01 (Mon May 16 11:56:49 2005)

Preferred load address is 00400000

Start Length Name Class

0001:00000000 0000ddf1H .text CODE

0001:0000ddf1 0001000fH .textbss CODE

0002:00000000 00001346H .rdata DATA

0002:00001346 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 00000b93H .data DATA

0003:000015c4 00001974H .bss DATA

0004:00000000 00000014H .idata$2 DATA

0004:00000014 00000014H .idata$3 DATA

0004:00000028 00000110H .idata$4 DATA

0004:00000138 00000110H .idata$5 DATA

0004:00000248 000004afH .idata$6 DATA

Address Publics by Value Rva+Base Lib:Object

0001:00000020 _main 00401020 f Crash.obj

0001:00000060 ?Crash1@@YAXXZ 00401060 f Crash.obj

0001:000000a0 __chkesp 004010a0 f LIBCD:chkesp.obj

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

0001:00000210 __amsg_exit 00401210 f LIBCD:crt0.obj

0001:00000270 __CrtDbgBreak 00401270 f LIBCD:dbgrpt.obj

...

对于崩溃地址0x00401082而言,小于此地址中最接近的地址(Rva+Base中的地址)为00401060,其对应的函数名为?Crash1@@YAXXZ,由于所有以问号开头的函数名称都是 C++ 修饰的名称

,"@@YAXXZ"则为区别重载函数而加的后缀,所以?Crash1@@YAXXZ就是我们的源程序中,Crash1() 这个函数。

排错步骤2)定位出错行。打开编译生成的"cod"文件,我机器上生成的文件内容如下:

TITLEE:\Crash\Crash。cpp

.386P

include listing.inc

if @Version gt 510

.model FLAT

else

_TEXTSEGMENT PARA USE32 PUBLIC ''CODE''

_TEXTENDS

_DATASEGMENT DWORD USE32 PUBLIC ''DATA''

_DATAENDS

CONSTSEGMENT DWORD USE32 PUBLIC ''CONST''

CONSTENDS

_BSSSEGMENT DWORD USE32 PUBLIC ''BSS''

_BSSENDS

$$SYMBOLSSEGMENT BYTE USE32 ''DEBSYM''

$$SYMBOLSENDS

$$TYPESSEGMENT BYTE USE32 ''DEBTYP''

$$TYPESENDS

_TLSSEGMENT DWORD USE32 PUBLIC ''TLS''

_TLSENDS

;COMDAT _main

_TEXTSEGMENT PARA USE32 PUBLIC ''CODE''

_TEXTENDS

;COMDAT ?Crash1@@YAXXZ

_TEXTSEGMENT PARA USE32 PUBLIC ''CODE''

_TEXTENDS

FLATGROUP _DATA, CONST, _BSS

ASSUMECS: FLAT, DS: FLAT, SS: FLAT

endif

PUBLIC?Crash1@@YAXXZ; Crash1

PUBLIC_main

EXTRN__chkesp:NEAR

;COMDAT _main

_TEXTSEGMENT

_mainPROC NEAR; COMDAT

; 9 : {

0000055 push ebp

000018b ec mov ebp, esp

0000383 ec 40 sub esp, 64; 00000040H

0000653 push ebx

0000756 push esi

0000857 push edi

000098d 7d c0 lea edi, DWORD PTR [ebp-64]

0000cb9 10 00 00 00 mov ecx, 16; 00000010H

00011b8 cc cc cc cc mov eax, -858993460; ccccccccH

00016f3 ab rep stosd

; 10 : Crash1();

00018e8 00 00 00 00 call ?Crash1@@YAXXZ; Crash1

; 11 : return 0;

0001d33 c0 xor eax, eax

; 12 : }

0001f5f pop edi

000205e pop esi

000215b pop ebx

0002283 c4 40 add esp, 64; 00000040H

000253b ec cmp ebp, esp

00027e8 00 00 00 00 call __chkesp

0002c8b e5 mov esp, ebp

0002e5d pop ebp

0002fc3 ret 0

_mainENDP

_TEXTENDS

;COMDAT ?Crash1@@YAXXZ

_TEXTSEGMENT

_p$ = -4

?Crash1@@YAXXZ PROC NEAR; Crash1, COMDAT

; 15 : {

0000055 push ebp

000018b ec mov ebp, esp

0000383 ec 44 sub esp, 68; 00000044H

0000653 push ebx

0000756 push esi

0000857 push edi

000098d 7d bc lea edi, DWORD PTR [ebp-68]

0000cb9 11 00 00 00 mov ecx, 17; 00000011H

00011b8 cc cc cc cc mov eax, -858993460; ccccccccH

00016f3 ab rep stosd

; 16 : char * p =(char*)100;

00018c7 45 fc 64 00

00 00 mov DWORD PTR _p$[ebp], 100; 00000064H

; 17 : *p=100;

0001f8b 45 fc mov eax, DWORD PTR _p$[ebp]

00022c6 00 64 mov BYTE PTR [eax], 100; 00000064H

; 18 : }

000255f pop edi

000265e pop esi

000275b pop ebx

000288b e5 mov esp, ebp

0002a5d pop ebp

0002bc3 ret 0

?Crash1@@YAXXZ ENDP; Crash1

_TEXTENDS

END

其中

?Crash1@@YAXXZ PROC NEAR; Crash1, COMDAT为Crash1汇编代码的起始行。产生崩溃的代码便在其后的某个位置。接下去的一行为:

; 15 : {冒号后的"{"表示源文件中的语句,冒号前的"15"表示该语句在源文件中的行数。

这之后显示该语句汇编后的偏移地址,二进制码,汇编代码。如

0000055 push ebp其中"0000"表示相对于函数开始地址后的偏移,"55"为编译后的机器代码," push ebp"为汇编代码。从"cod"文件中我们可以看出,一条(c/c++)语句通常需要编译成数条汇编语句

。此外有些汇编语句太长则会分两行显示如:

00018c7 45 fc 64 00

00 00 mov DWORD PTR _p$[ebp], 100; 00000064H其中"0018"表示相对偏移,在debug版本中,这个数据为相对于函数起始地址的偏移(此时每个函数第一条语句相对偏移为0000);release版本中为相对于代码段第一条语句的偏移(即代码段第一条语句相对偏移为0000,而以后的每个函数第一条语句相对偏移就不为0000了)。"c7 45 fc 64 00 00 00 "为编译后的机器代码

,"mov DWORD PTR _p$[ebp], 100"为汇编代码, 汇编语言中";"后的内容为注释,所以";00000064H",是个注释这里用来说明100转换成16进制时为"00000064H"。

接下去,我们开始来定位产生崩溃的语句。

第一步,计算崩溃地址相对于崩溃函数的偏移,在本例中已经知道了崩溃语句的地址(0x00401082),和对应函数的起始地址(0x00401060),所以崩溃地址相对函数起始地址的偏移就很容易计算了:

崩溃偏移地址 = 崩溃语句地址 - 崩溃函数的起始地址 = 0x00401082 - 0x00401060 = 0x22。第二步,计算出错的汇编语句在cod文件中的相对偏移。我们可以看到函数Crash1()在cod文件中的相对偏移地址为0000,则

崩溃语句在cod文件中的相对偏移 = 崩溃函数在cod文件中相对偏移 + 崩溃偏移地址 = 0x0000 + 0x22 = 0x22第三步,我们看Crash1函数偏移0x22除的代码是什么?结果如下

00022c6 00 64 mov BYTE PTR [eax], 100; 00000064H这句汇编语句表示将100这个数保存到寄存器eax所指的内存单元中去,保存空间大小为1个字节(byte)。程序正是执行这条命令时产生了崩溃,显然这里eax中的为一个非法地址

,所以程序崩溃了!

第四步,再查看该汇编语句在其前面几行的其对应的源代码,结果如下:

; 17 : *p=100;其中17表示该语句位于源文件中第17行,而“*p=100;”这正是源文件中产生崩溃的语句。

至此我们仅从崩溃地址就查找出了造成崩溃的源代码语句和该语句所在源文件中的确切位置,甚至查找到了造成崩溃的编译后的确切汇编代码!

怎么样,是不是感觉更爽啊?

五、小节

1、新方法同样要注意可以适用的范围,即程序由一条语句当即引起的崩溃。另外我不知道除了VC6外,是否还有其他的编译器能够产生类似的"cod"文件。

2、我们可以通过比较 新方法产生的debug和releae版本的"cod"文件,查找那些仅release版本(或debug版本)有另一个版本没有的bug(或其他性状)。例如"罗文"中所举的那个用例

,只要打开release版本的"cod"文件,就明白了为啥debug版本会产生崩溃而release版本却没有:原来release版本中产生崩溃的语句其实根本都没有编译

。同样本例中的release版本要看到崩溃的效果,需要将编译选项改为为不优化的配置。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有