分类: C/C++
2008-04-23 21:59:12
对“仅通过崩溃地址找出源代码的出错行”一文的补充与改进
作者:
读了老罗的“仅通过崩溃地址找出源代码的出错行”(下称"罗文")一文后,感觉该文还是可以学到不少东西的。不过文中尚存在有些说法不妥,以及有些操作太繁琐的地方
。为此,本人在学习了此文后,在多次实验实践基础上,把该文中的一些内容进行补充与改进,希望对大家调试程序,尤其是release版本的程序有帮助
。欢迎各位朋友批评指正。
一、该方法适用的范围
在windows程序中造成程序崩溃的原因很多,而文中所述的方法仅适用与:由一条语句当即引起的程序崩溃。如原文中举的除数为零的崩溃例子。而笔者在实际工作中碰到更多的情况是:指针指向一非法地址
,然后对指针的内容进行了,读或写的操作。例如:
void Crash1() { char * p =(char*)100; *p=100; }这些原因造成的崩溃,无论是debug版本,还是release版本的程序,使用该方法都可找到造成崩溃的函数或子程序中的语句行,具体方法的下面还会补充说明。 另外,实践中另一种常见的造成程序崩溃的原因:函数或子程序中局部变量数组越界付值,造成函数或子程序返回地址遭覆盖,从而造成函数或子程序返回时崩溃。例如:
#include在vc中编译运行此程序的release版本,会跳出如下的出错提示框。void Crash2(); int main(int argc,char* argv[]) { Crash2(); return 0; } void Crash2() { char p[1]; strcpy(p,"0123456789"); }
崩溃行偏移 = 崩溃地址 - 0x00400000 - 0x1000的公式显然无法找到崩溃行偏移。 其实上述公式若改为
崩溃行偏移 = 崩溃地址 - 崩溃函数绝对地址 函数相对偏移即可通用了。仍以"罗文"中的例子为例:"罗文"中提到的在其崩溃程序的对应map文件中,崩溃函数的编译结果为
0001:00000020 ?Crash@@YAXXZ 00401020 f CrashDemo。obj对与上述结果,在使用我的公式时 ,"崩溃函数绝对地址"指00401020, 函数相对偏移指 00000020, 当崩溃地址= 0x0040104a时, 则 崩溃行偏移 = 崩溃地址 - 崩溃函数起始地址 函数相对偏移 = 0x0040104a - 0x00401020 0x00000020= 0x4a,结果与"罗文"计算结果相同 。但这个公式更通用。
01 //**************************************************************** 02 //文件名称:crash。cpp 03 //作用: 演示通过崩溃地址找出源代码的出错行新方法 04 //作者: 伟功通信 roc 05 //日期: 2005-5-16 06//**************************************************************** 07 void Crash1(); 08 int main(int argc,char* argv[]) 09 { 10 Crash1(); 11 return 0; 12 } 13 14 void Crash1() 15 { 16 char * p =(char*)100; 17 *p=100; 18 }准备步骤2)按本文所述设置产生map文件(不需要产生Line numbers信息)。
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() 这个函数。
TITLE E:\Crash\Crash。cpp .386P include listing.inc if @Version gt 510 .model FLAT else _TEXT SEGMENT PARA USE32 PUBLIC ''CODE'' _TEXT ENDS _DATA SEGMENT DWORD USE32 PUBLIC ''DATA'' _DATA ENDS CONST SEGMENT DWORD USE32 PUBLIC ''CONST'' CONST ENDS _BSS SEGMENT DWORD USE32 PUBLIC ''BSS'' _BSS ENDS $$SYMBOLS SEGMENT BYTE USE32 ''DEBSYM'' $$SYMBOLS ENDS $$TYPES SEGMENT BYTE USE32 ''DEBTYP'' $$TYPES ENDS _TLS SEGMENT DWORD USE32 PUBLIC ''TLS'' _TLS ENDS ; COMDAT _main _TEXT SEGMENT PARA USE32 PUBLIC ''CODE'' _TEXT ENDS ; COMDAT ?Crash1@@YAXXZ _TEXT SEGMENT PARA USE32 PUBLIC ''CODE'' _TEXT ENDS FLAT GROUP _DATA, CONST, _BSS ASSUME CS: FLAT, DS: FLAT, SS: FLAT endif PUBLIC ?Crash1@@YAXXZ ; Crash1 PUBLIC _main EXTRN __chkesp:NEAR ; COMDAT _main _TEXT SEGMENT _main PROC NEAR ; COMDAT ; 9 : { 00000 55 push ebp 00001 8b ec mov ebp, esp 00003 83 ec 40 sub esp, 64 ; 00000040H 00006 53 push ebx 00007 56 push esi 00008 57 push edi 00009 8d 7d c0 lea edi, DWORD PTR [ebp-64] 0000c b9 10 00 00 00 mov ecx, 16 ; 00000010H 00011 b8 cc cc cc cc mov eax, -858993460 ; ccccccccH 00016 f3 ab rep stosd ; 10 : Crash1(); 00018 e8 00 00 00 00 call ?Crash1@@YAXXZ ; Crash1 ; 11 : return 0; 0001d 33 c0 xor eax, eax ; 12 : } 0001f 5f pop edi 00020 5e pop esi 00021 5b pop ebx 00022 83 c4 40 add esp, 64 ; 00000040H 00025 3b ec cmp ebp, esp 00027 e8 00 00 00 00 call __chkesp 0002c 8b e5 mov esp, ebp 0002e 5d pop ebp 0002f c3 ret 0 _main ENDP _TEXT ENDS ; COMDAT ?Crash1@@YAXXZ _TEXT SEGMENT _p$ = -4 ?Crash1@@YAXXZ PROC NEAR ; Crash1, COMDAT ; 15 : { 00000 55 push ebp 00001 8b ec mov ebp, esp 00003 83 ec 44 sub esp, 68 ; 00000044H 00006 53 push ebx 00007 56 push esi 00008 57 push edi 00009 8d 7d bc lea edi, DWORD PTR [ebp-68] 0000c b9 11 00 00 00 mov ecx, 17 ; 00000011H 00011 b8 cc cc cc cc mov eax, -858993460 ; ccccccccH 00016 f3 ab rep stosd ; 16 : char * p =(char*)100; 00018 c7 45 fc 64 00 00 00 mov DWORD PTR _p$[ebp], 100 ; 00000064H ; 17 : *p=100; 0001f 8b 45 fc mov eax, DWORD PTR _p$[ebp] 00022 c6 00 64 mov BYTE PTR [eax], 100 ; 00000064H ; 18 : } 00025 5f pop edi 00026 5e pop esi 00027 5b pop ebx 00028 8b e5 mov esp, ebp 0002a 5d pop ebp 0002b c3 ret 0 ?Crash1@@YAXXZ ENDP ; Crash1 _TEXT ENDS END其中
?Crash1@@YAXXZ PROC NEAR ; Crash1, COMDAT为Crash1汇编代码的起始行。产生崩溃的代码便在其后的某个位置。接下去的一行为:
; 15 : {冒号后的"{"表示源文件中的语句,冒号前的"15"表示该语句在源文件中的行数。 这之后显示该语句汇编后的偏移地址,二进制码,汇编代码。如
00000 55 push ebp其中"0000"表示相对于函数开始地址后的偏移,"55"为编译后的机器代码," push ebp"为汇编代码。从"cod"文件中我们可以看出,一条(c/c )语句通常需要编译成数条汇编语句 。此外有些汇编语句太长则会分两行显示如:
00018 c7 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 = 0x22。第二步,计算出错的汇编语句在cod文件中的相对偏移。我们可以看到函数Crash1()在cod文件中的相对偏移地址为0000,则
崩溃语句在cod文件中的相对偏移 = 崩溃函数在cod文件中相对偏移 崩溃偏移地址 = 0x0000 0x22 = 0x22第三步,我们看Crash1函数偏移0x22除的代码是什么?结果如下
00022 c6 00 64 mov BYTE PTR [eax], 100 ; 00000064H这句汇编语句表示将100这个数保存到寄存器eax所指的内存单元中去,保存空间大小为1个字节(byte)。程序正是执行这条命令时产生了崩溃,显然这里eax中的为一个非法地址 ,所以程序崩溃了!
; 17 : *p=100;其中17表示该语句位于源文件中第17行,而“*p=100;”这正是源文件中产生崩溃的语句。