架构模式+架构思想(微内核架构,等架构)

java的asm若干实践

2017-08-04  本文已影响0人  赵海洋

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

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;
}
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);
上一篇 下一篇

猜你喜欢

热点阅读