Wagon实现一个log函数

[TOC]

实现方案

如果要实现一个自己的函数,有两种实现方式,一种是在虚拟机内部直接以内置指令的方式实现,如add函数等,但是这种方式需要修改编译器,并且通用性不够好,另一种就是通过解决外部引用的方式实现,这种的兼容性较好,需要解决的问题就是在内部解析引用的时候实现内部导入。

案例分析

如果我们需要实现一个log函数,那么就需要解决外部符号导入问题,如下面代码:

int main() { 


  Println("hello world");



  return 0;



}

它的wast文件为:

它的wasm文件内容如下:

解析器在解析文件时需要找到env中的Println函数,然后将hello world放入堆栈,接着调用call函数执行Println函数打印出hello world,那么我们需要解决的就是在虚拟机内部实现env中Println函数的解析部分。

解析流程

在上一章节中我们分析过,文件的解析主要在函数wasm.ReadModule(f, importer)中,它首先循环读取各个分区:

然后再去解决导入的函数:

这里的解决办法是从env的wasm文件中读取导入的函数:

然后再创建新的vm时会将函数列表放入vm中:

最后根据堆栈来调用相关函数:

函数从module到vm的拷贝过程如下:

那么这里的问题就是如何在保存现有解析新wasm文件的基础上,跳过对Println函数的解析呢?而且需要根据fn的规则实现一个内部的Println。

工程改造

首先是跳过部分,这部分比较简单,我们在解析外部引用时手动判断env并跳过即可:

这里就跳过了对env中Println的解析,注意这里需要添加code,否则会导致空指令的执行,这里的41表示call指令,我们需要在函数执行call时对自己的函数进行处理。 还有就是我们添加了两个变量,IsEnv和Name,这两个变量可以判断函数是否是我们自己实现的,从而实现外部函数的分支调用。

下一步就是创建新的VM了,创建新的VM时需要在函数列表中添加上我们加入的两个变量:

然后函数执行时就会调用到我们放入的code:

code里是0x41和0x2b,执行VM的函数调用:

funcTable是在创建VM时设置的,实际调用的是vm的call函数:

我们可以在这个函数中做分支,实现自己的API方法,当然,现在的Println函数并没有对堆栈进行处理,只是最简单的流程,如果涉及到堆栈处理,就更复杂一些了。

最后更新于

这有帮助吗?