YakVM:编写栈机虚拟机分离语法编译与运行时
2022-11-25 17:31:21 Author: Yak Project(查看原文) 阅读量:21 收藏

0.0

在以快速迭代功能试错为主的旧 Yaklang 模式下,我们遇到了很多新的需求无法实现:动态调试、更智能的类型“推断”、解释步骤中编译与执行…如果我们旨在满足小团队使用,上述需求不做也罢,大家更多的是在使用图灵完备粘合的各种安全能力。

上述的内容也都非常符合一款 DSL 的定位,以领域限定的工作内容为主,研发体验和开发效率并没有摆在很高的位置。

随着 Yakit 逐渐被广泛使用,Yaklang也开始被大家看见,用户会使用 Yaklang 编写热加载代码嵌入在 Yakit 中,从而让 Yaklang 成为一个非常强大的嵌入式功能扩展语言,同样,这也非常符合预期。

也正是因为这样,Yakit 和 Yaklang 的用户能力得到了非常大的提升,不论是灵活度还是使用上限都可以远超为数不多的同类产品。

01

语言规约与工程化

我们在 Yaklang 的发展上看到了痛点,不解决这些问题并不是我们团队的技术风格

VM 与语法分离的模式,我们很容易理解,大名鼎鼎的 Java - JVM,JS - V8,Python - Python VM 本质上都是这样的实现。

在旧的 Yaklang 版本中,我们的执行也是类似的 VM 模式,只是 VM 本身和 Yaklang 自身绑定的非常深,以至于我们无法接受任何新的语言前端,翻译过程直接由语法规则 Emit 到 Opcode,并且整个语言在实现过程中,非常依赖 Golang。

实验:
可行性探索

大概在 2022 年 10 月国庆假期中,我实现了一个版本的 Yaklang.g4(注:g4 是 Antlr4 的语法规约定义文件,采用 eBNF 并在其进行了精巧地扩展),以分离 Yaklang 的语言前端项目,做为 Yaklang 的瓶颈突破口。与此同时,根据Yaklang.g4新的语法规约,我们实现了一个最基础版本的 VM 和编译器,当然,我们说的编译是从 Yaklang 代码,编译成 VM 的字节码。

这个小型技术实验取得了非常好的效果,更精细的语法控制,更精细的细节把握,设计你自己想要的字节码。这个成功试验极大地鼓舞了团队对 Yaklang 发展的信心。

路径:每一步都是最佳实践

Yaklang.g4 和 VM 的原型机实现对我们都是一个非常棒的机会:一门正儿八经的现代化语言,应该是怎么样的?本着全链路都是最佳实践的原则,我们开始着手为新的 Yaklang 编写更规范的代码,实现了一个又一个非常棒的语言特性:

  1. 从老式的 “符号表定义域绑定” 到 “定义域实例化”:我们实现了和 Golang 保持一致的定义域特性

  1. 手动人为控制任何类型之间的自动转换,符号的任何级别的重载

  1. 修改了 Yaklang 语法,并且进行了极大地扩展,兼容旧的写法,增加了大量广受好评的语法糖

  1. 使用依赖注入来实现虚拟机执行代码的控制反转:我们为内置的类型增加了内置方法,可以完美实现 "abc123".HasPrefix("abc")这类操作

  1. Opcode 随时都可以查看,并且可以很完美地展示,方便我们调试以及后人学习 VM 的原理

  1. 实现了 VM 内置函数与 Yaklang 编写函数的无缝调用

  1. 抛开 Golang 本身的 Buff,我们实现了 Goroutine 类似的协程机制,增加了诸如 OpAsyncCall 的高级 Opcode。

  2. ...

与此同时,因为新AST 的引入,我们之前不好做的一些技术实现,现在变得唾手可得

  1. 代码的自动格式化;

  2. 可以尝试进行类型推断的自动补全机制;

  3. 智能检测:“未使用的变量”,“引入 undefined”,“是不是有 Typo”,“返回值有问题” 这类可以极大提升研发效能的功能;

同时,因为 VM 的独立实现,我们甚至可以为 VM 开启一个 Debug Server,为整个语言实现 Language Server Protocol,这样我们就可以在 VSCode 之类的地方进行动态调试与代码自动补全了。

意义:突破边界

对我和整个引擎研发团队来说,把实现整个Yaklang.g4 -> Yaklang Virtual Machine过程中的感受,形容成对编程语言发展史的“惊鸿一瞥”更合适。

也许在闭包出现以前,大家都是保留符号表,在执行过程中固定替换符号中的值而已,我们能以想要的语言特性来驱动语言翻译以及 VM 的架构研发,可以说本身就是一件幸运的事儿了。

当实现了这些内容之后,在和团队总结中,大家对基于栈机的语言无论是语法还是 Opcode 还是 VM 层面,都有了比较深的理解。以至于我们开玩笑说,我们可以把市面上很多语言都翻译到我们自己的 VM 上来执行,大家都觉得 “也不是特别难,就是要多写几行代码而已”。

这个事儿如果细想,就觉得很有意思了。长久以来,Lua 需要 Lua VM,Yaklang 自然也需要 Yak VM,但是对于我来说,Lua 需要 Yak VM 其实才是比较有意思的操作,我们已经实现了规范化工程化的 Yaklang 的编译字节码步骤,那么编译成 JVM 字节码,还是 Yak VM 字节码,当然也无所谓了。幸运的是,我们实现了自己的虚拟机,并且编译到了自己的字节码格式。也就是说,Lua 我也可以编译到 Yak VM 上,JS 也可以,Java 也可以(当然,静态类型需要做一些补充,禁用一些类型自动转换的特性),Lua 更可以了。

任何 Lua 写的脚本,只要 VM 提供合理的内置函数,均可以马上运行。我们仔细思考之后,VM 突破语言边界,和语言突破能力边界的道理是一样的,一旦接受了这个设定,我们就并不会觉得这些是遥不可及的梦想,至少对我来说,无非是多写几行代码而已。

02

未来

当然从技术角度来说,新版本的 Yaklang 语法在保持对原有语法完全兼容的基础上,扩增了一些非常优秀且有用的语法特性,这些语法特性对 AST 编译到字节码的过程要求其实非常高,其复杂度远超当前常见同类脚本语言。实际上 Yaklang 的语法复杂度和特性,复杂程度以及实现难度已经远超Lua / NASL

当我们完全实现 Yaklang 的时候,这类特性已经非常成熟了,在几个内部版本的测试中,我们扩充的语法:模版字符串,箭头函数实现了对 JavaScript 闭包的完全复制,也实现了 Golang 风格的定义域定义,以至于旧版本引擎的代码可以百分之百地运行在 YakVM 的新架构下,同时效果不打折,性能还能更强,基础设施接口完全可复用。

当然,未来我们也会推出 Lua-YakVM,Nasl-YakVM 的新版本。

YaklangParser.g4:快速预览

更新通知

往期推荐>>

仅需10秒!从批量爆破请求中提取关键数据,安全能力基座功能强化ing

安全基础设施:用Fuzztag优雅地生成与变形任何Payload

新功能:史上最好用的反连&JavaHack,安全能力基座强化ing

插件分析|Yaklang SQL Injection 检测启发式算法


文章来源: http://mp.weixin.qq.com/s?__biz=Mzk0MTM4NzIxMQ==&mid=2247491649&idx=1&sn=360ee0b9f57b8ad624bd883b20c2869d&chksm=c2d19ce5f5a615f37366d26e492015a5b9a42d33476b42c2e018071e33a4f484f26437a38c9e#rd
如有侵权请联系:admin#unsafe.sh