自新世界 #0x1a:六十四分之一的 Visual Studio

日期:
分類: 週報 13
標籤: Visual Studio 1 MFC 1

微軟說,

Visual Studio 在具有 64 GB RAM 的 Windows 11 和具有 16 個核心或更高版本的 CPU 上執行最佳。它執行速度更快,並且比同一硬體上的 Visual Studio 2022 響應更快。

官方的解釋是,這是為了方便開發者有理由向公司爭取到更好的硬體。

我這臺裝置是工控機,4 GB 的記憶體,處理器則是 2020 年的賽揚 N5095,效能比綠鏢上的還差些,只有 4 核。因此,這是推薦配置的 116。自然,不用再乘上 Windows 10Windows 11,因為後者反而更像是負最佳化——而且也確實是的,因為這臺機器看起來比我的筆記本更不拖泥帶水。

這個裝置定期讀取串列埠,測量資料,然後顯示在介面裡。我的主要任務是修復這個裝置上專用的陳年 MFC 程式無法長時間執行的問題。 具體來說,在兩三天後,程式會先彈出一個有程式名稱的、空白的、MsgBox 大小的窗體,然後點確定就會 crash。一看到這個問題,我就想是記憶體洩露,雖然這麼說可能是 hindsight。

程式碼自然是 GBK 的,甚至沒有 formatter 的痕跡。我檢查了一遍程式碼,沒有任何在異常時彈窗的程式碼,即這個彈窗是 MFC 自己畫的。程式碼中使用了 Windows 的臨界區 CRITICAL_SECTION,是 Windows 的使用者態 mutex,不過這些部分都沒什麼問題。讓 Claude 審了幾遍,只查出來幾個無關痛癢的問題,比如判斷 sleep 的 int 可能會溢位,但這個只會導致某一輪測量的間隔縮短。

x32dbg 除錯

這個程式需要讀串列埠,還用了廠家提供的 .h + 閉源 .dll,因此我需要在生產環境中除錯。

由於一開始實在是不想折騰開發環境,於是我在現場環境中,先是安裝了 x32dbg 企圖找到點什麼線索。然後成功捕獲到一次異常。異常發生時,偵錯程式停在了 RaiseException_CxxThrowException 上,同時還有 ERROR_NOT_ENOUGH_MEMORYSTATUS_NO_MEMORY。這說明程式是在某次記憶體分配失敗之後,透過 C++ 異常機制被拋了出來。

  • 工作集(記憶體):5,324 K
  • 峰值工作集:753,552 K
  • 提交大小:1,857,512 K

32 位程序預設使用者態地址空間通常只有 2GB,Commit size 1.77GB 對 32 位程序來說很危險了。

Registers

暫存器狀態轉儲中,關鍵的是:

...
EIP : 75670F62     kernelbase.75670F62
EFLAGS : 00000212
...
EBP : 07ECBA3C
ESP : 07ECB9E0
...
LastError : 00000008 (ERROR_NOT_ENOUGH_MEMORY)
LastStatus : C0000017 (STATUS_NO_MEMORY)
...

Log

執行緒 1596 已建立,入口:wer.6C853F70,引數:2DB43178
執行緒 1596 退出
EXCEPTION_DEBUG_INFO:
           dwFirstChance: 1
           ExceptionCode: E06D7363 (CPP_EH_EXCEPTION)
          ExceptionFlags: 00000001
        ExceptionAddress: kernelbase.75670F62
        NumberParameters: 3
ExceptionInformation[00]: 19930520
ExceptionInformation[01]: 07ECBA84
ExceptionInformation[02]: mscorwks.6FA04588
第一次異常於 75670F62 (E06D7363, CPP_EH_EXCEPTION)!

Call Stack

棧回溯中可以看到——

執行緒 ID     地址       返回到      返回自      大小  方    註釋
2528
          07ECBA40 74BA8E89 75670F62 38  系統模組 kernelbase.RaiseException+62
          07ECBA78 6F7C7550 74BA8E89 50  系統模組 msvcr80._CxxThrowException+46
          07ECBAC8 6F757C46 6F7C7550 30  系統模組 mscorwks.LogHelp_NoGuiOnAssert+24D
          07ECBAF8 6F5BF2AE 6F757C46 68  系統模組 mscorwks.LogHelp_TerminateOnAssert+3F32
          07ECBB60 76F46F5C 6F5BF2AE EC  系統模組 mscorwks.StrongNameErrorInfo+7DCA
          07ECBC4C 6EFFB1EB 76F46F5C C4  系統模組 ntdll.RtlAllocateHeap+109C
          07ECBD10 007D4987 6EFFB1EB 4   使用者模組 mscorlib.ni.6EFFB1EB
          07ECBD14 007DE6EA 007D4987 4   使用者模組 [資料刪除]e.007D4987
          07ECBD18 007DE6F4 007DE6EA 4   使用者模組 [資料刪除]e.007DE6EA
          07ECBD1C 00000000 007DE6F4     使用者模組 [資料刪除]e.007DE6F4
604 - 主執行緒
          006FF5BC 6FD1E53E 74D8106C 48  系統模組 win32u.NtUserGetMessage+C
          006FF604 6FCFB9A4 6FD1E53E 4C  系統模組 mfc90u.Ordinal#1161+1A
          006FF650 007D4578 6FCFB9A4 4B8 使用者模組 mfc90u.Ordinal#2208+12A
          006FFB08 6FCEAEC6 007D4578 14  系統模組 [資料刪除]e.007D4578

棧裡出現了 mscorwks 和 mscorlib,看著像是 .NET CLR 參與了呼叫。主執行緒停在訊息迴圈中,說明 crash 並不是 UI 邏輯主動觸發。

配置開發環境

為了除錯和修復這個問題,我需要在工控機上配置一個開發環境。機上已經有一份 VS 2008,惟點開後提示已經過期。從網際網路檔案館上 VisualStudio2008_Collection 這個 item 裡下載了一份 Visual Studio® 2008 Standard Edition。安裝的時候,為了節省空間,我只勾選了 C++ MFC 部分。然而,正是這個選擇讓我沒有辦法正常編譯程式——生成時會提示找不到 cl.exe。查詢了幾天,才發現是因為由於 bug 還要勾選 VC# 才能裝上

微軟怎麼這麼壞啊
微軟怎麼這麼壞啊

StackOverflow 問題 error PRJ0003 : Error spawning 'cl.exe',CC BY-SA 2.5,有 Cloudflare CAPTHCA,不建議人類和 agent 訪問。

There is a bug in the Visual Studio 2008 Standard Edition installer. It does not install cl.exe if you only install Visual C++ but not Visual C#. To work around this you have to install Visual C# even if you do not need this.

查詢紕漏

在 VS 上裝了 wakatime 和 Github Copilot 擴充,雖然 Copilot 也不是很好用,沒有 agent 模式。如果想要 apply 變更,只能手動點 diff 上的按鈕,它的方法是讓 LLM 生成一個編輯好的版本(這個是和 VS Code 上的一致的)。不過,值得讚揚的是,其介面都使用了原生控制元件開發。

初步檢查了一遍程式碼,又發現了一些可能導致記憶體洩露的地方。 比如,IP 地址只給了個 char[13],正好夠存 "192.168.1.xx\n",多了都不行——配置是可以經使用者輸入改的。

改動後,我把程式的一些時間間隔之類的引數設定為很短的幾秒,這樣可以在正常呼叫裝置庫來 I/O 的情況下加快記憶體佔用的復現。但是,過程中經常會彈窗停住,似乎是錯誤地 drop 了某些 MFC 的資源,會在 Debug 下觸發內部的斷點,非常煩人——我並不想除錯這些可忽略的錯誤。 之後的兩三天都在嘗試避免這個問題。最後發現,將 target 的 Debug 改成 Release 即可,同時並不影響正常的記憶體監控。

記憶體快照

開始執行後,右側的 panel 裡可以開啟——,抓取快照,後面括號裡的內容是與上一個快照相比的 diff。如果是第一行會變成 (不可用),並不是抓取失敗的意思。

VS 這個診斷面板不知道是怎麼搞的,記憶體那個圖示在我點開後就會變成空白,但滑鼠上去還是有值。

罪魁禍首

這個 GetSpectrumData

這個函式並沒有要求我們傳入一個 char*,所以其字串顯然是自行分配的,而我們每次將這個指標儲存,讀取解析後,在下一個迴圈又用新的返回值覆蓋了上一個指標而沒有 free。

跑了一週,不得問題。不過,彈出了一個 MsgBox,如下:

---------------------------
BackgroundDownload.exe - 應用程式錯誤
---------------------------
應用程式發生異常 未知的軟體異常 (0xe0434352),位置為 0x00007FFACC1EB699。


---------------------------
確定   取消   
---------------------------
微軟怎麼這麼壞啊x2

嚇我一跳,還以為又壞脫;查了查發現是 VS Installer 的問題。也許 VS 真的需要 64 GB RAM 才能正常執行吧。

版權許可

  1. 本作品 採用 知識共享 署名—相同方式共享 4.0 國際許可協議CC BY-SA 4.0 International)許可,閣下可自由地共享(複製、發行) 和演繹(修改、轉換或二次創作) 這一作品,唯須遵守許可協議條款。

評論

評論將在稽覈後顯示,閣下可以在本部落格的 Github 倉庫的 拉取請求列表 中檢視。提交成功後會自動跳轉。

本站不支持 Dark Reader 的暗色模式,请对本站关闭后再访问,亮色模式的对比度、亮度等选项不受影响。部分页面右上角提供暗色模式切换按钮,如果你没看到,说明你的浏览器尚不支持此特性。本提示不依赖于 JavaScript,你可自行查找其他用户在本站发表的关于如何关闭此提示的评论。