InvokedynamicJava的秘密武器

最早关于invokedynamic的工作至少可以追溯到2007年,首次成功进行的动态调用是在2008年8月26日进行的。这早于Sun被Oracle收购之前,并且按照大多数开发人员的标准,该功能已经开发了很长时间。 。 invokedynamic的卓越之处在于它是自Java 1.0以后的第一个新增的字节码。它加入了现有的调用字节码invokevirtual,invokestatic,invokeinterface和invokespecial。这四个现有操作码实现了Java开发人员通常熟悉的所有形式的方法分派,特别是: invokevirtual -实例方法的标准调用invokestatic -用于分派静态方法invokeinterface -用于通过接口调用方法invokespecial -在需要非虚拟(即“精确”)调度时使用一些开发人员可能对平台为何需要全部四个操作码感到好奇,所以让我们看一个使用不同的调用操作码的简单示例,以说明它们之间的区别: public class InvokeExamples { public static void main(String[] args) { InvokeExamples sc = new InvokeExamples(); sc.run(); } private void run() { List<String> ls = new ArrayList<>(); ls.add("Good Day"); ArrayList<String> als = new ArrayList<>(); als.add("Dydh Da"); }}这将产生字节码,我们可以使用javap工具将其反汇编: javap -c InvokeExamples.class结果输出: public class kathik.InvokeExamples { public kathik.InvokeExamples(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: new #2 // class kathik/InvokeExamples 3: dup 4: invokespecial #3 // Method "<init>":()V 7: astore_1 8: aload_1 9: invokespecial #4 // Method run:()V 12: return private void run(); Code: 0: new #5 // class java/util/ArrayList 3: dup 4: invokespecial #6 // Method java/util/ArrayList."<init>":()V 7: astore_1 8: aload_1 9: ldc #7 // String Good Day 11: invokeinterface #8, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z 16: pop 17: new #5 // class java/util/ArrayList 20: dup 21: invokespecial #6 // Method java/util/ArrayList."<init>":()V 24: astore_2 25: aload_2 26: ldc #9 // String Dydh Da 28: invokevirtual #10 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z 31: pop 32: return}这展示了四种调用操作码中的三种(其余一种,invokestatic 是微不足道的扩展)。首先,我们可以看到两个调用(在run方法的字节11和28处): ...

October 8, 2019 · 4 min · jiezi

保护-Nodejs-项目的源代码

SaaS(Software as a Service,软件即服务),是一种通过互联网提供软件服务的模式。服务提供商会全权负责软件服务的搭建、维护和管理,使得他们的客户从这些繁琐的工作中解放出来。对于许多中小型企业而言,SaaS 是采用先进技术的最好途径。 然而,对于大型企业而言,情况有所不同。出于产品定制、功能稳定以及掌握自身数据资产等方面的考虑,即使成本增加,他们也更乐意把相关服务部署在企业自己的硬件设备上,也就是常说的私有化部署。 在私有化部署的过程中,服务提供商首先要确保自己的源代码不被泄露,否则产品就可以随意复制和更改,得不偿失。传统的后端运行环境,如 Java、.NET,其源代码是经过编译才部署到服务器上运行的,不存在泄露的风险。而对于应用越来越广泛的 Node.js 而言,运行的则是源代码。即使经过压缩混淆,也可以很大程度地还原。 本文介绍一种可用于 Node.js 端的代码保护方案,使得 Node.js 项目也可以放心地进行私有化部署。 原理当 V8 编译 JavaScript 代码时,解析器将生成一个抽象语法树,进一步生成字节码。Node.js 有一个叫做 vm 的内置模块,创建 vm.Script 的实例时,只要在构造函数中传入 produceCachedData 属性,并设为 true,就可以获取对应代码的字节码。例如: const vm = require('vm');const CODE = 'console.log("Hello world");'; // 源代码const script = new vm.Script(CODE, { produceCachedData: true});const bytecodeBuffer = script.cachedData; // 字节码并且,这段字节码可以脱离源代码运行: const anotherScript = new vm.Script(' '.repeat(CODE.length), { cachedData: bytecodeBuffer});anotherScript.runInThisContext(); // 'Hello world'这段代码看起来不那么容易理解,主要体现在创建 vm.Script 实例时传入的第一个参数: 既然源代码的字节码已经在 bytecodeBuffer 中,为何还要传入第一个参数?为何传入与源代码长度相同的空格?首先,创建 vm.Script 实例时,V8 会检查字节码(cachedData)是否与源代码(第一个参数传入的代码)匹配,所以第一个参数不能省略。其次,这个检查非常简单,它只会对比代码长度是否一致,所以只要使用与源代码长度相同的空格,就可以“欺骗”这个检查。 ...

July 7, 2019 · 3 min · jiezi