java - 如何使用java代理和ASM动态记录任何调用的java方法的所有参数?

标签 java jvm instrumentation java-bytecode-asm javaagents

我想要做的是记录这些参数并与之前输入的参数进行一些比较。我需要为每个调用的方法记录参数,因此参数列表的长度是不确定的。
我想我可以解析方法描述符来知道这个方法有多少个参数。但问题是,如何从操作数堆栈中记录任意数量的值?
现在我只能写一些示例代码。在我的 MethodVisitorAdapter类(class):

public void visitMethodInsn(int opc, String owner, String name, String desc, boolean isInterface){
    int n = getParameterCount(desc);
    // How to duplicate arbitrary number of values at operand stack?
    // ...
    mv.visitMethodInsn(INVOKESTATIC, MyClass, "recordParameters",
                    /* should be what kind of descriptor (may have arbitrary number of parameters)? */, 
                    false);
    mv.visitMethodInsn(opc, owner, name, desc, isInterface);
}

最佳答案

您可以制作 recordParameters可变数量方法(varargs)。它将接受所有参数作为单个 Object[]争论。
因此,您需要创建一个数组并用(可能是装箱的)参数填充它。以下构建器样式的类将有助于从堆栈中收集参数。它包含 push所有可能类型的方法,还处理自动装箱。
记录参数后,helper 可以通过调用对应的 pop 将参数放回栈中。方法顺序相反。

public class ArgCollector {
    private final Object[] args;
    private int index;

    public ArgCollector(int length) {
        this.args = new Object[length];
        this.index = length;
    }

    public ArgCollector push(Object o) {
        args[--index] = o;
        return this;
    }

    public Object pop() {
        return args[index++];
    }

    public static ArgCollector push(boolean a, ArgCollector c) { return c.push(a); }
    public static ArgCollector push(byte    a, ArgCollector c) { return c.push(a); }
    public static ArgCollector push(char    a, ArgCollector c) { return c.push(a); }
    public static ArgCollector push(short   a, ArgCollector c) { return c.push(a); }
    public static ArgCollector push(int     a, ArgCollector c) { return c.push(a); }
    public static ArgCollector push(long    a, ArgCollector c) { return c.push(a); }
    public static ArgCollector push(float   a, ArgCollector c) { return c.push(a); }
    public static ArgCollector push(double  a, ArgCollector c) { return c.push(a); }
    public static ArgCollector push(Object  a, ArgCollector c) { return c.push(a); }

    public boolean popZ() { return (boolean) pop(); }
    public byte    popB() { return (byte)    pop(); }
    public char    popC() { return (char)    pop(); }
    public short   popS() { return (short)   pop(); }
    public int     popI() { return (int)     pop(); }
    public long    popJ() { return (long)    pop(); }
    public float   popF() { return (float)   pop(); }
    public double  popD() { return (double)  pop(); }

    public Object[] toArray() {
        return args;
    }
}
现在,任务是为以下 Java 代码生成等效的字节码:
    ArgCollector collector = new ArgCollector(N);
    recordParameters(
            ArgCollector.push(arg1,
                ArgCollector.push(arg2,
                    ArgCollector.push(argN, collector)))
            .toArray()
    );

    originalMethod(
            collector.popI(),
            collector.popJ(),
            (String) collector.pop()
    );
以下是使用 ASM 执行此操作的方法:
    Type[] args = Type.getArgumentTypes(desc);
    String collector = Type.getInternalName(ArgCollector.class);

    // new ArgCollector(argCount)
    mv.visitTypeInsn(NEW, collector);
    mv.visitInsn(DUP);
    mv.visitIntInsn(SIPUSH, args.length);
    mv.visitMethodInsn(INVOKESPECIAL, collector, "<init>", "(I)V", false);

    // For each argument call ArgCollector.push(arg, collector)
    for (int i = args.length; --i >= 0; ) {
        Type arg = args[i];
        String argDesc = arg.getDescriptor().length() == 1 ? arg.getDescriptor() : "Ljava/lang/Object;";
        mv.visitMethodInsn(INVOKESTATIC, collector, "push",
                "(" + argDesc + "L" + collector + ";)L" + collector + ";", false);
    }

    // Call recordParameters(collector.toArray())
    mv.visitInsn(DUP);
    mv.visitMethodInsn(INVOKEVIRTUAL, collector, "toArray", "()[Ljava/lang/Object;", false);
    mv.visitMethodInsn(INVOKESTATIC, MyClass, "recordParameters", "([Ljava/lang/Object;)V", false);

    // Push original arguments back on stack
    for (Type arg : args) {
        String argDesc = arg.getDescriptor().length() == 1 ? arg.getDescriptor() : "Ljava/lang/Object;";

        mv.visitInsn(DUP);
        if (argDesc.length() == 1) {
            mv.visitMethodInsn(INVOKEVIRTUAL, collector, "pop" + argDesc, "()" + argDesc, false);
        } else {
            mv.visitMethodInsn(INVOKEVIRTUAL, collector, "pop", "()Ljava/lang/Object;", false);
            if (!arg.getDescriptor().equals("Ljava/lang/Object;")) {
                // Need to cast object arguments to the original type
                mv.visitTypeInsn(CHECKCAST, arg.getDescriptor());
            }
        }

        // Swap the last argument with ArgCollector, so that ArgCollector is on top again
        if (arg.getSize() == 1) {
            mv.visitInsn(SWAP);
        } else {
            mv.visitInsn(DUP2_X1);
            mv.visitInsn(POP2);
        }
    }

    // Pop off the remaining ArgCollector, and call the original method
    mv.visitInsn(POP);
    mv.visitMethodInsn(opc, owner, name, desc, isInterface);

关于java - 如何使用java代理和ASM动态记录任何调用的java方法的所有参数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62816194/

相关文章:

http - Golang的(* http.ResponseWriter)Write()方法是否会阻塞,直到客户端接收到数据?

java - 我如何在javaagents中使用redefineClasses()方法

java - JVM 验证错误 'Illegal type at constant pool'

java - 不同物理机上 2 个 JVM 之间的 RMI

java - 测量和监控大型 HashMap 的大小

java - 如何从自己的 validator 验证每个流程对象?

android - 如何解决套件构建期间的异常?

java - 从短裤中获得 8 条短裤

java - 异步日志缓冲区的实现

java - 缓解 Logjam/weakdh.org 的正确 JBoss EAP 6.0.1 密码套件配置是什么?