目录
概要
技术名词解释
1、WebAssembly 指令集概览
1)基本结构
2)数据类型
3)模块和函数
4)指令概览
1.i32 整数运算
2.i32 浮点数运算(用法同整数运算)
3.逻辑运算和位移(用法同整数运算)
4.内存访问指令
6.控制流指令
7.模块和导出指令
8.其他常见指令
实战wasm补环境
实战wasm反编译还原算法
小结
概要
1.wasm 补环境
2.反编译分析伪代码
3.wasm 的指令学习
wasm-reference-manual/WebAssembly.md at master · sunfishcode/wasm-reference-manual · GitHubWebAssembly Reference Manual. Contribute to sunfishcode/wasm-reference-manual development by creating an account on GitHub.https://github.com/sunfishcode/wasm-reference-manual/blob/master/WebAssembly.md#instructions
4.wabt反编译
Releases · WebAssembly/wabt · GitHubThe WebAssembly Binary Toolkit. Contribute to WebAssembly/wabt development by creating an account on GitHub.https://github.com/WebAssembly/wabt/releases5.gcc反编译
MSYS2Software Distribution and Building Platform for Windowshttps://www.msys2.org/
技术名词解释
1、WebAssembly 指令集概览
WebAssembly的指令集可以分为以下几类:数值操作、内存访问、控制流、模块定义和导入导出等。以下是每个类别中一些常见的指令及其功能
1)基本结构
WebAssembly的指令集由一系列字节码组成,每个字节码对应一个特定的操作码(opcode)。
2)数据类型
WebAssembly支持的数据类型包括整数类型和浮点数类型。整数类型可以是8位(i8)、16位(i16)、32位(i32)或64位(i64);浮点数类型可以是32位(f32)或64位(f64)。
3)模块和函数
WebAssembly代码以模块(module)的形式组织,每个模块包含函数(function)和相关的全局变量、内存(memory)和表格(table)等。函数由一组指令序列构成。
4)指令概览
1.i32 整数运算
i32 指令 | 描述 |
i32.add | 32位有符号整数的加法运算 |
i32.sub | 32位有符号整数的减法运算 |
i32.mul | 32位有符号整数的乘法运算 |
i32.div_s | 有符号32位整数的除法 |
i32.div_u | 无符号32位整数的除法 |
i32.rem_s | 有符号32位整数的求余运算 |
i32.rem_u | 无符号32位整数的求余运算 |
示例代码👇
(func (param i32 i32) (result i32) local.get 0 ;; 获取函数的第一个参数(整数1) local.get 1 ;; 获取函数的第二个参数(整数2) i32.add) ;; 将栈顶的两个整数相加,并将结果作为函数的返回值
代码注解
(func (param i32 i32) (result i32))
:定义了一个函数,它接受两个参数(两个32位有符号整数),并返回一个32位有符号整数作为结果。
local.get 0
和local.get 1
:这两个指令分别将函数的第一个和第二个参数加载到本地变量栈上。在WebAssembly中,参数从索引0开始。
i32.add
:这是一个整数加法指令,它从栈顶弹出两个整数值,将它们相加,然后将结果推送回栈顶。在这个例子中,它将栈顶的两个整数(即函数的两个参数)相加。执行过程
当函数被调用时,传递给函数的两个参数被存储在本地变量中(索引0和索引1)。
local.get 0
将第一个参数加载到栈顶。
local.get 1
将第二个参数加载到栈顶。
i32.add
从栈顶弹出这两个参数,执行加法操作,并将结果再次推送到栈顶,作为函数的返回值。
2.i32 浮点数运算(用法同整数运算)
f32 指令 | 描述 |
f32.add | 32位浮点数的加法运算 |
f32.sub | 32位浮点数的减法运算 |
f32.mul | 32位浮点数的乘法运算 |
f32.div | 32位浮点数的除法 |
f64 指令 | 描述 |
f32.add | 64位浮点数的加法运算 |
f32.sub | 64位浮点数的减法运算 |
f32.mul | 64位浮点数的乘法运算 |
f32.div | 64位浮点数的除法 |
3.逻辑运算和位移(用法同整数运算)
运算和位移指令 | 描述 |
i32.and | 32位整数的按位与、按位或和按位异或操作 |
i32.or | 32位整数的按位与、按位或和按位异或操作 |
i32.xor | 32位整数的按位与、按位或和按位异或操作 |
i32.shl | 32位整数的左移、有符号右移和无符号右移操作 |
i32.shr_s | 32位整数的左移、有符号右移和无符号右移操作 |
i32.shr_u | 32位整数的左移、有符号右移和无符号右移操作 |
4.内存访问指令
WebAssembly允许程序直接访问内存,执行加载和存储操作。这对于处理大量数据或者与Web页面交互非常有用。
1.内存加载和存储
i32.load
、i64.load
、f32.load
、f64.load
:从内存中加载指定类型的数据。i32.store
、i64.store
、f32.store
、f64.store
:将指定类型的数据存储到内存中。
load
示例代码👇(func (param i32) (result i32) local.get 0 ;; 获取函数的第一个参数(内存地址) i32.load) ;; 加载指定内存地址处的整数并将其作为结果返回
代码注解:
(func (param i32) (result i32))
:定义了一个函数,它接受一个32位整数作为参数,并返回一个32位整数作为结果。
local.get 0
:这个指令将函数的第一个参数(索引为0的本地变量)加载到栈顶。在WebAssembly中,参数从索引0开始。
i32.load
:这是一个内存加载指令,它从内存中加载一个32位整数值到栈顶。在这个例子中,它加载的地址是栈顶的值(即函数的参数)所指向的内存地址处的32位整数值。执行过程
当函数被调用时,传递给函数的32位整数参数被存储在本地变量中(索引0)。
local.get 0
将参数加载到栈顶,即将要加载的内存地址。
i32.load
从内存中加载存储在指定地址处的32位整数值,并将加载的值推送回栈顶,作为函数的返回值。
store
示例代码👇(func (param i32 f32) local.get 0 ;; 获取第一个参数(内存地址) local.get 1 ;; 获取第二个参数(要存储的浮点数) f32.store) ;; 将栈顶的浮点数存储到指定内存地址
代码注解:
(func (param i32 f32))
:定义了一个函数,它接受一个32位整数(作为内存地址)和一个32位浮点数作为参数。
local.get 0
和local.get 1
:这两个指令分别将函数的第一个和第二个参数加载到本地变量栈上。在WebAssembly中,参数从索引0开始。
f32.store
:这是一个内存存储指令,它将栈顶的32位浮点数值存储到指定内存地址处。在这个例子中,它将第一个参数(内存地址)指向的位置存储为第二个参数(32位浮点数)的值。执行过程
当函数被调用时,传递给函数的第一个参数是一个32位整数,表示要存储数据的内存地址。
第二个参数是一个32位浮点数,即要存储到内存中的数据。
local.get 0
将第一个参数加载到栈顶,即内存地址。
local.get 1
将第二个参数加载到栈顶,即要存储的浮点数值。
f32.store
将栈顶的浮点数值存储到栈顶的地址处。2.内存增长
memory.size
:获取当前内存的大小。示例代码👇
(memory 1) (func $getMemorySize (result i32) memory.size)
代码注解:
memory 1
:这个指令定义了一个大小为1页(64KB)的静态内存。在实际应用中,内存可以根据需求定义为更大的页面数。
$getMemorySize
函数定义了一个没有参数的函数,返回当前内存的页数作为32位整数。执行过程:
memory 1
指令定义了一个1页(64KB)大小的静态内存。这个内存是WebAssembly模块的一部分,可以在模块内部进行操作和访问。
$getMemorySize
函数通过memory.size
指令获取当前内存的页数。
memory.size
指令将当前内存的页数推送到栈顶,作为函数的返回值。
memory.grow
:增加内存的大小。
示例代码👇(memory 1) ;; 定义一个1页(64KB)大小的内存 (func $growMemory (param i32) (result i32) local.get 0 ;; 获取函数参数,表示要增加的页数 memory.grow ;; 增加内存的页数 memory.size) ;; 获取新的内存页数
代码注解:
$growMemory
函数定义了一个接受一个i32
类型参数(表示要增加的页数)和返回一个i32
类型结果(表示新的内存页数)的函数。
memory 1
指令定义了一个大小为1页(64KB)的静态内存,作为示例中的初始内存。
memory.grow
指令用于增加内存的页数。它会尝试增加内存的页数,成功时返回新的内存页数,失败时返回-1。执行过程
memory 1
指令定义了一个1页(64KB)大小的静态内存。$growMemory
函数通过local.get 0
获取函数的第一个参数,即要增加的页数。memory.grow
指令尝试增加内存的页数。如果成功,它将新的内存页数推送到栈顶;如果失败(例如超过了内存限制),则推送-1。memory.size
指令获取当前内存的页数,作为函数的返回值。
6.控制流指令
WebAssembly支持灵活的控制流结构,包括条件执行、循环和函数调用。
1.基本控制流
block
、loop
、if
、else
、end
:定义基本块、循环和条件执行及其结束示例代码👇
(func (param i32) (result i32) block ;; 定义一个块 local.get 0 ;; 将函数的第一个参数加载到栈顶 i32.const 0 ;; 将常数0推送到栈顶 i32.eq ;; 比较栈顶的两个值是否相等,结果入栈(0或1) if ;; 如果条件为真,则执行下面的指令 i32.const 1 ;; 将常数1推送到栈顶(即条件为真时的返回值) else ;; 如果条件为假,则执行下面的指令 local.get 0 ;; 再次将函数的第一个参数加载到栈顶 i32.const 1 ;; 将常数1推送到栈顶 i32.sub ;; 将栈顶的两个值相减,计算递减后的值 end ;; 结束条件分支 end ;; 结束块 )
代码注解:
这段代码实现了一个简单的逻辑:如果参数为0,则返回1;否则返回参数减去1。
使用
block
和end
封装了整个逻辑块,使得条件分支(if
和else
)可以安全地组织和执行。这种结构允许在WebAssembly中实现基本的条件逻辑和数学运算,用于构建更复杂的算法和函数。
执行过程:
函数参数和块定义:
函数接受一个
i32
类型的参数,并返回一个i32
类型的结果。
block
指令定义了一个块,用于封装条件分支和相关操作。条件分支:
local.get 0
将函数的第一个参数(索引为0的本地变量)加载到栈顶。
i32.const 0
将常数0推送到栈顶。
i32.eq
指令比较栈顶的两个值是否相等,如果相等则将1推送到栈顶;否则将0推送到栈顶。条件分支执行:
if
指令根据前面比较的结果(0或1),决定执行哪个分支:
如果条件为真(栈顶值为1),则执行
i32.const 1
,将常数1推送到栈顶作为函数的返回值。如果条件为假(栈顶值为0),则执行
local.get 0
、i32.const 1
和i32.sub
,依次将函数参数加载到栈顶,推送常数1,并计算栈顶两个值的差,作为函数的返回值。块结束:
end
指令结束条件分支和块定义。示例代码👇
(func (param i32) (result i32) local.get 0 ;; 获取函数的第一个参数(整数) i32.const 10 ;; 将常数10推送到栈顶 i32.lt_s ;; 比较栈顶的两个整数,如果第一个小于第二个则推送1,否则推送0 if ;; 如果比较结果为真(栈顶值为1),则执行以下指令 i32.const 1 ;; 将常数1推送到栈顶 return ;; 返回栈顶的值并结束函数 end ;; 结束条件分支 i32.const 2 ;; 如果比较结果为假(栈顶值为0),将常数2推送到栈顶 return) ;; 返回栈顶的值并结束函数
代码注解:
这段代码定义了一个函数,接受一个
i32
类型的参数作为整数,并返回一个i32
类型的整数结果。
local.get 0
将函数的第一个参数(整数)加载到栈顶。
i32.const 10
将常数10推送到栈顶。
i32.lt_s
指令比较栈顶的两个整数,如果第一个整数小于第二个整数,则推送1到栈顶;否则推送0到栈顶。
if
指令根据比较的结果(栈顶值为1或0)决定执行不同的分支:
如果比较结果为真(即第一个参数小于10),则执行
i32.const 1
将常数1推送到栈顶,并使用return
返回栈顶的值并结束函数。如果比较结果为假(即第一个参数不小于10),则执行
i32.const 2
将常数2推送到栈顶,并使用return
返回栈顶的值并结束函数。执行过程:
当这个函数被调用时,它假定参数是一个有效的
i32
类型整数。
i32.lt_s
比较栈顶的整数和常数10,如果小于10则执行条件分支中的第一个分支(推送常数1并返回),否则执行第二个分支(推送常数2并返回)。2.函数调用和返回
call
:调用函数。
return
:从函数返回。示例代码👇
(func $add (param i32 i32) (result i32) local.get 0 ;; 获取第一个参数 local.get 1 ;; 获取第二个参数 i32.add) ;; 将两个参数相加并返回结果 (func $run (result i32) i32.const 5 ;; 常数5压入栈顶作为第一个参数 i32.const 7 ;; 常数7压入栈顶作为第二个参数 call $add ;; 调用函数add,将返回值推送到栈顶 return) ;; 返回栈顶的值并结束函数
$add代码注解:
$add
是函数的名称,接受两个i32
类型的整数参数,并返回一个i32
类型的整数结果。
local.get 0
将第一个参数(栈中的第一个值)复制到栈顶。
local.get 1
将第二个参数(栈中的第二个值)复制到栈顶。
i32.add
将栈顶的两个整数相加,并将结果推送回栈顶作为函数的返回值。$run代码注解:
这个函数没有名称,只返回一个
i32
类型的整数。
i32.const 5
将常数5推送到栈顶,作为第一个参数。
i32.const 7
将常数7推送到栈顶,作为第二个参数。
call $add
调用之前定义的$add
函数,执行加法操作,并将结果推送到栈顶。
return
返回栈顶的值作为函数的结果,并结束函数的执行。执行过程:
先将常数5和7分别压入栈顶作为参数。
然后,调用
$add
函数进行加法运算。
$add
函数执行完毕后,将结果推送到栈顶。最后,使用
return
返回栈顶的值(加法结果)并结束函数的执行。
7.模块和导出指令
WebAssembly代码以模块的形式组织,可以定义函数、全局变量、表格和内存等,还可以将这些元素导出给外部JavaScript环境。
WebAssembly代码以模块的形式组织,可以定义函数、全局变量、表格和内存等,还可以将这些元素导出给外部JavaScript环境。
1.模块定义
module
、func
、global
、table
、memory
:定义模块、函数、全局变量、表格和内存等。示例代码👇
(module (func $add (param i32 i32) (result i32) ;; 定义一个名为 $add 的函数,接受两个 i32 类型的参数,并返回一个 i32 类型的结果 local.get 0 ;; 获取第一个参数 local.get 1 ;; 获取第二个参数 i32.add) ;; 将两个参数相加并返回结果 (export "add" (func $add)) ;; 导出函数 $add,使其可在模块外部调用,并命名为 "add" )
代码注解:
模块定义:
module
是 Wasm 模块的起始关键字,用于定义一个模块。函数定义:
(func $add (param i32 i32) (result i32) ...)
:定义了一个名为$add
的函数,它有两个i32
类型的参数(param i32 i32)
,并且返回一个i32
类型的结果(result i32)
。
local.get 0
和local.get 1
分别用于获取函数的第一个和第二个参数,将它们压入栈顶。
i32.add
指令将栈顶的两个整数相加,并将结果推送回栈顶作为函数的返回值。导出函数:
(export "add" (func $add))
:通过export
指令将函数$add
导出为模块的公共接口。导出时使用字符串"add"
作为函数的外部名称,以便外部 JavaScript 或其他环境可以调用它。JavaScript中的导出示例代码👇
// 假设模块实例已经加载和实例化 const wasmInstance = ...; // 加载和实例化的 WebAssembly 模块实例 // 调用 WebAssembly 导出的函数 const result = wasmInstance.exports.add(3, 4); console.log(result); // 输出结果为 7
2.导出和导入
export
:导出函数、全局变量等至外部JavaScript环境。
import
:导入外部函数、全局变量等至模块内部。
示例代码👇(module (func $externalFunction (import "env" "externalFunction") (param i32) (result i32)) ;; 导入一个名为 externalFunction 的函数 (export "internalFunction" (func $internalFunction)) ;; 导出一个名为 internalFunction 的函数 )
代码注解:
导入函数定义:
(func $externalFunction (import "env" "externalFunction") (param i32) (result i32))
:定义了一个名为$externalFunction
的函数,它是从外部导入的函数。在模块中,使用import
关键字指定导入函数的名称和命名空间。
import "env" "externalFunction"
表示将名为"externalFunction"
的函数从命名空间为"env"
的环境中导入。这种导入通常用于从宿主环境(如 JavaScript)中导入函数。导出函数定义:
(export "internalFunction" (func $internalFunction))
:使用export
关键字将模块中的函数$internalFunction
导出,以便模块外部的其他代码可以调用它。
"internalFunction"
是导出函数的外部名称,可以通过这个名称在外部环境中引用该函数。注意事项
导入函数:通过
import
关键字可以在 WebAssembly 模块中导入来自宿主环境的函数或其他模块的函数。这种机制使得 WebAssembly 可以与宿主环境进行交互,执行特定的功能或操作。导出函数:通过
export
关键字可以将模块中定义的函数、变量或表导出,使它们对外部可见和可调用。JavaScript中的示例用法
// 加载和实例化 WebAssembly 模块 const module = new WebAssembly.Module(/* 模块的二进制数据 */); const instance = new WebAssembly.Instance(module, { env: { externalFunction: function(param) { // 实现 externalFunction 的具体逻辑 return param * 2; // 例如,简单地将参数乘以 2 并返回 } } }); // 调用导出的 internalFunction const result = instance.exports.internalFunction(); console.log(result); // 输出 internalFunction 的返回值
8.其他常见指令
除了上述基本的指令外,还有一些用于栈操作、类型转换和异常处理等的指令。更多指令请阅读官方文档
1.栈操作
drop
:丢弃栈顶元素。示例代码👇
(func (param i32 i32) (result i32) ;; 定义一个函数,接受两个 i32 类型的参数,返回一个 i32 类型的结果 local.get 0 ;; 获取第一个参数,将其压入栈顶 local.get 1 ;; 获取第二个参数,将其压入栈顶 i32.add ;; 将栈顶的两个整数相加,并将结果压入栈顶 drop ;; 丢弃栈顶的值(即相加后的结果) local.get 0 ;; 获取第一个参数的值,将其压入栈顶作为函数的返回值 )
代码注解:
数签名:
(func (param i32 i32) (result i32))
:定义了一个函数,该函数接受两个i32
类型的参数,并且返回一个i32
类型的结果。指令解释:
local.get 0
和local.get 1
:这两条指令用于分别获取函数的第一个和第二个参数,并将它们的值推送到栈顶。
i32.add
:将栈顶的两个整数相加,并将相加后的结果推送到栈顶。
drop
:丢弃栈顶的值。在这里,它被用来丢弃相加后的结果,表示我们不需要该值,只是将第一个参数的值作为结果返回。
local.get 0
:获取函数的第一个参数的值,并将其作为函数的返回值推送到栈顶。执行过程:
参数传递:
将两个参数依次压入栈顶,栈顶为第二个参数,次栈顶为第一个参数。
加法操作:
使用
i32.add
将栈顶的两个整数相加,将相加后的结果推送到栈顶。结果处理:
使用
drop
指令丢弃栈顶的结果值,保留第一个参数的值在栈顶。返回值:
最后使用
local.get 0
获取并返回第一个参数的值作为函数的结果。
select
:条件选择指令。(func (param i32 i32 i32) (result i32) ;; 定义一个函数,接受三个 i32 类型的参数,并返回一个 i32 类型的结果 local.get 0 ;; 获取第一个参数,将其压入栈顶 if ;; 进入条件分支判断 local.get 1 ;; 如果第一个参数为真(非零),则获取第二个参数的值并将其推送到栈顶 else ;; 否则(第一个参数为假,即为零),执行以下分支 local.get 2 ;; 获取第三个参数的值并将其推送到栈顶 end ;; 条件分支结束 )
代码注解:
函数签名:
(func (param i32 i32 i32) (result i32))
:定义了一个函数,接受三个i32
类型的参数,并返回一个i32
类型的结果。指令解释:
local.get 0
:获取函数的第一个参数,并将其值推送到栈顶。
if
:条件分支的开始标志,表示后续指令是一个条件判断。
local.get 1
:在条件为真(非零)时执行的指令块,获取第二个参数的值并将其推送到栈顶。
else
:条件为假(第一个参数为零)时执行的指令块的开始标志。
local.get 2
:在条件为假时执行的指令,获取第三个参数的值并将其推送到栈顶。
end
:条件分支的结束标志,表示条件判断结束。执行过程
参数传递:
将三个参数依次压入栈顶,栈顶为第三个参数,次栈顶为第二个参数,最底部为第一个参数。
条件判断:
使用
local.get 0
获取第一个参数的值。使用
if
指令根据第一个参数的值(真或假)选择执行相应的分支。返回值:
根据条件判断的结果,将第二个或第三个参数的值作为函数的返回值推送到栈顶。
2.类型转换
i32.wrap_i64
、i64.extend_i32_s
:类型转换指令,例如从64位整数到32位整数的转换示例代码👇
i64.const 4294967312 ;; 64 位整数常量 i32.wrap_i64 ;; 将 64 位整数转换为 32 位整数 i32.const -12345 ;; 32 位带符号整数常量 i64.extend_i32_s ;; 将 32 位整数扩展为 64 位整数
1)i32.wrap_i64
是一条将 64 位整数(i64)转换为 32 位整数(i32)的指令。它用于将一个较大范围的整数(64 位)转换为一个较小范围的整数(32 位),并且仅保留低 32 位的数值部分。如果 64 位整数超出了 32 位整数的表示范围,结果将会截断为低 32 位的值。2)i64.extend_i32_s
是一条将 32 位带符号整数(i32)转换为 64 位带符号整数(i64)的指令。它用于将一个较小范围的整数(32 位)扩展为一个较大范围的整数(64 位),并保持其符号位不变。
- gcc的安装
官网下载傻瓜式安装,打开MSYS2终端(可以通过开始菜单找到MSYS2 64bit)
1.执行命令来更新软件包数据库和核心软件包:pacman -Syu
2.在MSYS2终端中执行以下命令来安装GCC:pacman -S mingw-w64-x86_64-gcc
3.将MSYS2的bin目录添加到系统的环境变量中:C:\msys64\mingw64\bin
打开“控制面板” -> “系统和安全” -> “系统” -> “高级系统设置”,点击“环境变量”按钮,将上述路径添加到“Path”变量中
4.验证安装版本:gcc --version - wabt 各个可执行文件介绍
EXE操作 描述 spectest-interp.exe 运行和验证WebAssembly规范测试,确保WebAssembly实现符合规范。 wasm2c.exe 将WebAssembly二进制文件(.wasm)转换为C代码,可以用于进一步编译和嵌入应用中。
wasm2wat.exe 将WebAssembly二进制文件转换为文本格式(.wat),便于阅读和编辑。 wasm-decompile.exe 反编译WebAssembly二进制文件为更易读的伪代码,便于理解代码结构和逻辑。 wasm-interp.exe 解释和运行WebAssembly二进制文件,适用于测试和调试。 wasm-objdump.exe 显示WebAssembly二进制文件的内容和结构,类似于Unix系统上的 objdump
工具。wasm-stats.exe 显示WebAssembly模块的统计信息,如函数数量、内存使用等。 wasm-strip.exe 从WebAssembly二进制文件中去除不必要的调试信息和符号表,以减小文件大小。 wasm-validate.exe 验证WebAssembly二进制文件的结构和内容是否符合WebAssembly规范。 wast2json.exe 将WebAssembly文本格式文件转换为JSON格式,便于程序化处理。 wat2wasm.exe 将WebAssembly文本格式文件(.wat)转换为二进制格式(.wasm),用于运行和部署。 wat-desugar.exe 处理WebAssembly文本格式文件中的语法糖,将其转换为更基本的格式。 *.exe --help 选项查看详细的使用说明和选项
编译为C: wasm2wat.exe wasm.wasm -o wasm.c
编译伪代码:gcc -std=c99 -Wall -c wasm.c -o wasm.o
之后我们就可以拖入到idea反编译分析伪代码还原算法了尽管和原始的代码差别较大,但好歹可以开始分析了
编写和编译 Wasm 模块并反编译
官网安装emscripten
2.设置环境变量
C:\Users\*\Desktop\emsdk
C:\Users\*\Desktop\emsdk\upstream\emscripten
3.编写C程序(不一定要C写go、rust都可以,代码都大差不差)
long add_seconds_to_timestamp(long timestamp, int seconds) {
return timestamp + seconds;
}
int main() {
}
4.使用 Emscripten 编译 hello.c
文件: emcc encrypt.c -o encrypt.wasm -O3 -s WASM=1 --no-entry
5.浏览器打开hello.html,在控制台中就看见输出了我们打印的:Hello, WebAssembly!
实战wasm补环境
通过以上的基础学习与环境安装,我们了解了
实战wasm反编译还原算法
小结
未完成有空待续。。。