本文是《WebAssembly 权威指南》系列文章第 3 篇,系列文章列表:
操作系统运行通常以编译形式包含的程序。每个操作系统都有自己的格式,定义了从哪里开始运行,需要什么数据,以及不同功能位的指令是什么。WebAssembly 也不例外。在本章中,我们将了解这种行为是如何打包的以及宿主如何知道如何处理它。
软件工程师的整个职业生涯中可能都会忽略程序是如何通过这个过程加载和执行的。他们的世界从 int main (int argc, char **argv)
或 static void main (String [] args)
开始,仅仅到 if __name == "__main__":
就结束了。这些是 C、Java 和 Python 程序的众所周知的入口点,因此这是程序员承担控制流责任的地方。但是,操作系统或程序运行时需要在程序启动之前和退出之后构建和拆除可执行结构。加载器需要知道指令从哪里开始、数据元素是如何初始化的、需要加载哪些其他模块或库等。
这些细节通常由可执行文件的性质定义。在 Linux 上,这是由可执行和可链接格式 (ELF)[1] 定义的;在 Windows 上,它由可移植可执行格式 (PE)[2] 定义;在 macOS 上,它由 Mach-O 格式[3]定义。显然,这些是本机可执行文件的特定于平台的格式。Java 和 .NET 等更多可移植系统使用中间字节码表示,但仍具有定义良好的结构,并且它们的工作方式相似。
WebAssembly MVP 的首要设计考虑之一是定义模块结构,以便 WebAssembly 主机知道要查找和验证什么,以及在执行部署单元时从哪里开始。
在第 2 章中,你看到了比本章开始时更复杂的模块结构。我们将逐步介绍这些部分,然后向你展示一些用于探索 WebAssembly 模块的文本和视觉结构的工具。在上一章中,我们简要讨论了二进制结构。它结构紧凑,转移和装载速度快。你可能不会经常花很多时间查看二进制细节,因为你关注的是软件方面。熟悉模块的布局很有用,让我们来看看。
模块结构
空模块是 WebAssembly 最基本的模块。空模块中不需要任何内容,就是一个有效的模块,如例 3-1 所示。
例 3-1. 空模块,但是有效的 WebAssembly 模块。
(module)
显然,这没有什么可看的,但它可以转换为二进制形式。你会注意到在下面的输出中,它没有占用多少空间,而且它什么都不做。
brian@tweezer ~/g/w/s/ch03> wat2wasm empty.wat
brian@tweezer ~/g/w/s/ch03> ls -alF
total 16
drwxr-xr-x 4 brian staff 128 Dec 21 14:45 ./
drwxr-xr-x 4 brian staff 128 Dec 14 12:37 ../
-rw-r--r-- 1 brian staff 8 Dec 21 14:45 empty.wasm
-rw-r--r-- 1 brian staff 8 Dec 14 12:37 empty.wat
如果你以视觉为导向,你可能会喜欢使用 WebAssembly Code Explorer,可从 wasdk GitHub 存储库[4]获得。你可以在浏览器中在线使用[5]它或从克隆它以运行 HTTP 服务器。我将像以前一样使用 Python 3 Web 服务器。
brian@tweezer ~/g/wasmcodeexplorer> python3 -m http.server 10003
Serving HTTP on :: port 10003 (http://[::]:10003/) ...
同样,对于一个空模块来说,它看起来并不多,但一旦我们开始向其中添加一些元素,它将是一个有用的总结。操作系统通常从文件的前几个字节识别文件格式。它们通常被称为幻数。对于WebAssembly,这些字节被编码为 0x00 0x61 0x73 0x6D
,分别代表字符 a、s、m 的十六进制值,后面是版本号 1(用字节 0x01 0x00 0x00 0x00
表示)。
在图 3-1 中,你可以看到魔法字节,这是 WebAssembly 文件格式的版本 1,左侧是一系列数字,右侧是一个空模块结构。
对于模块的命令行检查,你有几个选项,Wabt 工具包中的 wasm-objdump
可执行文件非常有用。请参阅附录以帮助安装本书中讨论的各种工具。