lunes, 15 de noviembre de 2010

La estructura de un .class

Oolong
Para la investigación de la estructura de un .class, además de la herramienta javap de la que se habló un poco en nuestro primer post, utilizaremos una nueva herramienta llamada oolong, un lenguaje ensamblador para la JVM basado en jasmin. Este es el ensamblador que se utiliza en el libro Programming for the Java virtual machine. Esta diseñado para no tener que involucrarnos con los bytes directamente aunque es casi equivalente al bytecode que se generará finalmente.

La mejor herramienta que he podido encontrar para jugar con él es un plugin para jedit con el que podemos decompilar un .class y que, además de una descripción similar a la provista por javap, nos da la dirección y el contenido en hex de los bytes.

Por ejemplo, para la misma entrada que anteriormente:
//StructA.java
public class StructA{}
Compilamos y utilizamos la opción dump class y obtenemos el la siguiente información:
//StructA.class.Dump
000000 cafebabe          magic = ca fe ba be 
000004 0000              minor version = 0
000006 0032              major version = 50
000008 000d              13 constants
00000a 0a0003000a           1. Methodref class #3 name-and-type #10
00000f 07000b               2. Class name #11
000012 07000c               3. Class name #12
000015 010006               4. UTF length=6
000018 3c696e69743e                       <init>
00001e 010003               5. UTF length=3
000021 282956                             ()V
000024 010004               6. UTF length=4
000027 436f6465                           Code
00002b 01000f               7. UTF length=15
00002e 4c696e654e756d6265725461626c65     LineNumberTable
00003d 01000a               8. UTF length=10
000040 536f7572636546696c65               SourceFile
00004a 01000c               9. UTF length=12
00004d 537472756374412e6a617661           StructA.java
000059 0c00040005           10. NameAndType name #4 descriptor #5
00005e 010007               11. UTF length=7
000061 53747275637441                     StructA
000068 010010               12. UTF length=16
00006b 6a6176612f6c616e672f4f626a656374   java/lang/Object
00007b 0021              access_flags = 33
00007d 0002              this = #2
00007f 0003              super = #3
000081 0000              0 interfaces
000083 0000              0 fields
000085 0001              1 methods
                         Method 0:
000087 0001                 access flags = 1
000089 0004                 name = #4<<init>>
00008b 0005                 descriptor = #5<()V>
00008d 0001                 1 field/method attributes:
                            field/method attribute 0
00008f 0006                    name = #6<Code>
000091 0000001d                length = 29
000095 0001                    max stack: 1
000097 0001                    max locals: 1
000099 00000005                code length: 5
00009d 2a                      0 aload_0
00009e b70001                  1 invokespecial #1
0000a1 b1                      4 return
0000a2 0000                    0 exception table entries:
0000a4 0001                    1 code attributes:
                               code attribute 0:
0000a6 0007                       name = #7<LineNumberTable>
0000a8 00000006                   length = 6
                                  Line number table:
0000ac 0001                       length = 1
0000ae 00000001                      start pc: 0 line number: 1
0000b2 0001              1 classfile attributes
                         Attribute 0:
0000b4 0008                 name = #8<SourceFile>
0000b6 00000002             length = 2
0000ba 0009                 sourcefile index = #9
Done. 

El formato .class
A continuación se provee una descripción relativamente rápida del formato utilizado por la JVM. La descripción completa y avanzada se encuentra en el Capítulo 4 de la JVM specification. Una tabla un tanto más accesible la podemos encontrar en wikipedia.

Un archivo class consiste de una sola estructura ClassFile como sigue:
    ClassFile {
     u4 magic;
     u2 minor_version;
     u2 major_version;
     u2 constant_pool_count;
     cp_info constant_pool[constant_pool_count-1];
     u2 access_flags;
     u2 this_class;
     u2 super_class;
     u2 interfaces_count;

     u2 interfaces[interfaces_count];
     u2 fields_count;
     field_info fields[fields_count];
     u2 methods_count;
     method_info methods[methods_count];
     u2 attributes_count;
     attribute_info attributes[attributes_count];
    
 
En donde u2 o u4 hacen referencia al número de bytes que requiere cada elemento. Los elementos se detallan como sigue:
  • magic: provee el número mágico que identifica a un formato class; tiene el valor 0xCAFEBABE.
  • minor_version, major_version: Estos números detallan la versión del formato class. Para efectos prácticos mantendremos como 0 y 50 respectivamente.
  • constant_pool_count: este valor es igual al número de entradas en la constant_pool más uno. 
  • constant_pool[]: es una tabla de estructuras que representan varias constantes string, nombre de clases, y campos (fields) y otras constantes referidas dentro de la estructura ClassFile y sus subestructuras. El formato de cada entrada en esta tabla es indicado por su byte tag en la estructura cp_info. Esta tabla se indexa desde 1 hasta constant_pool_count-1.
  • access_flags: estos valores conforman una máscara de banderas que denotan los permisos y propiedades de esta clase. Los detalles de cada bandera pueden encontrarse aquí. De nuevo, para efectos prácticos todas nuestras clases serán públicas herderas de la clase Object, por lo que el valor de access_flags siempre será 0x0021.
  • this_class y super_class: los valores de estos elementos deben ser índices de la constant_pool cuyo contenido sea una estructura CONSTANT_Class_info que represente a la clase definida por este archivo class y su clase super, respectivamente. Detalles sobre esta estructura más adelante.
  • interfaces_count y interfaces[]: Dado que el compilador que queremos desarrollar no involucra ninguna clase de herencia, estos elementos no nos son relevantes. Solo nos interesa saber que el valor de interfaces_count puede ser cero, y la estructura interfaces[] estar vacía.
  • fields_count: da la cantidad de estructuras field_info en la tabla fields. Las estructuras field_info representan a cualquier campo (ya sea variable de instancia o de clase), declarado por esta clase o interfaz.
  • fields[]: cada entrada en esta tabla debe ser una estructura field_info que describa completamente a cada campo declarado en esta clase o interfaz (no incluye campos heredados). 
  • methods_count y methods[]: de manera similar a las estructuras anteriores, el primer campo da la cantidad de entradas en el segundo. El segundo consiste en una tabla de estructuras de tipo method_info. Nuevamente, por cuestiones prácticas no nos interesarán los métodos de tipo abstracto, aunque la JVM los soporten. Es necesario aclarar que esta tabla incluye también los métodos inicializadores (constructures) de la clase, mas no a los métodos heredados de superclasses o superinterfaces.
  • attributes_count y attributes[]: los atributos ayudan a definir información sobre las estructuras. En el caso de los atributos de una estructura ClassFile, la JVM specification solo define dos: SourceFile y Deprecated, ambos opcionales. Tampoco entraremos en detalle sobre estos atributos, aunque vale la pena hacer notar que la especificación deja las puertas abiertas para definir y trabajar con atributos personalizados, si bien no esta permitido que estos atributos afecten la semántica de un archivo class.
Casi todos estos elementos podemos observaros en el fragmento anterior de código: La constant_pool que abarca de la línea 6 a la 25, los access_flags en la línea 26, la definición del único método constructor de la línea 33 a la 53, etc.

Lo siguiente es entrar en detalle sobre algunas estructuras que son de especial importancia para nuestro objetivo. Esto lo dejaremos para la próxima entrada. Sin embargo, antes de terminar es necesario hablar sobre otro tema importante: descriptores.

Descriptores
Un descriptor (o bien, descriptor) es una cadena que representa el tipo de un campo o de un método. Los descriptores se representan en el formato de un archivo class utilizando cadenas UTF-8 y se especifican mediante las siguiente gramáticas.

Descriptores de campos
Representa el tipo de una variable de instancia, de clase o local.
FieldDescriptor -> FieldType
ComponentType -> FieldType
FieldType -> BaseType | ObjectType | ArrayType
BaseType -> B | C | D | F | I | J | S | Z
ObjectType -> L<classname>;
ArrayType -> [ ComponentType
Donde los símbolos en negrita son caracteres ASCII y <classname> representa el nombre completo de una clase.
La interpretación de los tipos de campos se muestra en esta tabla.
De aquí que, por ejemplo, el descriptor de una variable de tipo int sea simplemente I, el de una variable de instancia de tipo Object sea Ljava/lang/Object; y el de un arreglo tridimensional de double de la forma double d[][][]; sea [[[D.

Descriptores de métodos
Un descriptor de método representa los parámetros que toma el método y el valor que retorna:
MethodDescriptor -> ( ParameterDescriptor* ) ReturnDescriptor
ParameterDescriptor -> FieldType
ReturnDescriptor -> FieldType | V
Donde V indica que el método retorna void. De esta gramática que el descriptor para un método como
   Object mimetodo(int i,double d, Thread t) 
sea
   (IDLjava/lang/Thread;)Ljava/lang/Object;.

Un ejemplo práctico de estos descriptores lo vemos en la línea 12 del código anterior, en donde se especifica el descriptor del método constructor para nuestra clase StructA.java.

No hay comentarios:

Publicar un comentario