Package name aus Class-File auslesen

  • Themenstarter Themenstarter Harry
  • Beginndatum Beginndatum
Status
Nicht offen für weitere Antworten.
H

Harry

Gast
Hi
ich hab da mal nen kleines Problem
ich möchte aus einem Class-file (z.B "C.\Test.class")
den Packagenamen auslesen.

MFG

Harry
 
Das Problem ist ja, dass ich an die Class garnicht rankomme,
da ich für den URLClassLoader die Classe nur über den kompletten namen (package+Classname) Laden kann

MFG

Harry
 
???:L Wieso hast du denn eine Klasse von der du nicht weißt in welchem Paket sie liegt?
 
Hallo,

wenn ich das richtig verstehe, hat er das Problem, dass da ein einsames verwaistes class-File liegt und er will
wissen, in welches Package das gehört.

Es sind dabei zwei Situationen zu unterscheiden:
A) Die Klasse "liegt nur falsch", könnte aber geladen werden, wenn sie richtig läge.
B) Die Klasse kann definitiv erst einmal nicht geladen werden (z.B. weil Abhängigkeiten nicht erfüllt sind oder die
VM nicht passt)

zu A):
Wie bereits richtig bemerkt, versagt der URLClassLoader, weil dieser nämlich merkt, dass die Klasse nicht
da ist, wo sie hingehört oder einen falschen Dateinamen hat. Mit einem Trick kann man sie dennoch laden.
Man baut dazu einen ClassLoader, der bei defineClass(name, ...) name=null übergibt. Die Namens- und Paket-Prüfung
wird so unterbunden. Sobald die Klasse geladen ist, lassen sich ihre Eigenschaften per Reflection bestimmen.
Hinweis: Es könnte Probleme mit dieser Lösung geben, wenn eine Klasse gleichen Namens bereits über den regulären ClassPath
geladen wurde.

zu B):
Das ist unangenehm. Dazu muss man den Bytecode untersuchen (siehe VM-Spec). Das ist ein wenig aufwändig ;-)

Gruß,
Karl
 
Karl hat gesagt.:
zu B):
Das ist unangenehm. Dazu muss man den Bytecode untersuchen (siehe VM-Spec). Das ist ein wenig aufwändig ;-)

Das bringt mich auf eine Idee: Einfach mal das .class-File decompilieren und reingucken.
 
Hallo,

keine schlechte Idee, aber aus einem Java-Programm heraus einen Decompiler aufrufen und den Output parsen ist irgendwie auch nicht der Hit.

Wie ich gerade festgestellt habe, ist der Beginn des Bytecodes gar nicht so schwer lesbar. Das ist eigentlich eine große Tabelle mit Einträgen, die auf andere Verweisen können.

BTW: Wusstet Ihr, dass jede Java Klasse mit CAFEBABE (HEX) beginnt? Wer ist das, muss man die kennen? 😀

Gruß,
Karl
 
Hi Karl
kannst du mir mal nen kleines beispiel geben für deinen lösungsansatz a. ???
kann mir das noch nicht richtig vorstellen wie ich das mit dem ClassLoader anstellen soll ohne den KlassenNamen.

THX

Harry

ps: jaja die Cafe ist schon ne süße 😉
 
Hallo Harry,

also ich habe mal beide Varianten probiert.

Variante 1 (Wild Loading ;-) ):


Code:
    /**
     * Diese Methode ermittelt den voll qualifizierten Klassennamen.
     * Dazu wird die Klasse kurz geladen.
     * @param sFileName Dateiname (Class File)
     * @return String mit voll qualifiziertem Klassennamen
     * @throws ClassFormatError falls Name nicht ermittelbar (nicht ladbar)
     * @throws FileNotFoundException
     * @throws IOException
     */
    public static String getNameFromClassFile(String sFileName) throws FileNotFoundException, IOException {
        FileInputStream fis = null;
        BufferedInputStream bis = null;
        String sName = null;
        try {
            fis = new FileInputStream(sFileName);
            bis = new BufferedInputStream(fis);
            int size = 0;
            int iLastRead = 0;
            ByteArrayOutputStream bos = null;
            if (bis != null) {
                byte[] buffer = new byte[10000];
                try {
                    bos = new ByteArrayOutputStream(30000);
                    while (iLastRead > -1) {
                        iLastRead = bis.read(buffer, 0, 10000);
                        if (iLastRead > -1) {
                            size = size + iLastRead;
                            bos.write(buffer, 0, iLastRead);
                        }
                    }
                    buffer = bos.toByteArray();
                } finally {
                    if (bos != null) {
                        try {
                            bos.close();
                        } catch (Throwable t) {
                        }
                    }
                }
                class UglyLoader extends ClassLoader { //so baut man normaler weise keinen ClassLoader ;-)
                    public Class defineClassNow(byte[] b, int off, int len) throws ClassFormatError {
                        // Man beachte, dass durch das erste null verhindert wird, dass
                        // irgendwelche Prüfungen bzgl. Namen oder Package vorgenommen werden
                        return defineClass(null, b, off, len);
                    }
                }
                UglyLoader ul = new UglyLoader();
                Class cl = ul.defineClassNow(buffer, 0, buffer.length);
                sName = cl.getName();
            }
        }
        finally {
            try {
                fis.close();
            }
            catch (Throwable t) {}
        }
        if (sName == null) {
            throw new ClassFormatError("Name konnte nicht ermittelt werden.");
        }
        return sName;
    }

Variante 2 (Byte Code Analyse):


Code:
    /**
     * Diese Methode ermittelt den voll qualifizierten Klassennamen.
     * Dazu wird das Class File (Binary) untersucht.
     * @param sFileName Dateiname (Class File)
     * @return String mit voll qualifiziertem Klassennamen
     * @throws ClassFormatError falls Fehler im Format
     * @throws FileNotFoundException
     * @throws IOException
     */
    private static String getNameFromClassFile(String sFileName) throws FileNotFoundException, IOException {
        FileInputStream fis = null;
        BufferedInputStream bis = null;
        DataInputStream dis = null;
        String sName = null;
        try {
            fis = new FileInputStream(sFileName);
            bis = new BufferedInputStream(fis);
            dis = new DataInputStream(bis);
            int magic = dis.readInt();
            if (!"cafebabe".equals(Integer.toHexString(magic))) {
                throw new ClassFormatError("Kein Java Class File.");
            }
            dis.readUnsignedShort(); //minor version
            dis.readUnsignedShort(); //major version
            int constant_pool_count = dis.readUnsignedShort() - 1;
            //Konstantenpool teilweise auslesen (lückenhaft, nur relevante)
            String[] constant_pool = new String[constant_pool_count];
            for (int i = 0; i < constant_pool_count; i++) {
                int tag = dis.readUnsignedByte();
                if (tag == 7) { //Klasseninformation
                    constant_pool[i] = "" + (dis.readUnsignedShort()-1); //Position des Namens merken
                }
                else if (tag == 1) { //das ist der Inhalt eines Strings (also auch der Klassenname)
                    int len = dis.readUnsignedShort();
                    byte[] bytes = new byte[len];
                    for (int k = 0; k < len; k++) {
                        bytes[k] = (byte)dis.readUnsignedByte();
                    }
                    constant_pool[i] = new String(bytes, "UTF-8");
                } //alles andere skippen
                else if (tag > 8 && tag < 13) {
                    dis.readUnsignedShort();
                    dis.readUnsignedShort();
                }
                else if (tag == 8) dis.readUnsignedShort();
                else if (tag == 3) dis.readInt();
                else if (tag == 4) dis.readFloat();
                else if (tag == 5) dis.readLong();
                else if (tag == 6) dis.readDouble();                
                else throw new ClassFormatError("Unbekannter Eintrag in Constant Pool: " + tag);
            }
            dis.readUnsignedShort(); //access flags
            int this_class_idx = dis.readUnsignedShort()-1;
            int name_idx = Integer.parseInt(constant_pool[this_class_idx]);
            sName = constant_pool[name_idx];
            if (sName != null) {
                sName = sName.replace('/', '.');
            }
            else throw new ClassFormatError("Klassenname wurde nicht gefunden!");
        }
        catch (EOFException ex) {
            throw new ClassFormatError(ex.getMessage());
        }
        catch (ArrayIndexOutOfBoundsException ex) {
            throw new ClassFormatError(ex.getMessage());
        }
        catch (NumberFormatException ex) {
            throw new ClassFormatError(ex.getMessage());
        }
        finally {
            try {
                fis.close();
            }
            catch (Throwable t) {}
        }
        return sName;
    }

Ich würde Variante 2 vorziehen, denn sie ist allgemeiner und verlässt sich nicht auf das ClassLoading.

Viel Erfolg!

Gruß,
Karl
 
Hallo,

@Bleiglanz
stimmt, nur ist javap wie so vieles nicht Bestandteil des JRE ;-)

Gruß,
Karl
 
Status
Nicht offen für weitere Antworten.

Zurück
Oben