当前位置:编程学习 > C/C++ >>

调试技巧之调用堆栈

在计算机科学中,Callstack 是指存放某个程序的正在运行的函数的信息的栈。Call stack 由stack frames 组成,每个stack frame 对应于一个未完成运行的函数。

在当今流行的计算机体系架构中,大部分计算机的参数传递,局部变量的分配和释放都是通过操纵程序栈来实现的。栈用来传递函数参数,存储返回值信息,保存寄存器以供恢复调用前处理机状态。每次调用一个函数,都要为该次调用的函数实例分配栈空间。为单个函数分配的那部分栈空间就叫做 stack frame,也就是说,stack frame 这个说法主要是为了描述函数调用关系的。

Stackframe 组织方式的重要性和作用体现在两个方面:第一,它使调用者和被调用者达成某种约定。这个约定定义了函数调用时函数参数的传递方式,函数返回值的返回方式,寄存器如何在调用者和被调用者之间进行共享;第二,它定义了被调用者如何使用它自己的 stack frame 来完成局部变量的存储和使用。

简单介绍

    调试是程序开发者必备技巧。如果不会调试,自己写的程序一旦出问题,往往无从下手。本人总结10年使用VC经验,对调试技巧做一个粗浅的介绍。希望对大家有所帮助。

   

    今天简单的介绍介绍调用堆栈。调用堆栈在我的专栏的文章VC调试入门提了一下,但是没有详细介绍。

   

    首先介绍一下什么叫调用堆栈:假设我们有几个函数,分别是function1,function2,function3,funtion4,且function1调用function2,function2调用function3,function3调用function4。在function4运行过程中,我们可以从线程当前堆栈中了解到调用他的那几个函数分别是谁。把函数的顺序关系看,function4、function3、function2、function1呈现出一种“堆栈”的特征,最后被调用的函数出现在最上方。因此称呼这种关系为调用堆栈(callstack)。

   

    当故障发生时,如果程序被中断,我们基本上只可以看到最后出错的函数。利用call stack,我们可以知道当出错函数被谁调用的时候出错。这样一层层的看上去,有时可以猜测出错误的原因。常见的这种中断时ASSERT宏导致的中断。

   

    在程序被中断时,debug工具条的右侧倒数第二个按钮一般是callstack按钮,这个按钮被按下后,你就可以看到当前的调用堆栈。

   

    实例一:介绍

    我们首先演示一下调用堆栈。首先我们创建一个名为Debug的对话框工程。工程创建好以后,双击OK按钮创建消息映射函数,并添加如下代码:

   

    void CDebugDlg::OnOK()

    {

   

    // TODO: Add extravalidation here

    ASSERT(FALSE);

   

    }

   

    我们按F5开始调试程序。程序运行后,点击OK按钮,程序就会被中断。这时查看call stack窗口,就会发现内容如下:

   

    CDebugDlg::OnOK() line176 + 34 bytes

    _AfxDispatchCmdMsg(CCmdTarget* 0x0012fe74 {CDebugDlg}, unsigned int 1, int 0, void (void)* 0x5f402a00`vcall'(void), void * 0x00000000, unsigned int 12, AFX_CMDHANDLERINFO *0x00000000) line 88

    CCmdTarget::OnCmdMsg(unsigned int 1, int 0,void * 0x00000000, AFX_CMDHANDLERINFO * 0x00000000) line 302 + 39 bytes

    CDialog::OnCmdMsg(unsignedint 1, int 0, void * 0x00000000, AFX_CMDHANDLERINFO * 0x00000000) line 97 + 24bytes

    CWnd::OnCommand(unsignedint 1, long 656988) line 2088

    CWnd::OnWndMsg(unsignedint 273, unsigned int 1, long 656988, long * 0x0012f83c) line 1597 + 28 bytes

    CWnd::WindowProc(unsignedint 273, unsigned int 1, long 656988) line 1585 + 30 bytes

    AfxCallWndProc(CWnd *0x0012fe74 {CDebugDlg hWnd=???}, HWND__ * 0x001204b0, unsigned int 273,unsigned int 1, long 656988) line 215 + 26 bytes

    AfxWndProc(HWND__ *0x001204b0, unsigned int 273, unsigned int 1, long 656988) line 368

    AfxWndProcBase(HWND__* 0x001204b0, unsigned int 273, unsigned int 1, long 656988) line 220 + 21bytes

    USER32! 77d48709()

    USER32! 77d487eb()

    USER32! 77d4b368()

    USER32! 77d4b3b4()

    NTDLL! 7c90eae3()

    USER32! 77d4b7ab()

    USER32! 77d7fc9d()

    USER32! 77d76530()

    USER32! 77d58386()

    USER32! 77d5887a()

    USER32! 77d48709()

    USER32! 77d487eb()

    USER32! 77d489a5()

    USER32! 77d489e8()

    USER32! 77d6e819()

    USER32! 77d65ce2()

    CWnd::IsDialogMessageA(tagMSG* 0x004167d8 {msg=0x00000202 wp=0x00000000 lp=0x000f001c}) line 182

    CWnd::PreTranslateInput(tagMSG* 0x004167d8 {msg=0x00000202 wp=0x00000000 lp=0x000f001c}) line 3424

    CDialog::PreTranslateMessage(tagMSG* 0x004167d8 {msg=0x00000202 wp=0x00000000 lp=0x000f001c}) line 92

    CWnd::WalkPreTranslateTree(HWND__* 0x001204b0, tagMSG * 0x004167d8 {msg=0x00000202 wp=0x00000000 lp=0x000f001c})line 2667 + 18 bytes

    CWinThread::PreTranslateMessage(tagMSG* 0x004167d8 {msg=0x00000202 wp=0x00000000 lp=0x000f001c}) line 665 + 18 bytes

    CWinThread::PumpMessage()line 841 + 30 bytes

    CWnd::RunModalLoop(unsignedlong 4) line 3478 + 19 bytes

    CDialog::DoModal()line 536 + 12 bytes

    CDebugApp::InitInstance()line 59 + 8 bytes

    AfxWinMain(HINSTANCE__* 0x00400000, HINSTANCE__ * 0x00000000, char * 0x00141f00, int 1) line 39 + 11bytes

    WinMain(HINSTANCE__ *0x00400000, HINSTANCE__ * 0x00000000, char * 0x00141f00, int 1) line 30

    WinMainCRTStartup()line 330 + 54 bytes

    KERNEL32! 7c816d4f()

   

    这里,CDebugDialog::OnOK作为整个调用链中最后被调用的函数出现在callstack的最上方,而内核中程序的启动函数Kernel32! 7c816d4f()则作为栈底出现在最下方。

   

    实例二:学习处理方法

    微软提供了MDI/SDI模型提供文档处理的建议结构。有些时候,大家希望控制某个环节。例如,我们希望弹出自己的打开文件对话框,但是并不想自己实现整个文档的打开过程,而更愿意MFC完成其他部分的工作。可是,我们并不清楚MFC是怎么处理文档的,也不清楚如何插入自定义代码。

   

    幸运的是,我们知道当一个文档被打开以后,系统会调用CDocument派生类的Serialize函数,我们可以利用这一点来跟踪MFC的处理过程。

   

    我们首先创建一个缺省的SDI工程Test1,并在CTest1Doc::Serialize函数的开头增加一个断点,运行程序,并打开一个文件。这时,我们可以看到调用堆栈是(我只截取了感兴趣的一段):

   

    CTest1Doc::Serialize(CArchive& {...}) line 66

    CDocument::OnOpenDocument(constchar * 0x0012f54c) line 714

    CSingleDocTemplate::OpenDocumentFile(constchar * 0x0012f54c, int 1) line 168 + 15 bytes

    CDocManager::OpenDocumentFile(constchar * 0x0042241c) line 953

    CWinApp::OpenDocumentFile(constchar * 0x0042241c) line 93

    CDocManager::OnFileOpen()line 841

    CWinApp::OnFileOpen()line 37

    _AfxDispatchCmdMsg(CCmdTarget* 0x004177f0 class CTest1App theApp, unsigned int 57601, int 0, void (void)*0x00402898 CWinApp::OnFileOpen, void * 0x00000000, unsigned int 12,AFX_CMDHANDLERINFO * 0x00000000) line 88

    CCmdTarget::OnCmdMsg(unsignedint 57601, int 0, void * 0x00000000, AFX_CMDHANDLERINFO * 0x00000000) line 302+ 39 bytes

    CFrameWnd::OnCmdMsg(unsignedin

补充:软件开发 , C++ ,
CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,