2014-05-06 12 views
7

Jestem nowy w ASM i potrzebuję pomocy związanej z transformacją kodu bajtowego.Dodawanie bloku próbnego/catch w bajtode przez ASM

Pytanie: Chciałbym dodać blok try/catch dla całej metody w bajtoderze za pośrednictwem ASM i chcę uruchomić metodę bez użycia opcji java -noverify. Potrafię dodać blok try/catch dla całej metody, ale gdy próbowałem wykonać tę metodę, otrzymuję komunikat "java.lang.VerifyError". Jeśli użyję opcji java -noverify, uruchomi się. Proszę pomóż mi.

Poniżej znajdują się szczegóły.

public class Example { 
    public static void hello() { 
     System.out.println("Hello world"); 
    } 
} 

Chcę przetransformować powyższy kod, jak poniżej, wprowadzając bloki try/catch, z instrumentacją kodu bajtowego ASM.

public class Example { 
    public static void hello() { 
     try 
     { 
      System.out.println("Hello world"); 
     } catch(Exception ex) { 
     ex.printStackTrace(); 
     } 
    } 
} 

Poniższy kod dodaje blok try/catch, ale nie wykonuje kodu bez opcji java -noverify.

public class InstrumentExample { 

    /** 
    * Our custom method modifier method visitor class. It delegate all calls to 
    * the super class. Do our logic of adding try/catch block 
    * 
    */ 
    public static class ModifierMethodWriter extends MethodVisitor { 

     // methodName to make sure adding try catch block for the specific 
     // method. 
     private String methodName; 

     // below label variables are for adding try/catch blocks in instrumented 
     // code. 
     private Label lTryBlockStart; 
     private Label lTryBlockEnd; 
     private Label lCatchBlockStart; 
     private Label lCatchBlockEnd; 

     /** 
     * constructor for accepting methodVisitor object and methodName 
     * 
     * @param api: the ASM API version implemented by this visitor 
     * @param mv: MethodVisitor obj 
     * @param methodName : methodName to make sure adding try catch block for the specific method. 
     */ 
     public ModifierMethodWriter(int api, MethodVisitor mv, String methodName) { 
      super(api, mv); 
      this.methodName = methodName; 
     } 

     // We want to add try/catch block for the entire code in the method 
     // so adding the try/catch when the method is started visiting the code. 
     @Override 
     public void visitCode() { 
      super.visitCode(); 

      // adding try/catch block only if the method is hello() 
      if (methodName.equals("hello")) { 
       lTryBlockStart = new Label(); 
       lTryBlockEnd = new Label(); 
       lCatchBlockStart = new Label(); 
       lCatchBlockEnd = new Label(); 

       // set up try-catch block for RuntimeException 
       visitTryCatchBlock(lTryBlockStart, lTryBlockEnd, 
         lCatchBlockStart, "java/lang/Exception"); 

       // started the try block 
       visitLabel(lTryBlockStart); 
      } 

     } 

     @Override 
     public void visitMaxs(int maxStack, int maxLocals) { 

      // closing the try block and opening the catch block if the method 
      // is hello() 
      if (methodName.equals("hello")) { 
       // closing the try block 
       visitLabel(lTryBlockEnd); 

       // when here, no exception was thrown, so skip exception handler 
       visitJumpInsn(GOTO, lCatchBlockEnd); 

       // exception handler starts here, with RuntimeException stored 
       // on stack 
       visitLabel(lCatchBlockStart); 

       // store the RuntimeException in local variable 
       visitVarInsn(ASTORE, 2); 

       // here we could for example do e.printStackTrace() 
       visitVarInsn(ALOAD, 2); // load it 
       visitMethodInsn(INVOKEVIRTUAL, "java/lang/Exception", 
         "printStackTrace", "()V", false); 

       // exception handler ends here: 
       visitLabel(lCatchBlockEnd); 
      } 

      super.visitMaxs(maxStack, maxLocals); 
     } 

    } 

    /** 
    * Our class modifier class visitor. It delegate all calls to the super 
    * class Only makes sure that it returns our MethodVisitor for every method 
    * 
    */ 
    public static class ModifierClassWriter extends ClassVisitor { 
     private int api; 

     public ModifierClassWriter(int api, ClassWriter cv) { 
      super(api, cv); 
      this.api = api; 
     } 

     @Override 
     public MethodVisitor visitMethod(int access, String name, String desc, 
       String signature, String[] exceptions) { 

      MethodVisitor mv = super.visitMethod(access, name, desc, signature, 
        exceptions); 

      // Our custom MethodWriter 
      ModifierMethodWriter mvw = new ModifierMethodWriter(api, mv, name); 
      return mvw; 
     } 

    } 

    public static void main(String[] args) throws IOException { 

     DataOutputStream dout = null; 
     try { 
      // loading the class 
      InputStream in = InstrumentExample.class 
        .getResourceAsStream("Example.class"); 
      ClassReader classReader = new ClassReader(in); 
      ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); 

      // Wrap the ClassWriter with our custom ClassVisitor 
      ModifierClassWriter mcw = new ModifierClassWriter(ASM4, cw); 
      ClassVisitor cv = new CheckClassAdapter(mcw); 

      classReader.accept(cv, 0); 

      byte[] byteArray = cw.toByteArray(); 
      dout = new DataOutputStream(new FileOutputStream(new File("Example.class"))); 
      dout.write(byteArray); 

     } catch (Exception ex) { 
      ex.printStackTrace(); 
     } finally { 
      if (dout != null) 
       dout.close(); 
     } 

    } 
} 

Do debugowania Użyłem CheckClassAdapter i dostałem poniżej problem z weryfikacją.

Message:org.objectweb.asm.tree.analysis.AnalyzerException: Execution can fall off end of the code 
    at org.objectweb.asm.tree.analysis.Analyzer.findSubroutine(Unknown Source) 
    at org.objectweb.asm.tree.analysis.Analyzer.findSubroutine(Unknown Source) 
    at org.objectweb.asm.tree.analysis.Analyzer.analyze(Unknown Source) 
    at org.objectweb.asm.util.CheckClassAdapter.verify(Unknown Source) 
    at org.objectweb.asm.util.CheckClassAdapter.verify(Unknown Source) 
    at com.mfr.instrumentation.selenium.work.InstrumentExample.main(InstrumentExample.java:166) 
hello()V 
00000 ?  : L0 
00001 ?  :  GETSTATIC java/lang/System.out : Ljava/io/PrintStream; 
00002 ?  :  LDC "Hello world" 
00003 ?  :  INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V 
00004 ?  :  RETURN 
00005 ?  : L1 
00006 ?  :  GOTO L2 
00007 ?  : L3 
00008 ?  :  ASTORE 2 
00009 ?  :  ALOAD 2 
00010 ?  :  INVOKEVIRTUAL java/lang/Exception.printStackTrace()V 
00011 ?  : L2 
    TRYCATCHBLOCK L0 L1 L3 java/lang/Exception 

Nie udało mi się zrozumieć powyższego komunikatu weryfikacyjnego.

+0

Jakie jest pytanie? – biziclop

+0

Pytanie: w jaki sposób mogę przekształcić mój początkowy kod na blok try/catch na mój ostatni kod z blokiem try/catch z wykorzystaniem oprzyrządowania bajtowego ASM. –

Odpowiedz

4

Powyższy wyjątek dotyczy obliczania ramek stosu. ASM dostarczył mechanizm do tworzenia ramek stosu sam w sobie. Musimy użyć flagi parametru jako COMPUTE_FRAMES w konstruktorze ClassWriter.

Ex: ClassWriter cw = new ClassWriter (ClassWriter.COMPUTE_FRAMES);

publiczny końcowy statyczny int COMPUTE_FRAMES Flaga do automatycznego obliczania ramek mapek stosu metod od podstaw. Jeśli ta flaga zostanie ustawiona, wówczas wywołania metody MethodVisitor.visitFrame (int, int, java.lang.Object [], int, java.lang.Object []) zostaną zignorowane, a ramki mapy stosu zostaną ponownie obliczone z kod metod. Argumenty metody visitMaxs są również ignorowane i ponownie obliczane z kodu bajtowego. Innymi słowy, computeFrames oznacza computeMaxs.

z API klasy ASM ClassWriter.

5

Musisz przejechać swoją klasę i użyć zmodyfikowanego MethodVisitor w tym procesie. Jeśli zawiniesz całą swoją metodę w konstrukcie try - catch. Możesz to zrobić, wstawiając konstrukcję, przechwytując wywołania zwrotne dla początku i końca bloku wywołania. Metody te są visitCode i visitEnd które można przechwycić tak:

class MyMethodVisitor extends MethodVisitor { 
    // constructor omitted 

private final Label start = new Label(), 
        end = new Label(), 
        handler = new Label(); 

    @Override 
    public void visitCode() { 
    super.visitCode(); 
    visitTryCatchBlock(start, 
     end, 
     handler, 
     "java/lang/Exception"); 
    visitLabel(start); 
    } 

    @Override 
    public void visitEnd() { 
    visitJumpInsn(GOTO, end); 
    visitLabel(handler); 
    visitMethodInsn(INVOKEVIRTUAL, 
     "java/lang/RuntimeException", 
     "printStackTrace", 
     "()V"); 
    visitInsn(RETURN); 
    visitLabel(lCatchBlockEnd); 
    super.visitEnd(); 
    } 
} 

Należy jednak pamiętać, że ten przykład nie obejmuje stosu klatek na mapie, które trzeba dodać, jeśli produkować kod bajtowy Java 7+.

Należy jednak zauważyć, że to rozwiązanie zarejestruje dominujący moduł obsługi na początku tabeli wyjątków metody, która zastąpi wszystkie inne bloki try-catch-finally w twojej metodzie, które już istnieją!

+0

Zaimplementowałem Twoje sugestie i mogę skutecznie dodać blok try/catch dla całej metody. Dekompilowałem go i zweryfikowałem. Ale kiedy próbowałem uruchomić tę metodę, otrzymuję komunikat "java.lang.VerifyError". Jeśli uruchomię metodę z opcją -noverify, uda mi się pomyślnie wykonać tę metodę. Zmieniłem moje pytanie i dodałem kod, aby dodać try/catch. Czy możesz poinformować mnie, co powinienem zrobić, aby wykonać kod bez użycia opcji -noverify. –

+0

Co mówi weryfikator? I jakiej wersji Java używasz. Jak już powiedziałem w mojej odpowiedzi, może być konieczne zastosowanie klatek mapy stosu. Asm może je dla ciebie wyliczyć. –

+0

Właśnie zaktualizowałem pytanie za pomocą komunikatu Verifier. Używam wersji Java 7. i ASM 5.0.2 jar –