LibreCAD 在 macOS 26 Tahoe 打不開?這不是 LibreCAD 的鍋,是整個 codesigning 政策變了

實測環境:本機 crash report 抓到的真實環境如下——OS Version: macOS 26.5.1 (25F80)Hardware Model: Mac15,6(Apple Silicon)、Homebrew cask LibreCAD 2.2.1.4 升級到 2.2.1.5、Termination Reason 為 Namespace CODESIGNING, Code 2, Invalid Page。版本號完整字串取自 sw_vers 與 crash report,並非虛構或預測版本;不同 minor 版的讀者照本文步驟操作應仍然適用,但出現新症狀時記得貼出自己的 crash report 來比對。

TL;DR:升級到 macOS Tahoe 後,舊版 Homebrew cask 的 LibreCAD 點兩下會瞬間崩。三行指令治本:brew reinstall --cask librecadxattr -dr com.apple.quarantinecodesign --force --deep --sign -。但要知道,2026-09-01 之後這個 cask 會直接被 Homebrew 從本家停掉,所以順便講一下長期怎麼活下去。

LibreCAD 圖示在 Dock 跳一下就消失的視覺意象

我點兩下 LibreCAD 圖示。Dock 上的 icon 微微跳一下,然後沒了。沒有彈窗、沒有錯誤訊息、沒有「應用程式意外結束」的對話框,就純粹消失。ps aux | grep -i librecad 也是空的。

第一個直覺是:「啊不就 macOS 26 又把哪個權限收緊了」。打開 Console.app 看了一眼,crash report 寫得很乾脆:

Exception Type:    EXC_BAD_ACCESS (SIGKILL (Code Signature Invalid))
Termination Reason: Namespace CODESIGNING, Code 2, Invalid Page

夭壽,連 dyld 都還沒把第一個 dylib 載完就被 kernel 一刀砍了。這篇就是把整個排查到根因到修復走一遍,順便聊一下 Homebrew 在 2026 年丟出來的「Gatekeeper 一刀切」政策 後面的脈絡——因為這已經不是 LibreCAD 一個 app 的問題了,是接下來會有一卡車工具陸續陣亡。

點兩下沒反應,先別怪 LibreCAD

先把現場狀況做出來。我先確認 LibreCAD 還在:

ls /Applications/ | grep -i libre
# LibreCAD.app
# LibreOffice.app

App bundle 還在原位。再看 brew 怎麼說:

$ brew info --cask librecad
==> librecad (LibreCAD): 2.2.1.5
CAD application
https://librecad.org/
Deprecated because it does not pass the macOS Gatekeeper check!
It will be disabled on 2026-09-01.

兩條重點:已棄用過不了 Gatekeeper。但這只是高層敘述,不是根因。直接戳簽章本身:

$ codesign -dv /Applications/LibreCAD.app
Executable=/Applications/LibreCAD.app/Contents/MacOS/LibreCAD
Identifier=LibreCAD
Format=app bundle with Mach-O thin (arm64)
CodeDirectory v=20400 size=74049 flags=0x20002(adhoc,linker-signed)
Signature=adhoc
Sealed Resources=none
Internal requirements=none

$ spctl -a -vv /Applications/LibreCAD.app
/Applications/LibreCAD.app: code has no resources but signature indicates they must be present

重點不是 Signature=adhoc——adhoc 簽章本身沒罪。是 Sealed Resources=none 加上 spctl 那句「code has no resources but signature indicates they must be present」。意思是 bundle 內附的簽章 metadata 宣稱「我這 bundle 有打包好的 sealed resources」,但實際 bundle 裡面對應的 _CodeSignature/CodeResources 檔案內容對不上。

這種「簽章說有,但實際對不到」的 mismatch 在以前的 macOS 上會被 codesign 容忍。但在 macOS 26.5 上,新的 codesigning monitor 直接在 dyld 第一次 mmap 載 page 時就觸發 fault,回報 Invalid Page——也就是 crash report 那一行 mapped file 1031c4000-103224000 [ 384K] r--/rwx SM=COW,page protection 對不上預期的 hash,kernel 立刻 SIGKILL。整個過程 LibreCAD 自己的 main() 連執行都還沒走到,所以你才會看到 dock 跳一下就沒了,連 console log 都沒留下。

為什麼以前能跑,升完 macOS 26 就崩?

macOS 26 codesigning crash report 抽象視覺化

如果你以為這是個別 app 的問題,那你只看到冰山一角。我順手 google 了一下 Namespace CODESIGNING Invalid Page,整個 macOS 26 升級季的回報就沒停過:

  • 有人回報 Xcode 升完 macOS 26 直接崩在啟動 ,crash 訊息與 LibreCAD 同款。需注意該帖至今未獲官方解法,DTS Engineer 建議的重裝、runFirstLaunch 都沒效——我引這條只是要佐證「同款 crash code 不限 LibreCAD」,不是指該帖有答案
  • 在 macOS Sonoma 14.5 期間就有 kitty 終端機 因 bundle 內檔案被改動觸發同樣的 SIGKILL (Code Signature Invalid)——這個案例不在 26 時代,但它清楚示範了「sealed resources 對不上就死」的機制,到了 26 只是門檻拉更低
  • Lakr233/vphone-cli #211 紀錄了一批舊 IPA 在 macOS 26 上全部 CODESIGNING Invalid Page 起手;要注意這是越獄/kernel patch 環境的特殊情境,不能直接類比一般使用者,但 crash code 完全相同,可以看出 kernel 那層判斷邏輯確實升級了

共通點:adhoc 簽章 + bundle 內容曾被 (重新) 寫過 / 抽換過 / 重打包過,導致 sealed resource hash 對不上現在的檔案內容

這背後是 macOS 從 Sonoma → Sequoia → Tahoe 一路把 kernel-level codesigning monitor 收緊的結果。實際效果就是:以前 codesign 的 lazy validation(只在某些路徑驗)變成 eager validation(每個被 mmap 進來的 code page 都驗 hash)。一旦 hash 對不上,就 Invalid Page 給你看。

這邊要先承認一個對照觀點:Eclectic Light 的 Hoakley 在 2026 年 1 月寫 認為 Apple 目前並沒有預告要對 codesigning 推出更嚴格的新限制,他甚至覺得短期內收緊「不太可能」。我與他的差異在於:他談的是 Apple 的官方公告與政策層面(確實還沒有大動作),而我談的是實際使用者在新版上踩到的 kernel 行為差異——這兩件事不矛盾,但讀者要分清楚。Apple 不出 PR 不代表 kernel codesigning monitor 沒在背地裡更嚴。我這邊看到的證據是 crash report 與社群災情,而非 Apple 文件。

這也是為什麼 Homebrew 在 Discussion #6482 裡面用了「dumpster fire」這種字眼來形容 unsigned app 的 UX——他們不是要跟 Apple 站隊,是 Apple 從 kernel 把不合格的簽章 enforce 起來之後,那些靠 adhoc 簽章硬塞給使用者的 cask,再也不能「裝完就能跑」了。光是回報「裝不起來」的 issue 就會把他們淹死。

順帶一提,LibreCAD 這個 case 還有一個 cherry on top:cask 安裝過程中 Homebrew 會把 app 從 Caskroom 「搬」到 /Applications,搬完還會主動用 xattr 附加 com.apple.quarantine 旗標。搬移動作本身就會改動 bundle 的 mtime / 部分 metadata,後面再加上 quarantine xattr,對嚴格的 codesigning 驗證來說就是兩次外部介入,每一次都有可能讓 sealed resources 描述的「原始 build 樣貌」偏離。macOS 25 之前 kernel 對這類偏離還算容忍,26 一收緊就立刻起火。

三步驟治本:reinstall → 去隔離 → 重新 adhoc 簽

三步驟修復流程的意象圖

知道根因之後,治本就有方向了。三步驟按順序做,缺一不可:

# 1. 重灌 cask 拿最新版(2.2.1.5)
brew reinstall --cask librecad

# 2. 解除 quarantine 旗標(讓 Gatekeeper 不要在啟動時再驗一次)
xattr -dr com.apple.quarantine /Applications/LibreCAD.app

# 3. 重新做 adhoc 簽章,重建 sealed resources
codesign --force --deep --sign - /Applications/LibreCAD.app

我先說:只做前兩步是不夠的。我親自踩過——reinstall 完、quarantine 拔掉、open -a LibreCAD 結果還是同款 crash。crash report 依然是 Namespace CODESIGNING, Code 2, Invalid Page,乾。

為什麼?因為 quarantine 旗標只影響 Gatekeeper 在首次執行時會不會跳出「這 app 來自網路你確定要打開嗎」的對話框。把它拿掉只能避開那個 user-facing 流程,完全不影響 kernel 對 code page hash 的驗證。我這個 case 的問題是 sealed resources mismatch,那是 kernel 在 mmap 載 page 時就會 fail 的——對應到 crash report 那段 mapped file ... r--/rwx SM=COW 載入紀錄——跟 Gatekeeper 沒關係。

第三步 codesign --force --deep --sign - 才是治本。拆開來看:

  • --force:覆蓋既有簽章
  • --deep:遞迴對 bundle 內所有 nested code(plug-ins、frameworks、helper binaries)都重簽
  • --sign -:用「adhoc identity」簽——單一 dash 是 codesign 認的 magic value,定義可在本機 man codesign 查到(Apple 不提供穩定的線上 man page URL,社群可參考 Keith Smiley 維護的 Xcode man pages 非官方 mirror ,但仍以本機 man 為準)

重要的副作用是:codesign 重簽的時候會重新計算整個 bundle 的 sealed resources hash 並寫進 _CodeSignature/CodeResources。簽章宣稱的內容跟實際 bundle 對齊了,kernel 驗 page hash 就會過。

驗證簽章修好了:

$ codesign --verify --verbose=2 /Applications/LibreCAD.app
--prepared:/Applications/LibreCAD.app/Contents/PlugIns/printsupport/libcocoaprintersupport.dylib
--validated:/Applications/LibreCAD.app/Contents/PlugIns/printsupport/libcocoaprintersupport.dylib
--prepared:/Applications/LibreCAD.app/Contents/MacOS/ttf2lff
--validated:/Applications/LibreCAD.app/Contents/MacOS/ttf2lff
... (所有 dylib / framework 一路 validated)

$ open -a "LibreCAD"
$ ps aux | grep -i librecad | grep -v grep
oliver  79998  11.7  0.5 436277376 182336   ??  S   下午 1:21   0:01.04 /Applications/LibreCAD.app/Contents/MacOS/LibreCAD

PID 出來、記憶體 182MB、跑起來了。整個過程不到三分鐘。

這個指令不是 hack:LibreCAD 官方在 v2.2.1.3 release notes 就已經附上 xattr -rc LibreCAD.app && sudo codesign --force --deep --sign - LibreCAD.app 當作官方 workaround——他們很清楚 macOS 上的 unsigned build 會被打槍。2.2.1.5 release notes 沒重複這段文字,但同樣是未簽章的 dmg,所以邏輯一樣適用。我們這邊差別只是 cask 灌的、不需要 sudo,因為檔案 ownership 已經是 user 自己的。

Homebrew 為什麼要「一刀砍 387 個 cask」?

這邊我要把鏡頭拉遠一點。LibreCAD 不是孤例。根據 Homebrew 社群討論揭露的數字:

指標數字來源
Homebrew/cask 總 cask 數7,624Discussion #6482 維護者留言
因 Gatekeeper 失敗被棄用的 cask387Discussion #6482 維護者留言
占比約 5%由上兩列換算
強制 disable 日期2026-09-01brew info --cask librecad 直接讀 cask 檔案內 disable_date 程式碼欄位;release notes 措辭較模糊(「September 2026」),但個別 cask 的精確日期就是 9/1

這 387 個被點名的 cask 包含 LibreWolf、FreeTube、qutebrowser、cool-retro-term、love、darktable、powershell、qmk-toolbox 等等——很多是工程師日常工具。我自己就有 4、5 個中槍。

Discussion #6482 裡,Homebrew 維護者群輪番給出講白話的理由(這邊我刻意不點名到個別 ID,避免引言歸屬出錯——有興趣的人請自行翻原串):

The user experience for unsigned software is a dumpster fire.

另一位維護者補充說明:

Software installed from an official tap should just work, otherwise users are inclined to submit bug reports for things that are outside of Homebrew's control.

翻譯成台灣工程師的話就是「我們不想再幫 unsigned 上游擦屁股」。考慮到 Homebrew 完全是志工專案、每個 deprecated cask 後面都是 N 個「為什麼裝不起來」的 issue,這個決定不能說不合理。但對下游使用者來說,意思很直接:2026-09-01 之後brew install --cask librecad 會直接給你「No available cask」。在此之前剩多少時間,請自己對照當下日期換算——倒數時鐘比寫一句固定的「還有 N 個月」實在多了。

另外維護者還補了一句:「Apple has no say over how we do anything in Homebrew」。也就是這次清洗不是 Apple 強迫的,是 Homebrew 自己對「使用者體驗 vs 維護成本」算出的天平。靠杯,但合理。

2026-09-01 之後怎麼辦?三條長期出路

棄用 cask 墓園與遠方燈塔的意象圖

不想等死,現在就要動。對 LibreCAD(以及其他類似情境)的長期策略,我整理出三條路:

路線 A:直接從 GitHub 下載官方 dmg + 自己 adhoc 簽

最直觀。到 LibreCAD/LibreCAD releases 抓最新 dmg,掛載後拖到 /Applications,然後跑:

xattr -dr com.apple.quarantine /Applications/LibreCAD.app
codesign --force --deep --sign - /Applications/LibreCAD.app

優點:版本完全跟上游,沒有 Homebrew 那層中介可能加的問題。

缺點:要手動升級。LibreCAD 官方 release 一樣沒簽 Developer ID(Issue #2054 有討論過 notarization,最後沒做),所以還是要 adhoc 簽。每次升級都要重做一次。

路線 B:自己開 tap 接手維護

Homebrew 在禁用聲明裡明說:「你可以自己開一個 tap、把這些 cask 撿回去用」。技術上:

# 假設你想自己維護 librecad cask
brew tap-new YOUR_GITHUB/casks
# 把 Homebrew/homebrew-cask 裡 librecad.rb 抄一份
# 移除 deprecate 那行
# brew install --cask YOUR_GITHUB/casks/librecad

優點:保留 brew 工作流,升級也吃 brew upgrade

缺點:你要負責跟上 upstream 版本,並且每次更新都要驗證 cask 內附的簽章。一個人維護等於把 Homebrew 維護者拒絕做的事自己扛起來,我這肝大概撐不了。

路線 C:從 GitHub source 自己編

最硬派。git clone https://github.com/LibreCAD/LibreCAD.git → 裝 Qt6、boost、muparser → qmake6make

小注意:Homebrew 裝的 Qt6 不會把 qmake6 直接放進預設 PATH,通常你需要 $(brew --prefix qt)/bin/qmake6 或自己 export PATH="$(brew --prefix qt)/bin:$PATH"。我第一次就被這個小細節絆到,浪費了 10 分鐘以為是 boost 沒裝對。

優點:你完全掌握。編譯產出的 binary 是 linker-signed(也是 adhoc 的一種),但因為是當下的 toolchain 簽的、sealed resources 是 fresh 的,跟 macOS 26 的 codesigning monitor 完全相容,不需要再手動跑 codesign

缺點:要 30 分鐘起跳,相依套件不少,每次大版本升級都要 rebuild。如果你不熟 Qt6 build chain,第一次裝大概會踩 3、4 個坑。


我自己這次的決策過程其實有點掙扎。一開始我是想直接走路線 C 自己編——畢竟原則上最乾淨——但跑到 qmake6 階段卡了 PATH,又看到還要解 muparser 的 brew formula 跟 LibreCAD repo 內 librecad.pro 的相對路徑出入,我這肝就軟掉了。乾,後來想想 LibreCAD 我一年也開不到 5 次,花一小時編 source 完全不划算,最後落腳路線 A:抓官方 dmg + 手動 adhoc 簽。簡單、可重複、新版出來時知道怎麼補簽就好。

如果哪天我變成天天用 LibreCAD 的人,路線 B(自己開 tap)會比較划算——把 codesign 步驟寫進 cask 的 postflight,未來 brew upgrade 就會幫你自動補簽。路線 C 適合「我就是要改原始碼」的人,跟前兩條的用戶 persona 完全不同。三條路沒有絕對優劣,只有時間 / 折騰 / 自由度三軸的權衡。

Troubleshooting:常見踩坑

幾個我自己踩過或看別人踩過的坑:

Q1: 跑完三步驟還是 crash?

先確認 codesign --verify --verbose=2 /Applications/LibreCAD.app 有沒有報錯。如果中間任何一個 dylib 顯示 not validated 或 hash mismatch,代表 --deep 沒有走完。試試先用 sudo rm -rf /Applications/LibreCAD.app 砍掉再重灌,再簽一次。Apple Silicon 與 Intel 的二進位有時會混到,搬移時可能殘留舊檔。

Q2: 重新 adhoc 簽完,幾個月後又突然壞掉?

如果你裝了會主動掃 /Applications 的軟體(防毒、Time Machine 之外的某些備份工具、Sentinel 系列),它們可能會修改 bundle 內 timestamp 或 xattr,把 sealed resource hash 打偏。重跑第三步就好。或者更乾脆——寫一個 shell alias:

alias resign-librecad='codesign --force --deep --sign - /Applications/LibreCAD.app'

Q3: codesign 命令在我的機器報 errSecInternalComponent

通常是 keychain 在搞鬼。先跑 security unlock-keychain ~/Library/Keychains/login.keychain-db 解鎖。Apple Silicon 上偶爾要重開機 codesign daemon 才會醒過來。

Q4: 為什麼不能直接 sudo spctl --global-disable 一次解掉?

兩個原因。一是這指令在 macOS Sequoia 之後需要先到「系統設定 → 隱私權與安全性」按實體按鈕確認,沒法純 CLI 解決。二更重要——它只關掉 Gatekeeper 對 unsigned app 的封鎖,並不關掉 kernel codesigning monitor。本 case 的問題在 kernel 那層,spctl 沒救。

Q5: 那 csrutil disable 呢?

別。SIP 關掉會把整個系統的安全基線打開,得不償失。本 case 不需要動到 SIP。

Q6: --deep flag 不是被 deprecated 了嗎?

技術上 Apple 在文件裡 是建議 distribution 流程用更精細的方式取代 --deep,但對「本機 adhoc 重簽」這個情境,--deep 仍然是最直接的選擇。Apple deprecate 它主要是針對 Developer ID 流程的可重現性問題,跟我們無關。

寫在最後

這次的事件對我來說,與其說是 LibreCAD 壞掉,不如說是一個提醒:Apple 在 kernel level 把 codesigning 從 advisory 變 mandatory 的這條路,沒有要回頭的意思。Sonoma → Sequoia → Tahoe 一路收緊,下一個版本只會更嚴。

對開發者來說,這代表「我隨手 build 一個 binary 給朋友裝」這種輕便的分享模式,在 macOS 上已經接近壽終正寢。要嘛你乖乖申請 Apple Developer 帳號(每年 99 美金)做 Developer ID 簽 + notarization,要嘛你的使用者就要學會自己 codesign --force --deep --sign -。沒有中間地帶。

對 macOS 使用者來說,這代表你必須多認識一點 codesign 的基本概念。不再是「裝了能跑就好」的時代了。當你下次看到 dock icon 跳一下就消失、console 沒留下任何訊息——直接打開 Console.app 找 crash report,搜尋 CODESIGNINGInvalid Page 這幾個關鍵字,大概率就是同款問題。

最後,2026-09-01 之後 Homebrew 會把這 387 個 cask 從本家停掉。我建議你現在就跑一遍:

brew list --cask | xargs -I{} brew info --cask {} 2>&1 | grep -B1 "deprecated.*Gatekeeper"

看看你的機器上有哪些 cask 在這份「死亡名單」上,提早決定要轉路線 A / B / C 哪一條。我自己跑完就發現我裝過的 LibreCAD、LibreWolf、qutebrowser、love2d 全都中槍,等到 9 月才在那邊靠杯,要一次學三個 workaround 真的很煩。

至於 LibreCAD 修不好的話——別只貼「打不開」,先去 ~/Library/Logs/DiagnosticReports/ 找最新的 LibreCAD-*.crash.ips,把 Termination Reason 那行貼出來。沒這個我也只能猜,反正大概率還是 CODESIGNING / Invalid Page,那答案就是這篇文章的第三步。

參考資料

沒有留言:

張貼留言

TTS 文字轉語音完全指南:技術原理、聲音克隆與 2026 選型實戰

前陣子我在測 Qwen3-TTS,官方技術報告寫著首包延遲 97ms,聽起來就是為即時對話而生的。結果我照官方範例跑起來,按下生成、等了整整 10 秒才聽到第一個字。乾,差了一百倍。 這不是我環境爛,而是 TTS 這個領域的日常:論文數字、行銷頁數字、你實際跑出來的數字,是三個...