WebAssembly 模块
WebAssembly 字节码从前导开始,前导指明这个模块是一个 WebAssembly 模块,前导之后是段。WebAssembly 有两种类型的段:
- 已知段
- 自定义段
这些段会在 WebAssembly 模块初始化时验证。
已知段有专门的用途和定义,自定义段可以放在已知段的前面、后面或者之间。已知段和自定义段都是可选的,而且已知段最多只能出现一次,从技术上来说,可以创建一个没有任何段的空模块。
结构描述
已知段
类型段(Type(ID=1))
该段列出 WebAssembly 模块用到的所有函数类型(或函数签名、函数原型)。图1中列举了三个函数签名:
- 第一个有两个32位整型(i32)参数,一个32位整型(i32)返回值;
- 第二个有两个64位整型(i64)参数,没有返回值;
- 第三个不接受任何参数,也没有返回值。
导入段和导出段(Import(ID=2) 和 Export(ID=7))
这两个段分别列出模块所有的导入项和导出项,多个模块可以通过导入和导出项链接在一起。
函数段和代码段(Function(ID=3) 和 Code(ID=10))
内部函数信息分别被分开存储在两个段中,其中函数段实际上是一个索引表,列出内部函数所对应的签名索引。代码段存储内部函数的局部变量信息和字节码。函数和代码段中的项目数量必须一致,且一一对应。
表段和元素段(Table(ID=4) 和 Element(ID=9))
表段列出模块内定义的所有表,元素段列出表初始化数据。WebAssembly规范规定模块最多只能导入或定义一张表,所以即使模块有表段,里面也只能有一个项目。表主要和间接函数调用有关。
内存段和数据段(Memory(ID=5) 和 Data(ID=11))
内存段列出模块内定义的所有内存,数据段列出内存初始化数据。WebAssembly 规范规定模块最多只能导入或定义一块内存,所以即使模块有内存段,里面也是能有一个项目。
全局段(Global(ID=6))
该段列出模块内定义的所有全局变量信息, 包括值类型、可变性和初始值。
起始段(Start(ID=8))
该段给出模块的起始函数索引。和其它段有所不同,起始段只能有一个项目。起始函数主要起到两个作用,一个是在模块加载后进行一些初始化工作;另一个是把模块编程可执行程序。如果模块有起始段,那么 WebAssembly 实现在加载模块后会自动执行起始函数(一般是有 main 函数的到代码被编译后就会有起始段,里面放着 main 函数的索引)
内存段
内存段是 WebAssembly 核心安全性的一部分,因为 WebAssembly 不能直接访问设备内存。模块实例化时外部环境传入一个ArrayBuffer, 模块实例会以这个 ArrayBuffer 实例当作线性内存使用。这个线性内存就像 C++ 的堆,并且每次请求访问内存时,框架都会验证请求是否在这个 ArrayBuffer 的范围内。
模块的内存被定义为 WebAssembly 页, 每页 64KB。外部可以指定模块内存的初始页数和最大页数。如果指定了最大页数,那么框架会防止内存增长超过这个点,如果没有,则内存可以无限增长。
多个 WebAssembly 模块可以共享同一个线性内存,动态连接模块时比较有用。
C++ 中, 执行栈和线性内存位于同一内存中。尽管C++代码不应该修改执行栈,但可以使用指针进行该操作。WebAssembly 的安全性不仅在于代码不能访问设备内存,而且它还分隔了执行栈和线性内存。
自定义段(ID=0)
自定义段可以出现在模块的任意位置,任意多次,多个自定义段可以复用同一个名称。自定义段是给编译器等工具使用的,里面可以存放函数名等调试信息,或者其他任何附加信息。自定义段不参与 WebAssembly 语义,所以即便是完全忽略自定义段也不影响模块的执行。自定义段即使布局错误,也不会触发验证错误。框架可以惰性加载自定义段。
结构关系说明
除了自定义段,其它段必须按ID递增的顺序出现。这是因为很多段之间存在信息的依赖关系,例如导入段、函数段、代码段都需要知道函数类型信息,所以类型段必须在这个三个段之前出现; 由于导入的函数、表、内存、全局变量在各自索引空空间的最前面,所以导入段必须在这4个段之前出现;由于导出函数、表、内存、全局变量时需要知道其索引,所以导出段必须在这4个段之后出现。
WebAssembly 二进制格式的设计原则之一是可以一遍完成模块的解析、验证、和编译(AOT 或 JIT)。WebAssembly 可以在下载模块同时进行解码、验证和编译。可流式处理是 WebAssembly 特点之一,二进制模块中各个段的排列方式一定程度上是为了满足这一点。另外之所以要把函数的签名信息和其他信息分别存放在这两个段里,也是为了满足这一特点。
类型段、函数段和代码段配合示例
调用表段中某个条目的示例
表段会被给定一个初始长度, 还有最大长度限制。对于表来说,长度就是表中元素的个数。可以通过指定元素的个数来请求增加表的长度。如果指定了元素的最大个数,系统就会阻止表表增长超过最大个数。但如果没有指定最大长度,那么这个表就可以无限增长。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗