go版本wasm解析器分析

[TOC]

解析部分

第一步是打开我们传入的文件:

f, err := os.Open(flag.Arg(0))

然后调用下面函数来解析文件:

m, err := wasm.ReadModule(f, importer)

参数importer是用来解决传入的wasm文件导入函数问题的。 ReadModule函数负责将文件进行解析:

func ReadModule(r io.Reader, resolvePath ResolveFunc) (*Module, error) {
    magic, err := readU32(reader)


    if err != nil {



        return nil, err



    }



    if magic != Magic {



        return nil, ErrInvalidMagic



    }



    if m.Version, err = readU32(reader); err != nil {



        return nil, err



    }



    for {



        done, err := m.readSection(reader)



        if err != nil {



            return nil, err



        } else if done {



            break



        }



    }
    m.LinearMemoryIndexSpace = make([][]byte, 1)


    if m.Table != nil {



        m.TableIndexSpace = make([][]uint32, int(len(m.Table.Entries)))



    }



    if m.Import != nil && resolvePath != nil {



        err := m.resolveImports(resolvePath)



        if err != nil {



            return nil, err



        }



    }

首先判断魔数和版本号,这个是固定的:

然后解析各个分区:

关于文件格式分区可参考链接: https://rsms.me/wasm-intro 解析完成后有两个变量需要格外处理,一个是表格,一个是引用,可以参考链接: https://developer.mozilla.org/zh-CN/docs/WebAssembly/Understanding_the_text_format 表格是一个存储函数引用的替代,解决动态操作的问题,而引用则是使用其他wasm的export部分,也可以是虚拟机内部实现的部分。 m.resolveImports调用的就是main函数中的importer方法,它从当前文件中重新打开相应的wasm文件并使用。 我们可以做个测试。参见下面的引用导入测试。 这样我们就可以在检测到env时手动解决导入问题,从而实现区块链的API。

执行部分

执行过程则是根据export出来的部分进行顺序执行:

具体执行过程则是线性堆栈的入栈和出栈操作,我们需要关心的是如何实现自己的API。本体是通过实现了一个自己的memory包来对堆栈进行处理的。

那么函数是怎么一步一步被调用的呢,首先是上面的解析过程,解析时会得到一个module的Type,Code和Data几个部分,然后会调用populateFunctions将函数添加到FunctionIndexSpace列表中:

然后调用NewVM生成虚拟机对象,同时生成函数列表:

goFunction带了一个call的方法,在执行函数时就是调用这个call方法,对虚拟机堆栈进行一些处理。

引用导入测试

首先编写一个wasm文件,并引用其他的方法:

它的wat文件为:

编写一个add函数,并编译成env.wasm,然后编写main并编译成main.wasm:

它的wat文件为:

可以看到,这里import了env的add方法,wasm使用两级命名空间,这里表示要从env模块导入add方法,因此需要在env.wasm中实现add方法,下面执行调用:

最后更新于

这有帮助吗?