8

Próbuję uzyskać wartości parametrów metody programu Java. Używam ASM do instrument kod bajtowy i uzyskiwanie tych wartości. Jednak mam pewne problemy.Wartości parametrów metody Java w ASM

Oto metoda visitCode() używana do instrumentowania kodu. Co to jest:

  1. Utwórz pustą tablicę do przechowywania zebranych parametrów.
  2. Dla każdego parametru, załaduj jego wartość do tablicy.
  3. Wyślij tę tablicę do metody OnMethodEntry mojego agenta (w której zostaną użyte wartości).

.

@Override 
public void visitCode() { 
    int paramLength = paramTypes.length; 

    // Create array with length equal to number of parameters 
    mv.visitIntInsn(Opcodes.BIPUSH, paramLength); 
    mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); 
    mv.visitVarInsn(Opcodes.ASTORE, paramLength); 

    // Fill the created array with method parameters 
    int i = 0; 
    for (Type tp : paramTypes) { 
     mv.visitVarInsn(Opcodes.ALOAD, paramLength); 
     mv.visitIntInsn(Opcodes.BIPUSH, i); 

     if (tp.equals(Type.BOOLEAN_TYPE) || tp.equals(Type.BYTE_TYPE) || tp.equals(Type.CHAR_TYPE) || tp.equals(Type.SHORT_TYPE) || tp.equals(Type.INT_TYPE)) 
      mv.visitVarInsn(Opcodes.ILOAD, i); 
     else if (tp.equals(Type.LONG_TYPE)) { 
      mv.visitVarInsn(Opcodes.LLOAD, i); 
      i++; 
     } 
     else if (tp.equals(Type.FLOAT_TYPE)) 
      mv.visitVarInsn(Opcodes.FLOAD, i); 
     else if (tp.equals(Type.DOUBLE_TYPE)) { 
      mv.visitVarInsn(Opcodes.DLOAD, i); 
      i++; 
     } 
     else 
      mv.visitVarInsn(Opcodes.ALOAD, i); 

     mv.visitInsn(Opcodes.AASTORE); 
     i++; 
    } 

    // Load id, class name and method name 
    this.visitLdcInsn(new Integer(this.methodID)); 
    this.visitLdcInsn(this.className); 
    this.visitLdcInsn(this.methodName); 

    // Load the array of parameters that we created 
    this.visitVarInsn(Opcodes.ALOAD, paramLength); 

    mv.visitMethodInsn(Opcodes.INVOKESTATIC, 
      "jalen/MethodStats", 
      "onMethodEntry", 
      "(ILjava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V"); 
    super.visitCode(); 
} 

Jednak to nie działa, gdy podobno metoda ma więcej niż jeden parametr.

pokazy

pliku klasy uzyskuje takie rzeczy:

static void moveDisk(char arg0, char arg1, PrintStream arg2) { 
Object[] arrayOfObject = new Object[3]; arrayOfObject[0] = ???; arrayOfObject[1] = ???; 
Object localObject; 
arrayOfObject[2] = localObject; MethodStats.onMethodEntry(5, "hanoi/TowersOfHanoi", "moveDisk", arrayOfObject); 

Gdzie 2 lokalne obiekty są tworzone zamiast ładowania parametrów.

Kod bajtowy nie pokazuje nic dziwnego:

static void moveDisk(char, char, java.io.PrintStream); 
Code: 
    0: bipush  3 
    2: anewarray  #4     // class java/lang/Object 
    5: astore_3  
    6: aload_3  
    7: bipush  0 
    9: iload_0  
    10: aastore  
    11: aload_3  
    12: bipush  1 
    14: iload_1  
    15: aastore  
    16: aload_3  
    17: bipush  2 
    19: aload_2  
    20: aastore  
    21: ldc   #118    // int 5 
    23: ldc   #12     // String hanoi/TowersOfHanoi 
    25: ldc   #119    // String moveDisk 
    27: aload_3  
    28: invokestatic #19     // Method jalen/MethodStats.onMethodEntry:(ILjava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V 

i wreszcie pokazał się błąd (przy użyciu -noverify):

param: [Ljava.lang.String;@420e54f3 
Exception in thread "Jalen Agent" java.lang.NullPointerException 
at hanoi.TowersOfHanoi.solveHanoi(TowersOfHanoi.java) 
at hanoi.TowersOfHanoi.main(TowersOfHanoi.java:29) 

Inaczej, to jest:

Exception in thread "Jalen Agent" java.lang.VerifyError: (class: hanoi/TowersOfHanoi, method: moveDisk signature: (CCLjava/io/PrintStream;)V) Expecting to find object/array on stack 
    at java.lang.Class.getDeclaredMethods0(Native Method) 
    at java.lang.Class.privateGetDeclaredMethods(Class.java:2442) 
    at java.lang.Class.getMethod0(Class.java:2685) 
    at java.lang.Class.getMethod(Class.java:1620) 
    at sun.launcher.LauncherHelper.getMainMethod(LauncherHelper.java:492) 
    at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:484) 

Zwykle powinno to działać, ponieważ właśnie ładuję informacje z ramki stosu. Próbowałem również sprawdzić statyczne metody statyczne & (od wyjaśnienia tutaj: http://www.artima.com/insidejvm/ed2/jvm8.html), ale wciąż bez powodzenia.

Każdy pomysł, dlaczego tak się dzieje, a może pomysł rozwiązania?

Dzięki :)

EDIT:

Obecnie pracuje gdy boks się prymitywne typy (dzięki sugestii przez INT3 poniżej :)). Oto kod działa metody visitCode():

@Override 
public void visitCode() { 
    int paramLength = paramTypes.length; 

    // Create array with length equal to number of parameters 
    mv.visitIntInsn(Opcodes.BIPUSH, paramLength); 
    mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); 
    mv.visitVarInsn(Opcodes.ASTORE, paramLength); 

    // Fill the created array with method parameters 
    int i = 0; 
    for (Type tp : paramTypes) { 
     mv.visitVarInsn(Opcodes.ALOAD, paramLength); 
     mv.visitIntInsn(Opcodes.BIPUSH, i); 

     if (tp.equals(Type.BOOLEAN_TYPE)) { 
      mv.visitVarInsn(Opcodes.ILOAD, i); 
      mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;"); 
     } 
     else if (tp.equals(Type.BYTE_TYPE)) { 
      mv.visitVarInsn(Opcodes.ILOAD, i); 
      mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;"); 
     } 
     else if (tp.equals(Type.CHAR_TYPE)) { 
      mv.visitVarInsn(Opcodes.ILOAD, i); 
      mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;"); 
     } 
     else if (tp.equals(Type.SHORT_TYPE)) { 
      mv.visitVarInsn(Opcodes.ILOAD, i); 
      mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;"); 
     } 
     else if (tp.equals(Type.INT_TYPE)) { 
      mv.visitVarInsn(Opcodes.ILOAD, i); 
      mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;"); 
     } 
     else if (tp.equals(Type.LONG_TYPE)) { 
      mv.visitVarInsn(Opcodes.LLOAD, i); 
      mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;"); 
      i++; 
     } 
     else if (tp.equals(Type.FLOAT_TYPE)) { 
      mv.visitVarInsn(Opcodes.FLOAD, i); 
      mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;"); 
     } 
     else if (tp.equals(Type.DOUBLE_TYPE)) { 
      mv.visitVarInsn(Opcodes.DLOAD, i); 
      mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;"); 
      i++; 
     } 
     else 
      mv.visitVarInsn(Opcodes.ALOAD, i); 

     mv.visitInsn(Opcodes.AASTORE); 
     i++; 
    } 

    // Load id, class name and method name 
    this.visitLdcInsn(new Integer(this.methodID)); 
    this.visitLdcInsn(this.className); 
    this.visitLdcInsn(this.methodName); 

    // Load the array of parameters that we created 
    this.visitVarInsn(Opcodes.ALOAD, paramLength); 

    mv.visitMethodInsn(Opcodes.INVOKESTATIC, 
      "jalen/MethodStats", 
      "onMethodEntry", 
      "(ILjava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V"); 
    super.visitCode(); 
} 
+0

Jak mogę przywrócić argumenty do stosu, aby funkcja mogła nadal z nich korzystać? – Shay

Odpowiedz

6

Używasz aastore do przechowywania char na tablicę obiektów, które jest błąd typu. aastore powinien być używany tylko do przechowywania obiektów i tablic, prawdopodobnie dlatego błąd mówi "oczekiwany obiekt/tablica na stosie". Znaki powinny być przechowywane w tablicy znaków przy użyciu castore. Jednakże, ponieważ chcesz, aby to działało dla dowolnych podpisów, prawdopodobnie będziesz chciał umieścić typy pierwotne w obiektach, które następnie możesz użyć na przykład przy użyciu np. aastore. char powinien być zapakowany w obiekt java.lang.Character.

Powiązane problemy