Question

I want to replace method body with new content(sample.class:sayHello method) and then executes sample.class. The original sayHelo declaration is:

    public int sayHello(String args){

}

I want to modify its body to be:

    System.out.println("sxu says: hello world!");
    return 1;

But sample execution result throws exception:

Exception in thread "main" java.lang.VerifyError: Operand stack overflow
Exception Details:
  Location:
    code/sxu/demo/data/sample.sayHello(Ljava/lang/String;)I @4: ldc
  Reason:
    Exceeded max stack size.
  Current Frame:
    bci: @4
    flags: { }
    locals: { 'code/sxu/demo/data/sample', 'java/lang/String' }
    stack: { 'code/sxu/demo/data/sample', 'java/io/PrintStream' }
  Bytecode:
    0000000: 2ab2 0028 122a b600 1704 acb2 0028 2bb6
    0000010: 0017 04ac    

I use asm tools and there are three java classes in my code:

public class Adapt extends ClassLoader {

    @Override
    protected synchronized Class<?> loadClass(final String name,
            final boolean resolve) throws ClassNotFoundException {
        if (name.startsWith("java.")) {
            return super.loadClass(name, resolve);
        } else {
            System.err.println("Adapt: loading class '" + name
                    + "' with on the fly adaptation");
        }

        // gets an input stream to read the bytecode of the class
        String resource = name.replace('.', '/') + ".class";
        InputStream is = getResourceAsStream(resource);
        byte[] b;

        // adapts the class on the fly
        try {
            ClassReader cr = new ClassReader(is);
            ClassWriter cw = new ClassWriter(0);
            ClassVisitor cv = new ClassAdapter(cw);
            cr.accept(cv, 0);
            b = cw.toByteArray();
        } catch (Exception e) {
            throw new ClassNotFoundException(name, e);
        }

        FileOutputStream fos = new FileOutputStream("/tmp/"+resource.substring(resource.lastIndexOf("/")) );
            fos.write(b);
            fos.close();


        // returns the adapted class
        return defineClass(name, b, 0, b.length);
    }

    public static void main(String[] args) {
          // loads the application class (in args[0]) with an Adapt class loader
        ClassLoader loader = new Adapt();
        Class<?> c;
        try {

            c = loader.loadClass(args[0]);
            // calls the 'main' static method of this class with the
            // application arguments (in args[1] ... args[n]) as parameter
            Method m = c.getMethod("main", new Class<?>[] { String[].class });
            String[] applicationArgs = new String[args.length - 1];
            System.arraycopy(args, 1, applicationArgs, 0, applicationArgs.length);
            m.invoke(null, new Object[] { applicationArgs });

    }

}

public class ClassAdapter extends ClassVisitor {

    private String owner;

    public ClassAdapter(ClassVisitor cv) {
        super(Opcodes.ASM4, cv);
    }

    @Override
    public void visit(final int version, final int access, final String name,
            final String signature, final String superName,
            final String[] interfaces) {
        owner = name;
        super.visit(version, access, name, signature, superName, interfaces);
    }

    @Override
    public FieldVisitor visitField(final int access, final String name,
            final String desc, final String signature, final Object value) {
        FieldVisitor fv = super
                .visitField(access, name, desc, signature, value);
        if ((access & Opcodes.ACC_STATIC) == 0) { ...
        }
        return fv;
    }

    @Override
    public MethodVisitor visitMethod(final int access, final String name,
            final String desc, final String signature, final String[] exceptions) {
        System.out.println("calling method name: "+ name);
        MethodVisitor mv = cv.visitMethod(access, name, desc, signature,
                exceptions);

        //Rewrite sayHello method 
        if(name.equals("sayHello")){
            mv = new MethodModifierAdapter(mv);
            return mv;
            /*
            mv.visitCode();
            mv.visitVarInsn(Opcodes.ALOAD, 0);

            mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",
                    "Ljava/io/PrintStream;");

            // pushes the "Hello World!" String constant
            mv.visitLdcInsn("Sxu says: Hello world!");
            // invokes the 'println' method (defined in the PrintStream class)
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println",
                    "(Ljava/lang/String;)V");
            mv.visitInsn(Opcodes.ICONST_1);
            mv.visitInsn(Opcodes.IRETURN);
            // this code uses a maximum of two stack elements and two local
            // variables
            mv.visitMaxs(5, 5);
            mv.visitEnd();
            */

        }

        return mv == null ? null : new TraceFieldCodeAdapter(Opcodes.ASM4, mv, owner);
    }

}

public class MethodModifierAdapter extends MethodVisitor implements Opcodes {

    boolean _modified ;
    public MethodModifierAdapter(MethodVisitor mv) {
        super(Opcodes.ASM4, mv);
        _modified = false;
    }

    @Override
    public void visitCode() {

        if(!_modified){
            _modified = true;

            mv.visitCode();
            mv.visitVarInsn(Opcodes.ALOAD, 0);

            mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",
                    "Ljava/io/PrintStream;");

            // pushes the "Hello World!" String constant
            mv.visitLdcInsn("Sxu says: Hello world!");
            // invokes the 'println' method (defined in the PrintStream class)
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println",
                    "(Ljava/lang/String;)V");
            mv.visitInsn(Opcodes.ICONST_1);
            mv.visitInsn(Opcodes.IRETURN);
            // this code uses a maximum of two stack elements and two local
            // variables
            mv.visitMaxs(5, 5);
            mv.visitEnd();

        }
    }
}

In the ClassAdaptor::visitMethod, i created a new MethodModifierAdapter. visitCode of MethodModifierAdapter then rewrites method body.

The javap result of new sample.class is:

 public int sayHello(java.lang.String);
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0       
         1: getstatic     #40                 // Field java/lang/System.out:Ljava/io/PrintStream;
         4: ldc           #42                 // String Sxu says: Hello world!
         6: invokevirtual #23                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         9: iconst_1      
        10: ireturn       
        11: getstatic     #40                 // Field java/lang/System.out:Ljava/io/PrintStream;
        14: aload_1       
        15: invokevirtual #23                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        18: iconst_1      
        19: ireturn       
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
              11       9     0  this   Lcode/sxu/demo/data/sample;
              11       9     1  args   Ljava/lang/String;
      LineNumberTable:
        line 12: 11
        line 13: 18

According to its content, MethodModifierAdapter::visitcode do updates method content.

I guess there some wrong in my way using ASM API, but donot clear which one..

Was it helpful?

Solution

You are inserting code that requires more stack space than the original one. So you have to either update the max stack declaration manually or tell ASM to calculate the required stack space automatically. Replace your construction new ClassWriter(0) with new ClassWriter(ClassWriter.COMPUTE_MAXS). The flag ClassWriter.COMPUTE_MAXS will tell ASM to (re)calculate the stack requirements.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top