全局變量初始化順序探究


全局變量初始化順序探究

緣起

我在上一篇文章——《調試實戰 —— dll 加載失敗之全局變量初始化篇》中,跟大家分享了一個由於全局變量初始化順序導致的 dll 加載失敗的例子。感興趣的小伙伴兒可以點擊閱讀。

雖然我們知道了是由於全局變量初始化順序導致的問題,也給出了解決方案。但是有一點卻沒有刨根問底——為什么改變文件在工程文件中的順序就可以改變全局變量初始化順序?是怎么影響的呢?本篇文章力求解決這個問題。

了解 vs 編譯

我們可以簡單的把整個構建過程分成三個步驟(當然實際還有其它步驟,我們一般不關心):預編譯,編譯,鏈接。

預編譯: 處理宏,#include 展開等。

編譯: 以編譯單元為單位生成對應的 .obj 文件。

鏈接: 把生成的.obj 文件和必要的文件鏈接成最后的應用程序。

猜想

因為編譯是把符號放到對應的 .obj 中,鏈接的時候才把對應的 .obj 文件鏈接成最后的應用程序。鏈接的時候應該是按照 .obj 文件出現的先后順序依次把 .obj 中的符號放到對應的位置。

思路

對比觀察調整順序前和調整順序后的編譯參數,鏈接參數。因為猜測是鏈接導致的問題,我們主要關注鏈接參數。

編譯過程初探

當我們在 vs 中執行 build 時的整個過程如下圖(使用 process monitor 捕獲的):

vs-msbuild-cl-link
vs-msbuild-cl-link

可以清晰的看到,vs 在內部會啟動 msbuild.exe 執行后續的操作。msbuild.exe 會間接啟動 cl.exe 進行編譯,link.exe 進行鏈接。我們還發現黃色高亮部分的 Tracker.exe ,這個進程主要用來加速編譯的。具體可以參考《Inside the Microsoft Build Engine Using MSBuild and Team Foundation Build》 這本書的介紹,簡單截圖如下:

filetracker-introduce
filetracker-introduce

簡化編譯過程

因為 vs 會通過 msbuild.exe 執行操作,我們可以直接使用 msbuild.exe 進行構建。msbuild 有一個選項 TrackFileAccess 可以用來控制是否使用 FileTracker。為 false 時,不啟用 FileTracker

為了簡化問題,我們直接執行 msbuild.exe -p:TrackFileAccess=false project_file_to_build.vcxproj

msbuild-cl-link-param
msbuild-cl-link-param

我們發現,傳遞給 cl.exelink.exe 的參數都是文件。猜測,應該是把參數保存到文件中傳遞的。據觀察,這些文件會在執行完后被清理。得想辦法在這些文件被刪除之前保存一份,各位小伙伴兒有什么好辦法嗎?

我們先看下這些參數文件是誰創建和刪除的,什么時候刪除的。創建很簡單,肯定是 msbuild.exe。刪除呢?是 cl.exe / link.exe 還是 msbuild.exe 呢?又是什么時候刪除的呢?相信下圖能很好的回答這些問題了。

msbuild-remove-param-file
msbuild-remove-param-file

我想到兩個思路:

  1. 因為這些文件是 msbuild.exe 創建/刪除的,可以在 msbuild.exe 中文件操作的地方加斷點。
  2. 可以暫停 cl.exe/link.exe 的執行,拷貝我們需要的文件到桌面。

第一個思路相對來說比較復雜,今天我們嘗試第二個思路。我們該如何暫停呢?請出 gflags.exe

中斷 link.exe

我們可以在 gflags.exe 中進行如下設置,這樣當 link.exe 啟動時就會中斷到 windbg.exe 中了。

gflags-set-debug-link
gflags-set-debug-link

斷下來后,我們可以在 windbg 中輸入 !peb 觀察參數,里面包含了我們需要拷貝的文件路徑。

windbg-command-line
windbg-command-line

有了文件路徑,我們就可以手動復制對應的文件到桌面慢慢研究了。

對比鏈接參數

調整 Test1.cpp Test2.cpp.vcxproj 中的順序,按上面的方法分別保存傳遞給 link.exe 的參數文件,對比如下圖(格式有調整):

obj-link-order
obj-link-order

發現在兩次鏈接過程中,Test1.obj Test2.obj 出現的順序是不一樣的。

結論

哪個源碼文件在 .vcxproj 中先出現,其對應的 .obj 文件在傳遞給 link.exe 的參數文件(.rsp)中越靠前,會被優先處理。.obj 中包含的全局變量會被優先處理。當進程啟動時,執行全局變量初始化的時候會按照先后順序初始化。

總結

  • vs 內部會使用 msbuild.exe 編譯,我們也可以直接使用 msbuild.exe 進行編譯。
  • 使用 msbuild.exe -p:TrackFileAccess=false 可以在編譯的過程中不啟動 Tracker.exe,對我們調查問題有幫助。
  • 我們可以在一個進程啟動時就中斷到調試器,可以使用 gflags.exe 幫我們實現這一點。
  • !peb 可以查看啟動參數,環境變量等信息。
  • .vcxproj 中文件的順序會影響最后鏈接時的順序。

參考資料

《Inside the Microsoft Build Engine Using MSBuild and Team Foundation Build》

https://docs.microsoft.com/en-us/cpp/c-runtime-library/crt-initialization?redirectedfrom=MSDN&view=vs-2019

http://www.cppblog.com/xlshcn/archive/2007/12/07/37088.html


免責聲明!

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



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