Obsah
1. Pohled pod kapotu JVM (3.část – pokračování popisu struktury souborů .class)
2. Definice příznaků třídy či rozhraní
4. Jméno třídy/rozhraní/výčtu/anotace a jméno nadřazené třídy
5. Jména tříd a nadřazených tříd u vybraných souborů .class pocházejících z balíčku java.lang
6. Seznam implementovaných rozhraní
1. Pohled pod kapotu JVM (3.část – pokračování popisu struktury souborů .class)
V předchozích dvou částech seriálu o programovacím jazyce Java i o vlastnostech virtuálního stroje tohoto jazyka jsme si podrobně popsali strukturu constant poolu, jenž tvoří nedílnou součást každého bajtkódu, tj. každého souboru s koncovkou .class generovaného překladačem Javy (většinou programem javac). Pro výpis obsahu constant poolu byl použit relativně jednoduchý nástroj naprogramovaný v céčku, který budeme v dnešním článku postupně rozšiřovat. Připomeňme si, že v constant poolu jsou umístěny všechny řetězcové konstanty (literály), celočíselné i reálné konstanty, jména tříd, signatury metod a konečně i signatury atributů. Signatura metody se skládá z jejího jména, zakódovaného návratového typu i zakódovaných typů všech argumentů metody. U signatury atributu je vedle jeho jména uložen (opět v zakódované podobě) i jeho datový typ. V dnešním článku si alespoň zhruba popíšeme další údaje, které je možné v souborech .class nalézt.
Celkovou strukturu bajtkódu jsme si již popsali. Z minula již víme, co obsahuje hlavička souborů .class a známe i způsob uložení informací v constant poolu. Další údaje v bajtkódu jsou však neméně důležité. Jedná se především o příznaky třídy či rozhraní, informace o jméně třídy i nadtřídy, seznam implementovaných rozhraní, atributy třídy a konečně i o nejdůležitější typ údajů – instrukce tvořící těla jednotlivých metod:
+-----------------------------------+ | Hlavička | +-----------------------------------+ | Constant pool | | ..... | | ..... | | ..... | +-----------------------------------+ | Definice příznaků třídy/rozhraní | +-----------------------------------+ | Jméno třídy a nadtřídy | +-----------------------------------+ | Implementovaná rozhraní | | ..... | +-----------------------------------+ | Atributy třídy | | ..... | +-----------------------------------+ | Kódy jednotlivých metod | | ..... | | ..... | | ..... | +-----------------------------------+ | Další metadata třídy | +-----------------------------------+
2. Definice příznaků třídy či rozhraní
Pojďme si tedy podrobněji popsat další údaje, z nichž je sestaven každý javovský bajtkód. Ihned za posledním záznamem uloženým v constant poolu se nachází další důležitá informace. Jedná se o příznaky přiřazené ke třídě, k výčtovému typu, k rozhraní či k anotaci, z níž soubor .class vznikl. Všechny tyto příznaky jsou společně zakódované ve dvojici bajtů, jejichž pořadí opět (stejně jako u jiných typů záznamů v bajtkódu) odpovídá pořadí big endian. V následující tabulce jsou vypsány všechny příznaky, s nimiž se můžeme setkat v různých verzích bajtkódu. Prvních pět příznaků se používá již od prvních verzí JVM, další příznaky byly postupně přidávány s tím, jak se rozšiřovala syntaxe a samozřejmě i sémantika programovacího jazyka Java. Jedná se především o příznak ACC_ANNOTATION, který se objevil společně s anotacemi (Java 5) a taktéž o příznak ACC_ENUM označující výčtový typ (enumeration):
# | Jméno příznaku | Bitová maska | Význam příznaku |
---|---|---|---|
1 | ACC_PUBLIC | 0×0001 | veřejná třída či rozhraní |
2 | ACC_FINAL | 0×0010 | finální třída (nelze z ní vytvářet další odvozené třídy) |
3 | ACC_SUPER | 0×0020 | mění význam volání metod nadtřídy pomocí instrukce invokespecial |
4 | ACC_INTERFACE | 0×0200 | příznak označující, že se jedná o rozhraní |
5 | ACC_ABSTRACT | 0×0400 | abstraktní třída (nelze vytvořit její přímou instanci) |
6 | ACC_SYNTHETIC | 0×1000 | obvykle se jedná o třídu generovanou přímo překladačem (neexistuje k ní zdrojový kód) |
7 | ACC_ANNOTATION | 0×2000 | jedná se o anotaci |
8 | ACC_ENUM | 0×4000 | jedná se o výčet |
Vzhledem ke vztahu mezi výše popsanými příznaky a sémantikou programovacího jazyka Java je zřejmé, že zdaleka ne všechny kombinace příznaků jsou korektní. Například platí, že příznak ACC_INTERFACE by měl být použit společně s příznakem ACC_ABSTRACT. Podobně platí, že pokud se jedná o anotaci, tj. je nastaven příznak ACC_ANNOTATION, měl by být společně nastaven příznak ACC_INTERFACE. Taktéž je pochopitelné, že příznaky ACC_FINAL a ACC_ABSTRACT nesmí být nastaveny společně – nelze totiž vytvořit finální a současně i abstraktní třídu. Speciální význam má příznak ACC_SUPER, který mění význam instrukce invokespecial. O významu této instrukce se zmíníme v následující části tohoto seriálu.
3. Úprava demonstračního dekompileru a příznaky nastavené u vybraných souborů .class pocházejících z balíčku java.lang
Přidání výpisu příznaků třídy, rozhraní, anotace či výčtu do pomocného nástroje popsaného v předchozí části tohoto seriálu je velmi snadné, protože postačuje ihned po zpracování constant poolu přečíst z bajtkódu dvojici bajtů nacházejících se za posledním záznamem uloženým v constant poolu a následně správně interpretovat jednotlivé bity uložené v této dvojici bajtů. Do programu tedy stačí doplnit jeden výčet (skupinu celočíselných konstant) a jednu novou funkci:
/* * Priznaky tridy ci rozhrani. */ enum { ACC_PUBLIC = 0x0001, /* verejna trida/rozhrani */ ACC_FINAL = 0x0010, /* finalni trida */ ACC_SUPER = 0x0020, /* semantika instrukce invokespecial */ ACC_INTERFACE = 0x0200, /* rozliseni trida/rozhrani */ ACC_ABSTRACT = 0x0400, /* abstraktni trida */ ACC_SYNTHETIC = 0x1000, /* synteticka trida */ ACC_ANNOTATION = 0x2000, /* anotace */ ACC_ENUM = 0x4000, /* vycet */ };
Tyto konstanty jsou použity v nové funkci process_class_flags:
/* * Nacteni a vypis atributu tridy ci rozhrani. */ void process_class_flags(FILE *fin) { uint16_t flags; flags = read_two_bytes(fin); printf("\nClass/interface attributes: 0x%04x\n", flags); if (flags & ACC_PUBLIC) puts(" ACC_PUBLIC"); if (flags & ACC_FINAL) puts(" ACC_FINAL"); if (flags & ACC_SUPER) puts(" ACC_SUPER"); if (flags & ACC_INTERFACE) puts(" ACC_INTERFACE"); if (flags & ACC_ABSTRACT) puts(" ACC_ABSTRACT"); if (flags & ACC_SYNTHETIC) puts(" ACC_SYNTHETIC"); if (flags & ACC_ANNOTATION) puts(" ACC_ANNOTATION"); if (flags & ACC_ENUM) puts(" ACC_ENUM"); }
Zdrojový text tímto způsobem upraveného dekompileru souborů .class můžete nalézt zde, popř. si můžete prohlédnout i zdrojový kód se zvýrazněnou syntaxí.
Ukažme si nyní výpis příznaků pro standardní .class soubory získané z archivu rt.jar, který většinou můžete nalézt v adresáři /usr/lib/jvm/java***/jre/lib. Připomeňme si, že v archivu rt.jar se nachází všechny třídy tvořící standardní API Javy:
java.lang.Object:
Class/interface attributes: 0x0021 ACC_PUBLIC ACC_SUPER
java.lang.String:
Class/interface attributes: 0x0031 ACC_PUBLIC ACC_FINAL ACC_SUPER
java.lang.Number:
Class/interface attributes: 0x0421 ACC_PUBLIC ACC_SUPER ACC_ABSTRACT
java.lang.Integer:
Class/interface attributes: 0x0031 ACC_PUBLIC ACC_FINAL ACC_SUPER
java.lang.Comparable:
Class/interface attributes: 0x0601 ACC_PUBLIC ACC_INTERFACE ACC_ABSTRACT
java.lang.Override:
Class/interface attributes: 0x2601 ACC_PUBLIC ACC_INTERFACE ACC_ABSTRACT ACC_ANNOTATION
java.lang.Exception:
Class/interface attributes: 0x0021 ACC_PUBLIC ACC_SUPER
java.lang.NullPointerException:
Class/interface attributes: 0x0021 ACC_PUBLIC ACC_SUPER
java.lang.Class:
Class/interface attributes: 0x0031 ACC_PUBLIC ACC_FINAL ACC_SUPER
4. Jméno třídy/rozhraní/výčtu/anotace a jméno nadřazené třídy
Ihned za dvojicí bajtů, v nichž jsou uloženy příznaky třídy, výčtu, rozhraní či anotace, se nachází další důležitý údaj. Tento údaj reprezentuje jméno třídy, rozhraní, anotace či výčtu, ke které přísluší daný soubor .class. Ve skutečnosti však na tomto místě bajtkódu není uložen přímo řetězec s názvem příslušného jazykového prvku, ale pouze dvoubajtový odkaz na záznam umístěný v constant poolu. Musí se přitom vždy jednat o odkaz na záznam typu CONSTANT_Class, což je samozřejmě náležitě kontrolováno již při načítání bajtkódu do virtuálního stroje Javy. Připomeňme si, že záznam typu CONSTANT_Class obsahuje odkaz (index) na záznam typu CONSTANT_Utf8, který již obsahuje kýženou řetězcovou konstantu (literál) se jménem třídy. Poznamenejme, že jméno třídy je uvedeno v plné podobě, tj. i se jménem balíčku, v němž se třída nachází. Pro oddělení jednotlivých částí „cesty“ ke třídě je použit znak lomítka, který lze pro účely tisku změnit na znak tečky.
Velmi podobný význam má i další dvojice bajtů uložená v bajtkódu. Tento údaj obsahuje informaci o jménu nadřazené třídy (super class) a opět se musí jednat o index do constant poolu, jenž odkazuje na záznam typu CONSTANT_Class (není snad nutné připomínat, že při načítání bajtkódu je jeho korektnost opět kontrolována). Mezi oběma zmíněnými názvy je však jeden rozdíl – zatímco název třídy musí být vždy korektně vyplněn, v případě názvu nadřazené třídy je možné, aby tento údaj obsahoval nulu, ovšem pouze v tom speciálním případě, že bajtkód byl získán překladem třídy Object, která nemá žádnou nadtřídu, což vyplývá ze samotné specifikace programovacího jazyka Java. Ostatní třídy, včetně tříd bez explicitně specifikované nadtřídy, mají tento údaj vyplněn.
5. Jména tříd a nadřazených tříd u vybraných souborů .class pocházejících z balíčku java.lang
Úprava našeho demonstračního dekompileru bajtkódu takovým způsobem, aby dokázal zobrazit jméno třídy (atributu, výčtu…) a jméno příslušné nadtřídy, není vůbec složitá, neboť postačuje načíst obě dvojice bajtů a následně je použít jako indexy do constant poolu, jehož obsah je uložen v poli pool_entries. Funkce pro výpis jména třídy (či jiného jazykového prvku) vypadá následovně:
/* * Nacteni a vypis jmena tridy. */ void process_class_name(FILE *fin) { /* nacist index z bajtkodu */ uint16_t index = read_two_bytes(fin); /* v cecku se indexuje od 0, v constant poolu od 1 */ index--; printf("\nClass name is stored in constant pool #%d\n", index); PoolEntry pool_entry = pool_entries[index]; print_class_info(&pool_entry); }
Výpis jména nadřazené třídy je nepatrně složitější, neboť je zapotřebí detekovat speciální případ třídy Object:
/* * Nacteni a vypis jmena nadrazene tridy. */ void process_superclass_name(FILE *fin) { /* nacist index z bajtkodu */ uint16_t index = read_two_bytes(fin); /* test na specialni pripad */ if (index == 0) { printf("\nIt's a class without super class!\n"); } else { /* v cecku se indexuje od 0, v constant poolu od 1 */ index--; printf("\nSuper class name is stored in constant pool #%d\n", index); PoolEntry pool_entry = pool_entries[index]; print_class_info(&pool_entry); } }
Zdrojový text třetí verze demonstračního dekompileru souborů .class můžete nalézt zde, popř. si můžete prohlédnout i zdrojový kód se zvýrazněnou syntaxí.
Opět si můžeme vyzkoušet, jaké údaje získáme analýzou .class souborů ze standardního balíčku java.lang:
java.lang.Object:
Class name is stored in constant pool #17 Class 74 java/lang/Object It's a class without super class!
java.lang.String:
Class name is stored in constant pool #39 Class 414 java/lang/String Super class name is stored in constant pool #116 Class 493 java/lang/Object
java.lang.Number:
Class name is stored in constant pool #2 Class 34 java/lang/Number Super class name is stored in constant pool #3 Class 35 java/lang/Object
java.lang.Integer:
Class name is stored in constant pool #32 Class 231 java/lang/Integer Super class name is stored in constant pool #69 Class 258 java/lang/Number
java.lang.Comparable:
Class name is stored in constant pool #0 Class 10 java/lang/Comparable Super class name is stored in constant pool #1 Class 11 java/lang/Object
java.lang.Override:
Class name is stored in constant pool #0 Class 14 java/lang/Override Super class name is stored in constant pool #1 Class 15 java/lang/Object
java.lang.Exception:
Class name is stored in constant pool #4 Class 32 java/lang/Exception Super class name is stored in constant pool #5 Class 33 java/lang/Throwable
java.lang.NullPointerException:
Class name is stored in constant pool #2 Class 19 java/lang/NullPointerException Super class name is stored in constant pool #3 Class 20 java/lang/RuntimeException
java.lang.Class:
Class name is stored in constant pool #26 Class 681 java/lang/Class Super class name is stored in constant pool #237 Class 882 java/lang/Object
Pokud pro jednoduchost nebudeme brát do úvahy implementovaná rozhraní, lze celou objektovou hierarchii všech tříd zrekonstruovat pomocí dvojice údajů popsaných v předchozích odstavcích.
6. Seznam implementovaných rozhraní
Z informací, které jsme až doposud z bajtkódu získali, je již možné částečně zrekonstruovat hlavičku libovolné třídy, protože již známe její příznaky (public, abstract, final atd.) a samozřejmě i její plné jméno i jméno nadřazené třídy (včetně uvedení balíčku). Ovšem ve skutečnosti může třída implementovat i libovolný počet rozhraní, což je další informace, kterou je nutné nějakým způsobem přečíst z bajtkódu. To je ve skutečnosti velmi jednoduché, neboť ihned za dvojicí indexů odkazujících na jméno třídy a nadřazené třídy se v bajtkódu nachází šestnáctibitové slovo s počtem všech implementovaných rozhraní (čistě teoreticky jich tedy může být až 65535) a ihned za tímto údajem se nachází n šestnáctibitových indexů do constant poolu, kde n odpovídá počtu implementovaných rozhraní (toto pole indexů může samozřejmě být i prázdné, pokud třída žádné rozhraní neimplementuje, tj. n==0). Podobně jako u jména třídy i jména nadtřídy, musí každý index v poli s rozhraními odkazovat na záznam typu CONSTANT_Class, který sám obsahuje odkaz na záznam typu CONSTANT_Utf8, tj. na vlastní řetězec se jménem rozhraní.
7. (Prozatím) finální verze dekompileru souborů .class – doplnění o výpis všech implementovaných rozhraní
Doplnění našeho demonstračního dekompileru bajtkódu o výpis všech implementovaných rozhraní je obstaráno funkcí process_all_implemented_interfaces(), kterou je nutné zavolat ihned po funkci process_superclass_name() (přesněji řečeno se nemusí tyto funkce volat ihned po sobě, mezi voláním těchto funkcí však nesmí dojít ke čtení údajů z bajtkódu, protože by došlo k posunu interně udržovaného offsetu posledně načteného bajtu). Funkce process_all_implemented_interfaces je velmi jednoduchá – pouze přečte počet indexů v tabulce implementovaných rozhraní a posléze celou tabulku zpracuje a vytiskne příslušné referencované hodnoty získané z constant poolu:
/* * Nacteni a vypis vsech implementovanych rozhrani */ void process_all_implemented_interfaces(FILE *fin) { int i; /* nacist pocet implementovanych rozhrani */ uint16_t interfaces = read_two_bytes(fin); /* zakladni informace pro uzivatele */ printf("\nNumber of implemented interfaces: %d\n", interfaces); /* vypis vsech rozhrani */ for (i = 0; i < interfaces; i++) { /* nacist index do constant poolu */ int index = read_two_bytes(fin); /* v cecku se indexuje od 0, v constant poolu od 1 */ index--; /* ziskat a vypsat pozadovanou informaci */ PoolEntry pool_entry = pool_entries[index]; print_class_info(&pool_entry); putchar('\n'); } }
Zdrojový text již čtvrté varianty dekompileru souborů .class můžete nalézt zde, popř. si můžete prohlédnout i zdrojový kód se zvýrazněnou syntaxí.
8. Výpis všech implementovaných rozhraní u vybraných souborů .class pocházejících z balíčku java.lang
S využitím prozatím finální verze demonstračního dekompileru bajtkódu je možné snadno zjistit seznam všech rozhraní implementovaných vybranými třídami ze standardního balíčku java.lang:
java.lang.Object:
Number of implemented interfaces: 0
java.lang.String:
Number of implemented interfaces: 3 Class 494 java/io/Serializable Class 495 java/lang/Comparable Class 496 java/lang/CharSequence
java.lang.Number:
Number of implemented interfaces: 1 Class 36 java/io/Serializable
java.lang.Integer:
Number of implemented interfaces: 1 Class 259 java/lang/Comparable
java.lang.Comparable:
Number of implemented interfaces: 0
java.lang.Override:
Number of implemented interfaces: 1 Class 16 java/lang/annotation/Annotation
java.lang.Exception:
Number of implemented interfaces: 0
java.lang.NullPointerException:
Number of implemented interfaces: 0
java.lang.Class:
Number of implemented interfaces: 4 Class 923 java/io/Serializable Class 924 java/lang/reflect/GenericDeclaration Class 925 java/lang/reflect/Type Class 926 java/lang/reflect/AnnotatedElement
9. Odkazy na Internetu
- The JavaTM Virtual Machine Specification, Second Edition
http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html - The class File Format
http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html - javap – The Java Class File Disassembler
http://docs.oracle.com/javase/1.4.2/docs/tooldocs/windows/javap.html - javap-java-1.6.0-openjdk(1) – Linux man page
http://linux.die.net/man/1/javap-java-1.6.0-openjdk - Using javap
http://www.idevelopment.info/data/Programming/java/miscellaneous_java/Using_javap.html - Examine class files with the javap command
http://www.techrepublic.com/article/examine-class-files-with-the-javap-command/5815354 - BCEL Home page
http://commons.apache.org/bcel/ - BCEL Manual
http://commons.apache.org/bcel/manual.html - Byte Code Engineering Library (Wikipedia)
http://en.wikipedia.org/wiki/BCEL - Java programming dynamics, Part 7: Bytecode engineering with BCEL
http://www.ibm.com/developerworks/java/library/j-dyn0414/ - Bytecode Engineering
http://book.chinaunix.net/special/ebook/Core_Java2_Volume2AF/0131118269/ch13lev1sec6.html - BCEL Tutorial
http://www.smfsupport.com/support/java/bcel-tutorial!/ - ASM Home page
http://asm.ow2.org/ - Seznam nástrojů využívajících projekt ASM
http://asm.ow2.org/users.html - ObjectWeb ASM (Wikipedia)
http://en.wikipedia.org/wiki/ObjectWeb_ASM - Java Bytecode BCEL vs ASM
http://james.onegoodcookie.com/2005/10/26/java-bytecode-bcel-vs-asm/ - Bytecode Outline plugin for Eclipse (screenshoty + info)
http://asm.ow2.org/eclipse/index.html - aspectj (Eclipse)
http://www.eclipse.org/aspectj/ - Aspect-oriented programming (Wikipedia)
http://en.wikipedia.org/wiki/Aspect_oriented_programming - AspectJ (Wikipedia)
http://en.wikipedia.org/wiki/AspectJ - EMMA: a free Java code coverage tool
http://emma.sourceforge.net/ - Cobertura
http://cobertura.sourceforge.net/ - FindBugs
http://findbugs.sourceforge.net/ - GNU Classpath
www.gnu.org/s/classpath/ - Java VMs Compared
http://bugblogger.com/java-vms-compared-160/ - JSRs: Java Specification Requests – JSR 223: Scripting for the Java Platform
http://www.jcp.org/en/jsr/detail?id=223 - Scripting for the Java Platform
http://java.sun.com/developer/technicalArticles/J2SE/Desktop/scripting/ - Scripting for the Java Platform (Wikipedia)
http://en.wikipedia.org/wiki/Scripting_for_the_Java_Platform - Java Community Process
http://en.wikipedia.org/wiki/Java_Specification_Request - Java HotSpot VM Options
http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html - Great Computer Language Shootout
http://c2.com/cgi/wiki?GreatComputerLanguageShootout - Java performance
http://en.wikipedia.org/wiki/Java_performance - Trying the prototype
http://mail.openjdk.java.net/pipermail/lambda-dev/2010-August/002179.html - Better closures (for Java)
http://blogs.sun.com/jrose/entry/better_closures - Lambdas in Java: An In-Depth Analysis
http://www.infoq.com/articles/lambdas-java-analysis - Class ReflectiveOperationException
http://download.java.net/jdk7/docs/api/java/lang/ReflectiveOperationException.html - Proposal: Indexing access syntax for Lists and Maps
http://mail.openjdk.java.net/pipermail/coin-dev/2009-March/001108.html - Proposal: Elvis and Other Null-Safe Operators
http://mail.openjdk.java.net/pipermail/coin-dev/2009-March/000047.html - Java 7 : Oracle pushes a first version of closures
http://www.baptiste-wicht.com/2010/05/oracle-pushes-a-first-version-of-closures/ - Groovy: An agile dynamic language for the Java Platform
http://groovy.codehaus.org/Operators - Better Strategies for Null Handling in Java
http://www.slideshare.net/Stephan.Schmidt/better-strategies-for-null-handling-in-java - Control Flow in the Java Virtual Machine
http://www.artima.com/underthehood/flowP.html - Java Virtual Machine
http://en.wikipedia.org/wiki/Java_virtual_machine - ==, .equals(), compareTo(), and compare()
http://leepoint.net/notes-java/data/expressions/22compareobjects.html - New JDK7 features
http://openjdk.java.net/projects/jdk7/features/ - Project Coin: Bringing it to a Close(able)
http://blogs.sun.com/darcy/entry/project_coin_bring_close - CloseableFinder source code
http://blogs.sun.com/darcy/resource/ProjectCoin/CloseableFinder.java - Joe Darcy blog about JDK
http://blogs.sun.com/darcy - Java 7 – more dynamics
http://www.baptiste-wicht.com/2010/04/java-7-more-dynamics/