Question

For my thesis research I need to inject a piece of code to a definable method in a test suite of which I do not have the source ( the DaCapo benchmark suite in this case, http://dacapobench.org/ ). Previous research, on which this part of my thesis is based, used bytecode injection for this which caused me to do this as well.

I used Apache's BCEL library ( http://commons.apache.org/proper/commons-bcel/ ) to build a small program which enables me to inject the fibonacci algorithm in a methods body, before other statements.

Right now, I have made this, but it does not work properly. Some methods I injected work fine (as in they are slower because of the fibonacci code), and running the DaCapo framework works good, while other injected methods break the code.

The problem is, I don't know why, and even though I know which methods broke down and which methods succeeded I'm unable to find a recurring pattern in the broken methods.

  • The bytecode seems to be fine, for so far I can see but I'm far from an expert. When I compare the bytecode before and after injection I see the fibonacci algoritm followed by the remaining method, with as only difference the increased stack positions (because the injected code also uses stack spaces).
  • The succeeded methods contained public as well as private ones. With and without parameters.
  • Some failed methods contain exceptions, others don't. some have try catches in them, others don't. etc etc.

I could paste in some failed methods, but that would make this post even longer than it already is. So I was wondering, is there some thing I'm not thinking about or am overlooking?

Below you'll find an example java file, it's outcome and the BCEL program I wrote.

A simple example, I have a java file called DemoClass.java:

public class DemoClass {

    public static void main(String[] argv) {
        System.out.println("Demo body");
        test();
    }

    public static void test() {
        System.out.println("Demo test");
    }
}

After calling the following java command in my shell:

javac DemoClass.java; java -cp bcel-5.2.jar:. InjectCodeBCEL DemoClass test 123456789 ; java DemoClass

( The bcel-5.2.jar file can be found on the apache website mentioned earlier )

The program will look like this:

public class DemoClass {

    public static void main(String[] argv) {
        System.out.println("Demo body");
        test();
    }

    public static void test() {
        int[] fibNumbers = new int[100]; 
        for (int i = 0; i < 123456789; i++) { 
            int j = i % 100; 
            if (i == 0) { 
                fibNumbers[i] = 0; 
            } 
            else if (i == 1) { 
                fibNumbers[i] = 1; 
            } 
            else { 
                int k = (i - 1) % 100; 
                int m = (i - 2) % 100; 
                int n = fibNumbers[k] + fibNumbers[m]; 
                fibNumbers[j] = n; 
            }  
        } 
        System.out.println("Demo test");
    }
}

This is the code of InjectCodeBCEL.java:

import java.io.IOException;
import org.apache.bcel.classfile.*;
import org.apache.bcel.generic.*;
import org.apache.bcel.*;

public class InjectCodeBCEL {

    static public void main(String args[]) {
        //Get class to modify from program argument
        JavaClass mod = null;
        String methodName = (args.length >= 2) ? args[1] : "";
        int loopsize = (args.length >= 3) ? Integer.parseInt(args[2]) : 1;
        try {
            mod = Repository.lookupClass(args[0]);
        }
        catch (Exception e) {
            System.err.println("Could not get class " + args[0]);
            return;
        }

        //Create a generic class to modify
        ClassGen modClass = new ClassGen(mod);
        //Create a generic constantpool to modify
        ConstantPoolGen cp = modClass.getConstantPool();
        boolean methodEdited = false;

        Method[] methods = mod.getMethods();
        for (int i = 0; i < methods.length; i++) {
            if (methods[i].getName().equals(methodName)) {

                System.out.println("Method: " + methods[i]);
                // System.out.println("before:\n" + methods[i].getCode());
                modClass.removeMethod(methods[i]);
                Method newMethod = insertCodeInMethod(mod, methods[i], cp, loopsize);
                // System.out.println("after:\n" + newMethod.getCode());
                modClass.addMethod(newMethod);

                methodEdited = true;
            }
        }
        if (methodEdited) {
            modClass.update();
            try {
                //Write modified class
                JavaClass newClass = modClass.getJavaClass();
                String classname = args[0].replace(".","/");
                newClass.dump(classname + ".class");
                System.out.println("Class " + classname + " modified");
            }
            catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static Method insertCodeInMethod(JavaClass mod, Method method, ConstantPoolGen cp, int loopsize) {
        MethodGen mg = new MethodGen(method, mod.getClassName(), cp);

        InstructionList il = mg.getInstructionList();
        InstructionHandle ihs = il.getStart();
        InstructionList ils = new InstructionList();
        InstructionFactory f = new InstructionFactory(cp);

        String CLASS_NAME = mod.getClassName();

        int ARRAY_SIZE = 100;
        int LOOP_SIZE = loopsize;

        int INCREASE_ID = mg.isStatic() ? 0 : 1; // if not static, this has position 0 on the stack
        Type[] types = mg.getArgumentTypes();
        // Increase the stack location(?) so they don't collide with the methods parameters.
        for (int i = 0; i < types.length; i++) {
            INCREASE_ID += types[i].getSize();
        }

        int VAR_ARRAY = 0 + INCREASE_ID;
        int VAR_I = 1 + INCREASE_ID;
        int VAR_II = 2 + INCREASE_ID;
        int VAR_I_MIN_1 = 3 + INCREASE_ID;
        int VAR_I_MIN_2 = 4 + INCREASE_ID;
        int VAR_SUM = 5 + INCREASE_ID;
        int VAR_JUMPTO = 6 + INCREASE_ID;

        // init array
        ils.append(new PUSH(cp, ARRAY_SIZE));
        ils.append(new NEWARRAY(Type.INT));
        ils.append(new ASTORE(VAR_ARRAY));

        // create iterator = 0 for while 
        ils.append(new PUSH(cp, 0));
        ils.append(new ISTORE(VAR_I));

        // Main while loop:
        InstructionHandle beforeWhile = ils.append(new ILOAD(VAR_I));
        ils.append(new PUSH(cp, LOOP_SIZE));
        // While condition:
        BranchHandle whileCondition = ils.append(new IF_ICMPLT(null)); // if (VAR_I < LOOP_SIZE): jump to "whileBody"
        BranchHandle whileConditionFalseGoto = ils.append(new GOTO(null)); // if not: jump to "afterWhile"

            // While body:
            InstructionHandle whileBody = ils.append(new ILOAD(VAR_I));
            ils.append(new PUSH(cp, ARRAY_SIZE));
            ils.append(new IREM());
            ils.append(new ISTORE(VAR_II)); // create int ii = i % ARRAY_SIZE;

            // if (i == 0)
            ils.append(new ILOAD(VAR_I));
            ils.append(new PUSH(cp, 0));
            BranchHandle ifIteratorIs0 = ils.append(new IF_ICMPEQ(null));
            BranchHandle ifIteratorIs0FalseGoto = ils.append(new GOTO(null));
                // If true body
                InstructionHandle ifIteratorIs0Body = ils.append(new ALOAD(VAR_ARRAY));
                ils.append(new ILOAD(VAR_I));
                ils.append(new PUSH(cp, 0));
                ils.append(new IASTORE());
                BranchHandle ifIteratorIs0Done = ils.append(new GOTO(null));

            // "else" if (i != 1)
            InstructionHandle beginIfIteratorIsNot1 = ils.append(new ILOAD(VAR_I));
            ils.append(new PUSH(cp, 1));
            BranchHandle ifIteratorIsNot1 = ils.append(new IF_ICMPNE(null));
                // false: else: so in this case: if (!(i != 1)): 
                ils.append(new ALOAD(VAR_ARRAY));
                ils.append(new ILOAD(VAR_I));
                ils.append(new PUSH(cp, 1));
                ils.append(new IASTORE());
                // done, go to i++;
                BranchHandle ifIteratorIsNot1FalseGoto = ils.append(new GOTO(null));

                // If true body (so if i != 1)..
                // create variable VAR_I_MIN_1 for array index (i-1)
                InstructionHandle ifIteratorIsNot1Body = ils.append(new ILOAD(VAR_I));
                ils.append(new PUSH(cp, 1));
                ils.append(new ISUB());
                ils.append(new PUSH(cp, ARRAY_SIZE));
                ils.append(new IREM());
                ils.append(new ISTORE(VAR_I_MIN_1)); // create int i_min_1 = (i - 1) % ARRAY_SIZE;
                // create variable VAR_I_MIN_2 for array index (i-2)
                ils.append(new ILOAD(VAR_I));
                ils.append(new PUSH(cp, 2));
                ils.append(new ISUB());
                ils.append(new PUSH(cp, ARRAY_SIZE));
                ils.append(new IREM());
                ils.append(new ISTORE(VAR_I_MIN_2)); // create int i_min_2 = (i - 2) % ARRAY_SIZE;
                // load the array values:
                ils.append(new ALOAD(VAR_ARRAY));
                ils.append(new ILOAD(VAR_I_MIN_1));
                ils.append(new IALOAD());
                ils.append(new ALOAD(VAR_ARRAY));
                ils.append(new ILOAD(VAR_I_MIN_2));
                ils.append(new IALOAD());
                // add the two values, and save them
                ils.append(new IADD());
                ils.append(new ISTORE(VAR_SUM));
                // add the new calculated number to the array
                ils.append(new ALOAD(VAR_ARRAY));
                ils.append(new ILOAD(VAR_II));
                ils.append(new ILOAD(VAR_SUM));
                ils.append(new IASTORE());
                // Done; go to i++;
                BranchHandle ifIteratorIsNot1Done = ils.append(new GOTO(null));

            // Increment i with 1
            InstructionHandle generalIfDoneGoto = ils.append(new IINC(VAR_I,1));

            // Goto that whil restart this loop:
            BranchHandle whileGotoBegin = ils.append(new GOTO(null)); // jumps to "beforeWhile"

        // We need something to jump to when done with the outer loop.
        InstructionHandle afterWhile = ils.append(new PUSH(cp, 0));
        ils.append(new ISTORE(VAR_JUMPTO));

        // While targets:
        whileCondition.setTarget(whileBody);
        whileConditionFalseGoto.setTarget(afterWhile);
        whileGotoBegin.setTarget(beforeWhile);
        // if (i == 0)
        ifIteratorIs0.setTarget(ifIteratorIs0Body);
        ifIteratorIs0FalseGoto.setTarget(beginIfIteratorIsNot1);
        ifIteratorIs0Done.setTarget(generalIfDoneGoto);
        // if (i == 1)
        ifIteratorIsNot1.setTarget(ifIteratorIsNot1Body);
        ifIteratorIsNot1FalseGoto.setTarget(generalIfDoneGoto);
        ifIteratorIsNot1Done.setTarget(generalIfDoneGoto);

        InstructionHandle ihss = il.insert(ihs,ils);
        il.redirectBranches(ihs, ihss);
        il.update();

        mg.setMaxStack();
        mg.setMaxLocals();
        mg.update();
        return mg.getMethod();
    }
}

Update

Below you can see the complete error after a failed injection of the visitAll method in net.sourceforge.pmd.AbstractRuleChainVisitor

===== DaCapo 9.12 pmd starting =====
java.lang.reflect.InvocationTargetException
java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.dacapo.harness.Pmd.iterate(Pmd.java:58)
    at org.dacapo.harness.Benchmark.run(Benchmark.java:166)
    at org.dacapo.harness.TestHarness.runBenchmark(TestHarness.java:218)
    at org.dacapo.harness.TestHarness.main(TestHarness.java:171)
    at Harness.main(Harness.java:17)
Caused by: java.lang.ClassFormatError: LVTT entry for 'nodes' in class file net/sourceforge/pmd/AbstractRuleChainVisitor does not match any LVT entry
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:615)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:141)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:283)
    at java.net.URLClassLoader.access$000(URLClassLoader.java:58)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:197)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
    at org.dacapo.harness.DacapoClassLoader.loadClass(DacapoClassLoader.java:124)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:615)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:141)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:283)
    at java.net.URLClassLoader.access$000(URLClassLoader.java:58)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:197)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
    at org.dacapo.harness.DacapoClassLoader.loadClass(DacapoClassLoader.java:124)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
    at net.sourceforge.pmd.RuleSets.<init>(RuleSets.java:27)
    at net.sourceforge.pmd.RuleSetFactory.createRuleSets(RuleSetFactory.java:82)
    at net.sourceforge.pmd.RuleSetFactory.createRuleSets(RuleSetFactory.java:70)
    at net.sourceforge.pmd.PMD.doPMD(PMD.java:359)
    at net.sourceforge.pmd.PMD.main(PMD.java:415)
    ... 9 more

The code of this method (generated by JD-GUI):

  public void visitAll(List<CompilationUnit> astCompilationUnits, RuleContext ctx)
  {
    initialize();
    clear();

    long start = System.nanoTime();
    indexNodes(astCompilationUnits, ctx);
    long end = System.nanoTime();
    Benchmark.mark(8, end - start, 1L);

    for (RuleSet ruleSet : this.ruleSetRules.keySet())
      if (ruleSet.applies(ctx.getSourceCodeFile()))
      {
        visits = 0;
        start = System.nanoTime();
        for (Rule rule : (List)this.ruleSetRules.get(ruleSet)) {
          List nodeNames = rule.getRuleChainVisits();
          for (int j = 0; j < nodeNames.size(); j++) {
            List nodes = (List)this.nodeNameToNodes.get(nodeNames.get(j));
            for (SimpleNode node : nodes)
            {
              while ((rule instanceof RuleReference)) {
                rule = ((RuleReference)rule).getRule();
              }
              visit(rule, node, ctx);
            }
            visits += nodes.size();
          }
          end = System.nanoTime();
          Benchmark.mark(1, rule.getName(), end - start, visits);
          start = end;
        }
      }
    int visits;
  }

This is an comparable error as the one I got when my code missed the stack position increasing part in 'insertCodeInMethod'. Which caused the parameters and when not static the this to collide with the defined variables inside the fibonacci code.

Was it helpful?

Solution

LVTT stands for LocalVariableTypeTable and LVT for LocalVariableTable. These are debugging information that have to be either adapted or removed when changing the method.

By the way, it is not a good way to success injecting tons of code into a method, as it creates various sources of errors, implies re-inventing the wheel (building a compiler) and leads to code duplication in the transformed classes. Most applications using code injection/class file transformations will only inject small invocations of precompiled methods.

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