ByteScope

Mach-O(universal/fat)檔案格式

把多個架構包在一起的 macOS universal(fat)Mach-O 執行檔。

MIME 類型
application/x-mach-binary
副檔名
.dylib, .bundle
分類
執行檔與位元碼

Magic number(檔案簽章)

0x00000000CAFEBABE····'CAFEBABE' fat magic

關於 Mach-O(universal/fat)格式

universal binary 是個外殼,不是一個執行檔:8 位元組的標頭之後,每個架構各有一筆 20 位元組的描述子,再後面才是完整、彼此獨立的 Mach-O 映像本體,各自對齊在描述子指名的位移上。所以 universal 檔案的大小大致就是各片相加,而不是什麼精巧的合併;lipo 也因此能加減一個架構而完全不動到任何程式碼。整個 fat 標頭一律是大端序,不管裡面的 slice 是什麼——這是 NeXT 年代的決定,之所以留到現在,是因為這個格式原封不動撐過了三次 CPU 轉換。

檔頭結構,一個位元組一個位元組看

位移長度欄位意義
04magic (FAT_MAGIC)CA FE BA BE,以大端序讀是 0xCAFEBABE——和 Java class 檔一模一樣的 4 個位元組。變體 CA FE BA BF(FAT_MAGIC_64)代表底下的架構描述子改用 64 位元的位移與大小,給超過 4 GB 的執行檔用。
44nfat_arch後面有幾筆架構描述子,大端序。實際上是 1 到 4 左右——兩個是壓倒性的常態,x86-64 加 arm64。正因為這個數字很小,它的高位兩個位元組是 0,而這正是把這個標頭和 Java class 檔區分開來的關鍵性質。
84fat_arch[0].cputype第一片的 CPU:0x01000007 是 x86-64、0x0100000C 是 arm64,沒有 0x01000000 旗標的則是 32 位元形式。建置系統要判斷這個檔案支不支援眼前這台機器,讀的就是這個欄位。
124fat_arch[0].cpusubtype該架構內的變體——x86-64 ALL 是 3,arm64e 是 0x80000002。兩片可以有相同的 cputype、只差在這裡,arm64 和 arm64e 能同時存在就是靠這個。
164fat_arch[0].offset這一片自己的 Mach-O 映像從哪個檔案位移開始,大端序。seek 過去應該要看到 CF FA ED FE。片內所有東西都是從那個點起算,不是從 fat 檔案的開頭起算,所以一片可以逐位元組複製出來、照樣跑得動。
204fat_arch[0].size那一片的位元組長度。最後一個架構的位移加大小通常就等於檔案大小;再後面的東西不是填補就是追加的簽章。
244fat_arch[0].align以 2 的次方表示的對齊——14 代表這一片從 16384 位元組的邊界開始,那正是 Apple silicon 的頁面大小。這就是為什麼片與片之間會有一大段零位元組,也是為什麼 universal binary 比各部分相加還大一點。
2820fat_arch[1]下一筆描述子,同樣五個欄位、同樣順序。描述子固定 20 個位元組(FAT_MAGIC_64 形式則是 32),所以第 n 筆從 8 + 20n 開始,而且沒有清單結束標記——nfat_arch 是唯一的邊界。

怎麼跟長得像的格式分開

容易搞混的怎麼分
Java class 檔這個撞號讓 magic number 對照表困擾了三十年:兩者開頭都是 CA FE BA BE。把位元組 4-7 當成兩個 16 位元值來讀。Java class 檔是 minor_version 接 major_version,而任何真實的 class 檔 major_version 至少是 45。fat binary 那個位置放的是 nfat_arch,一個小數字,所以 major_version 該在的那兩個位元組是 0。先檢查 major_version 是否非零,這件事就乾乾淨淨解決了。
Mach-O 執行檔thin 的 Mach-O 開頭是 CF FA ED FE 或 CE FA ED FE,標頭欄位是小端序;fat 外殼開頭是 CA FE BA BE,每個欄位都是大端序。如果你把某一片抽出來單獨看,它就是一個 thin 執行檔——fat 標頭不會跟著複製過去,而這是正確的行為。
FAT_MAGIC_64 (CAFEBABF)和 32 位元形式只差一個位元,而搞混它們是無聲的、不是大聲的:描述子變成 32 個位元組而不是 20,位移與大小都是 64 位元。假設短版面配置的解析器,會從第一筆描述子的中間讀出第二個架構的欄位,然後報出一堆看起來很像樣的垃圾。
Java JAR值得一提,因為這個撞號通常就是這樣找上門的:JAR 是 ZIP(50 4B 03 04),裡面那個 CA FE BA BE 是一個壓縮過的 class 檔,跟 Apple 一點關係都沒有。位移 0 出現什麼,那才算數。

常見的壞法

  • 最後一片被截斷。fat 標頭在最前面,把每個架構的位移與大小都列了出來,所以下載一半的檔案照樣回報「universal binary,2 個架構」,而最後一片的位元組提早結束。把最後一筆描述子的位移加大小和實際檔案大小比一比。
  • 描述子和實際的片對不上了。就地編輯某一片、長度變了卻沒更新 fat 標頭,下一個架構的位移就會指到前一片的中間,而第二片的 magic 也不再是 CF FA ED FE。每個 slice 位移都應該正好落在一個 Mach-O magic 上。
  • 前面被塞了垃圾。錯誤頁或 BOM 被存在執行檔前面,每個 slice 位移都偏移了同樣的量,所以檔案還是被認成 CAFEBABE 開頭的某種東西,而裡面沒有一樣解析得出來。修法是把 magic 前面的東西刪掉,不是去改描述子。
  • 程式碼簽章逐片失效。每一片是各自簽章的,所以只修補其中一個架構,其他片依然有效,做出一個「在 Intel Mac 上跑得動、在 Apple silicon 上被殺掉」的執行檔,反過來也一樣——這種令人困惑的症狀常常害人跑去找硬體問題。

這個格式現在還在用嗎

universal binary 是 Apple 用來度過 CPU 轉換、又不必出兩份下載的機制:68k 到 PowerPC、PowerPC 到 Intel、Intel 到 Apple silicon,三次都跑在同一個標頭上。今天的 universal 檔案通常是 x86-64 加 arm64,多數 Mac 應用程式,以及在「只支援 Apple silicon」之前的 Homebrew bottle 裝的都是這個,Xcode 在專案同時鎖定兩者時吐的也是它。代價是體積——檔案裡帶著完整的程式副本——所以 App Store 會在下載前先做瘦身,多數自用工具現在也只出 arm64。即使如此這個格式應該不會消失,因為 arm64 和 arm64e 映像共存在同一個檔案裡靠的也是它,而且 lipo 讓「加一個架構」或「拿掉一個架構」變成改寫標頭而不是重新建置。

在這個站上打開 Mach-O(universal/fat)檔

這些全部在你的瀏覽器裡跑,檔案不會離開你的電腦。

相關格式

容易和 Mach-O(universal/fat)搞混的格式,可以比對它們的開頭位元組。

格式開頭位元組副檔名
Mach-O 執行檔CF FA ED FE.dylib
Java class 檔CA FE BA BE.class
ELF 執行檔7F 45 4C 46.elf

常見問題

檔案的 magic number 是什麼?

檔案開頭的前幾個位元組,不管檔名寫什麼,它標明的是真正的格式。PNG 一定以 89 50 4E 47 開頭,ZIP 則是 50 4B 03 04。作業系統、瀏覽器和 file 指令讀的都是這幾個位元組,而不是相信副檔名 — 這就是為什麼把檔案改名並不會改變它實際上是什麼。

我要怎麼自己檢查 magic number?

在 macOS 或 Linux 上,xxd -l 16 檔名 會印出前 16 個位元組;在 Windows 上,PowerShell 的 Format-Hex -Path 檔名 -Count 16。或者把檔案丟進本站的檔案類型辨識工具,它會在本機讀取位元組並告訴你是什麼格式,不會上傳任何東西。

為什麼兩種格式的 magic number 會一樣?

因為其中一種是建立在另一種之上。docx、xlsx、pptx、apk、jar 與 epub 骨子裡都是 ZIP 壓縮檔,所以全都以 ZIP 的簽章開頭;要分辨它們得看裡面的項目。Java class 檔和 macOS 的通用二進位檔都以 CA FE BA BE 開頭,靠緊接其後的欄位區分。

有完全沒有 magic number 的檔案嗎?

有。XML、HTML、JSON、SVG、CSV 這類以文字為基礎的格式沒有固定的位元組簽章,所以改用結構來辨識:開頭的 <?xml 宣告、<svg 元素,或是 { 後面接著帶引號的鍵。這種偵測是合理推測而非確定,因此本參考頁會把這些格式標示為結構偵測。

magic number 相符就代表檔案安全嗎?

不代表。它告訴你的是格式,不是內容是否無害 — 一個貨真價實的 PDF 或 docx 仍然可能夾帶惡意內容。辨識真實類型是有用的第一道檢查,尤其在副檔名和位元組不一致的時候,但它不是惡意軟體掃描。

手上有檔案可能是 Mach-O(universal/fat)?用檔案類型辨識工具查出來 — 它在本機讀取 magic number,不上傳任何東西。或者瀏覽所有格式。