对“仅通过崩溃地址找出源代码的出错行”一文的补充与改进
2012-08-13 17:25
387 查看
读了老罗的“仅通过崩溃地址找出源代码的出错行”(下称"罗文")一文后,感觉该文还是可以学到不少东西的。不过文中尚存在有些说法不妥,以及有些操作太繁琐的地方。为此,本人在学习了此文后,在多次实验实践基础上,把该文中的一些内容进行补充与改进,希望对大家调试程序,尤其是release版本的程序有帮助。欢迎各位朋友批评指正。
一、该方法适用的范围
在windows程序中造成程序崩溃的原因很多,而文中所述的方法仅适用与:由一条语句当即引起的程序崩溃。如原文中举的除数为零的崩溃例子。而笔者在实际工作中碰到更多的情况是:指针指向一非法地址,然后对指针的内容进行了,读或写的操作。例如:
这些原因造成的崩溃,无论是debug版本,还是release版本的程序,使用该方法都可找到造成崩溃的函数或子程序中的语句行,具体方法的下面还会补充说明。另外,实践中另一种常见的造成程序崩溃的原因:函数或子程序中局部变量数组越界付值,造成函数或子程序返回地址遭覆盖,从而造成函数或子程序返回时崩溃。例如:
在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",最后选中"Generatemapfile"的可选项。若要在在map文件中显示Linenumbers的信息的话,还需在projectoptions中加入/mapinfo:lines。Linenumbers信息对于"罗文"所用的方法来定位出错源代码行很重要
,但笔者后面会介绍更加好的方法来定位出错代码行,那种方法不需要Linenumbers信息。
图二设置产生MAP文件
三、定位崩溃语句位置的方法
"罗文"所述的定位方法中,找到产生崩溃的函数位置的方法是正确的,即在map文件列出的每个函数的起始地址中,最近的且不大于崩溃地址的地址即为包含崩溃语句的函数的地址。但之后的再进一步的定位出错语句行的方法不是最妥当,因为那种方法前提是,假设基地址的值是0x00400000,以及一般的PE文件的代码段都是从0x1000偏移开始的。虽然这种情况很普遍,但在vc中还是可以基地址设置为其他数,比如设置为0x00500000,这时仍旧套用
的公式显然无法找到崩溃行偏移。其实上述公式若改为
即可通用了。仍以"罗文"中的例子为例:"罗文"中提到的在其崩溃程序的对应map文件中,崩溃函数的编译结果为
对与上述结果,在使用我的公式时,"崩溃函数绝对地址"指00401020,函数相对偏移指00000020,当崩溃地址=0x0040104a时,则崩溃行偏移=崩溃地址-崩溃函数起始地址+函数相对偏移=0x0040104a-0x00401020+0x00000020=0x4a,结果与"罗文"计算结果相同。但这个公式更通用。
四、更好的定位崩溃语句位置的方法。
其实除了依靠map文件中的Linenumbers信息最终定位出错语句行外,在vc6中我们还可以通过编译程序产生的对应的汇编语句,二进制码,以及对应c/c++语句为一体的"cod"文件来定位出错语句行。先介绍一下产生这种包含了三种信息的"cod"文件的设置方法:先点击菜单"Project"->"Settings。。。",弹出的属性页中选中"C/C++"页,然后在"Category"中选则"ListingFiles",再在"Listingfiletype"的组合框中选择"Assembly,Machine
code,andsource"。接下去再通过一个具体的例子来说明这种方法的具体操作。
图三设置产生"cod"文件
准备步骤1)产生崩溃的程序如下:
准备步骤2)按本文所述设置产生map文件(不需要产生Linenumbers信息)。
准备步骤3)按本文所述设置产生cod文件。
准备步骤4)编译。这里以debug版本为例(若是release版本需要将编译选项改为不进行任何优化的选项,否则上述代码会因为优化时看作废代码而不被编译,从而看不到崩溃的结果),编译后产生一个"exe"文件,一个"map"文件,一个"cod"文件。
运行此程序,产生如下如下崩溃提示:
图四上面例子运行结果
排错步骤1)定位崩溃函数。可以查询map文件获得。我的机器编译产生的map文件的部分如下:
对于崩溃地址0x00401082而言,小于此地址中最接近的地址(Rva+Base中的地址)为00401060,其对应的函数名为?Crash1@@YAXXZ,由于所有以问号开头的函数名称都是C++修饰的名称,"@@YAXXZ"则为区别重载函数而加的后缀,所以?Crash1@@YAXXZ就是我们的源程序中,Crash1()这个函数。
排错步骤2)定位出错行。打开编译生成的"cod"文件,我机器上生成的文件内容如下:
其中
为Crash1汇编代码的起始行。产生崩溃的代码便在其后的某个位置。接下去的一行为:
冒号后的"{"表示源文件中的语句,冒号前的"15"表示该语句在源文件中的行数。这之后显示该语句汇编后的偏移地址,二进制码,汇编代码。如
其中"0000"表示相对于函数开始地址后的偏移,"55"为编译后的机器代码,"pushebp"为汇编代码。从"cod"文件中我们可以看出,一条(c/c++)语句通常需要编译成数条汇编语句。此外有些汇编语句太长则会分两行显示如:
其中"0018"表示相对偏移,在debug版本中,这个数据为相对于函数起始地址的偏移(此时每个函数第一条语句相对偏移为0000);release版本中为相对于代码段第一条语句的偏移(即代码段第一条语句相对偏移为0000,而以后的每个函数第一条语句相对偏移就不为0000了)。"c745fc64000000"为编译后的机器代码,"movDWORDPTR_p$[ebp],100"为汇编代码,汇编语言中";"后的内容为注释,所以";00000064H",是个注释这里用来说明100转换成16进制时为"00000064H"。
接下去,我们开始来定位产生崩溃的语句。
第一步,计算崩溃地址相对于崩溃函数的偏移,在本例中已经知道了崩溃语句的地址(0x00401082),和对应函数的起始地址(0x00401060),所以崩溃地址相对函数起始地址的偏移就很容易计算了:
第二步,计算出错的汇编语句在cod文件中的相对偏移。我们可以看到函数Crash1()在cod文件中的相对偏移地址为0000,则
第三步,我们看Crash1函数偏移0x22除的代码是什么?结果如下
这句汇编语句表示将100这个数保存到寄存器eax所指的内存单元中去,保存空间大小为1个字节(byte)。程序正是执行这条命令时产生了崩溃,显然这里eax中的为一个非法地址,所以程序崩溃了!
第四步,再查看该汇编语句在其前面几行的其对应的源代码,结果如下:
其中17表示该语句位于源文件中第17行,而“*p=100;”这正是源文件中产生崩溃的语句。
至此我们仅从崩溃地址就查找出了造成崩溃的源代码语句和该语句所在源文件中的确切位置,甚至查找到了造成崩溃的编译后的确切汇编代码!
怎么样,是不是感觉更爽啊?
五、小节
1、新方法同样要注意可以适用的范围,即程序由一条语句当即引起的崩溃。另外我不知道除了VC6外,是否还有其他的编译器能够产生类似的"cod"文件。
2、我们可以通过比较新方法产生的debug和releae版本的"cod"文件,查找那些仅release版本(或debug版本)有另一个版本没有的bug(或其他性状)。例如"罗文"中所举的那个用例,只要打开release版本的"cod"文件,就明白了为啥debug版本会产生崩溃而release版本却没有:原来release版本中产生崩溃的语句其实根本都没有编译。同样本例中的release版本要看到崩溃的效果,需要将编译选项改为为不优化的配置。
一、该方法适用的范围
在windows程序中造成程序崩溃的原因很多,而文中所述的方法仅适用与:由一条语句当即引起的程序崩溃。如原文中举的除数为零的崩溃例子。而笔者在实际工作中碰到更多的情况是:指针指向一非法地址,然后对指针的内容进行了,读或写的操作。例如:
1.
void
Crash1()
2.
{
3.
char
*p=(
char
*)100;
4.
*p=100;
5.
}
这些原因造成的崩溃,无论是debug版本,还是release版本的程序,使用该方法都可找到造成崩溃的函数或子程序中的语句行,具体方法的下面还会补充说明。另外,实践中另一种常见的造成程序崩溃的原因:函数或子程序中局部变量数组越界付值,造成函数或子程序返回地址遭覆盖,从而造成函数或子程序返回时崩溃。例如:
01.
#include
02.
void
Crash2();
03.
int
main(
int
argc,
char
*argv[])
04.
{
05.
Crash2();
06.
return
0;
07.
}
08.
09.
void
Crash2()
10.
{
11.
char
p[1];
12.
strcpy
(p,
"0123456789"
);
13.
}
在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",最后选中"Generatemapfile"的可选项。若要在在map文件中显示Linenumbers的信息的话,还需在projectoptions中加入/mapinfo:lines。Linenumbers信息对于"罗文"所用的方法来定位出错源代码行很重要
,但笔者后面会介绍更加好的方法来定位出错代码行,那种方法不需要Linenumbers信息。
图二设置产生MAP文件
三、定位崩溃语句位置的方法
"罗文"所述的定位方法中,找到产生崩溃的函数位置的方法是正确的,即在map文件列出的每个函数的起始地址中,最近的且不大于崩溃地址的地址即为包含崩溃语句的函数的地址。但之后的再进一步的定位出错语句行的方法不是最妥当,因为那种方法前提是,假设基地址的值是0x00400000,以及一般的PE文件的代码段都是从0x1000偏移开始的。虽然这种情况很普遍,但在vc中还是可以基地址设置为其他数,比如设置为0x00500000,这时仍旧套用
1.
崩溃行偏移=崩溃地址-0x00400000-0x1000
的公式显然无法找到崩溃行偏移。其实上述公式若改为
1.
崩溃行偏移=崩溃地址-崩溃函数绝对地址+函数相对偏移
即可通用了。仍以"罗文"中的例子为例:"罗文"中提到的在其崩溃程序的对应map文件中,崩溃函数的编译结果为
1.
0001:00000020?Crash@@YAXXZ00401020fCrashDemo。obj
对与上述结果,在使用我的公式时,"崩溃函数绝对地址"指00401020,函数相对偏移指00000020,当崩溃地址=0x0040104a时,则崩溃行偏移=崩溃地址-崩溃函数起始地址+函数相对偏移=0x0040104a-0x00401020+0x00000020=0x4a,结果与"罗文"计算结果相同。但这个公式更通用。
四、更好的定位崩溃语句位置的方法。
其实除了依靠map文件中的Linenumbers信息最终定位出错语句行外,在vc6中我们还可以通过编译程序产生的对应的汇编语句,二进制码,以及对应c/c++语句为一体的"cod"文件来定位出错语句行。先介绍一下产生这种包含了三种信息的"cod"文件的设置方法:先点击菜单"Project"->"Settings。。。",弹出的属性页中选中"C/C++"页,然后在"Category"中选则"ListingFiles",再在"Listingfiletype"的组合框中选择"Assembly,Machine
code,andsource"。接下去再通过一个具体的例子来说明这种方法的具体操作。
图三设置产生"cod"文件
准备步骤1)产生崩溃的程序如下:
01.
01
//****************************************************************
02.
02
//文件名称:crash。cpp
03.
03
//作用:演示通过崩溃地址找出源代码的出错行新方法
04.
04
//作者:伟功通信roc
05.
05
//日期:2005-5-16
06.
06
//****************************************************************
07.
07
void
Crash1();
08.
08
int
main(
int
argc,
char
*argv[])
09.
09{
10.
10Crash1();
11.
11
return
0;
12.
12}
13.
13
14.
14
void
Crash1()
15.
15{
16.
16
char
*p=(
char
*)100;
17.
17*p=100;
18.
18}
准备步骤2)按本文所述设置产生map文件(不需要产生Linenumbers信息)。
准备步骤3)按本文所述设置产生cod文件。
准备步骤4)编译。这里以debug版本为例(若是release版本需要将编译选项改为不进行任何优化的选项,否则上述代码会因为优化时看作废代码而不被编译,从而看不到崩溃的结果),编译后产生一个"exe"文件,一个"map"文件,一个"cod"文件。
运行此程序,产生如下如下崩溃提示:
图四上面例子运行结果
排错步骤1)定位崩溃函数。可以查询map文件获得。我的机器编译产生的map文件的部分如下:
01.
Crash
02.
03.
Timestampis42881a01(MonMay1611:56:492005)
04.
05.
Preferredloadaddressis00400000
06.
07.
StartLengthNameClass
08.
0001:000000000000ddf1H.textCODE
09.
0001:0000ddf10001000fH.textbssCODE
10.
0002:0000000000001346H.rdataDATA
11.
0002:0000134600000000H.edataDATA
12.
0003:0000000000000104H.CRT$XCADATA
13.
0003:0000010400000104H.CRT$XCZDATA
14.
0003:0000020800000104H.CRT$XIADATA
15.
0003:0000030c00000109H.CRT$XICDATA
16.
0003:0000041800000104H.CRT$XIZDATA
17.
0003:0000051c00000104H.CRT$XPADATA
18.
0003:0000062000000104H.CRT$XPXDATA
19.
0003:0000072400000104H.CRT$XPZDATA
20.
0003:0000082800000104H.CRT$XTADATA
21.
0003:0000092c00000104H.CRT$XTZDATA
22.
0003:00000a3000000b93H.dataDATA
23.
0003:000015c400001974H.bssDATA
24.
0004:0000000000000014H.idata$2DATA
25.
0004:0000001400000014H.idata$3DATA
26.
0004:0000002800000110H.idata$4DATA
27.
0004:0000013800000110H.idata$5DATA
28.
0004:00000248000004afH.idata$6DATA
29.
30.
AddressPublicsbyValueRva+BaseLib:Object
31.
32.
0001:00000020_main00401020fCrash.obj
33.
0001:00000060?Crash1@@YAXXZ00401060fCrash.obj
34.
0001:000000a0__chkesp004010a0fLIBCD:chkesp.obj
35.
0001:000000e0_mainCRTStartup004010e0fLIBCD:crt0.obj
36.
0001:00000210__amsg_exit00401210fLIBCD:crt0.obj
37.
0001:00000270__CrtDbgBreak00401270fLIBCD:dbgrpt.obj
38.
...
对于崩溃地址0x00401082而言,小于此地址中最接近的地址(Rva+Base中的地址)为00401060,其对应的函数名为?Crash1@@YAXXZ,由于所有以问号开头的函数名称都是C++修饰的名称,"@@YAXXZ"则为区别重载函数而加的后缀,所以?Crash1@@YAXXZ就是我们的源程序中,Crash1()这个函数。
排错步骤2)定位出错行。打开编译生成的"cod"文件,我机器上生成的文件内容如下:
001.
TITLEE:\Crash\Crash。cpp
002.
.386P
003.
includelisting.inc
004.
if
@Versiongt510
005.
.modelFLAT
006.
else
007.
_TEXTSEGMENTPARAUSE32PUBLIC
''
CODE
''
008.
_TEXTENDS
009.
_DATASEGMENT
DWORD
USE32PUBLIC
''
DATA
''
010.
_DATAENDS
011.
CONSTSEGMENT
DWORD
USE32PUBLIC
''
CONST
''
012.
CONSTENDS
013.
_BSSSEGMENT
DWORD
USE32PUBLIC
''
BSS
''
014.
_BSSENDS
015.
$$SYMBOLSSEGMENT
BYTE
USE32
''
DEBSYM
''
016.
$$SYMBOLSENDS
017.
$$TYPESSEGMENT
BYTE
USE32
''
DEBTYP
''
018.
$$TYPESENDS
019.
_TLSSEGMENT
DWORD
USE32PUBLIC
''
TLS
''
020.
_TLSENDS
021.
;COMDAT_main
022.
_TEXTSEGMENTPARAUSE32PUBLIC
''
CODE
''
023.
_TEXTENDS
024.
;COMDAT?Crash1@@YAXXZ
025.
_TEXTSEGMENTPARAUSE32PUBLIC
''
CODE
''
026.
_TEXTENDS
027.
FLATGROUP_DATA,CONST,_BSS
028.
ASSUMECS:FLAT,DS:FLAT,SS:FLAT
029.
endif
030.
PUBLIC?Crash1@@YAXXZ;Crash1
031.
PUBLIC_main
032.
EXTRN__chkesp:NEAR
033.
;COMDAT_main
034.
_TEXTSEGMENT
035.
_mainPROCNEAR;COMDAT
036.
037.
;9:{
038.
039.
0000055pushebp
040.
000018becmovebp,esp
041.
0000383ec40subesp,64;00000040H
042.
0000653pushebx
043.
0000756pushesi
044.
0000857pushedi
045.
000098d7dc0leaedi,
DWORD
PTR[ebp-64]
046.
0000cb910000000movecx,16;00000010H
047.
00011b8ccccccccmoveax,-858993460;ccccccccH
048.
00016f3abrepstosd
049.
050.
;10:Crash1();
051.
052.
00018e800000000call?Crash1@@YAXXZ;Crash1
053.
054.
;11:
return
0;
055.
056.
0001d33c0xoreax,eax
057.
058.
;12:}
059.
060.
0001f5fpopedi
061.
000205epopesi
062.
000215bpopebx
063.
0002283c440addesp,64;00000040H
064.
000253beccmpebp,esp
065.
00027e800000000call__chkesp
066.
0002c8be5movesp,ebp
067.
0002e5dpopebp
068.
0002fc3ret0
069.
_mainENDP
070.
_TEXTENDS
071.
;COMDAT?Crash1@@YAXXZ
072.
_TEXTSEGMENT
073.
_p$=-4
074.
?Crash1@@YAXXZPROCNEAR;Crash1,COMDAT
075.
076.
;15:{
077.
078.
0000055pushebp
079.
000018becmovebp,esp
080.
0000383ec44subesp,68;00000044H
081.
0000653pushebx
082.
0000756pushesi
083.
0000857pushedi
084.
000098d7dbcleaedi,
DWORD
PTR[ebp-68]
085.
0000cb911000000movecx,17;00000011H
086.
00011b8ccccccccmoveax,-858993460;ccccccccH
087.
00016f3abrepstosd
088.
089.
;16:
char
*p=(
char
*)100;
090.
091.
00018c745fc6400
092.
0000mov
DWORD
PTR_p$[ebp],100;00000064H
093.
094.
;17:*p=100;
095.
096.
0001f8b45fcmoveax,
DWORD
PTR_p$[ebp]
097.
00022c60064mov
BYTE
PTR[eax],100;00000064H
098.
099.
;18:}
100.
101.
000255fpopedi
102.
000265epopesi
103.
000275bpopebx
104.
000288be5movesp,ebp
105.
0002a5dpopebp
106.
0002bc3ret0
107.
?Crash1@@YAXXZENDP;Crash1
108.
_TEXTENDS
109.
END
其中
1.
?Crash1@@YAXXZPROCNEAR;Crash1,COMDAT
为Crash1汇编代码的起始行。产生崩溃的代码便在其后的某个位置。接下去的一行为:
1.
;15:{
冒号后的"{"表示源文件中的语句,冒号前的"15"表示该语句在源文件中的行数。这之后显示该语句汇编后的偏移地址,二进制码,汇编代码。如
1.
0000055pushebp
其中"0000"表示相对于函数开始地址后的偏移,"55"为编译后的机器代码,"pushebp"为汇编代码。从"cod"文件中我们可以看出,一条(c/c++)语句通常需要编译成数条汇编语句。此外有些汇编语句太长则会分两行显示如:
1.
00018c745fc6400
2.
0000mov
DWORD
PTR_p$[ebp],100;00000064H
其中"0018"表示相对偏移,在debug版本中,这个数据为相对于函数起始地址的偏移(此时每个函数第一条语句相对偏移为0000);release版本中为相对于代码段第一条语句的偏移(即代码段第一条语句相对偏移为0000,而以后的每个函数第一条语句相对偏移就不为0000了)。"c745fc64000000"为编译后的机器代码,"movDWORDPTR_p$[ebp],100"为汇编代码,汇编语言中";"后的内容为注释,所以";00000064H",是个注释这里用来说明100转换成16进制时为"00000064H"。
接下去,我们开始来定位产生崩溃的语句。
第一步,计算崩溃地址相对于崩溃函数的偏移,在本例中已经知道了崩溃语句的地址(0x00401082),和对应函数的起始地址(0x00401060),所以崩溃地址相对函数起始地址的偏移就很容易计算了:
1.
崩溃偏移地址=崩溃语句地址-崩溃函数的起始地址=0x00401082-0x00401060=0x22。
第二步,计算出错的汇编语句在cod文件中的相对偏移。我们可以看到函数Crash1()在cod文件中的相对偏移地址为0000,则
1.
崩溃语句在cod文件中的相对偏移=崩溃函数在cod文件中相对偏移+崩溃偏移地址=0x0000+0x22=0x22
第三步,我们看Crash1函数偏移0x22除的代码是什么?结果如下
.1.
00022c60064mov
BYTE
PTR[eax],100;00000064H
这句汇编语句表示将100这个数保存到寄存器eax所指的内存单元中去,保存空间大小为1个字节(byte)。程序正是执行这条命令时产生了崩溃,显然这里eax中的为一个非法地址,所以程序崩溃了!
第四步,再查看该汇编语句在其前面几行的其对应的源代码,结果如下:
1.
;17:*p=100;
其中17表示该语句位于源文件中第17行,而“*p=100;”这正是源文件中产生崩溃的语句。
至此我们仅从崩溃地址就查找出了造成崩溃的源代码语句和该语句所在源文件中的确切位置,甚至查找到了造成崩溃的编译后的确切汇编代码!
怎么样,是不是感觉更爽啊?
五、小节
1、新方法同样要注意可以适用的范围,即程序由一条语句当即引起的崩溃。另外我不知道除了VC6外,是否还有其他的编译器能够产生类似的"cod"文件。
2、我们可以通过比较新方法产生的debug和releae版本的"cod"文件,查找那些仅release版本(或debug版本)有另一个版本没有的bug(或其他性状)。例如"罗文"中所举的那个用例,只要打开release版本的"cod"文件,就明白了为啥debug版本会产生崩溃而release版本却没有:原来release版本中产生崩溃的语句其实根本都没有编译。同样本例中的release版本要看到崩溃的效果,需要将编译选项改为为不优化的配置。
相关文章推荐
- 对“仅通过崩溃地址找出源代码的出错行”一文的补充与改进
- 对“仅通过崩溃地址找出源代码的出错行”一文的补充与改进
- 对“仅通过崩溃地址找出源代码的出错行”一文的补充与改进
- 对“仅通过崩溃地址找出源代码的出错行”一文的补充与改进,转自vckbase,记录一下
- 转贴:对“仅通过崩溃地址找出源代码的出错行”一文的补充与改进
- 苦于崩溃(2) ------对“仅通过崩溃地址找出源代码的出错行”一文的补充与改进
- 对“仅通过崩溃地址找出源代码的出错行”一文的补充与改进
- 对“仅通过崩溃地址找出源代码的出错行”一文的补充与改进(转)
- 对“仅通过崩溃地址找出源代码的出错行”一文的补充与改进
- 对“仅通过崩溃地址找出源代码的出错行”一文的补充与改进
- 对“仅通过崩溃地址找出源代码的出错行”一文的补充与改进
- 对“仅通过崩溃地址找出源代码的出错行”一文的补充与改进
- 对“仅通过崩溃地址找出源代码的出错行”一文的补充与改进
- 对“仅通过崩溃地址找出源代码的出错行”一文的补充与改进
- 对“仅通过崩溃地址找出源代码的出错行”一文的补充与改进
- 对“仅通过崩溃地址找出源代码的出错行”一文的补充与改进 /原文
- 对“仅通过崩溃地址找出源代码的出错行”一文的补充与改进
- 对“仅通过崩溃地址找出源代码的出错行”一文的补充与改进
- 对“仅通过崩溃地址找出源代码的出错行”一文的补充与改进 /原文
- 对“仅通过崩溃地址找出源代码的出错行”一文的补充与改进