15. Módulo de gestión de errores.

Anuncio
15. Módulo de gestión de errores.
En capítulos anteriores se han estudiado diferentes módulos y en determinadas
circunstancia se han producido condiciones que llevan aun funcionamiento anómalo de
la KVM. Un ejemplo de esto sucede en el gestor de memoria: se desea reservar
memoria para un nuevo objeto pero hay memoria libre. En este caso la maquina virtual
debe realizar dos tareas:
•
•
Gestionar el error.
Informar del error al usuario.
Previamente al estudio de cómo gestiona y reporta los errores la maquina virtual
se expone una breve introducción a la gestión de errores en los sistemas informáticos
haciendo especial énfasis en aquellos que soportan lenguaje Java. En un segundo
capítulo ya se adentra en profundidad en la implementación del módulo de gestión de
errores de la KVM.
En particular se estudiara el flujo completo de seguimiento de errores:
•
•
•
Generación del error: en caso de que se cumpla una determinada
condición que pueda influir en el funcionamiento normal de la KVM.
Captura del error: se recoge el error que se ha producido para gestionarlo
y hacer las tareas adecuadas.
Reportar error: se informa al usuario a través del canal adecuado del
error producido.
15.1. Introducción.
Cuando hablamos de errores en un lenguaje de programación o un entorno del
mismo hemos de distinguir principalmente dos tipos de errores: los provocados por el
programador de la aplicación (errores sintácticos o errores lógicos en el algoritmo del
problema) y los errores conocidos como excepciones. Estos últimos se deben a
determinadas condiciones que se cumplen cuando en el flujo de ejecución del programa
se produce un evento desconocido o no esperado.
El manejo de excepciones es un mecanismo hardware o software diseñado para
gestionar la aparición de una condición que cambia el flujo de ejecución normal de los
programas. Y esta condición es lo que se denomina excepción.
En general, el estado actual es salvado en una determinada localización y la
ejecución del programa continúa por lo que sería un manejador de excepciones.
Dependiendo de la situación, el manejador puede provocar que se sigua ejecutando el
programa mas tarde desde la posición original usando la información de estado
guardada. Sin embargo existen excepciones como por ejemplo la división por cero que
provocan una interrupción inmediata del programa.
Desde el punto de vista del procesado las interrupciones hardware son iguales a
las software excepto que no pueden ser controladas por ningún manejador desde el flujo
de programa.
En este capítulo nos centraremos en la forma en la que la KVM controla los
errores que se producen en ejecución y los mecanismos que esta ofrece al lenguaje Java
para que el usuario pueda emplear el manejo de excepciones mediante bloques try/catch
bastante conocido.
15.1.1.
Libre de excepciones.
Un trozo de código esta libre de excepciones si un error en tiempo de ejecución
no produce efectos colaterales tales como pérdidas de memoria, salidas no válidas,
etc.… Hay distintos niveles a este respecto:
•
•
•
•
•
Fallos transparentes: operaciones que se garantizan son correctas y que
satisfacen todos los requisitos incluso aunque se produzcan excepciones.
Semántica commit o rollback: operaciones que pueden fallar pero si se produce
el fallo se garantiza que no habrá efectos secundarios.
Mecanismo básico de excepciones: funcionamiento parcial de operaciones
erróneas puede provocar efectos colaterales pero mantiene el estado correcto de
la operación (es decir no modifica los datos de la misma).
Mecanismo mínimo de excepciones: igual al anterior solo que puede conllevar
pérdida de información.
Sin mecanismo de excepciones: no se garantiza nada.
Normalmente con el mecanismo básico de excepciones suele ser suficiente. El
caso de los fallos transparentes es bastante difícil de implementar y más en aplicaciones
donde se empleen librerías externas cuyo código se desconoce.
15.1.2.
Soporte de excepciones en los lenguajes de
programación.
Muchos lenguajes de programación tales como Ada, C++, Common Lisp,
Delphi, Java, .NET tienen soporte interno para la gestión de excepciones. En estos
lenguajes cuando se produce una excepción en una función se va consultando la pila de
ejecución de dicha función hasta que se encuentra el manejador de excepciones. Es
decir, si la función f contiene un manejador de excepciones H para la excepción E, la
función de llamada g, la cual llama a su vez a la función h y ocurre una excepción E en
la función interna h entonces las funciones h y g son finalizadas y el manejador H en la
función f se encarga de gestionar la excepción E.
Excluyendo diferencias sintácticas menores, existen básicamente dos estilos para
la gestión de excepciones. En el estilo más popular, una excepción es iniciada por una
sentencia especial (throw o raise) con un objeto excepción asociado a ella. La
visibilidad del gestor de excepciones comienza y termina con marcas especiales (trycatch). Después le pueden seguir números manejadores de eventos y cada uno puede
manejar algún tipo de excepción específico.
Unos pocos lenguajes también permiten incluir una cláusula una vez que el
manejador de eventos ha finalizado, tal es el caso de la cláusula finally que se usa en
Java y se ejecuta tanto si ha lanzado una excepción como si no. Como variación menor
algunos lenguajes usan un único manejador de excepciones e internamente dicho
manejador se encarga de distinguir que tipo de excepción se ha producido.
15.1.3.
Excepciones comprobadas.
Los diseñadores de Java introducieron el concepto de checked exceptions que
son un subconjunto especial de excepciones. Estas excepciones tienen la particularidad
de que pueden ser lanzadas como parte constitutiva del método. Por ejemplo, si un
método puede lanzar la excepción IOException se declara en el método y no es
necesario capturar la excepción dentro del propio método. Dicha declaración es forzosa
pues si no genera un error de compilación.
Podemos ver brevemente las ventajas e inconvenientes de este tipo de
excepciones. Este mecanismo permite reducir desde la propia compilación el numero de
excepciones no controladas que se pueden producir en el programa. Sin embargo esto
implica que para método especialmente largos el numero de excepciones que hay que
declarar en ellos puede ser elevado reduciendo de esta forma la capacidad de
encapsulación o bien obligar al uso excesivo de bloques try-catch de captura de
excepciones.
Sin embargo debe quedar claro que el emplear este tipo de excepciones no
implica que el resto de las excepciones no deban ser controladas. Pues si se cae en este
error se pueden provocar errores de ejecución frecuentes en la maquina virtual.
15.1.4.
Condiciones de sistema.
Common Lisp, Dylan y SmallTalk posee un sistema de condiciones que puede
luchar perfectamente con cualquier tipo de gestión de excepciones. En estos lenguajes o
entornos cuando se produce una condición (que no es más que una generalización de un
error) implica una llamada a una función, y únicamente mas tarde en el manejador de
excepciones se toma la decisión de examinar la pila de ejecución o no.
De esta forma las condiciones no son mas que una generalización de las
excepciones. Cuando una condición ocurre un manejador de condiciones es buscado y
ejecutado dentro del orden de la pila de ejecución para manejar la condición. Las
condiciones que no impliquen errores se puede dejar sin gestionar directamente y solo
se genera un aviso al usuario.
15.2. Implementación gestión de excepciones Java en la
KVM.
Desde cualquier punto de ejecución de un programa Java se pueden lanzar
excepciones a petición del usuario o bien la propia maquina virtual puede lanzar dichas
excepciones de forma automática para asegurar que no se llegue a un estado caótico. Tal
es el caso la excepción NullPointerExcepcion.
Esta operación se recoge en el método throwException y puede ser invocado
desde dos tipos de lugares diferentes:
•
•
Desde el interprete de la maquina virtual a través de la primitiva ATHROW.
Desde la propia maquina virtual en caso de que ocurra una situación
excepcional.
Básicamente esta operación se encarga de gestionar la excepción inspeccionando
los manejadores de excepciones contenidos en el marco de ejecución del método que se
este ejecutando y buscando en la pila de ejecución para encontrar un manejador
adecuado. En caso de encontrar el manejador se fija el interprete para que continué su
ejecución por el.
Conviene especificar que en caso de modificar la estructura de la maquina
virtual en algún punto nunca se debe poner ningún tipo de código inmediatamente
después de la invocación throwException porque dicho código se ejecutaría antes de que
el interprete pudiera llegar al manejador.
15.2.1.
Estructura manejador de excepciones.
Los manejadores de eventos asociados a las excepciones que se pueden producir
en un determinado método se almacenan junto con el resto de la información del
método. La forma de almacenarlos es en una tabla que contiene una serie de elementos
y cada uno de estos elementos representa un manejador específico.
Estas tablas de manejadores de eventos responden a estructuras de la forma
siguiente:
struct exceptionHandlerTableStruct {
long length;
struct exceptionHandlerStruct handlers[1];
};
Donde cada uno de los elementos de esta tabla tiene un manejador de
excepciones. De esta forma cada método tiene una tabla donde están todos los
manejadores de excepciones definidos para el y cada manejador tiene la siguiente
estructura:
struct exceptionHandlerStruct {
unsigned short startPC;
unsigned short endPC;
unsigned short handlerPC;
unsigned short exception;
};
Cada uno de estos manejadores entonces lleva la siguiente información: rango
del contador de programas para el cual es válido esa excepción (startPC y endPC), el
punto por el que se continúa la ejecución una vez producida la excepción (handlerPC) y
la clase de la excepción (exception). Se definen además una serie de macros para
obtener los tamaños de los manejadores y de la tabla:
#define SIZEOF_HANDLER
StructSizeInCells(exceptionHandlerStruct)
#define SIZEOF_HANDLERTABLE(n) \
(StructSizeInCells(exceptionHandlerTableStruct)
SIZEOF_HANDLER)
15.2.2.
+
(n
-
1) *
Operación lanzar excepción.
Dado que al lanzar la excepción la KVM ya ha actualizado previamente
contador de programa en el estado del contador de programas de la llamada
correspondiente, es necesario por tanto corregir el contador de programas para todos los
marcos de ejecución excepto para el primero y el único debajo del RunCustomMethod.
Pero si se esta ejecutando una función nativa entonces existe un marco de ejecución
perdido al principio de la pila de ejecución ya que las funciones nativas no añaden
marcos de ejecución:
int ipCorrection = (CurrentNativeMethod == NULL) ? 0 : 1;
Una vez realizada la corrección se toman los registros FP e IP de la maquina
virtual:
thisFP = getFP();
thisIP = getIP();
Se comprueba si no existe ningún hilo activo. En este caso se genera el error
correspondiente mediante el método fatalError mostrando el detalle del error
almacenado en el segundo slot de la excepción. Después de generar el error se fuerza la
salida de la maquina virtual:
if (CurrentThread == NULL) {
STRING_INSTANCE string = unhand(exceptionH)->message;
if (string != NULL) {
fatalError(getStringContents(string));
}
ERROR_THROW(-1);
}
A continuación se recorren cada uno de los marcos de ejecución presentes en la
lista indicada por el puntero thisFP:
while (thisFP != NULL) {
/* Tratamiento para cada marco */
}
Para cada marco lo primero que se hace en este tratamiento individual es tomar
del método la tabla donde se almacenan los manejadores de excepciones del método:
METHOD thisMethod = thisFP->thisMethod;
HANDLERTABLE handlerTable = thisMethod->u.java.handlers;
Si hay manejadores definidos en esta tabla buscamos el manejador que se
corresponda con la excepción que se desea lanzar para lo cual se hace uso de la función
auxiliar findHandle (que se puede ver en detalle en el siguiente apartado acerca de las
funciones auxiliares):
INSTANCE_CLASS thisClass = thisFP->thisMethod->ofClass ;
DECLARE_TEMPORARY_FRAME_ROOT(thisFPx, thisFP);
ipOffset = thisIP - thisMethod->u.java.code;
thisHandler = findHandler(thisClass, handlerTable,
exceptionH,
(unsigned short)(ipOffset ipCorrection));
thisFP = thisFPx;
Como se puede observar para realizar la búsqueda se emplean varios parámetros:
la clase de la excepción (thisClass), la tabla en la que buscar (handlerTable), la
instancia del manejador de excepciones (exceptionH) y la desviación del contador de
programas necesaria (ipOffset).
Si del resultado de esta búsqueda se obtiene un manejador apropiado se fijan los
registros adecuados de la maquina virtual para que el interprete puede seguir
ejecutándose desde el manejador que se ha encontrado:
setFP(thisFP);
setIP(thisMethod->u.java.code + thisHandler->handlerPC);
setLP(FRAMELOCALS(thisFP));
setCP(thisFP->thisMethod->ofClass->constPool);
Como se puede ver el contador de programas (IP) se actualiza al punto donde se
encuentra el manejador dentro del método. Además se fija el tamaño de la pila de
ejecución del hilo en 1 pues ya no se ejecutan más métodos al haberse lanzado la
excepción:
setStackHeight(1);
Si el depurador esta activo además se lanza el evento correspondiente a este
indicando que se ha producido una excepción y a partir de aquí el interprete seguiría
ejecutando el código del manejador que habíamos buscado pues aquí finalizaría la
operación.
Si por el contrario no se encuentra la tabla de manejadores dentro del método se
busca si existe un manejador por defecto para tratar dicha excepción
(RunCustomCodeMethod). En este caso se toma la función de callback que se ubica al
final de la pila de ejecución:
void **bottomStack = (void **)(thisFPx + 1);
CustomCodeCallbackFunction func =
(CustomCodeCallbackFunction)(bottomStack[0]);
Y se sobrescribe con la instancia del manejador de la excepción por defecto:
bottomStack[0] = unhand(exceptionH);
func(&thisFPx);
thisFP = thisFPx;
bottomStack = (void **)(thisFP + 1);
unhand(exceptionH) = bottomStack[0];
Independientemente de que se haya encontrado o no tabla de manejadores
asociada al método para el marco en cuestión se comprueba si el método esta
sincronizado para compartir el acceso entre los distintos hilos. Si se da ese caso, hay que
salirse del monitor asociado al objeto para que otro hilo pueda acceder a él:
char *errorIfFailure;
enum MonitorStatusType result =
monitorExit(synchronized, &errorIfFailure);
thisFP->syncObject = NULL;
Si el resultado de liberar el monitor ha sido erróneo, se fijan los registros de la
maquina virtual con los valores de thisFP y thisIP que aún no han sido actualizados, se
fija el tamaño de la pila en cero y se genera una excepción genérica para atender dicho
error volviendo a ejecutar la operación de throwExceptión desde el principio
(restartOnError):
if (result == MonitorStatusError) {
setFP(thisFP);
setIP(thisIP);
setStackHeight(0);
unhand(exceptionH) =
(THROWABLE_INSTANCE)instantiate((INSTANCE_CLASS)getClass(er
rorIfFailure));
goto restartOnError;
}
Finalmente se actualizan los valores de thisIP y thisFP con los valores que se
han de pasar al manejador y se actualiza la corrección en el contador de programas:
thisIP = thisFP->previousIp;
thisFP = thisFP->previousFp;
ipCorrection = (thisMethod == RunCustomCodeMethod) ? 0 : 1;
Si hubiéramos llegado a este punto quiere decir que no existía ningún manejador
de excepciones disponible, estamos en una situación de una excepción no controlada en
cuyo caso la KVM opera primero generando información de depuración:
•
Si el depurador esta activo y en funcionamiento hay preparar y lanzar el evento
de que se ha producido una excepción a este:
if (vmDebugReady) {
CEModPtr cep = GetCEModifier();
cep->exp.classID =
GET_CLASS_DEBUGGERID(&((THROWABLE_INSTANCE)unhand(exceptionH))>ofClass->clazz);
cep->exp.sig_caught = FALSE;
cep->exp.sig_uncaught = TRUE;
cep->threadID = getObjectID((OBJECT)thisThread->javaThread);
setEvent_Exception(exceptionH, saveFp, saveIp, NIL,
(unsigned long)0, cep);
FreeCEModifier(cep);
}
•
Si no hay hilo ejecutándose actualmente (CurrentThread) se elimina el hilo una
vez que el evento ha sido lanzado (mediante Dismantlehread) y si existiera un
hilo ejecutándose se detiene la ejecución de dicho hilo (mediante stopThread) y
en el caso en que no existieran mas hilos que ejecutar se termina la ejecución de
la maquina virtual mediante el código de salida FATAL_ERROR_EXIT_CODE):
if (CurrentThread == NIL) {
DismantleThread(thisThread);
} else {
stopThread();
if (AliveThreadCount == 0 && AllThreads == NULL) {
ERROR_THROW(FATAL_ERROR_EXIT_CODE);
}
}
•
Además mediante la función auxiliar printExceptionStackTrace se muestra por
la salida estándar la traza de la excepción:
#if INCLUDEDEBUGCODE
printExceptionStackTrace(exceptionH);
#endif
Por último se detiene la ejecución del hilo independientemente de las tareas que
se hayan aplicado a cada marco de ejecución y su resultado y en caso de que no
existieran más hilos activos se produce la finalización de la KVM usando para ello el
código de salida FATAL_ERROR_EXIT_CODE :
stopThread();
if (AliveThreadCount == 0 && AllThreads == NULL) {
ERROR_THROW(FATAL_ERROR_EXIT_CODE);
}
15.2.3.
Funciones auxiliares.
De entre las funciones auxiliares que apoyan el funcionamiento de la operación
throwException estudiada en detalle en este apartado destaca la operación findHandler
que se encarga de buscar un manejador de excepciones apropiado para una excepción
dentro de la tabla de manejadores de un método. Su prototipo es el siguiente:
static HANDLER
findHandler(INSTANCE_CLASS thisClass, HANDLERTABLE handlerTable,
THROWABLE_INSTANCE_HANDLE exceptionH, unsigned short
ipOffset)
Su forma de operar se basaría en iterar sobre cada una de lo s maneadores dentro
de la tabla de manejadores (handlerTable) que le es pasada como parámetro. Para ello
hace uso de la macro FOR_EACH_HANDLER:
FOR_EACH_HANDLER(thisHandler, handlerTable)
/* Tratamiento para cada excepción */
END_FOR_EACH_HANDLER
Para cada manejador contenido en la tabla se realizan las siguientes
comprobaciones una detrás de otra:
•
Se comprueba si el offset del contador de programas también pasado como
parámetro (ipOffset) esta fuera del rango del contador de programas del
manejador. Si este es el caso se continua iterando pues este no es el manejador
que estamos buscando:
if (ipOffset < thisHandler->startPC) {
continue;
} else if (ipOffset >= thisHandler->endPC) {
continue;
}
•
Se comprueba si el índice de la excepción dentro del manejador es cero lo cual
indica que es el manejador que estamos buscando luego se interrumpe la
iteración y se devuelve dicho manejador (result):
if (thisHandler->exception == 0) {
result = thisHandler;
break;
}
•
En cambio si el objeto excepción anterior no es cero se toma dicho valor
(thisException) y se invoca la operación resolveClassReference que a partir del
índice de la excepción obtiene la clase correspondiente a la misma:
unsigned short thisException =
(unsigned short)thisHandler->exception;
CLASS handlerClass =
resolveClassReference(thisClass->constPool,
thisException,
thisClass);
Si dicha clase es asignable a la excepción que estamos tratando de manejar se
interrumpe la iteración y se devuelve el manejador obtenido. Si no se sigue
iterando para el siguiente manejador:
if (isAssignableTo((CLASS)(unhand(exceptionH)->ofClass),
(CLASS)handlerClass)) {
result = thisHandler;
break;
}
15.3. Implementación gestión de excepción y errores
internos en KVM.
La gestión de errores o excepciones que se produzcan durante la ejecución de la
maquina virtual se realiza mediante una serie de métodos declarados en el archivo
frame.h. Como se puede comprobar este módulo es en realidad forma parte del módulo
de gestión de frames que se analiza en otro capítulo pero dada la distintita filosofía y
objetivos de ambos los hemos separado en dos.
Dentro de este apartado nos centraremos de forma exclusiva en la forma en la
cual la KVM trata los errores que se pueden producir debido al funcionamiento interno
propio de la KVM y no de las excepciones que puedan lanzar un programa Java o el
código de una determinada clase.
15.3.1.
Tipos de errores.
Se pueden establecer distintos clasificaciones de errores en función del
parámetro al que se atienda. Así en función de la criticidad del error podemos
distinguir entre:
•
Errores críticos: errores que afectan a la seguridad de la KVM, es decir
errores que provoquen la KVM realice acciones peligrosas tales como
accesos a zonas de memoria no adecuadas o escritura en zonas de
memoria protegidas. Se considera error crítico cualquier situación que
lleve a un estado caótico de la maquina virtual.
•
Errores medios: errores que si bien afectan al funcionamiento de la KVM
haciendo que el comportamiento de esta no sea el correcto, no implican
errores irrecuperables del sistema.
•
Errores leves: errores que alteran el funcionamiento de la KVM a nivel
de usuario provocando que los programas ejecutados por el usuario
tengan un comportamiento inesperado.
Cuando la KVM detecta una condición de error o excepción se produce el
lanzamiento de esta excepción o error que es reportado al usuario, también se dice que
es extendido. En la KVM se emplean tres formas distintas de reportar estos errores
según su naturaleza:
•
•
•
Elevar la excepción.
Error fatal.
Errores internos graves de la KVM.
Veremos en los siguientes apartados estos tres métodos para gestionar los errores
en profundidad.
15.3.2.
Elevar la excepción.
Esta es considerada la forma más elegante para reportar errores y sobre todo
excepciones pues permite a la maquina virtual reportar errores a través del mecanismo
de manejo de excepciones tradicional de Java. Esta forma de reportar errores debe ser
empleada para excepciones y errores no críticos, es decir para errores medios y/o leves.
Los prototipos de los métodos que realizan estas tareas son los que se muestran a
continuación:
void raiseException(const char* exceptionClassName);
void raiseExceptionMsg(const char* exceptionClassName, STRING_INSTANCE
msg);
Ambos métodos funcionan de forma id éntica. Reciben como parámetro el
nombre de la clase que representa la excepción que se quiere extender y a partir de este
nombre se crea una instancia de la clase de excepción que la representa para de esta
forma pasar esta instancia al manejador de excepciones apropiado.
Veamos en detalle como funcionan estas operaciones. A partir del nombre de la
excepción pasada como parámetro se obtiene la clase de la excepción:
INSTANCE_CLASS
(INSTANCE_CLASS)getClass(exceptionClassName);
exceptionClass=
En el caso en el que no se pueda encontrar la clase correspondiente al nombre
pasado se muestra por la salida estándar un mensaje indicando ello mediante la función
de sistema AlertUser (propia del sistema operativo en cuestión) y se toma como clase la
clase genérica JavaLangThrowable:
if (exceptionClass == NULL) {
sprintf(str_buffer, KVM_MSG_ILLEGAL_EXCEPTION_NAME_1STRPARAM,
exceptionClassName);
AlertUser(str_buffer);
exceptionClass = JavaLangThrowable;
}
Una vez obtenida la clase a partir de esta se crea una instancia de la misma
añadiéndola a las raíces temporales del sistema para evitar que el recolector de basura
las trate como elementos de memoria inaccesibles:
DECLARE_TEMPORARY_ROOT(THROWABLE_INSTANCE, exception,
(THROWABLE_INSTANCE)instantiate(exceptionClass));
Seguidamente se muestra por pantallas la traza de la pila de ejecución del hilo en
el momento en el que se provoco la excepción (fillInStackTrace) y finalmente se emplea
el mecanismo genérico para lanzar excepciones Java recogido en throwException y
comentado en el apartado anterior:
if (exception != NULL) {
#if PRINT_BACKTRACE
fillInStackTrace(&exception);
#endif
throwException(&exception);
}
Todo esto es la operativa para la función raiseException, la operación
raiseExceptionMsg opera exactamente igual solo que justo antes de llamar a
throwException se incluye dentro de la instancia de objeto excepción el mensaje pasado
como parámetro:
exception->message = msg;
throwException(&exception);
15.3.3.
Error fatal.
Los errores que alteran la ejecución de la maquina virtual gravemente pero que
son responsabilidad directa de la propia KVM se generan mediante la operación
fatalError. Esta función emplea el método de sistema AlertUser para mostrar el mensaje
de error pasado como parámetro e invoca la macro ERROR_THROW con el código de
salida:
void fatalError(const char* errorMessage)
{
AlertUser(errorMessage);
ERROR_THROW(FATAL_ERROR_EXIT_CODE);
}
Esta macro es la que se encarga de enviar el código de salida al interprete de la
maquina virtual para detener la ejecución de la misma.
15.3.4.
Error fatal interno de la KVM.
Los errores fatales internos se generan mediante la operación fatalVMError. Esta
función es muy sencilla y lo único que realiza es mostrar por la salida estándar el estado
actual de la maquina virtual empleando la función auxiliar printVMStatus.
Seguidamente invoca la función genérica de errores vista en el apartado anterior:
void fatalVMError(const char* errorMessage)
{
if (INCLUDEDEBUGCODE) {
printVMstatus();
}
fatalError(errorMessage);
}
15.4. Comunicación del módulo de gestión de errores con el
intérprete.
Hemos visto a lo largo del capítulo y en distintos puntos del mismo como la forma
en la cual el módulo de gestión de errores se comunica con el intérprete es a través
de la macro ERROR_THROW que recibe como parámetro un entero.
Pues bien, en el momento en que hay una invocación a esta macro, la maquina
virtual salta inmediatamente al código que se encarga de gestionar el error. Así
ERROR_THROW salta al código ERROR_CATCH más cercano.
Así el bloque completo de try-catch en forma de macros quedaría como sigue:
#ifndef ERROR_TRY
# include <setjmp.h>
# define ERROR_TRY \
{ jmp_buf *__prev__ = topJumpBuffer;
int *__prev__value = topJumpBuffer_value;
jmp_buf __jmp_buf__;
int __value__;
int __state__;
topJumpBuffer = &__jmp_buf__;
topJumpBuffer_value = &__value__;
if ((__state__ = setjmp(__jmp_buf__)) == 0) {
#
define ERROR_CATCH(var)
}
topJumpBuffer = __prev__;
topJumpBuffer_value = __prev__value;
if (__state__ != 0) {
long var = __value__;
#
define ERROR_END_CATCH
#
define ERROR_THROW(i)
\
\
\
\
\
\
\
\
\
\
\
\
} }
\
*topJumpBuffer_value = i;
longjmp(*topJumpBuffer, i)
\
extern jmp_buf *topJumpBuffer;
extern int
*topJumpBuffer_value;
#define NEED_GLOBAL_JUMPBUFFER 1
#endif /* ERROR_TRY */
#ifndef FATAL_ERROR_EXIT_CODE
#define FATAL_ERROR_EXIT_CODE 127
#endif
Por defecto este comportamiento es emulado empleando setjmp y longjmp. Sin
embargo existen plataformas proveen de un mecanismo nativo similar que puede ser
empleado.
15.5. Parámetros de configuración del módulo de gestión de
errores y excepciones.
Igual que sucede en el resto de módulo del sistema tenemos los parámetros de
configuración relacionados con el depurador y la generación de logs del sistema, es
decir ENABLE_JAVA_DEBUGGER e INCLUDEDEBUGCODE que como ya sabemos
introducen una carga adicional al procesamiento del módulo que se puede eliminar en
entornos donde la KVM este suficientemente testada.
15.6. Conclusiones.
Uno de los aspectos mas relevantes a la hora de evaluar una plataforma es como se
comporta ante las situaciones anómalas que se puedan producir durante la ejecución de
la plataforma. De esta forma en este capítulo se ha estudiado como gestiona errores la
maquina virtual de la plataforma J2ME.
La forma en la cual se implementan en Java la gestión de errores es a través de
las excepciones. De esta forma la KVM trata las excepciones que se producen como
eventos que genera el sistema operativo o la propia maquina virtual. Así emplea un
manejador para gestionar estas excepciones de forma similar a como se hace con los
eventos.
Estos
manejadores
se
implementan
mediante
la
estructura
exceptionHandlerStruct.
Cada uno de estos manejadores mantiene la siguiente información:
•
•
•
Rango de instrucciones controlado por el manejador.
Conjunto de instrucciones que realizan el tratamiento específico de la excepción.
El tipo de la excepción que se ha producido, en realidad la clase que representa
dicha excepción.
De esta forma cuando se produce un error dentro del rango anterior se procede a
lanzar la excepción que representa dicho error. Este lanzamiento consiste en ejecutar las
instrucciones de gestión de la misma e invocar a la operación fatalError para avisar al
usuario y terminar la ejecución de la maquina virtual. Si el error se ha produc ido en un
hilo específico debe suspender la ejecución del hilo y eliminarlo para que de esta forma
se puedan seguir ejecutando el resto de los hilos activos.
Hasta aquí se ha estado hablando de errores, por tales entendemos aquellos que
se producen en la interacción de la maquina virtual con el sistema operativo sin que la
KVM los contemple en su implementación. Por otro lado, nos encontramos con
aquellos errores que son generados por la propia maquina virtual cuando el usuario
realiza una acción inválida. Esto es lo que comúnmente se denominan en Java
excepciones que la maquina virtual trata de forma interna.
Los tipos de errores internos que maneja la KVM son:
•
Errores críticos: errores que afectan a la seguridad de la KVM, es decir
errores que provoquen la KVM realice acciones peligrosas tales como
accesos a zonas de memoria no adecuadas o escritura en zonas de
memoria protegidas. Se considera error crítico cualquier situación que
lleve a un estado caótico de la maquina virtual.
•
Errores medios: errores que si bien afectan al funcionamiento de la KVM
haciendo que el comportamiento de esta no sea el correcto, no implican
errores irrecuperables del sistema.
•
Errores leves: errores que alteran el funcionamiento de la KVM a nivel
de usuario provocando que los programas ejecutados por el usuario
tengan un comportamiento inesperado.
Cuando la KVM detecta una condición de error o excepción se produce el
lanzamiento de esta excepción o error que es reportado al usuario, también se dice que
es extendido. En la KVM se emplean tres formas distintas de reportar estos errores
según su naturaleza:
•
•
•
Elevar la excepción.
Error fatal.
Errores internos graves de la KVM.
Descargar