外观
WASM 内部模块
模块结构概述
WebAssembly 模块是一个结构化的二进制单元,采用自定义的二进制格式设计,旨在实现紧凑的编码和快速的解码。每个模块都遵循严格的格式规范,由一系列有序的段 (section) 组成。
基础模块结构示意图:
wasm 二进制文件
├── 魔数头 (0x00 0x61 0x73 0x6D)
├── 版本号 (0x01 0x00 0x00 0x00)
└── 段序列
├── 类型段
├── 导入段
├── 函数段
├── 表段
├── 内存段
├── 全局段
├── 导出段
├── 起始段
├── 元素段
├── 代码段
└── 数据段核心段结构详解
类型段 (Type Section)
类型段定义了模块中所有函数的签名,采用函数类型形式化描述。每个函数类型由参数类型列表和返回类型列表组成。
函数类型编码示意图:
函数类型结构:
0x60 → 函数类型前缀
参数数量 → 参数类型序列
返回数量 → 返回类型序列类型段二进制布局:
类型段头部: [段ID: 0x01] [段长度]
类型数量: N
重复N次:
0x60 (函数类型标识)
参数个数 → 参数类型序列 (i32=0x7F, i64=0x7E, f32=0x7D, f64=0x7C)
返回个数 → 返回类型序列函数段 (Function Section)
函数段声明了模块中所有函数的类型索引,将函数体与类型签名关联起来。
函数声明流程:
类型段: [签名1, 签名2, ...]
↓ 类型索引引用
函数段: [函数1:类型索引, 函数2:类型索引, ...]
↓ 对应函数体
代码段: [函数1体, 函数2体, ...]代码段 (Code Section)
代码段包含所有函数的实际指令代码,采用局部变量声明后接指令序列的格式。
函数体结构:
函数体大小 (u32)
局部变量声明数量
重复局部变量声明:
局部变量数量 → 值类型
指令序列:
操作码 → 立即数 (可选) → ...
结束符: 0x0B (end)内存段 (Memory Section)
内存段定义了模块的线性内存配置,采用页为单位 (64KiB) 进行管理。
内存类型编码:
内存标志:
0x00 → 无最大值
0x01 → 有最大值
初始页数
最大页数 (如果标志为0x01)内存布局示例:
线性内存地址空间:
0x00000000 ┌─────────────────┐
│ 代码和数据段 │
0x00001000 ├─────────────────┤
│ 堆区域 │ → 可动态增长
0x00002000 ├─────────────────┤
│ 栈区域 │
└─────────────────┘执行模型与指令集
基于栈的虚拟机
WebAssembly 采用基于栈的执行模型,所有操作都通过操作数栈进行。
栈执行示例:
指令序列: i32.const 5 → i32.const 3 → i32.add → end
栈状态变化:
初始: []
i32.const 5: [5]
i32.const 3: [5, 3]
i32.add: [8]
end: []控制流指令
控制流指令采用结构化编程范式,确保代码的安全性和可验证性。
块结构示意图:
block $label → 结果类型
... 指令序列 ...
end
loop $label → 结果类型
... 循环体 ...
end
if $label → 结果类型
... 条件为真时的指令 ...
else
... 条件为假时的指令 ...
end内存指令
内存指令提供对线性内存的精细控制,支持多种访问模式和地址计算。
内存访问模式:
加载指令: i32.load [offset=imm] [align=imm]
存储指令: i32.store [offset=imm] [align=imm]
地址计算: base_address + offset
对齐要求: 2^align 字节边界高级模块特性
表段与间接调用
表段实现了函数指针机制,支持动态函数调用。
间接调用流程:
表段: [函数引用1, 函数引用2, ...]
↓
call_indirect 类型索引 → 表索引
↓
运行时类型检查 → 函数调用表段结构示例:
表类型: 0x70 (anyfunc) → 初始大小 → 最大大小
元素段: 初始化表内容
[表索引] → [偏移量] → [函数索引列表]全局段 (Global Section)
全局段定义了模块的全局变量,支持可变和不可变两种类型。
全局变量编码:
全局数量: N
重复N次:
值类型
可变性 (0x00=不可变, 0x01=可变)
初始化表达式初始化表达式示例:
i32.const 42 → end ; 常量42
global.get 0 → i32.add → end ; 基于其他全局的计算元素段 (Data Section)
数据段用于初始化线性内存的内容,支持复杂的初始化模式。
数据段结构:
数据段数量: N
重复N次:
内存索引 (通常为0)
偏移量表达式
数据大小 → 原始字节数据模块链接与实例化
导入与导出机制
导入导出系统实现了模块间的依赖管理和接口暴露。
模块链接示意图:
模块A
├── 导出: [func1, memory1, global1]
└── 依赖: [模块B的funcX]
模块B
├── 导入: [env.memory, 模块A.func1]
└── 导出: [funcX]
实例化流程:
解析导入 → 初始化内存/表 → 运行起始函数起始段 (Start Section)
起始段指定了模块实例化后自动执行的函数,用于初始化逻辑。
起始函数特征:
- 无参数无返回值
- 在实例化完成后自动调用
- 主要用于模块初始化
自定义段与元数据
名称段 (Name Section)
名称段存储调试信息,包含函数名、局部变量名等符号信息。
名称段结构:
模块名称子段
函数名称子段
局部变量名称子段源码映射段
源码映射段将 WASM 指令映射回原始源代码位置,支持源码级调试。
验证与安全特性
模块验证流程
WebAssembly 模块在加载时必须通过严格的验证过程。
验证阶段:
结构验证 → 类型检查 → 指令验证 → 内存安全验证
↓
可安全执行的模块实例类型安全保证
类型系统确保所有操作在编译时类型正确,防止运行时类型错误。
类型检查规则:
- 操作数栈类型必须匹配指令期望
- 控制流必须保持栈高度平衡
- 函数调用必须匹配类型签名
内存安全机制
线性内存模型提供强大的内存安全保证。
内存保护特性:
边界检查: 所有内存访问验证在有效范围内
隔离性: 模块无法访问外部内存
初始化: 数据段确保内存正确初始化二进制编码细节
LEB128 变长编码
WebAssembly 广泛使用 LEB128 编码实现紧凑的整数表示。
编码示例:
值: 127 → 编码: 0x7F
值: 128 → 编码: 0x80 0x01
值: 624485 → 编码: 0xE5 0x8E 0x26段序列化格式
每个段都遵循统一的序列化格式:
段ID (1字节) → 段长度 (u32) → 段内容高级模块模式
动态链接模块
通过共享内存和表实现模块间的动态链接。
动态链接架构:
主模块
├── 导出共享内存
├── 导出函数表
└── 导入外部函数
侧模块
├── 导入共享内存
├── 导入函数表
└── 导出新增函数组件模型
基于接口类型的组件模型支持高级的跨模块交互。
组件结构:
组件包装器
├── 实例化参数
├── 导入适配器
└── 导出适配器通过深入理解 WebAssembly 模块的内部结构,开发者能够更好地优化模块性能、调试复杂问题,并充分利用 WASM 的安全和可移植特性。