2016-01-19 9 views
6

Ich arbeite an a fork von FernFlower von Jetbrains und ich habe kleinere Verbesserungen hinzugefügt.JVM Bytecode, wie finde ich den Typ der lokalen Variablen?

Eine Sache, die mich wirklich nervt über FernFlower ist, dass es den Typ der lokalen Variablen basierend auf seinem Wert in bpush/Spush usw. basiert. Jode und Procyon finden irgendwie eine Möglichkeit, den ursprünglichen Wert einer lokalen Variablen zu finden.

Hier ist der ursprüngliche Quellcode.

public static void main(String[] args) throws Exception { 
    int hello = 100; 
    char a2 = 100; 
    short y1o = 100; 
    int hei = 100; 

    System.out.println(a2+" "+y1o+", "+hei+", "+hello); 
} 

Wenn mit FernFlower dekompilierten, gibt er dies:

public static void main(String[] args) throws Exception { 
    byte hello = 100; 
    char a2 = 100; 
    byte y1o = 100; 
    byte hei = 100; 
    System.out.println(a2 + " " + y1o + ", " + hei + ", " + hello); 
} 

Aber wenn sie mit Jode/Procyon dekompilierten gibt es die ursprünglichen lokalen Variablen-Typen:

public static void main(String[] args) 
    throws Exception 
    { 
    int hello = 100; 
    char a2 = 'd'; 
    short y1o = 100; 
    byte hei = 100; 

    System.out.println(a2 + " " + y1o + ", " + hei + ", " + hello); 
    } 

ich mich gefragt, wie ist dies möglich, weil ich dachte, dass keine lokale Variable Typ Informationen zur Kompilierzeit gespeichert ist? Wie kann ich die gleiche Funktionalität zu FernFlower hinzufügen?

+0

Ist es möglich, dass die Dekompiler Freiheiten bei der Entscheidung, welchen lokalen Typ zu verwenden (z. B. "Byte" gegenüber "Int") genommen haben? –

+3

Sie sollten in vielen Fällen aus den Anweisungen, die diese Slots verwenden, schließen können. Sehen Sie sich den Byte-Code selbst an, insbesondere die verschiedenen Überladungen von 'String.valueOf()', die aufgerufen werden. In diesem Fall scheint es auch einige Debug-Informationen zu geben, ansonsten wären die Namen auch nicht verfügbar gewesen. – EJP

+0

Ohne Debugging-Informationen würde es keine LocalVariableTable im kompilierten Code geben, so dass ich nicht sehe, wie der ursprüngliche deklarierte Typ abgeleitet werden kann.

Antwort

2

Also nach dem Suchen und Debuggen fand ich, dass aus irgendeinem Grund FernFlower entscheidet, einige der Daten in der LocalVariableTable vollständig zu ignorieren.

ist hier Farne Original-Code zum Dekodieren der LocalVariableTable:

@Override 
public void initContent(ConstantPool pool) throws IOException { 
    DataInputFullStream data = stream(); 

    int len = data.readUnsignedShort(); 
    if (len > 0) { 
     mapVarNames = new HashMap<Integer, String>(len); 
     mapVarTypes = new HashMap<Integer, String>(len); 
     for (int i = 0; i < len; i++) { 
      int start = data.readUnsignedShort(); 
      int end = start + data.readUnsignedShort(); 
      int nameIndex = data.readUnsignedShort(); 
      int typeIndex = data.readUnsignedShort(); 
      int varIndex = data.readUnsignedShort(); 
      mapVarNames.put(varIndex, pool.getPrimitiveConstant(nameIndex).getString()); 
      mapVarTypes.put(varIndex, pool.getPrimitiveConstant(typeIndex).getString()); 
     } 
    } else { 
     mapVarNames = Collections.emptyMap(); 
     mapVarTypes = Collections.emptyMap(); 
    } 
} 

Es gibt nun den gleichen Code wie Jode mit der richtigen:

public void initContent(ConstantPool pool) throws IOException { 
    DataInputFullStream data = stream(); 

    int len = data.readUnsignedShort(); 
    if (len > 0) { 
     mapVarNames = new HashMap<Integer, String>(len); 
     for (int i = 0; i < len; i++) { 
      data.discard(4); 
      int nameIndex = data.readUnsignedShort(); 
      data.discard(2); 
      int varIndex = data.readUnsignedShort(); 
      mapVarNames.put(varIndex, pool.getPrimitiveConstant(nameIndex).getString()); 
     } 
    } else { 
     mapVarNames = Collections.emptyMap(); 
    } 
} 

Wenn Sie Informationen wünschen Sie Folgendes hinzufügen müssen eingeben Variablentypen :)

Ich frage mich, warum FernFlower diese Informationen ignorieren wollte.

+0

Also ignoriert dieser Code die Gültigkeitsbereiche der Variablen vollständig? Das funktioniert wahrscheinlich nur für die einfachsten Codebeispiele ... – Holger

+0

Könnten Sie Ihre Antwort ein wenig erweitern? Ich bin neu in Bytecode. –

+0

Die Begriffe 'start' und' end' beschreiben einen Bereich innerhalb des Bytecodes, in dem die Variable gültig ist. Außerhalb dieses Bereichs kann dieselbe "varIndex" -Nummer für eine andere Variable verwendet werden, die einen anderen Namen und Typ haben kann. Da Sie den 'varIndex' als Map-Schlüssel verwenden, zeichnen Sie nur die zuletzt gefundene Variable für jeden Index auf. Denken Sie an Code wie 'für (int ix = 0; ix <10; ix ++) {/ * loop body * /} für (String str: list) {/ * ein anderer loop body * /}'.Hier haben "ix" und "str" ​​disjunkte Bereiche und können denselben Var-Index im Byte-Code verwenden. – Holger

4

.class Dateien enthalten optional ein Attribut 'LocalVariableTable' für Debugging-Zwecke. Wenn Sie den Befehl javap -l <Class>.class aufrufen, können Sie die Daten sehen, wenn sie vorhanden sind.

Verwandte Themen