ByteScope

WebAssembly 檔案格式

編譯完成的 WebAssembly 二進位模組。

MIME 類型
application/wasm
副檔名
.wasm
分類
執行檔與位元碼

Magic number(檔案簽章)

0x000000000061736D·asm0x00 'asm'

關於 WebAssembly 格式

WebAssembly 模組是這份目錄裡最規律的二進位格式:8 位元組的前導,接著一連串 section,每個 section 是一個位元組的 id、一個 LEB128 長度,然後是那麼多個位元組的內容。位元組 8 之後沒有任何東西在固定位移上,但也不需要——讀取端逐個走過 section,不在乎的可以直接跳過,這正是瀏覽器能一邊下載模組其餘部分、一邊開始編譯的原因。已知的 section 必須依固定順序出現,所以驗證器不必跑第二遍就能拒收格式錯誤的模組。

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

位移長度欄位意義
04magic00 61 73 6D——一個 NUL 位元組後面接 ASCII 的 asm。開頭那個 NUL 是刻意的:它保證這個檔案不是合法的文字,所以只要模組經過任何會動到文字編碼的東西,它會在第一個位元組就失敗,而不是在某個難以預料的地方出事。
44version32 位元小端序的版本號,目前是 01 00 00 00,也就是版本 1,從這個格式問世以來就沒變過,因為之後的每個擴充都做成向後相容,而不是把這個數字往上加。
81First section id一個位元組指名 section:0 custom、1 type、2 import、3 function、4 table、5 memory、6 global、7 export、8 start、9 element、10 code、11 data、12 data count。已知 section 必須遞增排列、各最多出現一次,所以前導之後的第一個位元組通常是 1(type)或 0(工具鏈塞在最前面的 custom section)。
9variableSection size (LEB128)無號 LEB128 的 section 內容位元組數——一到五個位元組,每個帶 7 位元的值,最高位元是「還有下一個」的旗標。這是整個格式裡唯一的長度欄位,每個 section 邊界都是靠加上它算出來的,所以位移 8 之後沒有任何東西有固定位址。
variablevariableSection body由項目組成的向量,向量本身也帶著 LEB128 的數量前綴。下一個 section id 緊接在長度欄位所宣稱的最後一個位元組之後開始,所以把所有 id 與長度加總,必須剛好落在檔案結尾——這是這個格式內建的截斷檢查。
variablevariableCustom sections (id 0)一段具名的資料,沒有任何語意:包含放函式名稱給堆疊追蹤用的 'name' section、DWARF 除錯資訊、產生器中繼資料,以及 target-features 記錄。它們可以出現在其他 section 之間的任何位置,而 release 建置要縮小模組,做的就是把它們剝掉。

怎麼跟長得像的格式分開

容易搞混的怎麼分
WAT text同一個模組的文字形式是原始碼,不是二進位的變體:它以左括號加上 module 這個字開頭,全文都是 UTF-8。開頭是 00 61 73 6D 的就是二進位形式,而瀏覽器只實體化這一種。
GZIP實務上最常見的混淆,因為 .wasm 幾乎一定是壓縮後才送出來的。開頭是 1F 8B 的檔案是 gzip 資料流,wasm 的 magic 要解壓縮之後才出現。直接從網路分頁存下來的建置產物,常常是掛著 .wasm 名字的 gzip。
ELF 執行檔兩者都是給虛擬機器或真實機器的編譯產出,而 emscripten 或 wasi-sdk 的建置目錄裡兩種都有——.o 檔是 ELF,只有最終模組是 wasm。位移 0 的 7F 45 4C 46 對上 00 61 73 6D,分得清清楚楚。
asm.js它的前身,在舊的打包產物裡還找得到:asm.js 就是 JavaScript,一個帶著 'use asm' 指示詞的純文字檔,以 .js 交付。工具要是拿 WebAssembly.instantiate 去載它,會卡在 magic,因為裡面根本沒有二進位模組。

常見的壞法

  • 被截斷。section 大小是宣告在內容之前的,所以下載一半的檔案會一路正確解析,直到某個 section 宣稱的位元組數超過剩下的量——解碼器會報 'unexpected end of section or function',這是這份目錄裡所有格式中最清楚的截斷訊息。
  • 以 gzip 壓縮的形式送出或存下來。用 Content-Encoding gzip 下載、原封不動寫到磁碟的 .wasm 開頭是 1F 8B,WebAssembly.instantiate 會卡在 magic 拒收。位元組本身沒問題,錯的只是層次。
  • section 內容裡有一個位元組跑掉。只要長度欄位還完好,section 走訪照樣落在正確的邊界上,所以模組常常載得起來,然後在執行期 trap 或行為異常,這比標頭失敗難診斷得多。
  • 文字模式傳輸,或編輯器在檔尾補了一個換行。多出來的一個位元組會讓 section 走訪結束在離檔尾差一個位元組的地方,嚴格的解碼器會回報有多餘的尾端位元組,而不是接受它。

這個格式現在還在用嗎

WebAssembly 是既有原生程式碼進入瀏覽器的方式:影片與影像編解碼器、PDF 與 CAD 引擎、SQLite、Python 與 Ruby 執行環境、模擬器,以及瀏覽器內壓縮工具背後的壓縮函式庫,全都以 .wasm 模組出貨。它不是 JavaScript 的替代品,也不拿來做 UI——它自己碰不到 DOM——但只要是必須在使用者機器上跑的計算密集工作,預設答案就是它,而這也正是「純瀏覽器端、不用上傳」的工具能成立的原因。在瀏覽器之外它長出了第二種生命,變成可攜的沙箱:WASI 執行環境、邊緣運算平台與外掛系統用的都是同一種二進位格式,所以替網站建置的模組常常原封不動就能在伺服器上跑。

在這個站上打開 WebAssembly 檔

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

相關格式

容易和 WebAssembly 搞混的格式,可以比對它們的開頭位元組。

格式開頭位元組副檔名
ELF 執行檔7F 45 4C 46.elf
Java class 檔CA FE BA BE.class

常見問題

檔案的 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 仍然可能夾帶惡意內容。辨識真實類型是有用的第一道檢查,尤其在副檔名和位元組不一致的時候,但它不是惡意軟體掃描。

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