通过相关命令将如上代码编写为动态链接库,如下:
g++ -std=c++11 -Wall -fPIC -c TestException.cpp -I ./-I /home/mazhi/workspace/jdk1.8.0_192/include/linux/ -I /home/mazhi/workspace/jdk1.8.0_192/include/g++ -Wall -rdynamic -shared -o libdiaoyong.so TestException.o相关命令就不再过多解释,有兴趣可自行查阅相关资料 。
现在编写一个抛出异常的Java应用,如下:
public class CatchAllException {public static void main(String[] args) throws Exception {try {throw new NullPointerException("空指针异常");} catch (Exception e) {// e.printStackTrace();}}}在启动Java应用时,为虚拟机配置参数
-agentpath:/home/mazhi/workspace/projectcplusplus/TestException/src/libdiaoyong.so,打印的异常信息如下:
测试结果-定位类的签名:LCatchAllException;测试结果-定位方法信息:main -> ([Ljava/lang/String;)V测试结果-定位方法位置:0 -> 12测试结果-异常类的名称:Ljava/lang/NullPointerException;测试结果-输出异常信息(能够分析行号):java.lang.NullPointerException: 空指针异常at CatchAllException.main(CatchAllException.java:9)如上功能的实现要依赖于JVMTI这套接口,有了这套接口能够做出许多重要的功能 。
JVMTI接口的中文文档:
https://blog.caoxudong.info/blog/2017/12/07/jvmti_reference
另外还有JavaAgent,他是JVMTIAgent的一个特例,可做的操作有限,但好处就是可用Java语言来实现 。下面举一个JavaAgent的例子 。创建一个Maven工程,编写JavaAgent,实现如下:
package lesson5.example1;// ...public class MyAgent {public static void premain(String agentArgs, Instrumentation inst) throws Throwable {System.out.println("loading static agent...");MyTransformer monitor = new MyTransformer();inst.addTransformer(monitor);}}编写Transformer,如下:
package lesson5.example1;// ...public class MyTransformer implements ClassFileTransformer {public byte[] transform(ClassLoader loader,String className,Class<?> classBeingRedefined,ProtectionDomain protectionDomain,byte[] classfileBuffer ) throws IllegalClassFormatException {ClassReader cr = new ClassReader(classfileBuffer);ClassNode classNode = new ClassNode(Opcodes.ASM6);cr.accept(classNode, ClassReader.SKIP_FRAMES);for (MethodNode methodNode : classNode.methods) {if ("main".equals(methodNode.name)) {InsnList instrumentation = new InsnList();instrumentation.add(new FieldInsnNode(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"));instrumentation.add(new LdcInsnNode("Hello, Instrumentation!"));instrumentation.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println","(Ljava/lang/String;)V", false));methodNode.instructions.insert(instrumentation);break;}}ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);classNode.accept(cw);return cw.toByteArray();}}然后在Maven中配置:
<plugin><groupId>org.Apache.maven.plugins</groupId><artifactId>maven-assembly-plugin</artifactId><executions><execution><goals><goal>single</goal></goals><phase>package</phase><configuration><descriptorRefs><descriptorRef>jar-with-dependencies</descriptorRef></descriptorRefs><archive><manifestEntries><Premain-Class>lesson5.example1.MyAgent</Premain-Class><Can-Redefine-Classes>true</Can-Redefine-Classes><Can-Retransform-Classes>true</Can-Retransform-Classes></manifestEntries></archive></configuration></execution></executions></plugin>在运行Maven Install后,会在target目录下生成一个jar包,这就是JavaAgent,我们可以在启动任何一个Java应用启动时,通过-javaagent参数来指定JavaAgent,如下:
public class Test {public static void main(String args[]){System.out.println("execute main method ...");}}运行结果如下:
Hello, Instrumentation!
execute main method ...
可以看到,通过JavaAgent对原有的Test类生成的字节码程序进行了增强,这就让我们的想像空间变的非常大,因为你可以更改任何方法体中的字节码,甚至替换整个类 。例如,可以在字节码前后打印时间,这样就能输出调用方法的耗时;可以给整个方法体增加异常捕获的try-catch,在不修改、不重新部署应用程序的情况下修复某些Bug等等 。
有些资料总结了Agent可以实现的功能,如下:
1、使用JVMTI对Class文件加密
有时一些涉及到关键技术的Class文件或者jar包不希望对外暴露,所以需要加密 。使用一些常规的手段(例如使用混淆器或者自定义类加载器)来对Class文件进行加密很容易被反编译 。反编译后的代码虽然增加了阅读的难度,但花费一些功夫也是可以读懂的 。使用JVMTI可以将解密的代码封装成.dll或.so 文件 。这些文件想要反编译就很麻烦了 。不过个人认为,这样并不能完全避免代码泄漏,不要忘记Agent中提供的一些API,这些API能够将加载到虚拟机中的Class文件的内容Dump出来 。
推荐阅读
- 使用Java读取、编写、确认Excel文档
- Pandas 28种常用方法使用总结
- 饵料|夏季钓鲤鱼常用的调漂技巧
- 鲫鱼|野钓鲫鱼的特殊窝料,钓王邓刚最常用的鲫鱼窝料
- 黑客必修:批处理常用网络命令和符号——网络命令
- 11 种糟糕的 JavaScript 编写方式
- JavaScript中的四种枚举方式
- JavaScript检测用户是否在线!
- 你必须知道的常用的足够简练的 11 个 Python代码
- JavaScript 的 Anti-Debugging 技術
