WebAssembly之wasm格式解析

时间:2022-07-24
本文章向大家介绍WebAssembly之wasm格式解析,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

WebAssembly 二进制格式

WebAssembly 使用LEB128编码。

LEB128编码

LEB128编码是一种使用广泛的可变长度编码格式,在DWARF调试格式信息、Android 的Dalvik虚拟机、xz压缩文件等诸多领域中都有广泛的使用,WebAssembly二进制文件中也使用LEB128编码表示整数与字符串长度等信息。

LEB128编码的核心思想主要有两点:

  1. 采用小端序表示编码数据
  2. 采用128进制编码数据

主流编程语言中,一个整形数一般采用本地序表示,同时每个字节8位(bit)用于表达256进制的一个数位。如果每个字节只用于表达LEB128的128进制的一个数位,那么将只需要7位。LEB128将每个字节剩余的1位用于表达是否终结的标志位,如果标志位是1表示编码数据还没有结束,如果标志位位0则表示编码已经结束。

样例的格式:

wasm格式分析

首先因为大小端问题,这里的存放和源文件是不同的,wasm的魔数为0x6d736100,版本号为0x01,所以前四个为魔数00 61 73 6d,后四个是版本号01 00 00 00,大小端颠倒了。

头部

WebAssembly的头部是一个四字节的魔数: [0x00, 0x61, 0x73, 0x6d],对应"asm"字符串。其中0x61, 0x73, 0x6d分别对应着字母的ASCALL码。

魔数后面的四字节是当前WebAssembly文件的版本,目前是有版本1。

段类型

模块主体主要是由多个段组成,段数据包含了模块段全部信息,每个段都对应一个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)

用于存储初始化内存的静态数据

自定义段(Custom)

(暂未写)

类型段(Type)

接下来首先是类型段。

01 8C 80 80 80 00 02 60 01 7F 01 7F 60 02 7F 7F 01 7F

01是type,表示函数签名的Type Section,后面的 8C 80 80 80 00是LEB128编码方式表示长度为0c。

后续的表示:

02 60 01 7F 01 7F 60 02 7F 7F 01 7F

02表示entries数量为2,60表示是个函数的签名,01 表示参数为1,7f表示参数为i32类型,01表示有一个返回值,7f表示返回值为i32类型,这个是printf的函数声明; 同理,60 02 7F 7F 01 7F表示两个带有i32类型并返回i32类型的函数声明,也就是main的函数声明。

导入段(Import)
02 8C 80 80 80 00 01 03 65 6E 76 04 70 75 74 73 00 00

02表示Import Section.

长度为12, 02 8c 80 80 80 00,内容为:01 03 65 6E 76 04 70 75 74 73 00 00

01则只有一个引用,module长度为3,名称为env,field长度为4,名称为puts,最后的kind表示引入的类型,00表示引入的是个函数。

函数段(Function)
03 82 80 80 80 00 01 01

03是函数段Function Section

长度为82 80 80 80 00,内容为:01 01

01表示签名索引数量为1,01表示在type区中的索引值值1,即printf函数的签名。

表格段(Table)
04 84 80 80 80 00 01 70 00 00

04是Table Section。

长度为84 80 80 80 00 ,内容为:01 70 00 00

01表示数量为1,类型为70,目前只能是70,表示anyfunc。

内存段(Memory)
05 83 80 80 80 00 01 00 01

05是内存段(Memory)

长度为83 80 80 80 00 , 内容为: 01 00 01

全局段(Global)
06 81 80 80 80 00 00

06是全局段(Global)

长度为 81 80 80 80 00 , 内容为: 00

导出段(Export)
07 91 80 80 80 00 02 06 6D 65 6D 6F 72 79 02 00 04 6D 61 69 6E 00 01

07是Export Section

长度为91 80 80 80 00 , 内容为: 02 06 6D 65 6D 6F 72 79 02 00 04 6D 61 69 6E 00 01

02表示export数量为2,06表示第一个entry长度,名称为memory,02表示类型为memory类型,index为00,表示在对应索引空间的索引值; 04表示第二个entry长度为4,名称为main,类型为00,function类型,索引为01

代码段(Code)
0A 8F 80 80 80 00 01 89 80 80 80 00 00 41 10 10 00 1A 41 00 0B

0a表示Code Section

长度为 8F 80 80 80 00 , 内容为:01 89 80 80 80 00 00 41 10 10 00 1A 41 00 0B

01表示数量为1,89 80 80 80 00表示size为9,00是locals数量,为0则locals就没有,41 10 10 00 1a 41 00是code,0b是end。

数据段(Data)
0B 92 80 80 80 00 01 00 41 10 0B 0C 48 65 6C 6C 6F 20 57 6F 72 6C 64 00

0b是Data Section,

长度为92 80 80 80 00,内容为: 01 00 41 10 0B 0C 48 65 6C 6C 6F 20 57 6F 72 6C 64 00

01表示数量为1,00表示在线性内存中的索引为0,41表示iConst.32, 10表示call操作码, 0b表示offset,0c表示参数长度为12,68 65 6c 6c 6f 20 77 6f 72 6c 64 00表示我们打印的内容为hello world 至此,文件解析完毕。