5

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.

Robby Cornelissen
  • 91,784
  • 22
  • 134
  • 156
Peter
  • 620
  • 6
  • 13
  • So what error are you getting? There's not much we can do with "it doesn't work" – Antimony Oct 01 '13 at 15:33
  • Have you considered using [AspectJ](http://eclipse.org/aspectj/)? That might be easier to use to inject code. – Katona Oct 01 '13 at 15:36
  • If you put your code to the end of the method this might infer with return statments as well as finally blocks. In addition it might make sense to explain what braek down or fails means exaclty. Exception? -> Stacktrace! – Bernd Ebertz Oct 01 '13 at 15:36
  • @Antimony, you're right. I forgot to add it, i've updated the post. – Peter Oct 01 '13 at 16:43
  • @Katona, no I didn't, thanks for the tip. I'm not sure I quite get AspectJ but from what I see in the FAQ it requires me to compile the program with the AspectJ compiler. However I do not have the (complete) source code of this library. And more importantly, my thesis is about profiling, and adding AspectJ will add more code than just the piece i'm injecting if my assumptions are correct. This would mean I can't use the data for my thesis. – Peter Oct 01 '13 at 16:58
  • The error says that the LocalVariableTypeTable entry is malformed. So the part of your code that generates or modifies it must have a bug. – Antimony Oct 01 '13 at 20:06
  • Can you post the classfile for `net/sourceforge/pmd/AbstractRuleChainVisitor` please? – Antimony Oct 01 '13 at 20:18
  • @Peter so you are building your own profiler, then you might be interested in [JVMTI](http://docs.oracle.com/javase/6/docs/technotes/guides/jvmti/), that might be even better for this purpose. – Katona Oct 01 '13 at 21:43
  • @Antimony thats what I assume as well since an earlier bug with not counting parameters stack positions gave the same error. However what I don't know is where to look for. I was hoping for some tips. What can I have overlooked in the injecting bytecode in an existing method that causes some methods to be injected successfully while others cause the program to crash (like seen above) I've uploaded the class file ( original and injected broken one: http://we.tl/RydYOAgufK ). Be aware that you probably have to rename the injected one to the original name otherwise JD-GUI will show the old one.. – Peter Oct 02 '13 at 11:10
  • @Katona no i'm not building my own profiler. I'm using several existing profilers (xprof, hprof, yourkit, jprofiler and an experimental profiler) and compare their output to see if they recognize injected (and therefor slower) methods and how it impacts their results. I know JVMTI a little, if I remember correctly it provides a way to inject bytecode (for instance on class load) but the actual injecting part is not provided and has to happen by frameworks like BCEL (source: http://www.javalobby.org/java/forums/t19309.html). – Peter Oct 02 '13 at 11:27
  • @Pete most likely what happens is that it works if the method didn't have debugging information but it crashes if it does since you aren't correctly updating that information. – Antimony Oct 02 '13 at 12:49
  • @Antimony, I'm not sure I fully get your answer, but I found this question ( http://stackoverflow.com/q/3145826 ) about how to detect if your class contains debug information. And both the class AbstractRuleChainVisitor (which failed) and for example JavaParser.class (with several successful injections) contain a LineNumberTable.. Does this mean they both contain debug information? Can you give me some advice in how to detect if a class does? – Peter Oct 07 '13 at 09:57
  • @Peter There are several types of debug information. By default, SourceFile and LineNumberTable are included. If you compile with `-g:vars` it will add a LocalVariableTable, and I believe also a LocalVariableTypeTable if necessary. If you compile `-g:none` it won't include any debug info at all. At any rate, you can use any tool that lets you inspect classfiles to see which attributes it contains. – Antimony Oct 07 '13 at 13:07

1 Answers1

3

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.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • Thanks a lot @Holger! Removing the LocalVariableTypeTable fixed it! Simply calling the MethodGen's `removeLocalVariables()` at the end of the injecting made it work! – Peter Oct 08 '13 at 12:37