java的asm若干实践

作者: 赵海洋 | 来源:发表于2017-08-04 09:08 被阅读0次

前置知识、工具、代码库等

  • jdk里的javap,用来反汇编class文件查看生成的字节码
  • org.objectweb.asm 用来解析、修改、保存字节码的代码库
  • fernflower.jar 用来批量将class文件反编译成java代码(当然idea也可以手动单个反编译)
  • java的Instrumentation,可以编写代码使用javaagent代理执行其它java文件,在代码里使用上面的asm库动态修改代码。也可以直接将所有字节保存,并加载其它class文件。

Instrumentation

在代理jar包中MANIFEST.MF使用Premain-Class指定代理类,代理类需要有premain方法,此方法会在被代理的jar包的main执行前执行:

public static void premain(String var0, Instrumentation inst) {
   inst.addTransformer(new MyTransformer());
}

JVM在加载每一个类时,都会调用MyTransformer的transform方法对字节码进行处理:

public final byte[] transform(ClassLoader l, String className, Class<?> c,ProtectionDomain pd, byte[] b) throws IllegalClassFormatException {
   // 在这里可以根据className筛选到自己想要更改的类
   if (className.equal("example")){
     b = getAnotherCodeArray(b);
     b = modifyCodeArray(b);
   }

   return b;
}
  • 在transform函数也可保存修改前或修改后的字节为class文件,只需要直接将b保存成class文件即可。
  • 如果transform里抛出异常,程序也能正常执行,只不过类没有发生任何变化(因为没有返回修改过的字节码)。
  • 如果修改过的字节码中,有错误,比如在生成方法的局部变量时,生成了如下格式
LocalVariableTable:
Start  Length  Slot  Name   Signature
    45      65     0  this   Lnet/minecraft/client/network/NetHandlerLoginClient;
    45      65     1 packetIn   Lnet/minecraft/network/login/server/SPacketLoginSuccess;
    0       0     2 address   desc

其中address对应的类型为desc,显然是非法的,则启动程序时会报异常:

java.lang.NoClassDefFoundError: net/minecraft/client/network/NetHandlerLoginClient
...
Caused by: java.lang.ClassFormatError: Field "address" in class net/minecraft/client/network/NetHandlerLoginClient has illegal signature "desc"

asm

使用asm修改字节码的示例有很多,这里就不再详细列举,这里仅说几个我填过的坑。在破解forge版本minecraft游戏的过程中,有些asm代码在老版本执行正常,但是在v1.12版本就导致游戏崩溃。

在transform里将修改过的代码保存成class文件并且通过idea反汇编成java文件,对比1.11和1.12两个版本生成的java代码是一样的,在各种调整jvm参数、java版本、asm库版本之后,最终找到原因和解决办法。

Caused by: java.lang.VerifyError: Expecting a stackmap frame at branch target xxx

此bug是因为原有代码在插入了一些if(xxx)等分支流程,假设代码是:

MethodNode method = xxx;// 从原字节码里使用ClassReader得到方法对象。

// 插入自己的新的指令
InsnList rawMethodInstructions = method.instructions;
InsnList newCall = new InsnList();
...

// 在某处逻辑添加了跳转指令
LabelNode labelNode = new LabelNode();
newCall.add(new JumpInsnNode(Opcodes.IFEQ, labelNode));
...
newCall.add(labelNode);

...

 rawMethodInstructions.insertBefore(rawMethodInstructions.getFirst(), newCall);
 method.maxStack += 4;

以上代码在很多mc版本上都正常运行,只有v1.12版本才会报标题中的错误。是因为v1.12的代码在执行时对生成字节码的StackMapTable区域做了检测。在未修改之前的原方法中可能StackMapTable是这样的:

StackMapTable: number_of_entries = 6
  frame_type = 10 /* same */
  frame_type = 33 /* same */
  frame_type = 64 /* same_locals_1_stack_item */
    stack = [ int ]
  frame_type = 252 /* append */
    offset_delta = 20
    locals = [ int ]
  frame_type = 252 /* append */
    offset_delta = 34
    locals = [ class net/minecraft/entity/Entity ]
  frame_type = 250 /* chop */
    offset_delta = 42

以上方法修改后,生成的字节码是这样的:

StackMapTable: number_of_entries = 6
  frame_type = 10 /* same */
  frame_type = 33 /* same */
  frame_type = 64 /* same_locals_1_stack_item */
    stack = [ int ]
  frame_type = 252 /* append */
    offset_delta = 20
    locals = [ int ]
  frame_type = 252 /* append */
    offset_delta = 34
    locals = [ class net/minecraft/entity/Entity ]
  frame_type = 250 /* chop */
    offset_delta = 42
  frame_type = 6 /* same */

其实这个 frame_type 的顺序应该和代码块的顺序一致(至于StackMapTable对应代码结构的关系可以见(http://hllvm.group.iteye.com/group/topic/26545中R大的解释及其它资料)。

知道这个特性后,尝试了给java添加了某博客提到的 -noverify-XX:-UseSplitVerifier 这两个jvm参数来禁用字节码验证,然而不知道为什么并没有卵用。

最后使用asm在代码合适的位置补上了一个frame解决问题。

java.lang.VerifyError: Bad local variable type

hook代码增加了局部变量,需要手动设置LocalVariableTable,通过 methodnode.visitLocalVariable来增加变量,注意第三个参数signature如果不是generic types,则不需要设置。例如:

/**
* 必须要调该方法,手动设置LocalVariableTable,否则会报 Caused by: java.lang.VerifyError: Bad local variable type
*/
methodnode.visitLocalVariable("addressObj", "Ljava/net/SocketAddress;", null, newLabelBegin.getLabel(), newLabel2.getLabel(), 3);
methodnode.visitLocalVariable("addressStr", "Ljava/lang/String;", null, newLabelBegin.getLabel(), newLabel2.getLabel(), 4);

相关文章

  • java的asm若干实践

    前置知识、工具、代码库等 jdk里的javap,用来反汇编class文件查看生成的字节码 org.objectwe...

  • Java ASM 与Aop简单实现(Version:asm5.0

    准备知识:Java ASM与字节码(Version:asm5.0.3,asm-commons5.0.3) 测试类:...

  • 【性能优化】Arthas 原理浅谈

    Arthas 是基于 ASM 和 Java Agent 技术实现的 Java 诊断利器。① ASM 是指一个 Ja...

  • Android ASM使用

    ASM ASM是一种基于java字节码层面的代码分析和修改工具,ASM的目标是生成,转换和分析已编译的java c...

  • ASM

    一、ASM版本: 我们常用的java版本是java8和java11,针对java 8我们需要使用ASM5.0版本,...

  • ASM 概述

    0x00 什么是 ASM ASM is an all purpose Java bytecode manipula...

  • 01 - 初识Java ASM

    ASM是什么 简单来说,ASM[https://asm.ow2.io/]是一个Java字节码的类库。 问题一:AS...

  • ASM实践

    一、 Bytecode instructions 1. 组成 由opcode和arguments两部分组成。opc...

  • JAVA中ASM是什么?

    什么是 ASM ? ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可...

  • 深入开源框架底层之ASM

    什么是 ASM ? ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可...

网友评论

    本文标题:java的asm若干实践

    本文链接:https://www.haomeiwen.com/subject/knxvlxtx.html