2010-04-14 7 views
12

He estado trabajando durante algún tiempo en Bytecode (Java), sin embargo, nunca se me había ocurrido preguntar por qué se escriben algunas instrucciones. Entiendo que en una operación ADD, necesitamos distinguir entre una suma entera y una adición FP (es por eso que tenemos IADD y FADD). Sin embargo, ¿por qué tenemos que distinguir entre ISTORE y FSTORE? Ambas implican exactamente la misma operación, que está moviendo 32 bits desde la pila a una posición variable local.Tipos en Bytecode

La única respuesta que puedo pensar es para seguridad de tipo, para evitar esto: (ILOAD, ILOAD, FADD). Sin embargo, creo que la seguridad de tipo ya se aplica en el nivel de lenguaje Java. De acuerdo, el formato de archivo Class no está directamente acoplado con Java, ¿entonces esta es una manera de aplicar seguridad de tipo para lenguajes que no la soportan? ¿Cualquier pensamiento? Gracias.

EDIT: para dar seguimiento a la respuesta de Reedy. Escribí este programa mínimo:

public static void main(String args[]) 
{ 
    int x = 1; 
} 

el cual compila para:

iconst_1 
istore_1 
return 

usando un editor de código de bytes, cambié la segunda instrucción:

iconst_1 
fstore_1 
return 

y se volvió un java.lang .VerifyError: esperando encontrar float en la pila.

Me pregunto, si en la pila no hay información sobre el tipo, solo bits, ¿cómo sabía la instrucción FSTORE que se trataba de una int y no de una flotación?

Nota: No he podido encontrar un título mejor para esta pregunta. Siéntase libre de mejorarlo.

Respuesta

17

Estas instrucciones se escriben para garantizar que el programa sea seguro. Al cargar una clase, la máquina virtual realiza la verificación en los códigos de bytes para garantizar que, por ejemplo, un flotante no se pase como argumento a un método que espera un entero. Esta verificación estática requiere que el verificador pueda determinar los tipos y el número de valores en la pila para cualquier ruta de ejecución dada. Las instrucciones de carga y almacenamiento necesitan la etiqueta de tipo porque las variables locales en los marcos de la pila no están tipadas (es decir, puede istore a una variable local y luego almacenar en la misma posición). Las etiquetas de tipo en las instrucciones le permiten al verificador saber qué tipo de valor está almacenado en cada variable local.

El verificador examina cada código de operación en el método y realiza un seguimiento de qué tipos estarán en la pila y en las variables locales después de ejecutar cada uno. Tiene razón en que esta es otra forma de verificación de tipos y duplica algunas de las comprobaciones realizadas por el compilador de Java. El paso de verificación evita la carga de cualquier código que haga que la máquina virtual ejecute una instrucción ilegal y garantiza las propiedades de seguridad de la plataforma Java sin incurrir en la gran penalización de tiempo de ejecución de los tipos de comprobación antes de cada operación. La verificación del tipo de tiempo de ejecución para cada código de operación sería un golpe de rendimiento cada vez que se ejecuta el método, pero la verificación estática solo se realiza una vez cuando se carga la clase.

Caso 1:

Instruction    Verification Stack Types   Local Variable Types 
----------------------- --------------- ---------------------- ----------------------- 
<method entry>   OK    []      1: none 
iconst_1    OK    [int]     1: none 
istore_1    OK    []      1: int 
return     OK    []      1: int 

Caso 2:

Instruction    Verification Stack Types   Local Variable Types 
----------------------- --------------- ---------------------- ----------------------- 
<method entry>   OK    []      1: none 
iconst_1    OK    [int]     1: none 
fstore_1    Error: Expecting to find float on stack 

Se da el error porque el verificador sabe que fstore_1 espera un flotador en la pila, pero el resultado de la ejecución de las instrucciones anteriores deja una int en la pila.

Esta verificación se realiza sin ejecutar los códigos de operación, sino que se realiza al observar los tipos de instrucciones, al igual que el compilador de Java da un error al escribir (Integer)"abcd". El compilador no tiene que ejecutar el programa para saber que "abcd" es una cadena y no se puede convertir a Integer.

+0

Gracias por la respuesta. Entonces, el verificador hace un análisis de flujo de datos antes de ejecutar la clase para detectar este tipo de error. Interesante para aprender :) –

3

Para responder a su primera pregunta con mi mejor estimación: estos códigos de bytes son diferentes porque pueden requerir diferentes implementaciones. Por ejemplo, una arquitectura particular puede mantener operandos enteros en la pila principal, pero operandos de coma flotante en registros de hardware.

Para responder a su segunda pregunta, se lanza VerifyError cuando se carga la clase, no cuando se ejecuta. El proceso de verificación se describe here; nota pase # 3.

+0

+1 para el enlace. Y gracias por recordarme que VerifyError se lanza en tiempo de carga y no en tiempo de ejecución. –

4

Geoff Reedy explicó en su respuesta lo que hace el verificador cuando se carga una clase. Solo quiero agregar que puede desactivar el verificador usando un parámetro JVM. Esto no es recomendado!

Para su programa de ejemplo (con iconst y fstore), el resultado de ejecutar la verificación con discapacidad es un error de VM que detiene la JVM con el siguiente mensaje:

=============== DEBUG MESSAGE: illegal bytecode sequence - method not verified ================ 

# 
# An unexpected error has been detected by HotSpot Virtual Machine: 
# 
# EXCEPTION_PRIV_INSTRUCTION (0xc0000096) at pc=0x00a82571, pid=2496, tid=3408 
# 
# Java VM: Java HotSpot(TM) Client VM (1.5.0_15-b04 mixed mode, sharing) 
# Problematic frame: 
# j BytecodeMismatch.main([Ljava/lang/String;)V+0 
# 
... 
2

Todo código de bytes debe ser demostrablemente typesafe con una análisis de flujo de datos estáticos como se menciona anteriormente. Sin embargo, esto realmente no explica por qué las instrucciones como _store tienen diferentes tipos, ya que el tipo se puede inferir del tipo del valor en la pila. De hecho, hay algunas instrucciones como pop, dup y swap que hacen exactamente eso y operan en varios tipos. El por qué algunas instrucciones se escriben y otras no, es algo que solo pueden explicar los desarrolladores originales de Java.

Cuestiones relacionadas