Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Wrong number of arguments in stack when trying to instrument java bytecode

I am working on a small java bytecode instrumentation tool.

The general idea is to have all of a class methods renamed with a _CONGU suffix, creating then proxy methods with the original method names that will call their _CONGU counterparts.

For instance, if a class C contains a int m() { return 1; } method, the instrumented C class will have a int m_CONGU() { return 1; } method and a int m() { return m_CONGU(); } method.

Later on I'll add some extra logic on int m() that will do some checks before calling m_CONGU().

At the moment, I am in the process of instrumenting all constructor invocation calls to a method of mine with the _CONGU suffix.

Bellow you can see both the instrumented and non-instrumented versions of the inverse() method of a Fraction class. enter image description here

When trying to run this code

Fraction fraction = new Fraction(4, 1);

I get the following exception which is puzzling me for the last couple of hours:

Exception in thread "main" java.lang.VerifyError: (class: jorgeTestes/system/fraction/Fraction, method: inverse_CONGU signature: ()LjorgeTestes/system/fraction/Fraction;) Expecting to find object/array on stack at jorgeTestes.system.fraction.XyzTest.main(XyzTest.java:9)

I guess this must be a dead obvious mistake, but I can't get what the problem might be. It looks to be in some way related to having the wrong number of data in the stack, but it looks to me that the number of elements in the stack is the same both on the original and instrumented code (at least that's what should be happening). Any ideas?

Some more info:

1) here are the descriptors of both <init> and Fraction_CONGU(which are the same, as one would expect!): enter image description here

2) I'm wondering if the color of [0] Code being different in Bytecode Viewer could mean there's some other problem with my instrumented code? Maybe there is some metadata that's being broken in the process, so that could be the reason the code looks alright and there's still a problem when trying to run the code?

like image 517
devoured elysium Avatar asked Oct 09 '22 04:10

devoured elysium


1 Answers

It looks like the code on the first screenshot is fundamentally broken. Object construction in JVM bytecode can be split into two phass: allocating memory on the heap and calling a constructor (with optional parameters) against that allocated memory:

new #1 <Fraction>  //allocate memory
dup                //duplicate to not loose the object after calling constructor
                   //push c-tor args onto the stack here
invokespecial <Fraction.<init>>  //constructor, second `this` is lost
areturn            //returns first `this`

What you are basically doing is: allocate some raw (probably zeroed) memory on the heap and invoke virtual method Fraction_congu against that memory block. You haven't yet called the constructor!

Also there should be invokevirtual, I guess this generated methods are private.

UPDATE: I assume you want to transform the following class:

class Fraction {

    public Fraction(float den, float num) {
        //original constructor code here
    }

    public int m() {
        return 1;
    }
}

Into:

class Fraction {

    public Fraction(float den, float num) {
        //proxy method
        //place for extra logic 
        Fraction_CONGU(den, num);
    }

    private Fraction_CONGU(float den, float num) {
        //original constructor code here
    }

    public int m() {
        //proxy method
        //place for extra logic 
        return m_CONGU
    }

    private int m_CONGU() {
        return 1;
    }
}

As you can see it is perfectly possible (if I get your idea correctly). Just compile this Java code and see how compiler implemented this.

Which begs a question: can't you just use AspectJ with compile time-weaving?

like image 128
Tomasz Nurkiewicz Avatar answered Oct 13 '22 11:10

Tomasz Nurkiewicz