Excepciones en modo protegido

  1. Alpertron
  2. Microprocesadores de la línea Intel
  3. Excepciones en modo protegido

por Dario Alejandro Alpern

Transcripción

Hola. Mi nombre es Darío Alpern y hoy vamos a ver excepciones en modo protegido.

Excepción

Una excepción es el cambio temporario de flujo del programa que se está ejecutando debido a un evento interno al procesador, por ejemplo intentar ejecutar una instrucción que no existe, acceder a memoria más allá del límite del segmento, etc.

En el diagrama se puede ver el programa principal que está corriendo hasta que llega ese evento.

En ese momento el procesador no continúa ejecutando el programa en curso. En cambio, ejecuta el manejador de la excepción, que es una subrutina cuyo contenido depende del evento que debemos procesar.

Luego de terminar de ejecutar el manejador de excepción, el procesador continúa ejecutando el programa principal desde la instrucción que generó la excepción, o la siguiente, dependiendo del tipo de excepción.

Es importante que el manejador de excepción salve el contenido de los registros cuando entra y los recupere antes de salir, ya que en caso contrario, los valores de los registros no serían los esperados y se colgaría el programa o ejecutaría algo no esperado.

Clasificación

En los procesadores Intel existen tres tipos de excepciones: faults, traps y aborts.

Las excepciones tipo fault ocurren cuando hay un fallo en el programa y el objetivo del manejador es corregirlo. Cuando termina el manejador de la excepción mediante la instrucción IRET, el procesador va a volver a intentar ejecutar la instrucción que generó la excepción. Por ejemplo, en un sistema que use memoria virtual, con la unidad de paginación activada, parte de la datos puede encontrarse fuera de la memoria física del sistema, en un disco rígido.

Si una instrucción intenta acceder a una página que no está presente en memoria física, ocurrirá una excepción de fallo de página donde el procesador indica que la página no está presente. En este caso el manejador de la excepción de página deberá recuperar del disco rígido los datos requeridos por la instrucción que falló para ponerlos en memoria. Una vez hecho esto el manejador ejecuta la instrucción IRET para finalizar, y luego el procesador vuelve a ejecutar la instrucción, y esta vez no debería fallar.

Las excepciones tipo trap ocurren cuando termina la instrucción en curso y por eso cuando el procesador termina la ejecución del manejador de excepción, va a ejecutar la instrucción siguiente a la que generó la excepción.

Las excepciones tipo abort ocurren cuando hay fallos graves en el sistema y no se puede recuperar. En ese caso, no tiene sentido ejecutar IRET en el manejador de dicha excepción. Generalmente lo que se hace es mostrar los registros e información propia del sistema en pantalla o se guarda en disco, y luego se bloquea el sistema, esperando que el usuario reinicialice el equipo.

Faults

Aquí se ve la lista completa de las excepciones tipo fault. A la izquierda se puede ver el tipo de excepción, tanto en hexadecimal como en decimal. Como en el caso de las interrupciones, el tipo de excepción es el índice dentro de la tabla IDT donde se encuentra la compuerta que tiene la dirección lineal del manejador de la excepción.

En la tercera columna se encuentra un mnemónico de dos letras que identifica la excepción y en la columna de la derecha se puede ver una somera descripción que indica cuando ocurre la excepción.

La excepción tipo cero ocurre cuando hay una división por cero, o bien el resultado no entra en el cociente.

La excepción tipo 1 ocurre cuando se usan los registros de debug DR0 a DR3, DR6 y DR7 para poner breakpoints por ejecución de código. El manejador de la excepción tipo 1 pertenece generalmente a un debugger y debe almacenar los registros para ponerlos en la pantalla.

La excepción tipo 5 ocurre cuando se ejecuta una instrucción BOUND. El primer operando de esta instrucción es un registro y el segundo es un puntero a memoria donde se almacena el límite inferior y luego el superior. Si el valor del registro no se encuentra en el rango especificado, se produce esta excepción.

La excepción tipo 6 ocurre cuando el código de operación no existe o si el tamaño de la instrucción es mayor que 15 bytes. Esta última condición solo puede ocurrir si la instrucción tiene prefijos repetidos.

La excepción tipo 7 ocurre cuando se ejecuta una instrucción matemática o SIMD luego de un cambio de tarea y determinados bits del registro de control CR0 y CR4 están encendidos.

La excepción tipo 10 se da cuando hay errores en la TSS, que es una estructura que se usa para implementar multitarea.

La excepción tipo 11 ocurre cuando se desea acceder un segmento marcado como no presente, en el momento de cargar el registro de segmento.

La excepción tipo 12 ocurre cuando hay problemas con la pila, como PUSH, POP, llamadas o retornos de subrutinas, interrupciones o excepciones que escriban o lean más allá del límite del segmento de pila. Por ejemplo: MOV ESP,0 y luego PUSH EAX.

La excepción tipo 13 indica un error general de protección, como por ejemplo cargar un registro de segmento con un selector que apunte a un descriptor inválido.

La excepción tipo 14 indica un fallo de página, por ejemplo acceder una página no presente, intentar escribir en una página de solo lectura, etc. Para que ocurra esta excepción, la unidad de paginación debe estar activada.

La excepción tipo 16 ocurre cuando existen errores en instrucciones matemáticas de punto flotante. Algunos errores son: números muy pequeños o muy grandes, división por cero, desbordamiento de la pila de números o pérdida de precisión.

La excepción tipo 17 se da cuando un código que corre con nivel de privilegio 3 accede a datos que no están alineados.

La excepción tipo 19 ocurre cuando hay un error en una instrucción de punto flotante de SIMD. Los errores son similares a los que generan la excepción tipo 16.

La excepción tipo 20 ocurre cuando hay un error de virtualización. La virtualización es una característica del procesador que permite correr un sistema operativo (llamado invitado) dentro de otro (llamado anfitrión).

La excepción tipo 21 ocurre cuando hay un ataque al sistema si está activada la característica Control Flow Enforcement (control de cumplimiento de flujo), que posee varias herramientas para detener ataques al sistema operativo. En uno de ellos, el procesador posee una pila oculta donde solo se almacenan direcciones de retorno de subrutinas, interrupciones o excepciones. Los programas no pueden acceder a esta pila porque se encuentran en páginas especiales no accesibles por la aplicación. Cuando se ejecuta una instrucción de retorno, el procesador verifica que en ambas pilas se encuentre la misma dirección. Si eso no ocurre, se dispara la excepción tipo 21.

Existen dos excepciones más tipo fault en procesadores AMD, la 29 y la 30.

Traps

Aquí se pueden observar las tres excepciones tipo trap.

La excepción tipo 1 ocurre si se usan los registros de debug para poner un breakpoint por lectura o escritura de datos, o cuando el bit 8 de EFLAGS, que es el trap flag, está encendido. Esto último permite ejecutar paso a paso usando un debugger, aún cuando el procesador está corriendo en memoria de solo lectura.

La excepción tipo 3 ocurre cuando se utiliza el byte CC hexa para implementar breakpoints. El debugger salva el primer byte de la instrucción donde debe ir el breakpoint y luego escribe el byte 0xCC hexa en dicha posición de memoria. Cuando el procesador ejecuta ese byte 0xCC lo interpreta como INT 3 y se ejecuta el manejador de esta excepción, que debe estar capturado por el debugger.

La excepción tipo 4 ocurre cuando el procesador ejecuta la instrucción INTO (interrupt on overflow) y el indicador de sobrepasamiento (bit 11 de EFLAGS) está encendido.

Aborts

Aquí se pueden observar las dos excepciones tipo abort.

La excepción tipo 8 ocurre cuando el procesador está vectorizando una excepción y en ese momento ocurre otra. Esto no pasa siempre. Más adelante en este video vamos a ver cuándo ocurre.

La excepción tipo 18 ocurre cuando hay un error interno del procesador, del bus o si un hardware externo detecta que hay error en el bus.

IDT

Aquí se puede ver la tabla de vectores de excepción que usa el procesador cuando está en modo protegido.

La tabla de descriptores de interrupción (en inglés la sigla es IDT (Interrupt Descriptor Table) tiene una compuerta por cada tipo de interrupción o excepción. De los 256 tipos diferentes que soporta la IDT, los 32 primeros están reservados para excepciones, excepto la entrada número 2, que se usa para las interrupciones no enmascarables. El procesador tiene una pata NMI que si se activa, se ejecuta el manejador de esta entrada número 2 independientemente del estado del bit 9 de EFLAGS, que es el indicador de habilitación de interrupciones. El registro IDTR apunta a la compuerta correspondiente al tipo de excepción cero. A continuación se encuentra la compuerta correspondiente al tipo de excepción 1, y así sucesivamente hasta la excepción 31. Más arriba en la IDT estarán las compuertas de interrupción.

Las compuertas son direcciones lógicas que se componen de selector y offset y tienen el formato que se muestra a la derecha. El tipo de compuerta en el caso de excepciones es 1111 binario. La dirección lógica apunta al inicio del manejador de la excepción.

Uso de la pila

En este diagrama se puede observar lo que el procesador envía a la pila cuando ocurre la excepción. Cada elemento de la pila tiene 32 bits. Como las direcciones crecen hacia arriba en el diagrama, se puede comprobar que cuando el procesador pone estos registros e información adicional en la pila, el puntero ESP se decrementa. Al ejecutar la instrucción IRET, el puntero ESP se incrementa.

Como vamos a ver en multitarea, cada nivel de privilegio tiene su pila asociada. Cuando hay un cambio de nivel de privilegio, generalmente de privilegio 3 a privilegio cero, el procesador deja de usar la pila de privilegio 3 y pone en la nuevo pila (la de privilegio cero) la información que figura en el diagrama. Eso incluye la dirección lógica de la pila de nivel de privilegio 3.

Dependiendo de la excepción, el procesador puede poner o no un valor en la pila denominado "código de error".

Código de error

Ahora vamos a ver en más detalle el código de error.

Varias excepciones no ponen código de error en la pila: 0 a 7, 16, 18, 19 y 20. Otras ponen simplemente el valor cero, como las excepciones 8 y 17.

Aquí se puede ver el código de error correspondiente a las excepciones que se refieren a errores de segmentación. Cuando no se puede determinar el descriptor correspondiente al problema que generó la excepción, el código de error vale cero.

Fallo de página

En este diagrama se puede ver el formato particular del código de error para la excepción 14 de fallo de página. Aparte de poner el código de error en la pila, el procesador carga en el registro de control CR2 la dirección lineal que causó el fallo de página.

Doble falta

Como dijimos antes, la doble falta se genera cuando durante la vectorización de una excepción ocurre una segunda excepción, bajo determinadas condiciones.

Para saber cuáles son esas condiciones, dividiremos las excepciones en tres grupos: fallo de página, excepciones contributivas y excepciones benignas. Esta clasificación no tiene ninguna relación con la clasificación de excepciones en fault, trap y abort que vimos antes.

Tabla generación doble falta

En la tabla se puede ver en qué casos ocurre la doble falta. En la columna izquierda figura la excepción que se está vectorizando, es decir la primera interrupción, mientras que en la fila superior se puede ver la excepción que ocurre durante esa vectorización, es decir la segunda excepción.

Si ocurre una excepción contributiva o fallo de página durante la vectorización de la excepción de doble falta, ocurre un shutdown. En la PC el shutdown genera un reset del procesador. Por eso es bastante habitual cuando hay errores de código ver que el sistema se reinicia.

Ejemplo de shutdown

Por ejemplo, si ejecuto la instrucción MOV ESP,1 y luego PUSH EAX, el registro EAX iría en el offset 1 - 4 = 0xFFFFFFFD. No se pueden poner los cuatro bytes en la pila porque se supera el límite. Como estamos trabajando con la pila, se genera la excepción 12.

Al intentar vectorizar la excepción 12, el procesador va a intentar guardar en la pila la dirección de retorno y no va a poder, porque se supera el límite del segmento de pila igual que antes.

Como son dos excepciones contributivas, se genera la excepción 8 de doble falta. Pero otra vez tampoco se puede vectorizar, lo que genera el shutdown y posterior reset.

Espero que esta explicación les haya sido útil. Hasta luego.