Внедрение кода в существующий метод с использованием BCEL

Для моего дипломного исследования мне нужно внедрить кусок кода в определяемый метод в наборе тестов, источник которого у меня отсутствует (набор тестов DaCapo в данном случае, http://dacapobench.org/). Предыдущее исследование, на котором основана эта часть моей диссертации, использовало внедрение байт-кода для этого, что также заставило меня сделать это.

Я использовал библиотеку Apache BCEL ( http://commons.apache.org/proper/commons-bcel/), чтобы создать небольшую программу, которая позволяет вводить алгоритм Фибоначчи в тело метода перед другими утверждениями.

Прямо сейчас я сделал это, но это не работает должным образом. Некоторые методы, которые я вставил, работают нормально (так как они медленнее из-за кода Фибоначчи), и работа фреймворка DaCapo работает хорошо, в то время как другие внедренные методы нарушают код.

Проблема в том, что я не знаю почему, и хотя я знаю, какие методы были повреждены, а какие - успешными, я не могу найти повторяющийся шаблон в сломанных методах.

  • Байт-код, кажется, в порядке, насколько я могу видеть, но я далеко не эксперт. Когда я сравниваю байт-код до и после внедрения, я вижу алгоритм Фибоначчи, за которым следует оставшийся метод, с единственной разницей в увеличенных позициях стека (поскольку введенный код также использует пространства стека).
  • Успешные методы содержали как публичные, так и частные методы. С параметрами и без параметров.
  • Некоторые неудачные методы содержат исключения, другие нет. некоторые пытаются поймать их, другие нет. и т. д.

Я мог бы вставить некоторые неудачные методы, но это сделало бы этот пост еще длиннее, чем он есть. Так что мне было интересно, есть ли что-то, о чем я не думаю или пропускаю?

Ниже вы найдете пример файла Java, его результаты и программу BCEL, которую я написал.

Простой пример, у меня есть файл Java с именем 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");
    }
}

После вызова следующей команды Java в моей оболочке:

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

(Файл bcel-5.2.jar можно найти на упомянутом ранее веб-сайте apache)

Программа будет выглядеть так:

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");
    }
}

Это код 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();
    }
}

Обновить

Ниже вы можете увидеть полную ошибку после неудачной инъекции метода visitAll в 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

Код этого метода (сгенерированный 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;
  }

Эта ошибка сопоставима с той, которую я получил, когда мой код пропустил часть увеличения позиции стека в 'insertCodeInMethod'. Что вызвало параметры, а когда они не статичны, это вступило в противоречие с определенными переменными внутри кода Фибоначчи.

1 ответ

Решение

LVTT обозначает LocalVariableTypeTable а также LVT за LocalVariableTable, Это отладочная информация, которую нужно либо адаптировать, либо удалить при изменении метода.

Между прочим, это не очень хороший путь к успешному внедрению тонн кода в метод, поскольку он создает различные источники ошибок, подразумевает повторное изобретение колеса (сборка компилятора) и приводит к дублированию кода в преобразованных классах. Большинство приложений, использующих внедрение кода / преобразование файла класса, вводят только небольшие вызовы предварительно скомпилированных методов.

Другие вопросы по тегам