Qt4.8.5 QtWebKit QWebView 用戶棧檢查崩潰問題的思考


最近在項目中,發現在使用Qt4.8.5 提供的QWebView與網頁交互的時候,

m_pWebView->page()->mainFrame()->evaluateJavaScript(tmp);

QtWebKitd4.dll模塊偶爾會出現崩潰,如圖

 

 

中斷查看調用堆棧(加載QtWebkitd4.pdb 才可看到正確的堆棧信息)

 

 

最后停止在 QT  StackBounds::checkConsistency。從堆棧類名跟函數名看出,可能是跟堆棧相關,嘗試看看源文件,找到函數定義

 

 

函數很短,跟具體業務邏輯沒什么關系,可以得出release模式函數直接返回,debug模式下,在棧上創建一個對象來獲取此時棧指針大小,assert根據堆棧增長方向,

檢查此時棧指針跟 m_origin 和m_bound的關系。 從名字推測是與棧基址跟棧邊界比較。繼續看代碼,怎么給這兩個賦值的。

 

 

 

 

在X86CPU配合MSVC編譯器的平台下,棧基址 m_origin 通過FS寄存器中保存的 NT_TIB 線程信息塊中得到當前線程的棧的基址,沒有問題

那么棧邊界呢m_bound ?

通過源碼的注釋,似乎想通過NT_TIB獲得,但沒這樣做(后面驗證,此方法得到的棧邊界不可靠,只能獲得已提交棧大小。qt5.4中提供新的方式獲取,后面修改也是基於此)。

 

 

繼續看看 QT是怎么做的。

轉到函數定義:

 

 

QT把棧邊界的大小固定為512kB,顯然不適用所有平台,源碼的注釋也給出了說明 this code unsafely guesses stack sizes!,WINDOWS、WINCE等平台下,may be work wrong。

明知不可行但還是設置了固定值,而且是全平台通用的一個值,想想我們在以往的項目中是不是也做過類似的妥協呢?

可見實現Qt4.8.5時還是比較匆忙,並不是一個穩定的版本。

后面再看qt5.4時(中間哪個版本開始修改的,這個沒有關注。。),全平台都是代碼獲取,感覺很可靠,至少是在window平台。(Qt團隊效率還是可以的!)

 

 

 

問題原因大致定位了,但是是哪里導致棧空間被大量使用了呢?

猜測1:QtWebKit內部調用,消耗了大量棧空間。

驗證: 1.1 新建一個工程,用QWebView加載網頁 m_pWebView->load(QUrl("xxx"));

       1.2 注冊js調用對象 m_pWebView->page()->mainFrame()->addToJavaScriptWindowObject("servers", this);

       1.3 聲明接口供js調用

int JS2QT::MainCall(QString szCallOperate,QString szExternData)

{

    ….

    m_pWebView->page()->mainFrame()->evaluateJavaScript(tmp); 

}

1.4 在執行js之前查看堆棧ebp(棧基址) 與esp(棧頂指針)。 當前消耗 ebp-esp 才幾k,屬於正常現象。

1.5 執行js,正常。

 

猜測2:項目工程在進入QWebView調用之前=的調用鏈就已經消耗了大量棧空間。

驗證:  在進入MainCall之前下斷點,觀察到進入MainCall之后, ebp-esp 瞬間消耗了 850KB以上的棧空間(默認1MB),

       執行js,出現中斷。850KB 早已超過512KB, debug模式下QtWebKit只要執行stackCheck,必然assert。

       相同情況relese不會崩潰,正好驗證了之前的源碼在relese模式下不檢查stack。

 

結果: 查看實際項目中MainCall的實現,該函數內部確實聲明了大量的臨時對象,消耗了大量的棧空間。

 

 

修改意見:

 

主工程(生成exe的工程)屬性

QtWebKit

Debug

項目屬性-》鏈接器-》系統 修改堆棧保留大小(推薦2097152)

其他默認0

  1. 參考qt5.4修正m_bound棧邊界跟棧基址的獲取,重新編譯 QtWebKit.sln(附件替換qt4.8.5后重新編譯WebKit.sln工程即可)
  2. 二進制編輯器 直接找到對應代碼二進制,修改數字大小(適合本地使用)
  3. 增大m_bound 棧邊界固定值,重新編譯QtWebkit.sln 工程(不推薦)

Release

項目屬性-》鏈接器-》系統 修改堆棧保留大小(推薦2097152)

其他默認0

Release模式下QtWebKit不對堆棧使用做檢查。一旦發生棧空間不夠,直接崩潰。

 

Q&A

  1. 同樣操作為什么debug模式必崩潰,但是release模式不會崩潰?

    答:因為QtWebkit StackBounds類負責做棧邊界檢查的時候,認為棧的大小固定在512kB,而主線程的默認棧 1MB,當主線程使用超過512kB的棧空間時,QtWebkit必崩,

    但在release模式下,QtWebkit不做棧檢查,只要主線程使用棧不超過1MB,程序就不會崩潰。

 

  1. 為什么跟js做一些交互的時候,程序會崩潰?

    答:使用QWebView內核,與js交互都是通過我們項目中的 xxx::MainCall 完成分發的,MainCall中聲明的各種數組消耗了大量的棧空間,

    目前來看已使用850kB左右,此時函數調用繼續發生,堆棧進一步被消耗,當某些操作需要消耗大一點棧空間的時候,此時就會發生崩潰,而如果崩潰在

    Vs編譯的庫(不主動做棧檢查,不主動產生中斷),會友好提示 stackOverflow,崩潰在其他庫(不主動做棧檢查,不主動產生中斷),就會顯得莫名其妙了吧。      

 

!!隱藏的問題,雖然擴大默認棧大小,可以解決問題,但是,改變默認棧大小帶來的問題?

  1. 如果最后我們的工程生成的是 xxx.exe 以進程提供服務,那么我們設置的默認堆棧大小會起到作用。
  2. 如果我們工程生成的是 xxx.dll 或 xxx.ocx。我們的服務是被IE(其他進程)加載,主線程的堆棧是由加載進程決定的,我們工程設置的大堆棧將不起作用。(解決方法:修改IE默認堆棧大小字段,利用PE工具很方便)

 

總之,問題的根源在於,一個函數中大量使用堆棧資源,勢必不是良好的程序設計風格,就目前及以后會出現的問題,提兩點自己的建議

  1. 一個函數不要太長,應按照實際業務分發處理,多加些函數負責不同的操作;同時一個函數內部不要消耗太多的棧空間,這樣有可能導致后的函數調用時,stackOverflow。
  2. 使用標准庫容器來管理大量臨時對象(容器對象在棧上分配空間,容器中的內容在堆上分配,堆的釋放由標准庫負責,有一定的可靠性).

 

 

 

 

 

 

附:手動修改QtWebKitd4.dll文件,改變QtWebkit 設置的固定棧大小。

下斷點觀察 m_bound的指令地址

 

指令地址 0x10EC3636  查看模塊加載地址:0x10000000  則文件偏移地址 0x00EC3626

 

用二進制編輯器打開QtWebKit4d.dll (debug才需修改) 找到0x00EC3626 或直接搜內容 2D00000800

 

2D  00 00 08 00 對應匯編指令 sub eax 80000h     注意為小端字節序

保存即可。

替換QtWebkitd4.dll 斷點查看

 

修改成功


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM