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.