工程筆記

雲端 Mac 的 LLDB 附加與符號載入診斷

雲端 Mac 的 LLDB 附加與符號載入診斷

Xcode 已顯示「正在附加」,但雲端 Mac 的 CPU 使用率幾乎仍處於閒置狀態,斷點從藍色變成灰色,變數視窗也沒有任何結果。此時反覆點擊停止、重新啟動或刪除 DerivedData,往往只會破壞現場。更有效的做法,是將問題拆成三層:目標程序是否可供除錯、LLDB 與目標之間的工作階段是否已建立,以及目前產物能否找到相符的符號。

先判斷卡在哪一層

先記錄發生時間、工程提交號、Scheme、Configuration、目標裝置與啟動方式,再觀察 Xcode 的狀態。不要把「附加緩慢」、「應用程式啟動緩慢」和「符號解析緩慢」混為同一類問題。

現象 優先檢查 常見結論
應用程式未啟動,LLDB 持續等待 目標程序、啟動參數 程序已結束,或啟動了另一份產物
已顯示程序 PID,但介面沒有回應 程序狀態、除錯服務 目標已暫停、發生阻塞,或除錯交握尚未完成
斷點呈空心或灰色 模組與符號 模組尚未載入、路徑已變更,或 UUID 不相符
可以停住,但看不到區域變數 最佳化層級、除錯資訊 Release 最佳化導致變數遭合併或移除

在另一個終端機中儲存程序快照:

mkdir -p "$HOME/lldb-case"
date -u > "$HOME/lldb-case/time.txt"
ps -axo pid,ppid,state,%cpu,etime,command \
  > "$HOME/lldb-case/processes.txt"
xcrun simctl list devices \
  > "$HOME/lldb-case/simulators.txt"

state 長時間顯示 U,可能表示程序處於不可中斷的等待狀態;若持續顯示 T,則代表程序已暫停。單次快照無法證明變化趨勢,間隔十秒再擷取一份會更有價值。

先保留證據,再進行清理。重新啟動成功只能恢復工作,並不代表根本原因已經消失。

驗證程序與除錯工作階段

確認附加的是正確產物

同名應用程式可能同時存在於多個模擬器、測試目錄或舊的建置目錄中。先在 LLDB 中確認程序與已載入的映像檔:

(lldb) process status
(lldb) target list
(lldb) image list -o -f

target list 應指向本次建置產生的可執行檔;image list 中目標模組的路徑應位於預期的 Build Products 目錄。若路徑來自另一套 DerivedData,不要立即全部刪除;先記下舊路徑,再檢查 Scheme 是否重複使用了錯誤的建置產物。

如果 LLDB 命令列本身仍有回應,可以執行:

(lldb) thread list
(lldb) thread backtrace all

如果所有執行緒都有回溯資訊,通常表示除錯通道已經建立,問題更可能出在應用程式本身的等待狀態或符號解析。若命令持續沒有回傳,再從系統端對目標程序取樣:

sample <PID> 10 1 -file "$HOME/lldb-case/app-sample.txt"

不要連續執行多個 sample。十秒的樣本已足以判斷主執行緒是在鎖定、檔案 I/O、網路呼叫,還是系統框架中等待。

使用 UUID 核對 dSYM

檔名相同不代表符號相符。每次連結都可能產生不同的 UUID,LLDB 只會採用與 Mach-O 對應的 dSYM。

dwarfdump --uuid "/path/to/MyApp.app/MyApp"
dwarfdump --uuid "/path/to/MyApp.app.dSYM"

兩邊相同架構的 UUID 必須一致。如果應用程式包含 arm64,至少要比對 arm64 那一行;不要拿另一次 Archive、另一個提交,或重新連結後產生的 dSYM 代替。

檢查模組是否已載入

在 LLDB 中查詢目標模組:

(lldb) image lookup -n AppDelegate
(lldb) image lookup -r -n 'YourModule\..*'
(lldb) breakpoint list

image lookup 找不到符號,但 image list 已顯示模組時,應優先檢查除錯資訊格式。開發建置通常應產生 DWARF 或 DWARF with dSYM;如果使用高最佳化設定,函式可能已被內嵌,區域變數也可能無法顯示。此時應建立專用的診斷 Configuration,而不是臨時修改團隊共用的 Release 設定。

斷點顯示 pending 不一定代表發生錯誤。動態框架尚未載入時,斷點會等待對應映像檔進入程序。應先將斷點設在確定已載入的進入點,再觀察後續模組,而不是反覆刪除同一個斷點。

從系統日誌找出交握失敗原因

Xcode 介面通常只會顯示「附加失敗」,系統日誌則會保留更具體的程序結束、權限遭拒或連線中止資訊。重現問題前,先啟用一段範圍受限的日誌收集:

log stream --style compact --info \
  --predicate 'process == "debugserver" OR process == "lldb-rpc-server"' \
  > "$HOME/lldb-case/debug-session.log"

重現一次後,使用 Control-C 停止。日誌可能包含使用者名稱、工程路徑與裝置識別碼,因此在提交工單或與團隊分享前應先去識別化。不要無謂地長時間收集全系統 --debug 日誌;這會產生大量雜訊,也會增加篩選成本。

如果日誌顯示目標程序在附加瞬間結束,應先脫離 LLDB,單獨啟動應用程式,確認它能穩定執行。如果只有測試程序失敗,則應分別檢查測試宿主、測試 Bundle 與受測應用程式,不要只關注主應用程式的 PID。

建立可重複執行的恢復順序

恢復操作應從影響最小的步驟開始:

  1. 結束目前的除錯工作階段,但保留目標應用程式與日誌。
  2. 核對 Scheme、Configuration、執行目標與可執行檔路徑。
  3. 比較 Mach-O 與 dSYM UUID。
  4. 重新建置目前的 Target,不執行整個工程的清理。
  5. 只有在確認舊產物遭到重複使用後,才刪除對應工程的 DerivedData 目錄。
  6. 如果問題仍可重現,請儲存執行緒回溯、程序取樣、系統日誌與最小重現步驟。

可以先使用以下命令列出目錄及更新時間,避免誤刪其他任務的資料:

find "$HOME/Library/Developer/Xcode/DerivedData" \
  -maxdepth 1 -mindepth 1 -type d -print

雲端 Mac 上可能同時執行建置、測試與圖形化除錯工作。清理前應確認沒有其他管線正在使用同一目錄;更穩妥的做法,是讓不同工作使用獨立的 -derivedDataPath,將除錯現場與自動建置快取分開。

將排查結果轉化為驗收項目

一次修復完成後,至少應完成四項驗收:冷啟動時可以附加、可以附加至現有程序、原始碼斷點能夠命中,以及在異常暫停時能查看關鍵變數。接著離開並重新進入遠端工作階段,再測試一次,以排除結果依賴目前圖形工作階段或暫時環境變數的可能性。

團隊可以將 UUID 核對、產物路徑與 Configuration 納入建置清單,但不要在指令碼中寫死個人目錄。真正穩定的除錯流程並不是「清理後再試」,而是任何成員都能根據同一組證據,判斷故障位於程序、工作階段還是符號層,並且只修復發生偏差的那一層。

常見問題

LLDB 可以附加程序,但斷點一直未命中,應先檢查什麼?

先用 image list 確認目標模組已載入,再比對執行檔與 dSYM 的 UUID。模組尚未載入時斷點會保持 pending,UUID 不一致則無法取得正確行號。

除錯停住時可以立刻清除 DerivedData 嗎?

不建議。應先保存程序清單、LLDB 輸出、系統記錄及產物 UUID;直接清除會破壞可重現的現場。確認是舊建置目錄後,再精準刪除對應工程資料。

遠端除錯是否需要把除錯連接埠公開到網際網路?

通常不需要。優先在雲端 Mac 內執行 Xcode 與 LLDB,透過受控遠端桌面或 SSH 操作;確需轉送時,應使用受控通道並限制監聽位址。

VMDebug 雲端 Mac

需要一台由單一訂單獨享的實體 Mac mini?

核對 M4 與 M4 Pro 兩種配置、四種租用週期及 5 個可選節點,再依工作流程選擇裝置。

選擇配置並訂購