虚拟解释器承担的功能是在运行时解码虚拟指令编码得到的字节码程序,并调用相应的解释程序还原其语义和功能。因此,调度器(Dispatcher)、字节码程序(VMdata)、解释程序(Handler)以及虚拟执行环境(VMcontext)这四个组件是组成虚拟解释器的必备结构。如前文所述,本文中我们的目标代码是JavaScript,含有DOM对象和属性操作,进过虚拟化处理过程,不仅有编码得到的字节码程序VMdata,还有保存了属性语义的字符串数组VMarray。另外虚拟解释器模块采用WebAssembly技术编译实现,由于当前的浏览器环境不支持直接执行“.wasm”文件,我们还需要采用JavaScript实现的特殊胶接代码来加载WASM模块。综上,基于WebAssembly设计实现的虚拟解释器的组件组成及相互关系如下图所示,详细的组件介绍如下:
A. 虚拟执行环境VMcontext
二进制程序虚拟解释器直接运行在本地层,系统的栈和寄存器可以参与运算过程,其内部的虚拟环境只需要申请一段内存用以模拟映射本地的真实寄存器既可。本文中使用C语言来实现JavaScript代码的虚拟解释器逻辑,需要在源码层模拟本地层的执行环境,因此图上中所示,虚拟执行环境VMcontext需要包含指令执行所需的核心结构栈Stack,存储临时变量的寄存器Register,还有和内存作用类似的变量池VarList,主要用来转存外部变量和参数等,这些关键结构将会用数组来模拟。
B. 关键数据VMdata & VMarray
字节码程序VMdata是经由虚拟指令编码而来的,其表现形式就是一串难以理解的字节码,在运行时,通过调度器读取解码然户调度解释程序来还原目标代码的功能,其中蕴含了目标代码的语义逻辑。本文设计利用C语言实现一个整形数组保存,经过编译将会保存在WASM模块的data段。字符串数组VMarray,保存的是在目标JavaScript代码虚拟化过程中提取的字符串常量和对象属性名称。其中蕴含了属性的语义和操作信息,在运行时,特定的属性操作Handler会读取其中的元素,通过拼接还原对象属性的运算操作。本文中通过C语言实现的string类型数组保存,同样经过编译将会保存在WASM模块的data段。
C. 调度器Dispatcher
调度器是虚拟解释器的调度核心,其重要的作用如上图所示。首先,从VMdata中读取字节码并解码,字节码的读取顺序主要依靠一个变量VPC(程序计数器)来控制。然后,解码并按照索引调度相应的解释程序Handler来解释执行。执行结束后,返回Dispatcher继续上述过程,直到完成所有的字节码解码后结束循环。其主要的执行逻辑是依据VMdata的引导调度适当的Handler来还原原程序的功能。根据功能需要,设计使用循环加上选择结构来实现这一调度过程,示例代码如图下中所示。
D. 解释程序集Handlers
解释程序集是还原字节码语义的重要组件,如第一张图所示,调度器在解码字节码之后,会调用相应的解释程序Handler执行运算操作。虚拟指令可以分为有参和无参两类,如果有参数,则相关的解释程序会依据VMdata中读取参数从VMcontext中取值,如果没有参数,则直接从栈顶取值执行相关操作。并且其执行的结果是代码功能的还原和体现,因此会对VMcontext中存储的值产生影响,所以解释程序和虚拟执行环境也存在交互关系。
代码中的对象操作通常需要Handler执行对象和属性拼接来完成,前面提到,WebAssembly并不支持这一类型的计算和字符串操作,C语言也无法模拟这一过程。为了解决这一挑战,本文设计采用JavaScript实现最后的属性操作部分,并通过编译器提供的特殊宏来实现在C代码中内联JavaScript。图上中的Handler示例代码是有参的指令“lod_a”的解释程序。其主要的功能是根据参数索引,加载一个字符串常量作为接下来的操作对象。这里采用宏“EM_ASM_ARGS”完成内联通信,传递属性字符串,由JavaScript实现最终操作。
E. 胶接代码
虚拟解释器的主体部分最终将经过编译得到一个后缀为“.wasm”的文件,目前浏览器还不支持直接在执行过程中加载“.wasm”文件,需要通过由JavaScript实现的一段功能性的胶接代码负责加载到浏览器中使用。该胶接代码的主要操作过程是加载“.wasm”文件,读取二进制代码并转存到一个ArrayBuffer中,然后经过实例化提取生成的WASM模块。在“.wasm”文件里,可能还会引入一些环境变量,在实例化的同时还需要初始化内存空间和变量映射表,另外,在加载WASM模块时也可以向其传递变量参数,这些都需要通过胶接代码来完成。
JS一键VMP加密 jsvmp.com