Tool mit dem man die Major version im Bytecode patchen kann..?

sirbender

Top Contributor
Hi,

ich experimentiere gerne ein bischen rum - im Moment mit dem OpenJDK - und da waere es im Moment sehr hilfreich wenn ich zu Experimentierzwecken einen Haufen .class Dateien in version 53 (Java9) auf version 52 (Java8) patchen und dann mit einem Java8 ausfuehren koennte. Die Sourcen (die ich groesstenteils eh nicht habe) zu kompilieren waere sehr problematisch.

Koennte ihr mir weiterhelfen? Gibt es ein Tool, dass dies im Batch kann? Klar koennte ich jetzt ein paar Tage ASM oder BCEL oder so studieren und es per Hand machen. Vielleicht habt ihr ja einen Tip weil es schon existiert.
 
https://en.wikipedia.org/wiki/Java_class_file

Quick & Dirty:

Java:
import java.io.RandomAccessFile;
import java.io.IOException;

public class Patch {
    public static void main(String[] args) {
        if (args.length < 2) {
            System.err.println("Usage: <class-file-version> <class-file> ...");
            System.exit(1);
        }

        try {
            int version = Integer.parseInt(args[0]);
            for (int i = 1; i < args.length; i++) {
                patch(version, args[I]);
            }
        } catch (NumberFormatException ex) {
            System.err.printf("%s is not a valid class-file-version\n", args[0]);
            System.exit(2);
        }
    }

    private static void patch(int version, String filename) {
        try(RandomAccessFile raf = new RandomAccessFile(filename, "rw")) {
            byte[] header = new byte[8];
            raf.readFully(header);
            if (magic(header)) {
                header[7] = (byte)(version & 0xff);
                raf.seek(0);
                raf.write(header);
            } else {
                System.err.printf("Ingoring '%s': not a valid Java class file.\n", filename);
            }
        } catch (IOException ex) {
            System.err.printf("Got exception while processing file '%s': ", filename);
            ex.printStackTrace();
        }
    }

    private static boolean magic(byte[] data) {
        return data.length > 3 && data[0] == (byte)0xca && data[1] == (byte)0xfe &&
                data[2] == (byte)0xba && data[3] == (byte)0xbe;
    }
}

java Patch 52 build/*.class macht aus den class-Dateien im Verzeichnis build Java 8 class files.
 
Vielen Dank! Scheint zu funktionieren. Super!

Apropos, falls du es weisst:
Laut dem Wikipedia-Artikel zum class file layout werden ja zwei bytes fuer die Major Version vorgesehen. Warum ist man im Wikipedia-Artikel oder auch hier https://docs.oracle.com/javase/specs/jvms/se6/html/ClassFile.doc.html
eigentlich nicht erwaehnt, dass nur das byte an Position 7 genutzt wird und nicht 6 und 7 zusammen? Wozu ist 6 da? Fuer die Zukunft, falls groessere Zahlen noetig werden? Und wie werden die beiden bytes (Position 6 und 7) dann zu einer Major Version kombiniert?
 
Täusche Dich nicht, die Spec ist da ganz genau: u2 major_version;

u2 steht für eine eine vorzeichenlose, 2 Byte (also 16 Bit) breite Ganzzahl. Da mehr als ein Byte verwendet wird, ist die Reihenfolge wichtig. Lt. Spec werden die Werte in Big-Endian abgespeichert, d. h. das höherwertige Byte kommt zuerst.

Für Versionsnummern < 256 ist das höherwertige Byte immer 0 - daher habe ich nur das 7. Byte im Header gesetzt 🙂 Korrekt wäre es, den int auf zwei Bytes abzubilden:
Java:
header[6] = (byte)((version >> 8) & 0xff);
header[7] = (byte)(version & 0xff);

D. h. das höherwertige Byte (most significant byte - MSB) ist das Ergebnis der Division der Versionsnummer durch 256 ohne Rest. Das niederwertige Byte (LSB) ist der Rest. Umgekehrt: Versionsnummer = MSB * 256 + LSB
 
Bedenke auch, dass du natürlich nicht uneingeschränkt jede Java 9 Classdatei durch Anpassen der Classfile-Version auf einem JRE 8 ausführen kannst, da eventuell wichtige APIs fehlen, die erst in 9 eingeführt wurden, wie z.B. in der Stream API, der Process API, Variable Handles sowie Immutable Sets; oder tatsächlich Sprachfeatures nicht unterstützt werden, die in Java 9 hinzukamen, wie etwa Interface Private Methods.
 
Bedenke auch, dass du natürlich nicht uneingeschränkt jede Java 9 Classdatei durch Anpassen der Classfile-Version auf einem JRE 8 ausführen kannst, da eventuell wichtige APIs fehlen, die erst in 9 eingeführt wurden, wie z.B. in der Stream API, der Process API, Variable Handles sowie Immutable Sets; oder tatsächlich Sprachfeatures nicht unterstützt werden, die in Java 9 hinzukamen, wie etwa Interface Private Methods.

Machmal aendert sich das class-Format ja wirklich...zum Beispiel wenn neue keywords dazukommen (invokedynamic zum Beispiel). Irre ich mich, oder ist der Versionssprung (Major-Zahl) manchmal eigentlich auch unnoetig weil sich nichts geaendert hat?
 
"The class file version has been changed from 53 (or 44 + 9) to 54 (44 +10), even though JDK 10 did not introduce other changes to the class file format.

The OpenJDK community has adopted a new time-based release model, in which major releases of the Java platform occur every 6 months. As a consequence, it is anticipated that class file changes will also occur more rapidly. To ensure predictability for the tooling that processes class file bytes, the class file version will be incremented every major release even if there are no other changes to the class file format. In effect, the class file version will be 44 + $FEATURE, where $FEATURE is the feature-release counter (previously referred to as the major number) of the Java SE Platform and the JDK version string." (http://www.oracle.com/technetwork/java/javase/10-relnote-issues-4108729.html#Remaining)
 
Die Classfile-Version soll wohl nicht nur Änderungen im Classfile-Format reflektieren, wie etwa:
- Neue Opcodes (z.B. invokedynamic in 51.0)
- Neue Attribut-Sektionen (z.B. Stackmap Frames in 50.0)
- Neue Constant Pool Einträge (z.B. Methodhandles in 51.0 oder dynamic constants in 55.0)
- Erweiterung von möglichen Constant Pool Argumenten für Opcodes (z.B. Classenreferenzen für LDC in 50.0)

sondern soll auch die Möglichkeiten der Java-Plattform, z.B. der Classlibrary widerspiegeln. Also, welche Klassen sind überhaupt vorhanden. Zusätzlich soll sie auch die operationalen Semantiken für zwar classfile-format-syntaktisch nicht neue aber von der Java-Version neu unterstützte Sprach-Features wie etwa synthetische Bridge-Methoden für Covariant Return Types (in 49.0 bzw. Java 1.5) reflektieren.
Zusätzlich dazu gibt es auch noch neue, sowohl Java- als auch JVM-spezifische Features, wie eben Interface Private Methods in 53.0 bzw. Java 9.
 
Puhhh...Java wird immer komplexer. Wer heute einsteigt hat deutlich groessere Probleme Code zu lesen ohne sich vorher ausgiebig einzuarbeiten - wobei die IDEs heutzutage viel helfen. Ich liebe viele der neuen Features, aber insgesamt koennte das Java auf lange Sicht auch schaden. Geht ja nicht nur um Neulinge sondern auch wie stabil die Tools um die Sprache rum sind. Komplexitaet ist da nicht immer foerderlich.
Der ewige Stillstand seit vielen Jahren war natuerlich auch nicht gut. Ich will nur nicht dahin kommen wo C++ ist...das jede erdenkliche Idee und jeder Sonderwunsch umgesetzt wird.
 
Puhhh...Java wird immer komplexer. Wer heute einsteigt hat deutlich groessere Probleme Code zu lesen ohne sich vorher ausgiebig einzuarbeiten - wobei die IDEs heutzutage viel helfen.
Ehrlich gesagt - ich finde aktuelles Java nicht schwieriger zu lesen als alte Versionen, in vielen Fällen sogar deutlich einfacher.


Ich würde meinen Einstieg eher als „heut“ als als „gestern“ bezeichnen, in Java-Versionen gerechnet, und habe so einige Einsteiger mitbekommen.
In alten Versionen ist einfach viel mehr Boiler-Plate. Grad wenns um sowas wie irgendwas generisches mit Closeable oder in Richtung funktional geht.
 
##und da waere es im Moment sehr hilfreich wenn ich zu Experimentierzwecken einen Haufen .class Dateien in version 53 (Java9) auf version 52 (Java8) patchen und dann mit einem Java8 ausfuehren koennte. Die Sourcen (die ich groesstenteils eh nicht habe) zu kompilieren waere sehr problematisch.

Also wenn du die Sources nicht hast, ist erst einmal die Frage, ob du das darfst.
Wenn das Projekt nicht Open Source ist und du keine Bearbeitungsrechte besitzt, ist das ganze schon eine Urheberrechtsverletzung.

Desweiteren ist dein Vorgehen sehr fragwürdig:
Du gehst also davon aus, dass sich abgesehen von der Versionsnummer nichts im Bytecode ändert?
Ich glaube da denkst du falsch. Gerade Java 9 ist eine Version mit vielen Kompatibilitätsproblemen (Jigsaw usw.), da ist ein einfaches überschreiben der Versionsnummer in der Classdatei sicherlich keine gute Idee.
 

Neue Themen


Zurück
Oben