本文主要是介绍WebAssembly 二进制格式分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
前言
WebAssembly 是一种新兴的网页虚拟机标准,它的设计目标包括:高可移植性、高安全性、高效率、尽可能小的程序体积。
本文主要是通过分析wasm文件的结构来说明WebAssembly为什么会有更小的程序体积。
-
WebAssembly与html/CSS/JavaScript最不一样的特点: WebAssembly采用二进制的方式存储程序代码与数据,可以尽最大努力使得文件特别的小。
-
WebAssembly采用LEB128编码来存储程序数据,LEB128是一种压缩算法,可以大大缩小WebAssembly对数据存储的空间
-
WebAssembly采用数据段的方式分别存储不同作用的数据,使二进制数据更加紧密
WebAssembly 二进制格式
1. LEB128编码
LEB128编码是一种使用广泛的可变长度编码格式,在DWARF调试格式信息、Android 的Dalvik虚拟机、xz压缩文件等诸多领域中都有广泛的使用,WebAssembly二进制文件中也使用LEB128编码表示整数与字符串长度等信息。
1.1 LEB128编码原理
LEB128编码的核心思想主要有两点:
- 采用小端序表示编码数据
- 采用128进制编码数据
主流编程语言中,一个整形数一般采用本地序表示,同时每个字节8位(bit)用于表达256进制的一个数位。如果每个字节只用于表达LEB128的128进制的一个数位,那么将只需要7位。LEB128将每个字节剩余的1位用于表达是否终结的标志位,如果标志位是1表示编码数据还没有结束,如果标志位位0则表示编码已经结束。
对于一个32位的整数,LEB128编码后的数据长度最小为一个字节(小于128的数),最多为5字节(大于2^28的数)。
2. WebAssembly的头部与段数据
2.1 头部
WebAssembly的头部是一个四字节的魔数: [0x00, 0x61, 0x73, 0x6d],对应"\0asm"字符串。魔数后面的四字节是当前WebAssembly文件的版本,目前是有版本1。
empty.wat
(module)
wat2wasm.exe empty.wat -v
0000000: 0061 736d ; WASM_BINARY_MAGIC
0000004: 0100 0000 ; WASM_BINARY_VERSION
2.2 段类型列表
模块主体主要是由多个段组成,段数据包含了模块段全部信息,每个段都对应一个ID。
ID | 段 | 说明 |
---|---|---|
0 | 自定义段(Custom) | 主要用于存储调试信息等数据 |
1 | 类型段(Type) | 存储导入函数、模块内部函数的函数参数列表 |
2 | 导入段(Import) | 用于存储导入函数的函数名称、函数参数索引 |
3 | 函数段(Function) | 用于存储函数索引值 |
4 | 表格段(Table) | 用于存储对象引用,通过表格段可以实现函数指针的功能(call_indirect指令),可以从外部宿主导入,同时也可以导出到外部宿主环境 |
5 | 内存段(Memory) | 用于存储程序的运行时动态数据,可以从外部宿主导入,同时也可以导出到外部宿主环境 |
6 | 全局段(Global) | 用于存储全部变量值 |
7 | 导出段(Export) | 用于存储导出函数的函数名称、函数参数索引 |
8 | 开始段(Start) | 用于指定模块初始化时的函数索引值 |
9 | 元素段(Elem) | 表格段并没有显式地初始化,元素段用于存储函数的索引值 |
10 | 代码段(Code) | 用于存储函数的指令代码 |
11 | 数据段(Data) | 用于存储初始化内存的静态数据 |
2.3 自定义段(Custom)
(暂未写)
2.4 类型段(Type)
; section "Type" (1) | |
0000008: 01 | Type段ID |
0000009: 07 | 段大小 |
000000a: 01 | 一共有多少个类型 |
; type 0 | 类型0 |
000000b: 60 | 60为函数,目前只有函数类型 |
000000c: 02 | 有两个参数 |
000000d: 7f | 第一参数: i32 |
000000e: 7f | 第二参数: i32 |
000000f: 01 | 一个函数返回值,目前只支持一个返回值 |
0000010: 7f | 返回值: i32 |
2.5 导入段(Import)
(暂未写)
2.6 函数段(Function)
; section "Function" (3) | |
0000011: 03 | 函数段ID |
0000012: 03 | 段大小 |
0000013: 02 | 函数数量 |
0000014: 00 | 函数0的签名索引(函数类型索引) |
0000015: 00 | 函数1的签名索引(函数类型索引) |
2.7 表格段(Table)
(暂未写)
2.8 内存段(Memory)
(暂未写)
2.9 全局段(Global)
(暂未写)
2.10 导出段(Export)
; section "Export" (7) | |
0000016: 07 | 导出段的大小 |
0000017: 0c | 段大小 |
0000018: 02 | 一共有几个符号导出 |
0000019: 02 | 第一个符号长度 |
000001a: 6162 | 第一个导出名称("ab") |
000001c: 00 | 符号对象的类型,0x00为函数 |
000001d: 00 | 导出符号对应的函数索引 |
000001e: 03 | 第二个符号长度 |
000001f: 6162 63 | 第二个导出名称("abc") |
0000022: 00 | 符号对象的类型,0x00为函数 |
0000023: 01 | 导出符号对应的函数索引 |
2.11 开始段(Start)
(暂未写)
2.12 元素段(Elem)
(暂未写)
2.13 代码段(Code)
; section "Code" (10) | |
0000024: 0a | 代码段ID |
0000025: 1d | 段大小 |
0000026: 02 | 有两个函数的代码 |
; function body 0 | |
0000027: 0d | 函数0的大小 |
0000028: 03 | 参数总数 |
0000029: 01 | 一个参数(类型看下个字节) |
000002a: 7f | 一个i32类型的参数 |
000002b: 01 | 一个参数(类型看下个字节) |
000002c: 7e | 一个i64类型的参数 |
000002d: 01 | 一个参数(类型看下个字节) |
000002e: 7f | 一个i32类型的参数(三个参数结束) |
000002f: 20 | 代码指令:local.get |
0000030: 00 | local index |
0000031: 20 | 代码指令:local.get |
0000032: 01 | local index |
0000033: 6a | 代码指令:i32.add |
0000034: 0b | 代码指令:end (结束函数体) |
; function body 1 | |
0000035: 00 | 函数0的大小 |
0000036: 03 | 参数总数 |
0000037: 01 | 一个参数(类型看下个字节) |
0000038: 7f | 一个i32类型的参数 |
0000039: 01 | 一个参数(类型看下个字节) |
000003a: 7e | 一个i64类型的参数 |
000003b: 01 | 一个参数(类型看下个字节) |
000003c: 7f | 一个i32类型的参数(三个参数结束) |
000003d: 20 | 代码指令:local.get |
000003e: 00 | local index |
000003f: 20 | 代码指令:local.get |
0000040: 01 | local index |
0000041: 6a | 代码指令:i32.add |
0000042: 0b | 代码指令:end (结束函数体) |
2.14 数据段(Data)
(暂未写)
这篇关于WebAssembly 二进制格式分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!