Wasm(バイナリ)を読む
Wasm(バイナリ)を読む
ちょっと前にWasmに入門してみて興味を持ったので、もうちょっと深堀ってみる。
今回は、前回の入門で書いたWasmモジュール(ローカルホスト上でハロワしただけ)を気合で読んで仕様を垣間見てみる。
Wasmモジュール
入門で軽く触れたけどもう少し見てみる。
公式の記載としてはこんな感じ。
WebAssembly programs are organized into modules, which are the unit of deployment, loading, and compilation.
一言でまとめると、WebAssemblyのプログラムを意味のある単位でまとめたもの。
コンパイル後吐き出したり、実行対象として読み込んだり、デプロイしたりする単位。(脳死翻訳)
公式ドキュメントを見てみる
公式ドキュメントにある、Wasmモジュールのバイナリフォーマットの記載を見てみると、まさに求めてた情報が書いてあった。
magic ::= 0x00 0x61 0x73 0x6d
version ::= 0x01 0x00 0x00 0x00
... (以下色々と続いてる)
なるほど、.asm
ってマジックナンバーが書いてあるのか。
LinuxのELF
にも同じようなの書いてあったよな〜と思ったら書いてあった。
$ file ./target/debug/hello-world
./target/debug/rust: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, (以下省略)
$ hexdump -C ./target/debug/hello-world | head -5
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
00000010 03 00 3e 00 01 00 00 00 50 78 00 00 00 00 00 00 |..>.....Px......|
00000020 40 00 00 00 00 00 00 00 78 6c 3b 00 00 00 00 00 |@.......xl;.....|
00000030 00 00 00 00 40 00 38 00 0e 00 40 00 2c 00 2b 00 |....@.8...@.,.+.|
00000040 06 00 00 00 04 00 00 00 40 00 00 00 00 00 00 00 |........@.......|
実際のバイナリを見てみる
ちなみにソースはこれ。
fn main() {
println!("Hello, Wasm!");
}
サイズを確認してみる。
$ du -h ./hello-wasm.wasm
2.0M ./hello-wasm.wasm
hexdump
してみる。
$ hexdump -C ./hello-wasm.wasm | head -10
00000000 00 61 73 6d 01 00 00 00 01 74 11 60 00 00 60 01 |.asm.....t.`..`.|
00000010 7f 00 60 01 7f 01 7e 60 02 7f 7f 00 60 01 7f 01 |..`...~`....`...|
00000020 7f 60 02 7f 7f 01 7f 60 03 7f 7f 7f 00 60 03 7f |.`.....`.....`..|
00000030 7f 7f 01 7f 60 04 7f 7f 7f 7f 01 7f 60 00 01 7f |....`.......`...|
00000040 60 05 7f 7f 7f 7f 7f 00 60 04 7f 7f 7f 7f 00 60 |`.......`......`|
00000050 05 7f 7f 7f 7f 7f 01 7f 60 07 7f 7f 7f 7f 7f 7f |........`.......|
000000e0 11 65 6e 76 69 72 6f 6e 5f 73 69 7a 65 73 5f 67 |.environ_sizes_g|
000000f0 65 74 00 05 16 77 61 73 69 5f 73 6e 61 70 73 68 |et...wasi_snapsh|
00000100 6f 74 5f 70 72 65 76 69 65 77 31 09 70 72 6f 63 |ot_preview1.proc|
...
マジックナンバー
00 61 73 6d
で.asm
って書いてある実物を見ると「今Wasmを見てるんだわ」と実感が湧いてなんか改めて感動。
バージョン番号
01 00 00 00
はバージョン番号。今後バージョンアップしていくとここが変わっていくし、ここを元に互換性の判定とかするようになるんだろうか。
0x7f
畑
鬼のバージョン番号からその直後、なんか7f
が鬼のように乱立している。(怖い)
ついでに60
も大繁殖してて怖い。
公式ドキュメントを見てみるとこんな記載があった。
The preamble is followed by a sequence of sections.
どうやら、マジックナンバー・バージョン番号の後には「セクション」と呼ばれる領域が続いているらしい。
このox7f
畑もセクションの一部?
セクション
もう公式ドキュメントとバイナリの怒涛の往復を繰り返すしかしてないが、公式の記載はこんな感じ。
Each section consists of
- a one-byte section id,
- the
u32
size of the contents, in bytes,- the actual contents, whose structure is depended on the section id.
Every section is optional; an omitted section is equivalent to the section being present with empty contents.
セクションID(1 byte)が来て、セクション内のコンテンツのバイト数(最大u32幅)、その後にセクションの内容といった構成らしい。
セクションIDの値によって、そのセクションの役割が決まるみたい。(1
だったら type section とか)
セクション内のコンテンツのサイズが最大u32幅としているのは、セクションのサイズによってサイズ情報が1 byte
のセクションもあったり、3 bytes
使っているセクションもあったりする。
実際に、wasmer
とかでも使われているwasmparser
クレートの実装を見てみると、何をしているのかよくわかる。
impl<'a> TypeSectionReader<'a> {
/// Constructs a new `TypeSectionReader` for the given data and offset.
pub fn new(data: &'a [u8], offset: usize) -> Result<Self> {
let mut reader = BinaryReader::new_with_offset(data, offset);
let count = reader.read_var_u32()?; // ここでセクション内のサイズを読み込んでる
Ok(Self { reader, count })
}
pub fn read_var_u32(&mut self) -> Result<u32> {
// Optimization for single byte i32.
let byte = self.read_u8()?;
if (byte & 0x80) == 0 { // 先頭ビットが`1`かどうかチェックしてる
Ok(u32::from(byte))
} else {
self.read_var_u32_big(byte)
}
}
8 bit
読み込んで、先頭ビットが1
だったらこれ以上大きい数値は想定されないとして、そこまでで値を確定するようにしているみたい。
ちなみに先頭ビットが1
だった時に呼ばれるread_var_u32_big
はこんな感じ。
1 byte
ずつ読み込んでは先頭ビットをチェック、シフトして直前の値と論理和を取るを繰り返す。
fn read_var_u32_big(&mut self, byte: u8) -> Result<u32> {
let mut result = (byte & 0x7F) as u32;
let mut shift = 7;
loop {
let byte = self.read_u8()?;
result |= ((byte & 0x7F) as u32) << shift;
if shift >= 25 && (byte >> (32 - shift)) != 0 {
let msg = if byte & 0x80 != 0 {
"invalid var_u32: integer representation too long"
} else {
"invalid var_u32: integer too large"
};
// The continuation bit or unused bits are set.
return Err(BinaryReaderError::new(msg, self.original_position() - 1));
}
shift += 7;
if (byte & 0x80) == 0 {
break;
}
}
Ok(result)
}
こうして1 byte
も無駄なくしようという工夫なのか、と勝手に感じとって感動。
ちなみにセクションサイズの記載はオプションらしい。
セクションサイズの計算方法がわかった所で実際のバイナリを見てみる。
バージョン番号の直後が01 74 11 60 00
となっているので、一番先頭のセクションだけ見てみるとこんな感じ。
- Section ID
- 1 (= type section)
- Size
-
74
(= 116 bytes)
-
Type section
一発目Type Section
という知らない子が出てきたので、ドキュメントに戻って仕様を追ってみる。
The type section has the id 1. It decodes into a vector of function types that represent the component of a module.
このセクションにはモジュール内で使用される型情報が書き込まれており、どうやらFunction Type
のベクターで構成されるらしい。
Function Type
Function Type
も知らない子だったので、どんどん潜っていく。
Function types are encoded by the byte
0x60
followed by the respective vectors of parameter and result types.
こっちは0x60
から始まるResult Type
のベクター。
バイナリ上では60 {引数のResult Type} {戻り値のResult Type}
みたいな感じ。
Result Type
潜る潜る。
Result types are encoded by the respective vectors of value types.
Value Type
のベクター。
Value Type
そろそろ潜りすぎて窒息しそう。
Value types are encoded with their respective encoding as a number type, vector type, or reference type.
Number Type
・Vector Type
・Reference Type
のいずれかで構成される。
Number Type/Vector Type/Reference Type
もうひと踏ん張り。
Number Type
Number types are encoded by a single byte.
各型に対応した1 byteの数値。
-
0x7f
=> i32 -
0x7e
=> i64 -
0x7d
=> f32 -
0x7c
=> f64
Vector Type
Vector types are also encoded by a single byte.
128bit幅分の領域で、このサイズに収まるように自由な組み合わせで値をぶっこめる複合型。
0x7B
で定義される。
⚠️⚠️⚠️このVector Type
は Vectors
とは別物なので注意!! (Vectorsについては後述) ⚠️⚠️⚠️
Reference Type
Reference types are also encoded by a single byte.
関数、もしくは外部から差し込まれる値への参照を意味する型。参照先の値に応じて1 byteの数値が対応している。
-
0x70
=> funcref (関数への参照) -
0x6f
=> externref (外部から差し込まれる値への参照)
Vectors
Vectors are encoded with their
u32
length followed by the encoding of their element sequence.
前述のVector Type
が、128 bit幅の複合型だったのに対し、こちらは長さ指定のシーケンス。
u32
で先頭にサイズを定義して、定義した分の数だけ値が続く。
ここまでで散々出てきているベクターといっているのはこっちのVectors
のこと。
一般的にベクターというとこっちのイメージが個人的にある。
この辺までで道具が揃ってそうなので引き返す。
もう一度バイナリに立ち返る
少々窒息しそうになったが、ここでもう一度バイナリに戻ってみる。
$ hexdump -C ./hello-wasm.wasm | head -10
00000000 00 61 73 6d 01 00 00 00 01 74 11 60 00 00 60 01 |.asm.....t.`..`.|
00000010 7f 00 60 01 7f 01 7e 60 02 7f 7f 00 60 01 7f 01 |..`...~`....`...|
00000020 7f 60 02 7f 7f 01 7f 60 03 7f 7f 7f 00 60 03 7f |.`.....`.....`..|
00000030 7f 7f 01 7f 60 04 7f 7f 7f 7f 01 7f 60 00 01 7f |....`.......`...|
00000040 60 05 7f 7f 7f 7f 7f 00 60 04 7f 7f 7f 7f 00 60 |`.......`......`|
00000050 05 7f 7f 7f 7f 7f 01 7f 60 07 7f 7f 7f 7f 7f 7f |........`.......|
000000e0 11 65 6e 76 69 72 6f 6e 5f 73 69 7a 65 73 5f 67 |.environ_sizes_g|
000000f0 65 74 00 05 16 77 61 73 69 5f 73 6e 61 70 73 68 |et...wasi_snapsh|
00000100 6f 74 5f 70 72 65 76 69 65 77 31 09 70 72 6f 63 |ot_preview1.proc|
...
7f
畑の正体 (ついでに60
も)
感の良い方はもうお気づきかもしれないが、乱立している7f
は数値型のi32
であることがわかる。
さらに、60
は関数型が定義されている箇所に現れている。
Type Section
を分解
一番最初に定義されているセクションである、Type Section
を分解するとこんな感じになる。
01 ------------------------------------------ Section ID (= Type Section)
74 --------------------------------------- セクション内のサイズ
11 ------------------------------------ 定義されているFunction Typeの数 (= 17)
60 00 00 --------------------------- (void) -> void
60 01 7f 00 ------------------------ (i32) -> void
60 01 7f 01 7e --------------------- (i32) -> i64
60 02 7f 7f 00 --------------------- (i32, i32) -> void
60 01 7f 01 7f --------------------- (i32) -> i32
60 02 7f 7f 01 7f ------------------ (i32, i32) -> i32
60 03 7f 7f 7f 00 ------------------ (i32, i32, i32) -> void
60 03 7f 7f 7f 01 7f --------------- (i32, i32, i32) -> i32
60 04 7f 7f 7f 7f 01 7f ------------ (i32, i32, i32, i32) -> i32
60 00 01 7f ------------------------ (void) -> i32
60 05 7f 7f 7f 7f 7f 00 ------------ (i32, i32, i32, i32, i32) -> void
60 04 7f 7f 7f 7f 00 --------------- (i32, i32, i32, i32) => void
60 05 7f 7f 7f 7f 7f 01 7f --------- (i32, i32, i32, i32, i32) -> i32
60 07 7f 7f 7f 7f 7f 7f 7f 00 ------ (i32, i32, i32, i32, i32, i32, i32) -> void
60 06 7f 7f 7f 7f 7f 7f 01 7f ------ (i32, i32, i32, i32, i32, i32) -> i32
60 07 7f 7f 7f 7f 7f 7f 7f 01 7f --- (i32, i32, i32, i32, i32, i32, i32) -> i32
60 03 7e 7f 7f 01 7f --------------- (i64, i32, i32) -> i32
怒涛の7f
地獄で怯え散らかしていたがスッキリした。
Import Section
Type Sectionの次は、Section IDが2
のImport Section
が続いている。
The import section has the id 2. It decodes into a vector of imports that represent the imports component of a module.
「外部のWasmモジュールから取り込む情報」を定義したセクションらしく、「どのモジュールから、どんな名前で、何を取り込むか」といったリストが書き込まれている。
これも分解
02 ----------------------------- Section ID (= Import Section)
96 01 ----------------------- セクション内のサイズ
04 -------------------- 取り込む要素の数 (= 4)
16 ----------------- モジュール名の長さ (= 22 bytes)
77 61 73 ~ 31 --- モジュール名 (= "wasi_snapshot_preview1")
08 -------------- 読み込み対象の名前の長さ (= 8 bytes)
66 64 5f ~ 65 --- 読み込み対象の名前 (= "fd_write")
00 -------------- 読み込み先のベクター (= Type Section)
08 -------------- 参照先の要素番号 (= Type Sectionの8番目)
16 ----------------- モジュール名の長さ (= 22 bytes)
77 61 73 ~ 31 --- モジュール名 (= "wasi_snapshot_preview1")
0b -------------- 読み込み対象の名前の長さ (= 11 bytes)
65 6e 76 ~ 74 --- 読み込み対象の名前 (= "environ_get")
00 -------------- 読み込み先のベクター (= Type Section)
05 -------------- 参照先の要素番号 (= Type Sectionの5番目)
16 ----------------- モジュール名の長さ (= 22 bytes)
77 61 73 ~ 31 --- モジュール名 (= "wasi_snapshot_preview1")
11 -------------- 読み込み対象の名前の長さ (= 17 bytes)
65 6e 76 ~ 74 --- 読み込み対象の名前 (= "environ_sizes_get")
00 -------------- 読み込み先のベクター (= Type Section)
05 -------------- 参照先の要素番号 (= Type Sectionの5番目)
16 ----------------- モジュール名の長さ (= 22 bytes)
77 61 73 ~ 31 --- モジュール名 (= "wasi_snapshot_preview1")
09 -------------- 読み込み対象の名前の長さ (= 9 bytes)
70 72 6f ~ 74 --- 読み込み対象の名前 (= "proc_exit")
00 -------------- 読み込みタイプ (= 関数)
01 -------------- 参照先の要素番号 (= Type Sectionの1番目)
Function Section
Import Section
に続く、Section IDが3
のセクション。
関数のシグネチャのベクターが管理されている。
分解分解
ここはセクションサイズは省略されていて、Section IDの直後はシグネチャの要素数が来ている。
このセクションに関しては、要素数 == セクションサイズ
としてもほぼ同義なので省かれているのかも?
大部分は同じような繰り返しが続くので、分解も一部抜粋。
03 --------------- Section ID (= Function Section)
90 02 --------- セクション内の要素数
8e 02 --- `Type Section`上にある型情報の要素番号
(以下略)
Table Section
Function Section
に続く、Section ID4
のセクション。
The table section has the id 4. It decodes into a vector of tables that represent the tables component of a module.
Table
という「関数や外部から差し込まれた値への参照先を保持するデータ構造」のベクターが定義されたセクション。
Table
一つ一つには、テーブル内の最小エントリ数・最大エントリ数と、前述のReference Type
が定義されている。
コードからは、0から始まる要素番号でベクター内の特定のテーブルにアクセスする。
分解分解分解
04 ------------------ Section ID (= Table Section)
05 --------------- セクション内のサイズ
01 ------------ `Table`の数 (= 1)
70 --------- Reference Type (= 関数への参照)
01 60 60 --- テーブル内の最小・最大エントリ数 (= 96)
Memory Section
Table Section
に続くセクション。Section IDは5
。
The memory section has the id 5. It decodes into a vector of memories that represent the
mems
component of a module.
Linear Memory
という、「ヒープ空間のように動的に割当可能なメモリ領域」に関する情報のベクターが定義されたセクション。
公式のドキュメントではMemory
として省略されてる。
それぞれのMemory
には、空間の初期サイズと最大サイズが定義されている。
こちらもTable
と同様、コードからは「0から始まる要素番号」でベクター内の特定のMemory
を参照する。
分解分解分解分解
05 --------------- Section ID (= Memory Section)
03 ------------ セクション内のサイズ
01 --------- `Memory`の数 (= 1)
00 11 --- 割当可能なメモリサイズの最小値・最大値 (= 0~17ページ)
Global Section
Memory Section
に続くセクション。Section IDは6
。
The global section has the id 6. It decodes into a vector of globals that represent the component of a module.
「モジュール内のグローバル変数に関する情報」のベクターが定義されたセクション。
グローバル変数には、変数の型(Number Type)・可変性・初期値を決定する式から構成される。
可変性と初期値についてはこんな感じ。
可変性
-
{Value Type} 0x00
=> const {Value Type} -
{Value Type} 0x10
=> var {Value Type}
式
命令を並べて0x0b
で終端を表現。命令は結構たくさんあるので割愛。
これも分解するしかない
06 ------------------------------ Section ID (= Global Section)
19 --------------------------- セクション内のサイズ
03 ------------------------ グローバル変数の数 (= 3)
7f --------------------- 変数の型 (= i32)
01 ------------------ 可変性 (= var)
41 80 80 c0 00 0b --- 初期値 (= const i32 0x80)
7f --------------------- 変数の型 (= i32)
00 ------------------ 可変性 (= const)
41 90 da c0 00 0b --- 初期値 (= const i32 0x90)
7f -------------------- 変数の型 (= i32)
00 ------------------ 可変性 (= const)
41 84 da c0 00 0b --- 初期値 (= const i32)
Export Section
Global Section
の次の、Section ID7
のセクション。
The export section has the id 7. It decodes into a vector of exports that represent the
exports
component of a module.
「外部のWasmモジュールやランタイムから参照可能な情報」のベクターで構成されている。
エントリポイントとなる関数をエクスポートしてランタイムから参照できるようにしたりとか、グローバル変数などをエクスポートして外部Wasmモジュールから利用可能にしたりとか?
分解しないと (使命感)
07 ------------------------------ Section ID (= Export Section)
37 --------------------------- セクション内のサイズ
05 ------------------------ エクスポートする要素の数 (= 5)
06 --------------------- エクスポートする要素名の長さ (= 6 bytes)
6d 65 6d 6f 72 79 --- エクスポートする要素名 (= "memory")
02 ------------------ エクスポートする要素の場所 (= `Table Section`)
00 --------------- 要素番号
0b --------------------- エクスポートする要素名の長さ (= 11 bytes)
5f 5f 68 ~ 65 ------- エクスポートする要素名 (= "__heap_base")
03 ------------------ エクスポートする要素の場所 (= `Global Section`)
01 --------------- 要素番号
0a --------------------- エクスポートする要素名の長さ (= 10 bytes)
5f 5f 64 ~ 64 ------- エクスポートする要素名 (= "__data_end")
03 ------------------ エクスポートする要素の場所 (= `Global Section`)
02 --------------- 要素番号
06 --------------------- エクスポートする要素名の長さ (= 6 bytes)
5f 73 74 61 72 74 --- エクスポートする要素名 (= "_start")
00 ------------------ エクスポートする要素の場所 (= `Type Section`)
90 02 --------------- 要素番号
04 --------------------- エクスポートする要素名の長さ (= 4 bytes)
6d 61 69 6e --------- エクスポートする要素名 (= "main")
00 ------------------ エクスポートする要素の場所 (= `Type Section`)
91 02 --------------- 要素番号
Start Section
Section ID8
のセクション。
The start section has the id 8. It decodes into an optional start function that represents the
start
component of a module.
このセクションはオプションらしく、今回調査対象のバイナリには含まれていなかった。
どうやら、Wasmモジュールが読み込まれたタイミング(正確にはテーブルやリニアメモリが初期化された直後)に自動的に関数を呼び出してもらうことができるらしく、その呼び出し対象の関数をこのセクションで指定できるみたい。
Element Section
Section ID9
のセクション。
The element section has the id 9. It decodes into a vector of element segments that represent the
elems
component of a module.
Element
という「各テーブルの初期化情報?」のベクターが定義されたセクションみたい。
ここもFunction Section
同様、セクション内のサイズ保持せず要素数で管理されているっぽいように見える。
それぞれのElement
には、passive
・active
・declarative
といったモードがありそれぞれ初期化が実行されるタイミングが違う。
-
passive
-
table.init
命令が呼ばれたタイミングで、定義されているデータの塊を対象のテーブルにコピーして初期化する。
-
-
active
- 指定された初期化先を、モジュールのメモリ読み込み時のタイミングで自動的に初期化する。
-
declarative
- 実行時には使用できないらしいが、
ref.func
などで前方宣言?のために使われるらしい。(ここはちょっとイメージつかなかった)
- 実行時には使用できないらしいが、
分解
09 ------------------ Section ID (= Elem Section)
83 01 ------------ Elementの要素数
01 --------- Elementのモード (= passive)
00 ------ Elementの種類 (= funcref)
41 ------ Element内部の要素数 (= 65)
01 --- ここから下は要素数分の初期化情報の塊
0b
5f
0e
0b
08
12
e5 01
4b
(以下略)
Code Section
Section ID10
。
The code section has the id 10. It decodes into a vector of code entries that are pairs of value type vectors and expressions. They represent the
locals
andbody
field of the functions in thefuncs
component of a module. Thetype
fields of the respective functions are encoded separately in the function section.
実際の処理がCode
として書き込まれているセクションで、なかなかボリュームがある。
それぞれCode
という単位のベクターになっていて、それぞれのCode
には「ローカル変数の宣言」・「関数本体の処理になる式」が定義されている。
言うまでもなく分解
これまで通りセクションまるごと分解しようかと思ったが、めちゃくちゃ量が多かったので一部抜粋。
0a --------------------------------------------- Section ID (= Code Section)
a3 e1 03 ------------------------------------ セクション内のサイズ (= bytes)
8e 02 ------------------------------ コードの数 (= )
05 --------------------------- コードのサイズ (= 5 bytes)
00 ------------------------ ローカル変数の個数 (= 0)
10 ------------------------ 関数呼び出し (= call)
8e 01 ------------------ 呼び出す関数の要素番号 (= )
0b ------------------------ コードの終端
1b --------------------------- コードのサイズ (= 27 bytes)
01 ------------------------ ローカル変数の個数 (= 1)
01 --------------------- ローカル変数のサイズ (= 1 byte)
7f --------------------- ローカル変数の型 (= i32)
02 ------------------------ ブロックの開始
40 --------------------- ブロックの戻り値の型 (= void)
10 ------------------ 関数呼び出し (= call)
8f 80 80 80 00 --- 呼び出す関数の要素番号 (= )
22 ------------------ 直後に続く値をローカル変数に格納して値を返す (= local.tee)
00 --------------- 格納先のローカル変数のテーブル上の要素番号 (= 0)
45 ------------------ i32.eqz
0d ------------------ 直前の演算結果がtrueの場合、指定したラベルにジャンプする (= br_if)
00 --------------- ラベルの要素番号 (= 0)
20 ------------------ ローカル変数から値を取得 (= local.get)
00 --------------- 取得元のローカル変数のテーブル上の要素番号 (= 0)
10 ------------------ 関数呼び出し (= call)
9a 81 80 80 00 --- 呼び出す関数の要素番号
00 ------------------ ブロックの戻り値となる値 (= unreachable ※voidの代わり)
0b --------------------- ブロック内のコードの終端
0b ------------------------ ブロックの終端
(以下略)
Data Section
Section ID11
のセクション。
The data section has the id 11. It decodes into a vector of data segments that represent the datas component of a module.
ELFとかのデータセグメントに相当する領域。静的な値などがここに書き込まれている。
実態はdata segment
のベクター。
こちらもelement segment
のように、それぞれのデータセグメントはactive
もしくはpassive
といったモードを持っており、それぞれのモードの特徴としては以下。
-
active
- メモリ領域の指定によって、初期化段階で自動的に
Linear Memory
内にコピーされる。
- メモリ領域の指定によって、初期化段階で自動的に
-
passive
-
memory.init
命令で逐次Linear Memory
内にコピーする。
-
分解
これもめちゃくちゃ量が多かったので一部抜粋。出力してたHello, Wasm!
が出てきた。
# Data Section
0b --------------------------------------------------------- Section ID (= Data Section)
99 55 --------------------------------------------------- セクション内のサイズ
02 ------------------------------------------------ モード (= active)
00 --------------------------------------------- Linear Memoryの要素番号
41 --------------------------------------------- Linear Memoryのオフセットを決める式の型 (= i32.const)
80 80 c0 00 --------------------------------- オフセットの値
0b ------------------------------------------ 式の終端
f7 54 ------------------------------------------ データのサイズ
48 65 6c 6c 6f 2c 20 57 61 73 6d 21 0a --- Hello, Wasm!\n
(以下略)
Data Count Section
Section ID12
。
The data count section has the id 12. It decodes into an optional u32 that represents the number of data segments in the data section. If this count does not match the length of the data segment vector, the module is malformed.
このセクションもオプションらしい。(これも今回のバイナリには含まれてなかった)
Data Section
内のデータセグメントの数をu32
で指定するらしく、single-pass validationというのを簡素化するために使われるとのこと。
最後に
ここまでめちゃくちゃ人間の目でバイナリを読み込んできたわけだが、実はwasm2wat
とかwasm-decompile
を使えばもっと人間様向きのフォーマット(S式とかC-likeなシンタックス)で読める。
もっと複雑なプログラムをデバッグする時とかは、そのへんを使うのが懸命だし健康的な気がする。
(実際中盤以降くらいになると、7f
とか0b
とか頻出するものについては16進数を見て「あれか」と意味がわかるような頭のおかしい状態になってきてた)
ただ今回に関しては根本的なWasmの勉強にはなったし、調べながら読んでて個人的にめちゃくちゃ楽しめたのでOK。
個人的にWasm熱が高まってて今後も色々調べて行きたいし、「なぜWasmがセキュアなのか」とかも仕様レベルで調べてまとめてみたいな〜と思った。
Discussion