Hlavní navigace

Pohled pod kapotu JVM (4.část - dokončení popisu struktury souborů .class)

3. 1. 2012
Doba čtení: 22 minut

Sdílet

V dnešní části seriálu o programovacím jazyce Java i o vlastnostech JVM dokončíme popis struktury bajtkódu, tj. souborů s koncovkou .class generovaných (většinou) překladačem Javy. Řekneme si, jakým způsobem jsou uloženy informace o datových položkách tříd, rozhraní či výčtových typů, jak jsou uloženy jednotlivé metody a zmíníme se taktéž o atributech přiřazených k vlastním třídám.

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 me­tod

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í

9. Odkazy na Internetu

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ře­dek→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_Utf8constant 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 LocalVariable­TypeTable 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_in­dex 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(va­lue=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(va­lue=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 me­tod:

/*
 * 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:

Cloud23

/*
 * 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

  1. The JavaTM Virtual Machine Specification, Second Edition
    http://java.sun­.com/docs/book­s/jvms/second_e­dition/html/VMSp­ecTOC.doc.html
  2. The class File Format
    http://java.sun­.com/docs/book­s/jvms/second_e­dition/html/Clas­sFile.doc.html
  3. javap – The Java Class File Disassembler
    http://docs.o­racle.com/java­se/1.4.2/docs/to­oldocs/window­s/javap.html
  4. javap-java-1.6.0-openjdk(1) – Linux man page
    http://linux.di­e.net/man/1/j­avap-java-1.6.0-openjdk
  5. Using javap
    http://www.ide­velopment.info/da­ta/Programmin­g/java/miscella­neous_java/Usin­g_javap.html
  6. Examine class files with the javap command
    http://www.techre­public.com/ar­ticle/examine-class-files-with-the-javap-command/5815354
  7. BCEL Home page
    http://common­s.apache.org/bcel/
  8. BCEL Manual
    http://common­s.apache.org/bcel/ma­nual.html
  9. Byte Code Engineering Library (Wikipedia)
    http://en.wiki­pedia.org/wiki/BCEL
  10. Java programming dynamics, Part 7: Bytecode engineering with BCEL
    http://www.ib­m.com/developer­works/java/li­brary/j-dyn0414/
  11. Bytecode Engineering
    http://book.chi­naunix.net/spe­cial/ebook/Co­re_Java2_Volu­me2AF/0131118269/ch13lev­1sec6.html
  12. BCEL Tutorial
    http://www.smfsup­port.com/suppor­t/java/bcel-tutorial!/
  13. ASM Home page
    http://asm.ow2­.org/
  14. Seznam nástrojů využívajících projekt ASM
    http://asm.ow2­.org/users.html
  15. ObjectWeb ASM (Wikipedia)
    http://en.wiki­pedia.org/wiki/Ob­jectWeb_ASM
  16. Java Bytecode BCEL vs ASM
    http://james.o­negoodcookie.com/2005/10­/26/java-bytecode-bcel-vs-asm/
  17. Bytecode Outline plugin for Eclipse (screenshoty + info)
    http://asm.ow2­.org/eclipse/in­dex.html
  18. aspectj (Eclipse)
    http://www.eclip­se.org/aspectj/
  19. Aspect-oriented programming (Wikipedia)
    http://en.wiki­pedia.org/wiki/As­pect_oriented_pro­gramming
  20. AspectJ (Wikipedia)
    http://en.wiki­pedia.org/wiki/As­pectJ
  21. EMMA: a free Java code coverage tool
    http://emma.sou­rceforge.net/
  22. Cobertura
    http://cobertu­ra.sourceforge­.net/
  23. FindBugs
    http://findbug­s.sourceforge­.net/
  24. GNU Classpath
    www.gnu.org/s/clas­spath/
  25. Java VMs Compared
    http://bugblog­ger.com/java-vms-compared-160/
  26. JSRs: Java Specification Requests – JSR 223: Scripting for the Java Platform
    http://www.jcp­.org/en/jsr/de­tail?id=223
  27. Scripting for the Java Platform
    http://java.sun­.com/developer/techni­calArticles/J2SE/Des­ktop/scriptin­g/
  28. Scripting for the Java Platform (Wikipedia)
    http://en.wiki­pedia.org/wiki/Scrip­ting_for_the_Ja­va_Platform
  29. Java Community Process
    http://en.wiki­pedia.org/wiki/Ja­va_Specificati­on_Request
  30. Java HotSpot VM Options
    http://www.ora­cle.com/technet­work/java/java­se/tech/vmopti­ons-jsp-140102.html
  31. Great Computer Language Shootout
    http://c2.com/cgi/wi­ki?GreatCompu­terLanguageSho­otout
  32. Java performance
    http://en.wiki­pedia.org/wiki/Ja­va_performance
  33. Trying the prototype
    http://mail.o­penjdk.java.net/pi­permail/lambda-dev/2010-August/002179.html
  34. Better closures (for Java)
    http://blogs.sun­.com/jrose/en­try/better_clo­sures
  35. Lambdas in Java: An In-Depth Analysis
    http://www.in­foq.com/articles/lam­bdas-java-analysis
  36. Class ReflectiveOpe­rationExcepti­on
    http://downlo­ad.java.net/jdk7/doc­s/api/java/lan­g/ReflectiveO­perationExcep­tion.html
  37. Proposal: Indexing access syntax for Lists and Maps
    http://mail.o­penjdk.java.net/pi­permail/coin-dev/2009-March/001108.html
  38. Proposal: Elvis and Other Null-Safe Operators
    http://mail.o­penjdk.java.net/pi­permail/coin-dev/2009-March/000047.html
  39. Java 7 : Oracle pushes a first version of closures
    http://www.bap­tiste-wicht.com/2010/05­/oracle-pushes-a-first-version-of-closures/
  40. Groovy: An agile dynamic language for the Java Platform
    http://groovy­.codehaus.org/O­perators
  41. Better Strategies for Null Handling in Java
    http://www.sli­deshare.net/Step­han.Schmidt/bet­ter-strategies-for-null-handling-in-java
  42. Control Flow in the Java Virtual Machine
    http://www.ar­tima.com/under­thehood/flowP­.html
  43. Java Virtual Machine
    http://en.wiki­pedia.org/wiki/Ja­va_virtual_machi­ne
  44. ==, .equals(), compareTo(), and compare()
    http://leepoin­t.net/notes-java/data/expres­sions/22compa­reobjects.html
  45. New JDK7 features
    http://openjdk­.java.net/pro­jects/jdk7/fe­atures/
  46. Project Coin: Bringing it to a Close(able)
    http://blogs.sun­.com/darcy/en­try/project_co­in_bring_close
  47. CloseableFinder source code
    http://blogs.sun­.com/darcy/re­source/Projec­tCoin/Closeable­Finder.java
  48. Joe Darcy blog about JDK
    http://blogs.sun­.com/darcy
  49. Java 7 – more dynamics
    http://www.bap­tiste-wicht.com/2010/04­/java-7-more-dynamics/

Autor článku

Pavel Tišnovský vystudoval VUT FIT a v současné době pracuje ve společnosti Red Hat, kde vyvíjí nástroje pro OpenShift.io.