记录程序崩溃时的调用堆栈[转载]

简介
release版本的程序交付给用户使用后,如果应用程序崩溃了,怎么样通过windows提示的offset地址来定位源程序中对应的出错代码呢?这篇文章就来讨论这个问题。

建立调试环境
1.(VC 6)依次选择FileàNewàProjects,在列表中选择“MFC AppWizardEXE)”在“Project Name”中输入“Crash_Test”作为项目名,“OK”进入下一步。在“MFC Appwizard Step 1”对话框中选择“Dialog Based”单选框。“Finish”完成工程的建立,F7编译无误。
2.(VC 6)双击确定按钮,提示加入(重写)“OnOK”函数,OK,进入“CCrash_TestDlg::OnOK()”函数。
3.我们将在OnOK函数中构造一些能够引起程序崩溃的错误代码:
void CCrash_TestDlg::OnOK()
{
AfxGetApp()->GetMainWnd()->SetWindowText(_T(“Crash”));
int* pTest = NULL;
*pTest = 0; // 为空指针赋值,程序将崩溃
CDialog::OnOK();
}
加入“AfxGetApp()->GetMainWnd()->SetWindowText(_T(“Crash”));”的目的是使崩溃地址和“OnOK”的地址有一定偏移,使演示更具一般性。
4.(VC 6)首先设置Release编译(在BuildàSet as configuration中选择Win32 Release)。依次选择ProjectàSettings,选择“C/C++”tab页,Category中选择“Listing Files”,然后在“Listing file type”中选择“Assembly, Machine Code, and Source”,OK。这个选项将为每个源文件(*.cpp)生成机器码、汇编码和源代码的对应表,可以在“Release”目录下找到和查看这些文件。
5.(VC 6)依次选择ProjectàSettings,选择“Linktab页,Category中选择“Debug”,
钩选“Generate map file”,OK。这个选项将生成编译后的函数地址和函数名的对应表。

定位错误代码

1.现在运行“Release”目录下的测试程序,当点击“确定”按钮时,程序将崩溃。
Windows XP 会出现“Crash_Test.exe 遇到问题需要关闭。我们对此引起的不便表示抱歉。”…………
云云的对话框,在对话框的下方,单击“请单击此处”的超连接,在“错误签名”中,我们会得到如下信息:
AppName: crash_test.exeAppVer: 1 lass="pun">.0.0.1ModName: crash_test.exe
ModVer: 1.0.0.1Offset: 000014a0
其中的Offset值就是崩溃的地址啦,现在我们根据这个地址来定位源程序中出错的代码行。
2.用记事本打开“Release”目录下的,“Crash_Test.map”文件,在文件中会看到类似下面的内容:
0001:00000470?OnQueryDragIcon@CCrash_TestDlg@@IAEPAUHICON__@@XZ 00401470 fCrash_TestDlg.obj
0001:00000480?OnOK@CCrash_TestDlg@@MAEXXZ 00401480 fCrash_TestDlg.obj
0001:000004c0?BeginModalState@CWnd@@UAEXXZ 004014c0 f i Crash_TestDlg.obj
0001:000004d0?EndModalState@CWnd@@UAEXXZ 004014d0 f i Crash_TestDlg.obj
画线的部分就是各个函数编译运行后在内存中的相对地址 (高位部分是基地址,就不参与计算啦,基地址可能因机器不同而异,但相对地址都是一样的。) ,因为“Offset”指示的崩溃地址为“14a0”,
1480(OnOK地址) < 14a0(出错地址) < 14c0(BeginModalState地址),很显然,14a0的地址是在OnOK函数的地址范围内的,也就是说OnOK就是引起崩溃的函数。接下来再来确定出错的具体代码行。
3.因为OnOK是在“Crash_TestDlg.cpp”中实现的,因此,我们打开对应的“Crash_TestDlg.cod”文件,其中有如下代码:
?OnOK@CCrash_TestDlg@@MAEXXZ PROC NEAR; CCrash_TestDlg::OnOK, COMDAT
; 173: {
0000056pushesi
000018b f1movesi, ecx

; 175: int* pTest = NULL;
; 176: *pTest = 0;
; 177: CDialog::OnOK();
0001e8b cemovecx, esi
00020c7 05 00 00 00
00 00 00 00 00movDWORD PTR ds:0, 0
0002ae8 00 00 00 00call?OnOK@CDialog@@MAEXXZ; CDialog::OnOK
0002f5epopesi
; 178: }

很明显,这些就是OnOK函数的汇编表示和机器码表示,画线的地址就是函数中各个语句的相对(相对于函数首地址)地址。
在步骤1中,我们已经得
到,
Offset14a0OnOK的函数首地址为0480,现在,我们用14a01480就得到20(注意,这里都是16进制运算),可见,出错的代码行为:
00020c7 05 00 00 00 00 00 00 00 00movDWORD PTR ds:0, 0
对应上面注释的:
; 176: *pTest = 0;