• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            loop_in_codes

            低調做技術__歡迎移步我的獨立博客 codemaro.com 微博 kevinlynx

            記一次堆棧平衡錯誤

            最近在一個使用Visual Studio開發的C++程序中,出現了如下錯誤:

            Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call. This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention.

            這個錯誤主要指的就是函數調用堆棧不平衡。在C/C++程序中,調用一個函數前會保存當前堆棧信息,目標函數返回后會把堆棧恢復到調用前的狀態。函數的參數、局部變量會影響堆棧。而函數堆棧不平衡,一般是因為函數調用方式和目標函數定義方式不一致導致,例如:

            void __stdcall func(int a) {
            }
            
            int main(int argc, char* argv[]) {
                typedef void (*funcptr)(int);
                funcptr ptr = (funcptr) func;
                ptr(1); // 返回后導致堆棧不平衡
                return 0;
            }
            

            __stdcall修飾的函數,其函數參數的出棧由被調用者自己完成,而__cdecl,也就是C/C++函數的默認調用約定,則是調用者完成參數出棧。

            Visual Studio在debug模式下會在我們的代碼中加入不少檢查代碼,例如以上代碼對應的匯編中,就會增加一個檢查堆棧是否平衡的函數調用,當出現問題時,就會出現提示Run-Time Check Failure...這樣的錯誤對話框:

            call dword ptr [ptr]  ; ptr(1)
            add  esp,4  ; cdecl方式,調用者清除參數
            cmp  esi,esp  
            call @ILT+1345(__RTC_CheckEsp) (0B01546h) ; 檢查堆棧是否平衡
            

            但是我們的程序不是這種低級錯誤。我們調用的函數是放在dll中的,調用約定顯示定義為__stdcall,函數聲明和實現一致。大致的結構如下:

            IParser *parser = CreateParser();
            parser->Begin();
            ...
            ...
            parser->End();
            parser->Release(); // 返回后導致堆棧不平衡
            

            IParser的實現在一個dll里,這反而是一個誤導人的信息。parser->Release返回后,堆棧不平衡,并且僅僅少了一個字節。一個字節怎么來的?

            解決這個問題主要的手段就是跟反匯編,在關鍵位置查看寄存器和堆棧的內容。編譯器生成的代碼是正確的,而我們自己的代碼乍看上去也沒問題。最后甚至使用最傻逼的調試手段–逐行語句注釋查錯。

            具體查錯過程就不細說了。解決問題往往需要更多的冷靜,和清晰的思路。最終我使用的方法是,在進入Release之前記錄堆棧指針的值,堆棧指針的值會被壓入堆棧,以在函數返回后從堆棧彈出,恢復堆棧指針。Release的實現很簡單,就是刪除一個Parser這個對象,但這個對象的析構會導致很多其他對象被析構。我就逐層地檢查,是在哪個函數里改變了堆棧里的內容。

            理論上,函數本身是操作不到調用者的堆棧的。而現在看來,確實是被調用函數,也就是Release改寫了調用者的堆棧內容。要改變堆棧的內容,只有通過局部變量的地址才能做到。

            最終,我發現在調用完以下函數后,我跟蹤的堆棧地址內容發生了改變:

            call llvm::RefCountedBase<clang::TargetOptions>::Release (10331117h)
            

            因為注意到TargetOptions這個字眼,想起了在parser->Begin里有涉及到這個類的使用,類似于:

            TargetOptions TO;
            ...
            TargetInfo *TI = TargetInfo::CreateTargetInfo(m_inst.getDiagnostics(), TO);
            

            這部分初始化代碼,是直接從網上復制的,因為并不影響主要邏輯,所以從來沒對這塊代碼深究。查看CreateTargetInfo的源碼,發現這個函數將TO這個局部變量的地址保存了下來

            而在Release中,則會對這個保存的臨時變量進行刪除操作,形如:

            void Delete() const {
              assert (ref_cnt > 0 && "Reference count is already zero.");
              if (--ref_cnt == 0) delete static_cast<const Derived*>(this);
            }
            

            但是,問題并不在于對一個局部變量地址進行deletedelete在調試模式下是做了內存檢測的,那會導致一種斷言。

            TargetOptions包含了ref_cnt這個成員。當出了Begin作用域后,parser保存的TargetOptions的地址,指向的內容(堆棧)發生了改變,也就是ref_cnt這個成員變量的值不再正常。由于一些巧合,主要是代碼中各個局部變量、函數調用順序、函數參數個數(曾嘗試去除Begin的參數,可以避免錯誤提示),導致在調用Release前堆棧指針恰好等于之前保存的TargetOptions的地址。注意,之前保存的TargetOptions的地址,和調用Release前的堆棧指針值相同了。

            而在TargetOptionsDelete函數中,進行了--ref_cnt,這個變量是TargetOptions的第一個成員,它的減1,也就導致了堆棧內容的改變。

            至此,整個來龍去脈算是摸清。

            posted on 2013-08-15 23:01 Kevin Lynx 閱讀(5757) 評論(1)  編輯 收藏 引用 所屬分類: c/c++

            評論

            # re: 記一次堆棧平衡錯誤 2013-08-16 12:05 小二郎健康燈

            終于找到一個專業的C++博客了  回復  更多評論   

            久久妇女高潮几次MBA| 亚洲国产精品无码久久九九| 精品久久久无码人妻中文字幕 | 精品人妻久久久久久888| 亚洲精品乱码久久久久久按摩 | 久久婷婷五月综合色99啪ak| 久久人妻少妇嫩草AV无码蜜桃| 欧美午夜A∨大片久久 | 久久国产欧美日韩精品 | 天天影视色香欲综合久久| 久久久久亚洲AV成人网人人网站 | 国产精品成人久久久久三级午夜电影| 亚洲精品无码久久久久AV麻豆| 久久精品成人欧美大片| 天堂无码久久综合东京热| 国产精品一区二区久久国产| 亚洲国产精品一区二区三区久久| 中文字幕久久精品无码| 久久播电影网| 国内精品久久久久影院免费| 国产精品久久久久免费a∨| 久久香蕉综合色一综合色88| 九九精品久久久久久噜噜| 曰曰摸天天摸人人看久久久| 亚洲愉拍99热成人精品热久久| 久久精品国产黑森林| 精品精品国产自在久久高清| 男女久久久国产一区二区三区| 久久一区二区三区99| 中文字幕一区二区三区久久网站| 久久综合88熟人妻| 亚洲欧洲日产国码无码久久99| 亚洲а∨天堂久久精品9966| 91精品国产91久久久久久青草| 久久99亚洲网美利坚合众国| 亚洲αv久久久噜噜噜噜噜| 免费精品国产日韩热久久| 热久久视久久精品18| 中文字幕精品久久久久人妻| 日本高清无卡码一区二区久久| 精品人妻伦一二三区久久|