WABT wasm2wat 使用指南:3 分钟把 Wasm 二进制读成文本
【免费下载链接】wabtThe WebAssembly Binary Toolkit项目地址: https://gitcode.com/GitHub_Trending/wa/wabt
你拿到一个编译好的 .wasm 文件,想读懂里面的逻辑,打开却只有一片字节。WABT(WebAssembly Binary Toolkit)里的 wasm2wat 就是干这个的:把 WebAssembly 二进制模块还原成可读的 .wat 文本。它不做优化、不猜测意图,只做一件事——保真地把二进制翻译成规范文本格式。
先搞清楚:wasm2wat 是干什么的
wasm2wat 是 WABT 工具链里的反向翻译器(与 wat2wasm 互为逆操作),读入 .wasm 二进制,输出 .wat 文本。适合这几类人:
- 逆向分析拿到无源码 wasm 的开发者;
- 工具链开发者,需要检查自己的编译器生成了什么指令;
- 想学 Wasm 文本格式(wat)的人——用真实二进制做教材。
核心能力:
- 完整二进制解析,覆盖 SIMD、多线程、memory64 等提案(多数默认启用,以 README 的支持表为准);
- 转换前先做模块验证(validate),文本不合法的模块会直接报错;
--fold-exprs把栈式指令折叠成嵌套表达式,可读性大幅提升;--generate-names给无名函数、类型自动命名;- 目标明确:1:1 保真往返,指令逐条对应,不增不减。
它和 Binaryen 这类工具的定位不同:不追求优化或高级编译器目标,只追求对规范的完整还原。
第一次跑起来:编译环境与参数速查
环境只需要 CMake。clone 后按官方步骤编译(记得带子模块):
git clone --recursive https://gitcode.com/GitHub_Trending/wa/wabt cd wabt mkdir build && cd build cmake .. && cmake --build .注意不能在仓库根目录直接 cmake,因为会生成一个叫 wasm2c 的可执行文件,和 wasm2c/ 目录冲突(README 明确提醒)。
最小可运行命令:
bin/wasm2wat input.wasm不指定输出时结果直接打到 stdout。编译产物路径各系统略有差异(make 构建默认在 out/ 下),以仓库实际为准。
参数速查:
| 参数 | 作用 |
|---|---|
| 位置参数 | 输入 .wasm 文件 |
-o FILE | 输出 .wat 文件,缺省写 stdout |
-f, --fold-exprs | 能折叠的表达式写成嵌套 s 表达式 |
--generate-names | 给无名函数、类型等自动命名 |
--no-debug-names | 忽略二进制里的调试名称 |
--no-check | 跳过模块验证 |
-v | 输出更多诊断信息,可叠加使用 |
--enable-xxx/--disable-xxx | 提案开关,如--enable-simd,具体以--help为准 |
跑完bin/wasm2wat --help也能看到全部选项。
读懂输出的三层结构
一次转换的输出从外到内分三层。
第一层:type 声明。每个函数签名一条,带索引注释:
(module (type (;0;) (func (result f32))) (type (;1;) (func (result i32)))函数后面引用的type 0就指向这里的第一条签名。
第二层:func 声明头,把签名摊开写:
(func (;1;) (type 1) (result i32)result i32是该函数的返回值类型,后面跟参数则写成param。
第三层:函数体,即指令序列。默认输出是"扁平"的——一条指令一行,值靠隐式栈传递,这来自 Wasm 的栈机本质。
三层合起来就是一整个模块。示例取自仓库测试 test/roundtrip/fold-block.txt。
两个开关,把输出变得可读
熟悉基本读法后,值得试两个参数。
-f(fold-exprs):优化前,加法是一行三句
i32.const 1 i32.const 2 i32.add优化后,操作数直接挂在指令下,数据流向一眼看清:
(i32.add (i32.const 1) (i32.const 2))控制流折叠后,块边界会显式标出,跳转目标写在注释里:
(block ;; label = @1 (f32.const 0x1.8p+1 (;=3;)) (br 0 (;@1;)))没有-f时同样的代码要自己数end配几层块,有-f时br 0直接告诉你跳到 @1。
--generate-names:模块没带 name section 时,函数只会显示成(func (;0;)这样的匿名索引。加上这个参数,wasm2wat 会生成占位名称,引用关系立刻可读,配合调试名称恢复效果更完整。
完整走查:从 wat 到 wasm 再读回文本
按"输入→命令→输出→解读"四步走一遍。
输入,一个最小的 add 函数:
(module (func (export "add") (param i32 i32) (result i32) local.get 0 local.get 1 i32.add))命令,正反两个方向:
bin/wat2wasm add.wat -o add.wasm bin/wasm2wat add.wasm -o out.wat输出,out.wat 核心部分:
(module (type (;0;) (func (param i32 i32) (result i32))) (func (;0;) (type 0) (param i32 i32) (result i32) local.get 0 local.get 1 (i32.add)))解读:add.wasm 用十六进制打开基本不可读,而 out.wat 把 export、签名、三条指令全部还原,local.get 0到i32.add的逻辑与输入完全一致——这就是"往返保真"的含义。
避坑清单:四个高频问题
现象:转换失败,提示验证错误。 原因:wasm2wat 默认先 validate,模块不合法就中止。 处理:确认二进制确实有问题,或加--no-check只转换不验证。
现象:输出里全是(func (;0;)匿名索引。 原因:模块没带 name section,wasm2wat 默认不瞎编名字。 处理:加--generate-names生成占位名称。
现象:报出类似 simd 指令不支持的解析错误。 原因:相关提案未默认启用,或开关名记错。 处理:加上对应开关如--enable-simd,拿不准就--help查 README 的支持表。
现象:bad magic之类的头错误。 原因:文件根本不是 Wasm 模块(魔数不对)。 处理:这不是参数能解决的,先确认拿对了文件。
能力边界:它不做什么
- 不做优化。指令数量、顺序与二进制一一对应,别指望它"看懂"你的循环或结构体;
- 不还原高级语言语义。C++ 的类、Rust 的枚举,出了编译器就只剩指令,wasm2wat 不会帮你找回来;
- 不产出可编译的 C。要 C 代码用 wasm2c(见 wasm2c/README.md),那是另一个工具的活儿。
扩展方向一句话:想在文本之外直接执行模块,交给同工具链的 wasm-interp。
wasm2wat 的定位就是"看得见的保真":指令不多不少,名字能恢复多少算多少。相关工具:wat2wasm(反方向转换)、wasm-objdump(二进制级查看)、wasm-interp(直接执行)、wasm-validate(单独验证)。文档:docs/doc/wasm2wat.1.html;更多输出样例见 test/roundtrip/;源码:src/tools/wasm2wat.cc;项目源码:https://gitcode.com/GitHub_Trending/wa/wabt
【免费下载链接】wabtThe WebAssembly Binary Toolkit项目地址: https://gitcode.com/GitHub_Trending/wa/wabt
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考