使用java asm获取函数参数值以进行字节码检测

时间:2013-08-05 15:11:14

标签: java java-bytecode-asm

我正在使用asm在每个执行的函数中插入一个回调函数。 如何打印arguents值?

我正在使用MethodAdapter.visitCode将我的函数注入到每个运行的函数中。

我想将函数参数插入一个数组并发送我的callbackk函数这个数组并将参数返回给堆栈,以便函数可以继续使用它们

以下代码将方法参数插入到数组中,并作为Object数组发送到回调函数。 我无法将参数返回到原始函数

@Override public void visitCode() 
        {

            int paramLength = paramTypes.length;            
            System.out.println(className + "." + methodName + ": paramLength = " + paramLength);


            // Create array with length equal to number of parameters
            mv.visitIntInsn(Opcodes.BIPUSH, paramLength);
            mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
            mv.visitVarInsn(Opcodes.ASTORE, paramLength);

            // Fill the created array with method parameters
             int i = 0;
            for (Type tp : paramTypes) 
            {
                System.out.println("tp.getClassName() = " + tp.getClassName());             

                mv.visitVarInsn(Opcodes.ALOAD, paramLength);
                mv.visitIntInsn(Opcodes.BIPUSH, i);

                if (tp.equals(Type.BOOLEAN_TYPE)) 
                {
                    mv.visitVarInsn(Opcodes.ILOAD, i);
                    mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;");
                }
                else if (tp.equals(Type.BYTE_TYPE)) 
                {
                    mv.visitVarInsn(Opcodes.ILOAD, i);
                    mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;");
                }
                else if (tp.equals(Type.CHAR_TYPE)) 
                {
                    mv.visitVarInsn(Opcodes.ILOAD, i);
                    mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;");
                }
                else if (tp.equals(Type.SHORT_TYPE)) 
                {
                    mv.visitVarInsn(Opcodes.ILOAD, i);
                    mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;");
                }
                else if (tp.equals(Type.INT_TYPE)) 
                {
                    mv.visitVarInsn(Opcodes.ILOAD, i);
                    mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;");
                }
                else if (tp.equals(Type.LONG_TYPE)) 
                {
                    mv.visitVarInsn(Opcodes.LLOAD, i);
                    mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;");
                    i++;
                }
                else if (tp.equals(Type.FLOAT_TYPE)) 
                {
                    mv.visitVarInsn(Opcodes.FLOAD, i);
                    mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;");
                }
                else if (tp.equals(Type.DOUBLE_TYPE)) 
                {
                    mv.visitVarInsn(Opcodes.DLOAD, i);
                    mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;");
                    i++;
                }
                else
                    mv.visitVarInsn(Opcodes.ALOAD, i);

                mv.visitInsn(Opcodes.AASTORE);
                i++;
            }


            //System.out.println("end for");

            // Load class name and method name                      
            this.visitLdcInsn(className);
            this.visitLdcInsn(methodName);          
             // Load the array of parameters that we created
            this.visitVarInsn(Opcodes.ALOAD, i);                        

            this.visitMethodInsn(Opcodes.INVOKESTATIC, "callbackpackage/CallBack", "callbackfunc", "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V");
super.visitCode();
}                   

1 个答案:

答案 0 :(得分:3)

在for循环之前,您将数组存储/创建到paramLength索引的局部变量表中。但是在循环之后,您将从i索引访问它。为了安全起见:在第三个到最后一个源代码语句中用i替换paramLength;即执行此操作:this.visitVarInsn(Opcodes.ALOAD, paramLength);而不是此this.visitVarInsn(Opcodes.ALOAD, i)

此外,您的i已初始化为0。例如,这意味着从局部变量表中加载 this 参数(请参阅下面的一般说明)。确保它正是您打算做的。如果您不打算加载变量,请将i初始化为1,例如(非静态)方法。

一般说明:

鉴于您声称使用MethodAdapter.visitCode,我将假设您要访问方法参数的 方法的调用。

方法参数存储在本地变量表中,用于每个方法调用。因此,您可以通过简单地将操作数堆栈中的第一个 N 变量加载到局部变量表中来访问方法参数的值。其中N只是方法的参数个数。请记住,局部变量表是0索引的;因此索引从0开始。还要注意,在实例方法的情况下,“ this ”也被视为参数。在实例方法的情况下,索引为0的局部变量表示“变量”。

可以使用以下要点中的代码从方法描述计算方法的参数数量:https://gist.github.com/VijayKrishna/6160036。使用parseMethodArguments(String desc)方法,您可以轻松计算方法的参数数量。然后在visitCode()方法的某个地方,理想情况下尽快执行此操作:

@Override
public void visitCode() {
    ...
    char[] methodDescParsed = parseMethodArguments(methodDescription);
    int N = methodDescParsed.length + (isMethodStatic ? 0 : 1);
    ...
}

parseMethodArguments中的大多数计算只是解析方法描述,这是一个字符串。它用大写字母L替换数组和对象的类型描述符,并保留原语的类型描述符。它返回一个char[],数组中的每个元素大致表示传递的参数类型。

由于 this 参数的类型未反映在方法描述中,因此(isMethodStatic ? 1 : 0)三元表达式用于将参数计数递增1,以防方法不是静态的,以解释参数 当方法不是实例方法时,isMethodStatictrue,因此,没有参数,因此不需要增量;
当方法是实例方法时,isMethodStaticfalse,因此, this 参数存在,因此参数计数会递增。

一旦得到参数计数,使用ASM的松散Java代码如下所示,在方法调用后很快访问方法的第一个 N 局部变量:

for(int i = 0; i < N; i ++) {
    int opcode = 0;
    switch() {
       case 'L': opcode = Opcodes.ALOAD; break;
       case 'I': opocde = Opcodes.ILOAD; break;
       case 'J': opcode = Opocdes.LLOAD; break;
       case 'F': ...
       case 'D': ...
       // TODO: complete all the cases.
    }

    mv.visitVarInsn(opcode, i);
    // ith argument is now loaded on the operand stack.
    // add more ASM code to do what ever it is that you want to do with the argument.
}