Obsah
1. Pohled pod kapotu JVM (4.část – dokončení popisu struktury souborů .class)
2. Struktura obsahující seznam datových položek deklarovaných ve třídě či rozhraní
3. Bitové příznaky určující vlastnosti datové položky
4. Atributy přiřazené k datové položce
5. Demonstrační příklad – výpis příznaků, deskriptorů a atributů různých typů datových položek
6. Struktura obsahující seznam všech deklarovaných metod
7. Rozšíření demonstračního dekompileru o výpis seznamu datových položek a metod
8. Atributy přiřazené ke třídě nebo k rozhraní
1. Pohled pod kapotu JVM (4.část – dokončení popisu struktury souborů .class)
V dnešní – již dvacáté první – části seriálu o programovacím jazyce Java i o vlastnostech virtuálního stroje tohoto jazyka dokončíme popis interní struktury bajtkódu. V předchozích třech článcích [1][2][3] jsme postupně zpřesňovali původní velmi hrubě načrtnuté schéma interní struktury bajtkódu až na úroveň, která je zobrazená na konci této kapitoly. V dnešním článku si popíšeme poslední tři části bajtkódu – seznam datových položek (fields) deklarovaných přímo ve třídě či v rozhraní, jednotlivé metody (methods, opět deklarované přímo ve třídě) a konečně dodatečné informace, které mohou být ke třídě přiřazeny. Jedná se o takzvané atributy třídy či metadata, jejichž počet se postupně zvyšuje společně s rozšiřováním sémantiky programovacího jazyka Java.
Uvidíme, že prakticky všechny další části bajtkódu jsou do značné míry svázány s constant poolem, což je ostatně jeden z důvodů, proč byla popisu constant poolu věnována celá devatenáctá část tohoto seriálu. Veškeré nové informace, které si v následujících kapitolách sdělíme, budou samozřejmě zabudovány do demonstračního dekompileru bajtkódu:
+-----------------------------------------+ | Hlavička bajtkódu | | | | +-------------------------+---------+ | | | Magická konstanta | 4 bajty | | | +-------------------------+---------+ | | | Minoritní verze formátu | 2 bajty | | | +-------------------------+---------+ | | | Majoritní verze formátu | 2 bajty | | | +-------------------------+---------+ | | | +-----------------------------------------+ | Constant pool | | | | +-------------------------+---------+ | | | Počet záznamů v poolu | 2 bajty | | | +-------------------------+---------+ | | | záznam #1 | x bajtů | | | | záznam #2 | x bajtů | | | | ... | | | | | záznam #n | x bajtů | | | +-------------------------+---------+ | | | +-----------------------------------------+ | Definice příznaků třídy/rozhraní | | | | +-------------------------+---------+ | | | Bitové pole s příznaky | 2 bajty | | | +-------------------------+---------+ | | | +-----------------------------------------+ | Jméno třídy a nadtřídy | | | | +-------------------------+---------+ | | | Index do constant poolu | 2 bajty | | | +-------------------------+---------+ | | | Index do constant poolu | 2 bajty | | | +-------------------------+---------+ | | | +-----------------------------------------+ | Implementovaná rozhraní | | | | +-------------------------+---------+ | | | Počet jmen rozhraní | 2 bajty | | | +-------------------------+---------+ | | | Index do const. poolu #1| 2 bajty | | | | Index do const. poolu #2| 2 bajty | | | | ... | | | | | Index do const. poolu #n| 2 bajty | | | +-------------------------+---------+ | | | +-----------------------------------------+ | Datové položky deklarované ve třídě | | ..... | | ..... | | ..... | +-----------------------------------------+ | Kódy jednotlivých metod | | ..... | | ..... | | ..... | +-----------------------------------------+ | Další metadata třídy | | (atributy třídy) | | ..... | | ..... | | ..... | +-----------------------------------------+
2. Struktura obsahující seznam datových položek deklarovaných ve třídě či rozhraní
S využitím znalosti všech struktur bajtkódu uvedených v předchozích třech částech tohoto seriálu lze snadno získat základní informace o třídě (class), rozhraní (interface) či o výčtovém typu (enumeration), který je v bajtkódu uložen. Přečíst lze jméno třídy (popř. i jméno balíčku, do něhož třída náleží), jméno nadtřídy, přístupová práva zadaná programátorem (public atd.) i seznam všech rozhraní, která jsou třídou implementována. Ovšem ve skutečnosti nám k úplné znalosti celé struktury třídy chybí to hlavní – informace o datových položkách (fields) a taktéž o metodách (methods). Nejdříve si řekneme, jakým způsobem jsou v bajtkódu uloženy datové položky, protože informace o nich se nachází v sekci umístěné ihned za sekcí se seznamem implementovaných rozhraní. Začátek sekce s datovými položkami je snadno pochopitelný – ve dvou bajtech je zde uložen celkový počet datových položek deklarovaných přímo v dané třídě. Jinými slovy to znamená, že implicitně zděděné datové položky v této sekci bajtkódu nenalezneme.
Ukažme si tuto vlastnost na jednoduchém příkladu trojice tříd A, B a C, mezi nimiž existuje vztah prapředek→předek→potomek:
class A { int x; }
class B extends A { int y; }
class C extends B { int z; }
Po překladu vznikne trojice souborů .class, přičemž bajtkód uložený v souboru A.class bude obsahovat informaci pouze o datové položce x, soubor B.class informaci pouze o datové položce y a konečně soubor C.class bude obsahovat – jak již určitě víte – informaci o datové položce z, i když ve skutečnosti jsou v této třídě dostupné i zbylé dvě datové položky.
Vraťme se však zpět k popisu struktury informací o datových položkách uložených v bajtkódu. Po již zmíněném počtu všech deklarovaných datových položek je v bajtkódu uložen jejich seznam. Každá datová položka je reprezentována záznamem s proměnnou délkou. Na začátku každého záznamu je ve dvou bajtech uloženo bitové pole s příznaky, které určují vlastnosti datové položky. Jednotlivé příznaky budou popsány v následující kapitole. Ihned za bitovým polem je v bajtkódu uložena opět dvoubajtová hodnota, v níž je umístěn odkaz na záznam typu CONSTANT_Utf8 z constant poolu. Tento řetězec obsahuje jméno datové položky. Dalším údajem je opět dvoubajtová hodnota představující (opět) odkaz na záznam typu CONSTANT_Utf8 – tentokrát se jedná o řetězec obsahující deskriptor, tj. řetězec, v němž je zakódován datový typ položky (formát řetězce s datovým typem jsme si již popsali v devatenácté části tohoto seriálu). Další částí záznamu o datové položce je počet takzvaných atributů. Za tímto záznamem následuje seznam všech atributů popsaný podrobněji ve čtvrté kapitole.
Struktura se záznamem o jedné datové položce tedy vypadá následovně:
# | Název složky | Datový typ | Význam |
---|---|---|---|
1 | access_flags | uint16 | bitové příznaky určující vlastnosti datové položky |
2 | name_index | uint16 | jméno, odkaz do constant_poolu na záznam typu CONSTANT_Utf8 |
3 | descriptor_index | uint16 | deskriptor, odkaz do constant poolu na záznam typu CONSTANT_Utf8 |
4 | attributes_count | uint16 | celkový počet atributů přiřazených k datové položce |
5 | attributes | attribute_info[] | pole, jehož prvky jsou struktury typu attribute_info |
3. Bitové příznaky určující vlastnosti datové položky
V předchozí kapitole jsme si řekli, že na začátku každého záznamu s informacemi o datové položce se nachází bitové pole o velikosti 16 bitů, tj. dva bajty (ty jsou samozřejmě uloženy v pořadí big endian, podobně jako všechny další vícebajtové hodnoty v bajtkódu). V tomto bitovém poli jsou uložena přístupová práva k datové položce (public, private, protected, když není nastavena žádná z těchto masek, tak se jedná o package protected), dále pak příznak toho, zda je datová položka statická (tj. zda náleží ke třídě, nikoli k její instanci), finální (konstanta), volatilní (přístup k ní je obecně asynchronní, může k ní například přistupovat více vláken), zda se jedná o prvek výčtového typu atd. Všechny bitové masky, které mohou být u datové položky nastaveny, jsou uvedeny v následující tabulce. Důležité je, že se počet bitových masek s postupným vývojem syntaxe a sémantiky Javy rozšiřoval, což se týká především posledních dvou masek ACC_SYNTHETIC a ACC_ENUM:
# | Název masky | Hodnota | Význam, pokud je maska nastavena |
---|---|---|---|
1 | ACC_PUBLIC | 0×0001 | veřejná datová položka dostupná vně třídy i vně balíčku |
2 | ACC_PRIVATE | 0×0002 | privátní datová položka dostupná pouze uvnitř třídy |
3 | ACC_PROTECTED | 0×0004 | chráněná datová položka dostupná uvnitř třídy, z případných odvozených tříd i v rámci celého balíčku |
4 | ACC_STATIC | 0×0008 | statická datová položka, může se kombinovat s předchozí trojicí příznaků |
5 | ACC_FINAL | 0×0010 | finální datová položka |
6 | ACC_VOLATILE | 0×0040 | příznak volatile – hodnota se vždy přečte či zapíše do paměti (nezůstane uložena pouze v registrech) |
7 | ACC_TRANSIENT | 0×0080 | příznak transient – nebude se zpracovávat při (de)serializaci objektu |
8 | ACC_SYNTHETIC | 0×1000 | syntetická datová položka, která nemá svůj protějšek ve zdrojovém kódu (je generována překladačem) |
9 | ACC_ENUM | 0×4000 | jedná se o prvek výčtového typu |
Již ze samotné specifikace programovacího jazyka Java vyplývá, že ne všechny kombinace bitových příznaků je možné použít současně. Některá omezení jsou vypsána v další tabulce. Jedno z omezení se týká těch bitových příznaků, které není možné použít v případě datových položek rozhraní (interface), dále pak nelze libovolně kombinovat první tři příznaky (což je pochopitelné) a taktéž není možné současně použít příznaky ACC_FINAL a ACC_VOLATILE (což je opět pochopitelné, neboť u konstant nehrozí možnost, že by se mohly asynchronně změnit v jiném vláknu):
# | Název masky | Class | Interface | Nelze kombinovat s… | Poznámka |
---|---|---|---|---|---|
1 | ACC_PUBLIC | ano | ano | ACC_PRIVATE, ACC_PROTECTED | musí být použit pro atributy rozhraní |
2 | ACC_PRIVATE | ano | ne | ACC_PUBLIC, ACC_PROTECTED | |
3 | ACC_PROTECTED | ano | ne | ACC_PUBLIC, ACC_PRIVATE | |
4 | ACC_STATIC | ano | ano | × | musí být použit pro atributy rozhraní |
5 | ACC_FINAL | ano | ano | ACC_VOLATILE | musí být použit pro atributy rozhraní |
6 | ACC_VOLATILE | ano | ne | ACC_FINAL | |
7 | ACC_TRANSIENT | ano | ne | × | |
8 | ACC_SYNTHETIC | ano | ano | × | |
9 | ACC_ENUM | ano | ne | × | použit pouze u výčtu |
4. Atributy přiřazené k datové položce
Ke každé datové položce může být přiřazen libovolný počet atributů, které umožňují uložení dalších informací o této datové položce, resp. o jejích vlastnostech. Počet atributů je libovolný, samozřejmě může být i nulový (to se ostatně týká většiny nefinálních datových položek, kterým žádný atribut nebývá přiřazen). Každý atribut se obecně skládá z dvojice obsahující jméno atributu a jeho hodnotu. Jméno atributu je zadáno nepřímo jako odkaz do constant poolu, protože se v mnoha případech můžeme setkat s tím, že shodný atribut je použit u většího množství datových položek (jedná se například o atribut se jménem ConstantValue) odkazující na konstantu v poolu. Následující tabulka obsahuje jména a stručný popis atributů popsaných přímo ve specifikaci virtuálního stroje Javy:
# | Jméno atributu | Význam |
---|---|---|
1 | ConstantValue | odkaz na konstantu u finální datové položky |
2 | Code | instrukce tvořící tělo metody |
3 | Exceptions | informace o výjimkách, které může metoda vyhazovat |
4 | InnerClasses | informace o vnitřních třídách (jedná se o atribut třídy) |
5 | EnclosingMethod | informace uváděná u vnitřních a anonymních tříd |
6 | Synthetic | atribut bez hodnoty (délka=0) uváděný u syntetických tříd, metod či datových položek |
7 | Signature | atribut obsahující odkaz na signaturu třídy, metody či datové položky |
8 | SourceFile | atribut obsahující odkaz na jméno zdrojového souboru (uložené v constant poolu) |
9 | LineNumberTable | atribut obsahující pole s mapováním mezi instrukcemi metody a odpovídajícím zdrojovým řádkem |
10 | LocalVariableTable | atribut obsahující ladicí informace o lokálních proměnných |
11 | LocalVariableTypeTable | atribut obsahující ladicí informace o typech lokálních proměnných |
11 | Deprecated | atribut bez hodnoty (délka=0) uváděný u tříd, metod a datových položek s anotací @Deprecated |
Zdaleka ne všechny tyto atributy však mohou být přiřazeny k datovým položkám, protože mnohé z těchto atributů jsou použity zejména u metod a některé další i u tříd.
Hodnota atributu je v bajtkódu obecně zapsána formou řetězce v UTF-8. Před řetězcem je ve čtveřici bajtů zapsána jeho délka a řetězec NEní ukončený nulou (jak by tomu bylo u běžných céčkovských řetězců). Vzhledem k tomu, že délka udává délku řetězce v bajtech a ne ve znacích, je možné, aby virtuální stroj jazyka Java jednoduše celý řetězec přeskočil v případě, že daný atribut nedokáže zpracovat (i toto chování je ve specifikaci povoleno). V následující tabulce je pro přehled uveden formát uložení jednoho atributu. Pokud je k datové položce přidáno větší množství atributů, jsou jednoduše uloženy za sebou bez jakýchkoli oddělovačů. Hodnoty některých typů atributů, například již zmíněného atributu ConstantValue, mají speciální význam a nezpracovávají se jako řetězec, ale například jako dvoubajtový index do constant poolu (v němž jsou uloženy, jak již víme z předchozích dílů tohoto seriálu, taktéž celočíselné i reálné konstanty):
# | Název složky | Datový typ | Význam |
---|---|---|---|
1 | attribute_name_index | uint16 | odkaz do constant_poolu na záznam typu CONSTANT_Utf8 |
2 | attribute_length | uint32 | délka řetězce uvedená v bajtech (ne ve znacích!) |
3 | info | char[] | řetězec (NEukončený nulou) obsahující text atributu |
5. Demonstrační příklad – výpis příznaků, deskriptorů a atributů různých typů datových položek
Podívejme se nyní na jednoduchý demonstrační příklad s třídou obsahující dvanáct datových položek typu int. Položky mají nastavenou různou oblast viditelnosti (public, protected, private, implicitní oblast viditelnosti) i další modifikátory (static, final…). Navíc je u dvou položek použita anotace: u položky i11 se jedná o anotaci @Deprecated, která zůstane zachována i v bajtkódu (mimo jiné i proto, že má deklarováno @Retention(value=RUNTIME)), zatímco položka i12 má přiřazenu anotaci @SuppressWarning, která je využita pouze překladačem a v bajtkódu se neobjeví (deklarace @Retention(value=SOURCE)):
public class FieldTest { public int i1; protected int i2; private int i3; int i4; final int i5 = 6502; static int i6; static final int i7 = 42; transient int i8; volatile int i9; public transient int i10 = 0; @Deprecated int i11; @SuppressWarnings("foo") int i12; }
Seznam všech deklarovaných datových položek získaných přímo z bajtkódu vypadá následovně:
Number of declared fields: 12 Field name: 'i1' Descriptor: I Field flags: 0x0001 ACC_PUBLIC Attributes: 0 Field name: 'i2' Descriptor: I Field flags: 0x0004 ACC_PROTECTED Attributes: 0 Field name: 'i3' Descriptor: I Field flags: 0x0002 ACC_PRIVATE Attributes: 0 Field name: 'i4' Descriptor: I Field flags: 0x0000 Attributes: 0 Field name: 'i5' Descriptor: I Field flags: 0x0010 ACC_FINAL Attributes: 1 Name: 'ConstantValue' Value: binarni hodnota (ne retezec) Field name: 'i6' Descriptor: I Field flags: 0x0008 ACC_STATIC Attributes: 0 Field name: 'i7' Descriptor: I Field flags: 0x0018 ACC_STATIC ACC_FINAL Attributes: 1 Name: 'ConstantValue' Value: binarni hodnota (ne retezec) Field name: 'i8' Descriptor: I Field flags: 0x0080 ACC_TRANSIENT Attributes: 0 Field name: 'i9' Descriptor: I Field flags: 0x0040 ACC_VOLATILE Attributes: 0 Field name: 'i10' Descriptor: I Field flags: 0x0081 ACC_PUBLIC ACC_TRANSIENT Attributes: 0 Field name: 'i11' Descriptor: I Field flags: 0x0000 Attributes: 2 Name: 'Deprecated' Value: '' (nulova delka) Name: 'RuntimeVisibleAnnotations' Value: binarni hodnota (ne retezec) Field name: 'i12' Descriptor: I Field flags: 0x0000 Attributes: 0
Zajímavý je taktéž bajtkód, který vznikne překladem výčtového typu:
public enum EnumTest { PRVNI, DRUHA, TRETI }
Ve výsledném bajtkódu najdeme jak všechny tři explicitně zapsané datové položky (které jsou samozřejmě statické a finální), tak i jednu položku syntetickou, tj. položku vygenerovanou samotným překladačem (význam této položky je asi zřejmý při pohledu na dokumentaci k výčtovému typu):
Number of declared fields: 4 Field name: 'PRVNI' Descriptor: LEnumTest; Field flags: 0x4019 ACC_PUBLIC ACC_STATIC ACC_FINAL ACC_ENUM Attributes: 0 Field name: 'DRUHA' Descriptor: LEnumTest; Field flags: 0x4019 ACC_PUBLIC ACC_STATIC ACC_FINAL ACC_ENUM Attributes: 0 Field name: 'TRETI' Descriptor: LEnumTest; Field flags: 0x4019 ACC_PUBLIC ACC_STATIC ACC_FINAL ACC_ENUM Attributes: 0 Field name: '$VALUES' Descriptor: [LEnumTest; Field flags: 0x101a ACC_PRIVATE ACC_STATIC ACC_FINAL ACC_SYNTHETIC Attributes: 0
6. Struktura obsahující seznam všech deklarovaných metod
Po informacích o jednotlivých datových položkách deklarovaných v rozhraní či ve třídě se v bajtkódu nachází informace o jednotlivých metodách. U každé metody můžeme zjistit její jméno, typ návratové hodnoty, typy parametrů metody a taktéž vlastní tělo metody, které je složeno z instrukcí virtuálního stroje Javy. Nejprve je v bajtkódu uložena dvoubajtová hodnota s počtem metod. Za touto hodnotou pak následují záznamy s informacemi o jednotlivých metodách, které se v mnoha ohledech podobají struktuře záznamů s informacemi o datových položkách. Nejprve je pro každou metodu uložena dvoubajtová hodnota s bitovými příznaky, následuje dvoubajtový index do constant poolu, který musí ukazovat na jméno metody (záznam typu CONSTANT_Utf8). Třetím údajem je opět dvoubajtový index do constant poolu, který obsahuje odkaz na deskriptor metody. Z něj se dá vyčíst návratový typ metody i datové typy všech jejích parametrů. Nakonec může záznam metody obsahovat libovolný počet atributů, z nichž nejdůležitější je atribut s názvem Code, jehož hodnotou jsou instrukce tvořící tělo metody (instrukčním souborem se budeme podrobněji zabývat příště).
V následující tabulce je pro přehlednost vypsána struktura záznamu s informacemi o jedné metodě:
# | Název složky | Datový typ | Význam |
---|---|---|---|
1 access_flags | uint16 | bitové příznaky určující vlastnosti metody | |
2 name_index | uint16 | jméno, odkaz do constant_poolu na záznam typu CONSTANT_Utf8 | |
3 descriptor_index | uint16 | deskriptor, odkaz do constant poolu na záznam typu CONSTANT_Utf8 | |
4 attributes_count | uint16 | počet atributů přiřazených k metodě | |
5 attributes | attribute_info[] | pole struktur attribute_info |
V předchozím odstavci jsme si řekli, že záznam s informacemi o metodě obsahuje, podobně jako záznam s informacemi o datové položce, dvoubajtovou hodnotu s bitovým polem, jehož jednotlivé příznaky blíže určují například viditelnost metody, zda se jedná o statickou či finální metodu, zda byl při deklaraci metody použit modifikátor strictfp (aritmetika dle IEEE 754) atd. Některé z bitových příznaků mají stejná jména i hodnotu, jako příznaky použité u tříd a datových položek, další příznaky jsou odlišné, což je ostatně patrné i z následující tabulky:
# | Název masky | Hodnota | Význam, pokud je maska nastavena |
---|---|---|---|
1 | ACC_PUBLIC | 0×0001 | veřejná metoda dostupná vně třídy i vně balíčku |
2 | ACC_PRIVATE | 0×0002 | privátní metoda dostupná pouze uvnitř třídy |
3 | ACC_PROTECTED | 0×0004 | chráněná metoda dostupná uvnitř třídy a z případných odvozených tříd |
4 | ACC_STATIC | 0×0008 | statická metoda |
5 | ACC_FINAL | 0×0010 | finální metoda (nemůže být překryta v odvozené třídě) |
6 | ACC_SYNCHRONIZED | 0×0020 | volání metody je synchronizováno (použije se zámek) |
7 | ACC_BRIDGE | 0×0040 | metoda generovaná překladačem |
8 | ACC_VARARGS | 0×0080 | metoda s variabilním počtem argumentů |
9 | ACC_NATIVE | 0×0100 | nativní metoda, nemá bajtkód, protože volá nativní binární kód |
10 | ACC_ABSTRACT | 0×0400 | abstraktní metoda, taktéž nemá bajtkód |
11 | ACC_STRICT | 0×0800 | použito v případě, že se má používat aritmetika dle IEEE 754 |
12 | ACC_SYNTHETIC | 0×1000 | syntetická metoda, nemá svůj protějšek ve zdrojovém kódu |
7. Rozšíření demonstračního dekompileru o výpis seznamu datových položek a metod
S informacemi uvedenými v předchozích kapitolách je již možné rozšířit demonstrační dekompiler javovského bajtkódu. Rozšířit je nutné zejména výčet obsahující všechny bitové konstanty (masky) použité v bajtkódu. Povšimněte si, že některé bitové masky mají stejnou hodnotu, což však nevadí, protože některé masky jsou použity jen u tříd, jiné u datových položek a zbývající u metod:
/* * Priznaky tridy, rozhrani ci datove polozky */ enum { ACC_PUBLIC = 0x0001, /* verejna trida/rozhrani/datova polozka */ ACC_PRIVATE = 0x0002, /* privatni datova polozka dostupna pouze uvnitr tridy */ ACC_PROTECTED = 0x0004, /* chranena datova polozka dostupna uvnitr tridy a z pripadne odvozene tridy */ ACC_STATIC = 0x0008, /* staticka datova polozka */ ACC_FINAL = 0x0010, /* finalni trida ci datova polozka */ ACC_SUPER = 0x0020, /* semantika instrukce invokespecial */ ACC_VOLATILE = 0x0040, /* priznak volatile - hodnota se vzdy precte ci zapise do pameti */ ACC_TRANSIENT = 0x0080, /* priznak transient - nebude se zpracovavat pri (de)serializaci */ ACC_INTERFACE = 0x0200, /* rozliseni trida/rozhrani */ ACC_ABSTRACT = 0x0400, /* abstraktni trida */ ACC_SYNTHETIC = 0x1000, /* synteticka trida ci datova polozka, nema svuj protejsek ve zdrojovem kodu */ ACC_ANNOTATION = 0x2000, /* anotace */ ACC_ENUM = 0x4000, /* vycet */ /* cast platna pro metody, nikoli vsak pro datove polozky */ ACC_SYNCHRONIZED = 0x0020, /* volani metody je synchronizovano */ ACC_BRIDGE = 0x0040, /* metoda generovana prekladacem */ ACC_VARARGS = 0x0080, /* metoda s variabilnim poctem parametru */ ACC_NATIVE = 0x0100, /* nativni metoda */ ACC_STRICT = 0x0800 /* pouzito v pripade, ze se ma pouzivat aritmetika dle IEEE 754 */ };
Stejným způsobem bylo nutné rozšířit i funkci pro výpis všech příznaků. Ve funkci se nyní pomocí třetího parametru rozlišuje, zda se mají vypisovat příznaky pro metodu či příznaky pro datovou položku nebo třídu:
/* * Nacteni a vypis atributu tridy, rozhrani, datove polozky ci metody. */ void process_flags(const char *message, uint16_t flags, int ismethod) { printf("\n%s: 0x%04x\n", message, flags); if (flags & ACC_PUBLIC) puts(" ACC_PUBLIC"); if (flags & ACC_PRIVATE) puts(" ACC_PRIVATE"); if (flags & ACC_PROTECTED) puts(" ACC_PROTECTED"); if (flags & ACC_STATIC) puts(" ACC_STATIC"); if (flags & ACC_FINAL) puts(" ACC_FINAL"); if (flags & ACC_ABSTRACT) puts(" ACC_ABSTRACT"); if (flags & ACC_SYNTHETIC) puts(" ACC_SYNTHETIC"); if (ismethod) /* cast platna jen pro metody */ { if (flags & ACC_SYNCHRONIZED) puts(" ACC_SYNCHRONIZED"); if (flags & ACC_BRIDGE) puts(" ACC_BRIDGE"); if (flags & ACC_VARARGS) puts(" ACC_VARARGS"); if (flags & ACC_NATIVE) puts(" ACC_NATIVE"); if (flags & ACC_STRICT) puts(" ACC_STRICT"); } else /* cast platna jen pro tridy a/nebo datove polozky */ { if (flags & ACC_TRANSIENT) puts(" ACC_TRANSIENT"); if (flags & ACC_INTERFACE) puts(" ACC_INTERFACE"); if (flags & ACC_ANNOTATION) puts(" ACC_ANNOTATION"); if (flags & ACC_ENUM) puts(" ACC_ENUM"); if (flags & ACC_SUPER) puts(" ACC_SUPER"); if (flags & ACC_VOLATILE) puts(" ACC_VOLATILE"); } }
Následuje nová funkce určená pro výpis všech deklarovaných datových položek:
/* * Nacteni a vypis vsech deklarovanych datovych polozek */ void process_all_defined_fields(FILE *fin) { int i; /* nacist pocet deklarovanych datovych polozek */ uint16_t fields = read_two_bytes(fin); printf("\nNumber of declared fields: %d\n", (int)fields); /* vypis vsech datovych polozek */ for (i = 0; i < fields; i++) { /* nacist priznaky */ uint16_t flags = read_two_bytes(fin); /* nacist index do constant poolu */ uint16_t name_index = read_two_bytes(fin); /* nacist index do constant poolu */ uint16_t descriptor_index = read_two_bytes(fin); /* nacist pocet atributu */ uint16_t attributes_count = read_two_bytes(fin); /* v cecku se indexuje od 0, v constant poolu od 1 */ name_index--; descriptor_index--; printf(" Field name: '%s'\n", pool_entries[name_index].string); printf(" Descriptor: %s", pool_entries[descriptor_index].string); process_class_or_field_flags(" Field flags", flags); printf(" Attributes: %d\n", attributes_count); if (attributes_count) { print_all_attributes(fin, attributes_count); } putchar('\n'); } }
Prakticky stejný tvar má i funkce pro výpis všech deklarovaných metod:
/* * Nacteni a vypis vsech deklarovanych metod */ void process_all_defined_methods(FILE *fin) { int i; /* nacist pocet deklarovanych metod */ uint16_t methods = read_two_bytes(fin); printf("\nNumber of declared methods: %d\n", (int)methods); /* vypis vsech metod */ for (i = 0; i < methods; i++) { /* nacist priznaky */ uint16_t flags = read_two_bytes(fin); /* nacist index do constant poolu */ uint16_t name_index = read_two_bytes(fin); /* nacist index do constant poolu */ uint16_t descriptor_index = read_two_bytes(fin); /* nacist pocet atributu */ uint16_t attributes_count = read_two_bytes(fin); /* v cecku se indexuje od 0, v constant poolu od 1 */ name_index--; descriptor_index--; printf(" Method name:'%s'\n", pool_entries[name_index].string); printf(" Descriptor: %s", pool_entries[descriptor_index].string); process_flags(" Field flags", flags, 1); printf(" Attributes: %d\n", attributes_count); if (attributes_count) { print_all_attributes(fin, attributes_count); } putchar('\n'); } }
Poslední novou funkcí je funkce určená pro výpis všech atributů. Vzhledem k tomu, že některé atributy obsahují jako svoji hodnotu binární data, je hodnota každého atributu vypsána jako seznam hexadecimálních hodnot a nikoli ve formě řetězce:
/* * Vypis vsech atributu prirazenych k nejakemu objektu v bajtkodu */ void print_all_attributes(FILE *fin, int attributes_count) { int i, j; for (i = 0; i < attributes_count; i++) { /* nacist index do constant poolu */ uint16_t name_index = read_two_bytes(fin); /* nacist delku retezce obsahujiciho hodnotu atributu */ uint32_t length = read_four_bytes(fin); /* v cecku se indexuje od 0, v constant poolu od 1 */ name_index--; printf(" Name: '%s'\n", pool_entries[name_index].string); printf(" Value: '"); for (j = 0; j < length; j++) { unsigned char c = read_byte(fin); printf(" %02x", c); } putchar('\''); putchar('\n'); } }
Zdrojový text tímto způsobem upraveného dekompileru souborů .class můžete nalézt <a>zde, popř. si můžete prohlédnout i <a>zdrojový kód se zvýrazněnou syntaxí.
8. Atributy přiřazené ke třídě nebo k rozhraní
Poslední informací, kterou můžeme v bajtkódu nalézt, jsou atributy přiřazené přímo ke třídě. Ihned za seznamem metod (viz předchozí kapitoly) následuje obligátní dvoubajtová hodnota určující počet atributů, které jsou ke třídě přiřazeny a posléze již následuje seznam jednotlivých atributů – odkaz na jméno atributu v constant poolu, délka hodnoty atributu a konečně vlastní hodnota atributu. V současnosti lze ke třídám přiřazovat pouze některé atributy, zejména atribut InnerClasses, EnclosingMethod (pokud se jedná o vnitřní či anonymní třídu), Synthetic, SourceFile (pro účely ladění), Signature (zde lze například zakódovat informace o generikách) a taktéž atribut Deprecated, jehož účel je zřejmý (je získán ze shodně pojmenované anotace).
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/