我无法在ASM JAVA中加载用于invokedynamic的局部变量

问题描述 投票:1回答:1

我已经为方法创建了一个小型记录器,并且使用了ASM。我需要通过描述符方法参数确定并打印出来。但是我有一个错误

Exception in thread "main" java.lang.VerifyError: Bad type on operand stack
Exception Details:
  Location:
    ru/otus/TestLogging.calc(IFD)V @6: invokedynamic
  Reason:
    Type 'java/io/PrintStream' (current frame, stack[4]) is not assignable to double_2nd
  Current Frame:
    bci: @6
    flags: { }
    locals: { 'ru/otus/TestLogging', integer, float, double, double_2nd }
    stack: { 'ru/otus/TestLogging', float, double, double_2nd, 'java/io/PrintStream' }
  Bytecode:
    0000000: 2a24 29b2 0007 ba00 3e00 00b6 0011 b200
    0000010: 071b 2429 ba00 0d00 00b6 0011 b1      

这是我的代理代码

public class Agent {

  public static void premain(String agentArgs, Instrumentation inst) {

    inst.addTransformer(new ClassFileTransformer() {
        @Override
        public byte[] transform(ClassLoader loader,
                                String className,
                                Class<?> classBeingRedefined,
                                ProtectionDomain protectionDomain,
                                byte[] classfileBuffer) throws IllegalClassFormatException {
                if(className.contains("ru/otus/")) {
                    return changeMethod(classfileBuffer, className);
                }
                return classfileBuffer;
        }
    });
  }

  private static byte[] changeMethod(byte[] originalClass, String className) {
      ClassReader reader = new ClassReader(originalClass);
      ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS);
      ArrayList<String> list = new ArrayList<>();
      ClassVisitor visitor = new ClassVisitor(Opcodes.ASM5, writer) {

          @Override
          public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions){
              System.out.println("visitMethod: access="+access+" name="+name+" desc="+descriptor+" signature="+signature+" exceptions="+exceptions);
              Method thisMethod = new Method(name, descriptor);

              MethodVisitor mv = new MethodAnnotationScanner(Opcodes.ASM5, super.visitMethod(access, name, descriptor, signature, exceptions), thisMethod, className);
              return mv;
          }
      };

      reader.accept(visitor, Opcodes.ASM5);

      for(String methodName : list) {
          System.out.println(methodName);
      }

      byte[] finalClass = writer.toByteArray();

      if(className.contains("Test")) {
          try (OutputStream fos = new FileOutputStream("TestLogging.class")) {
              fos.write(finalClass);
          } catch (Exception e) {
              e.printStackTrace();
          }
      }

      return writer.toByteArray();
  }

  static class MethodAnnotationScanner extends MethodVisitor {

      private Method thisMethod;
      private boolean isChangeMethod = false;
      private String className = null;
      private StringBuilder descriptor = new StringBuilder("(");

      public MethodAnnotationScanner(int api, MethodVisitor methodVisitor, Method thisMethod, String className) {
          super(api, methodVisitor);
          this.thisMethod = thisMethod;
          this.className = className;
      }

      @Override
      public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
          System.out.println("visitAnnotation: desc="+desc+" visible="+visible);
          if(desc.contains("ru/otus/annotations/Log")) {
              this.isChangeMethod = true;
              return super.visitAnnotation(desc, visible);
          }
          this.isChangeMethod = false;
          return super.visitAnnotation(desc, visible);
      }

      @Override
      public void visitCode() {
          if(this.isChangeMethod) {
              super.visitVarInsn(Opcodes.ALOAD, 0);
                  int i = 1;
                  for(Type arg : thisMethod.getArgumentTypes()) {
                      this.descriptor.append(arg.getDescriptor());
                      if (arg.getDescriptor().equals("J")) {
                          super.visitVarInsn(Opcodes.LLOAD, i);
                          ++i;
                      } else if (arg.getDescriptor().equals("D")) {
                          super.visitVarInsn(Opcodes.DLOAD, i);
                          ++i;
                      } else if (arg.getDescriptor().equals("F")) {
                          super.visitVarInsn(Opcodes.FLOAD, i);
                      } else if(arg.getDescriptor().equals("I")) {
                          super.visitVarInsn(Opcodes.ILOAD, i);
                      }
                      i++;
                  }


                  Handle handle = new Handle(
                          H_INVOKESTATIC,
                          Type.getInternalName(java.lang.invoke.StringConcatFactory.class),
                          "makeConcatWithConstants",
                          MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, String.class, Object[].class).toMethodDescriptorString(),
                          false);
                  this.descriptor.append(")Ljava/lang/String;");
                  super.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                  super.visitInvokeDynamicInsn("makeConcatWithConstants", this.descriptor.toString(), handle, "executed method: " + this.thisMethod.getName()  + ", param: \u0001".repeat(i));
                  super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
                  super.visitMaxs(0, 0);
              }

          if (mv != null) {
              super.visitCode();
          }
          super.visitEnd();
      }
   }
 }

我有两个类来重现此功能。首先-TestLogging次-AutoLogger

在第一堂课中,我有一个需要记录的方法,第二秒,它的开始类包含方法main。

This is my project

java bytecode java-bytecode-asm
1个回答
0
投票

您在读取字段System.out之前先推动字符串连接的参数,然后尝试执行字符串连接。因此,用于执行字符串连接的invokedynamic指令在操作数堆栈上发现不匹配的PrintStream

一个简单的解决方法是更改​​说明

super.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
super.visitInvokeDynamicInsn("makeConcatWithConstants", this.descriptor.toString(), handle, "executed method: " + this.thisMethod.getName()  + ", param: \u0001".repeat(i));
super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);

to

super.visitInvokeDynamicInsn("makeConcatWithConstants", this.descriptor.toString(), handle, "executed method: " + this.thisMethod.getName()  + ", param: \u0001".repeat(i));
super.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
super.visitInsn(Opcodes.SWAP);
super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);

或将GETSTATIC移到将执行concat参数的代码之前,移至执行过时的super.visitVarInsn(Opcodes.ALOAD, 0);的位置。然后,您不需要SWAP

但是代码有更多问题。您在推入值时正在计算局部变量,正确地考虑了longdouble取两个变量,但是随后,您在", param: \u0001".repeat(i)表达式中使用了相同的数字,这将告诉StringConcatFactorylongdouble的情况下是两个值。您需要分开计数器。另外,您不是要推送引用类型参数,而是由于要对它们进行计数并将其包含在concat调用的签名中,因此还必须将它们推送到操作数堆栈。

[此外,visitMaxs(0, 0)调用和visitEnd()调用虽然在这里没有效果,但不合适。您正在注入代码的开头,随后将进行其他不会被拦截的访问,包括自动执行的visitMaxsvisitEnd

使用所有修复程序,代码看起来像

public void visitCode() {
    if(this.isChangeMethod) {
        super.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
        int varIndex = 1, numArgs = 0;
        for(Type arg : thisMethod.getArgumentTypes()) {
            this.descriptor.append(arg.getDescriptor());
            if (arg.getDescriptor().equals("J")) {
                super.visitVarInsn(Opcodes.LLOAD, varIndex);
                ++varIndex;
            } else if (arg.getDescriptor().equals("D")) {
                super.visitVarInsn(Opcodes.DLOAD, varIndex);
                ++varIndex;
            } else if (arg.getDescriptor().equals("F")) {
                super.visitVarInsn(Opcodes.FLOAD, varIndex);
            } else if(arg.getDescriptor().equals("I")) {
                super.visitVarInsn(Opcodes.ILOAD, varIndex);
            } else {
                super.visitVarInsn(Opcodes.ALOAD, varIndex);
            }
            varIndex++;
            numArgs++;
        }

        Handle handle = new Handle(
            H_INVOKESTATIC,
            Type.getInternalName(java.lang.invoke.StringConcatFactory.class),
            "makeConcatWithConstants",
            MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, String.class, Object[].class).toMethodDescriptorString(),
            false);
        this.descriptor.append(")Ljava/lang/String;");
        super.visitInvokeDynamicInsn("makeConcatWithConstants", this.descriptor.toString(), handle, "executed method: " + this.thisMethod.getName()  + ", param: \u0001".repeat(numArgs));
        super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
    }
    super.visitCode();
}

作为旁注,当为串联推送所有参数时,可以简化描述符的构造,因为concat描述符与方法的描述符几乎相同;您只需将返回类型替换为Ljava/lang/String;

仅使用已经传递给Method的两个字符串,就可以完成整个操作而无需处理TypevisitMethod对象。

@Override
public MethodVisitor visitMethod(int access, String name, String descriptor,
                                 String signature, String[] exceptions) {
    System.out.println("visitMethod: access="+access+" name="+name
        +" desc="+descriptor+" signature="+signature+" exceptions="+exceptions);
    MethodVisitor mv = new MethodAnnotationScanner(Opcodes.ASM5, name, descriptor,
        super.visitMethod(access, name, descriptor, signature, exceptions));
    return mv;
}
static class MethodAnnotationScanner extends MethodVisitor {

    private boolean isChangeMethod;
    private final String name, descriptor;

    public MethodAnnotationScanner(int api, String name,
                                   String methodDesciptor, MethodVisitor methodVisitor){
        super(api, methodVisitor);
        this.name = name;
        this.descriptor = methodDesciptor;
    }

    @Override
    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
        System.out.println("visitAnnotation: desc="+desc+" visible="+visible);
        if(desc.contains("ru/otus/annotations/Log")) {
            this.isChangeMethod = true;
            return super.visitAnnotation(desc, visible);
        }
        this.isChangeMethod = false;
        return super.visitAnnotation(desc, visible);
    }

    @Override
    public void visitCode() {
        if(this.isChangeMethod) {
            super.visitFieldInsn(Opcodes.GETSTATIC,
                                 "java/lang/System", "out", "Ljava/io/PrintStream;");
            int varIndex = 1, numArgs = 0, p;
            for(p = 1; descriptor.charAt(p) != ')'; p++) {
                switch(descriptor.charAt(p)) {
                    case 'J':
                        super.visitVarInsn(Opcodes.LLOAD, varIndex); ++varIndex; break;
                    case 'D':
                        super.visitVarInsn(Opcodes.DLOAD, varIndex); ++varIndex; break;
                    case 'F': super.visitVarInsn(Opcodes.FLOAD, varIndex); break;
                    case 'I': super.visitVarInsn(Opcodes.ILOAD, varIndex); break;
                    case 'L': super.visitVarInsn(Opcodes.ALOAD, varIndex);
                        p = descriptor.indexOf(';', p);
                        break;
                    case '[': super.visitVarInsn(Opcodes.ALOAD, varIndex);
                        do {} while(descriptor.charAt(++p)=='[');
                        if(descriptor.charAt(p) == 'L') p = descriptor.indexOf(';', p);
                        break;
                    default: throw new IllegalStateException(descriptor);
                }
                varIndex++;
                numArgs++;
            }
            String ret = "Ljava/lang/String;";
            String concatSig = new StringBuilder(++p + ret.length())
                .append(descriptor, 0, p).append(ret).toString();

            Handle handle = new Handle(
                H_INVOKESTATIC,
                "java/lang/invoke/StringConcatFactory",
                "makeConcatWithConstants",
                MethodType.methodType(CallSite.class, MethodHandles.Lookup.class,
                    String.class, MethodType.class, String.class, Object[].class)
                    .toMethodDescriptorString(),
                false);
            super.visitInvokeDynamicInsn("makeConcatWithConstants", concatSig, handle,
                "executed method: " + name  + ", param: \u0001".repeat(numArgs));
            super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
                "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
        }
        super.visitCode();
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.