ejemplos modo protegido .pdf
Nombre del archivo original: ejemplos_modo_protegido.pdf
Título: Microsoft Word - Ejemplos de Modo Protegido.doc
Autor: Mariano
Este documento en formato PDF 1.3 fue generado por PScript5.dll Version 5.2 / GNU Ghostscript 7.06, y fue enviado en caja-pdf.es el 07/03/2013 a las 20:25, desde la dirección IP 93.135.x.x.
La página de descarga de documentos ha sido vista 9758 veces.
Tamaño del archivo: 275 KB (91 páginas).
Privacidad: archivo público
Vista previa del documento
EJEMPLOS DE
MODO PROTEGIDO
Programación en Assembler
Mariano Cerdeiro
[email protected]
1era Edición
Octubre de 2004
Buenos Aires
Argentina
http://www.soix.com.ar/links/mpbyexamples.html
Cerdeiro, Mariano
Ejemplos de modo protegido: programación en assembler. -1a ed. - Buenos Aires: el autor, 2004.
Internet; 91 p.
ISBN 987-43-8245-7
1. Programación-Computación I. Título
CDD 005
Todos los derechos reservados.
Esta publicación no puede ser reproducida, archivada o transmitida en forma total o parcial, sea
por medios electrónicos, mecánicos, fotocopiados o grabados, sin el permiso previo del autor.
ISBN 987-43-8245-7
A mis profesores Lic. Mirta Bindstein, Ing. Alejandro Furfaro e Ing. Marcelo Doallo.
Agradezco a Manuela Cerdeiro, mi hermana, quien tradujo el documento al español.
; Introducción
7
; ej00.asm - programa hola mundo
9
; ej01.asm - entrar y salir a modo protegido
13
; ej02.asm - programa que guarda el BIOS en un archivo
17
; ej03.asm - programa con interrupciones
25
; ej04.asm - excepciones en modo protegido
33
; ej05.asm - programa multitarea
49
; ej06.asm - salto de niveles de privilegio en modo protegido
61
; ej07.asm - modo protegido 32 bits y pila expand down
73
; ej08.asm - paginación
79
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
Introducción
Este libro tiene como finalidad cubrir la gran brecha existente entre
los libros y manuales de modo protegido y lograr correr un programa
sin que el mismo bootee la PC por algún error. Para ello se explica la
teoría necesaria para entender el código a continuación.
Cabe aclarar que este libro NO es de teoría de modo protegido, por
ello se recomienda llevar el estudio teórico con algún otro libro del
tema. Personalmente recomiendo los manuales de Intel, que el día de la
fecha se encuentran en:
http://developer.intel.com/design/Pentium4/documentation.htm#man
De este link se pueden bajar los manuales del Pentium 4. Los
conocimientos sobre modo protegido se pueden obtener de los capítulos
2, 3, 4, 5 y 6 del IA-32 Intel Architecture Software Developer's
Manual Volume 3: System Programming Guide.
Para programar en modo protegido se puede utilizar cualquier editor de
texto, como vi, notepad, edit u otro. Para compilar se utiliza el nasm
y para correr los programas, el bochs, con el freedos cargado en una
imagen de disquete, o booteando la PC en modo real, ya sea mediante un
disquete o booteando en modo real, si el sistema operativo lo permite.
El nasm es un compilador de assembler, se puede bajar de:
http://nasm.sourceforge.net/
El bochs es una PC virtual, y es una herramienta IMPRESCINDIBLE para
poder debugear el código de modo protegido.
http://bochs.sourceforge.net/
Y el Free DOS:
http://www.freedos.org/
La instalación de los programas dependerá del sistema operativo, del
freedos sólo es necesario bajar la imagen e indicar en el archivo de
configuración del bochs dónde encontrarla.
Por último es recomendable tener a disposición un manual de
interrupciones y de hard. Para tal fin existe la página de Ralf Brown:
http://www.ctyme.com/rbrown.htm
El libro está formado por nueve ejemplos de los siguientes temas:
- ej00.asm tutorial de DOS
- ej01.asm entrar y salir a modo protegido
- ej02.asm utilizando un segmento de datos en modo protegido
- ej03.asm interrupciones en modo protegido
- ej04.asm excepciones en modo protegido
- ej05.asm multitarea en modo protegido
- ej06.asm salto de niveles de privilegio en modo protegido
- ej07.asm modo protegido 32 bits y pila expand down
- ej08.asm paginación en modo protegido
Personalmente espero que el libro les sea útil, y que me hagan llegar
cualquier comentario o duda. A su vez pido disculpas por cualquier
error.
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
ej00.asm - programa hola mundo
Protected Mode by Examples – 1era Edición - Octubre 2004
Autor:
Mariano Cerdeiro
<[email protected]>
http://www.soix.com.ar/links/mpbyexamples.html
compilar: nasm ej00.asm -o ej00.com
El primer ejercicio tiene por finalidad mostrar la estructura básica
de un programa en nasm y el modo en que es cargado en memoria por el
DOS. También se explican algunos conceptos básicos, como la
segmentación en modo real y las diferencias entre un segmento de 16 y
uno de 32 bits.
En modo real (el modo en que se enciende la CPU y en el que se
mantiene cuando corre el sistema operativo DOS) se accede a la memoria
mediante segmentos y desplazamientos. El desplazamiento (u offset) es
un valor de 16 bits que se suma a la base del segmento para conocer la
memoria lineal y física (las cuales, en modo real, coinciden).
Siempre que se direcciona memoria, se hace mediante un segmento y un
offset. Para conocer cuál es la posición física de la memoria se deben
sumar la base del segmento y el offset. En modo real, la base de un
segmento es 16 veces su valor.
Por ejemplo: el segmento 0040:0037, al escribirlo de esta forma, se
hace referencia al segmento:offset (ambos en hexadecimal) que apunta a
la dirección de memoria 00437h.
Por lo cual, para acceder a cualquier posición de memoria, existen
4096 formas distintas de hacerlo ya que, por ejemplo, 4000:FFFF
será físicamente la misma posición que 4001:FFEF (físicamente ambas
direcciones serán 4FFFF, o sea, el byte número 327679 de la memoria).
El DOS es quien carga los programas .com, que son los únicos que se
utilizan en este libro. A diferencia de los archivos .exe, que poseen
un header con información para el sistema, los archivos .com son
directamente copiados a memoria y ejecutados, pero existen ciertas
reglas a conocer:
- cuando se ejecuta un archivo .com, el mismo se carga en memoria
a partir del primer segmento libre de memoria que tenga
disponible el DOS.
- se le asigna toda la memoria hasta los 640 Kbytes.
- se carga el contenido del del archivo .com a partir del offset
100h, quedando así 256 bytes para el sistema, los cuales se
denominan PSP (Program Segment Prefix).
EL PSP almacena información de la aplicación (en Internet se puede
averiguar cómo está conformado). Sin embargo, lo más importante a
saber es que en el offset 80h se encuentra un byte que indica la
cantidad de caracteres que contiene la línea de comandos, y a partir
del offset 81h se encuentra la línea de comandos.
Al cargar el .com se inicializan los registros
0, el sp en FFFCh y cs,ds,es,ss en el segmento
ip en 100. Por lo que se puede observar que el
superponen en el mismo segmento y éstos con la
ax,bx,cx,dx,bp,si,di en
de memoria libre, y el
código y los datos se
PILA. Por eso se debe
; tener especial CUIDADO cuando se utiliza mucha pila, ya que una
; excesiva cantidad de pushs podría sobrescribir el código o los datos
; del programa.
;
; Lo primero que se escribe al comenzar un programa es la directiva
; use16 o use32. Ésta indica para qué tipo de segmento debe ser
; compilado el programa (para un segmento de 16 o de 32 bits).
;
use16
;
;
; Segmentos de 16 y de 32 bits.
;
; Que un segmento sea de 16 o de 32 bits, indica qué tipo de registros y
; direccionamiento se utilizan en forma predeterminada, de modo que las
; instrucciones que utilizan registros y direccionamientos
; predeterminados ocupan menos espacio. Veamos un ejemplo:
;
; 89 C8
mov ax,cx
;
; Se puede observar que la instrucción se codifica como 89 C8. Se trata
; de una instrucción que ocupa 2 bytes. Haciendo lo mismo con eax y ecx
; se obtiene:
;
; 66 89 C8
mov eax,ecx
;
; Aquí, al utilizar eax y ecx, se ocupa un byte más, el 66. Éste
funciona
; como prefijo, y su función es indicar al procesador que la siguiente
; instrucción se debe decodificar utilizando el tipo de registros no
; predeterminado. De esto que se deduce correctamente que las
; instrucciones anteriores han sido compiladas para 16 bits. Si, por el
; contrario, se compilan estas dos mismas instrucciones en código 32
; bits, se obtiene:
;
; 89 C8
mov eax,ecx
; 66 89 C8
mov ax,cx
;
; Se puede ver que la instrucción más corta es la de 32 bits. 66h no es
; el único prefijo que existe, también existe el 67h, que cumple una
; función similar, pero no con el tamaño del registro, sino con el tipo
; de direccionamiento (si es de 16 o de 32 bits). También existen otros
; prefijos, pero no están relacionados con el tipo de segmento.
;
; Por lo que una misma instrucción se puede codificar de dos formas
; distintas, según se trate de un segmento de 16 o de uno de 32 bits (se
; le debe indicar al procesador de qué tipo de segmento de código se
; trata). Para tal fin existe un bit en la definición del segmento. Sin
; embargo, en modo real, los segmentos pueden ser únicamente de 16 bits,
; por lo que se dejarán para más adelante los segmentos de 32 bits y se
; utilizarán, hasta el ejemplo 6 inclusive, segmentos de 16 bits.
;
org 100h
;
; Debido a que el compilador desconoce que los archivos .com son
; cargados y ejecutados a partir del offset 100h, se debe indicar el
; offset inicial. De esta forma, al referirse a la cadena mensaje (leer
; líneas más delante), mediante el puntero, el compilador sabrá que
; el mismo tiene valor de 102h (suponiendo 2 bytes que ocupa el jmp
; inicio).
;
jmp inicio
;
; En los archivos .com se cuenta con un solo segmento para código y
; datos, como se mencionó. Por eso se debe hacer un jmp para saltar los
; datos, o bien colocar los datos al final. En todos los ejemplos se
; colocarán los datos al comienzo.
;
mensaje
db
'Hola Mundo$'
;
; mensaje es la única variable de este ejercicio y es un array de db
; (data bytes), donde el primer byte es H, el segundo O y sucesivamente.
; El último caracter es un símbolo $, que es interpretado por el
; servicio DOS como fin del mensaje.
;
inicio:
;
; Es el label donde comienza el código.
;
mov ah,09h
mov dx,mensaje
int 21h
;
; En estas tres líneas se carga en ah el valor 9, en dx el offset que
; corresponde al mensaje. Al llamar a la interrupción 21h interrupción
; de DOS con ah en 09 se imprime en pantalla lo apuntado por ds:dx
; hasta encontrar un caracter $.
;
mov ah,4ch
int 21h
;
; Estas dos líneas llaman nuevamente al DOS pero al servicio 4C, el cual
; termina el programa volviendo al DOS.
;
; ej01.asm – entrar y salir a modo protegido
;
; Protected Mode by Examples – 1era Edición - Octubre 2004
;
; Autor:
; Mariano Cerdeiro
; <[email protected]>
; http://www.soix.com.ar/links/mpbyexamples.html
;
; compilar: nasm ej01.asm -o ej01.com
;
; Este programa entra y sale de modo protegido. Se mantiene en modo
; protegido hasta que se presione la tecla escape para entonces retornar
; a modo real.
;
; Entrar a modo protegido implica setear el bit 0 del registro cr0 PE
; (Protection Enable). Para retornar se debe hacer lo inverso, resetear
; nuevamente el bit PE.
;
; Como su nombre lo indica, modo protegido es un modo en el que el
; procesador brinda al SO herramientas para poder proteger tanto a una
; aplicación de otra, como al SO de todas las aplicaciones. De esta
; forma, si alguna genera un error, bastará con cerrarla. De modo que,
; si, estando ya en modo protegido (por ejemplo en una ventana de
; Windows), se intenta setear el bit PE (el cual ya estará seteado), el
; programa será cerrado por el sistema operativo, indicando que no se
; puede correr la aplicación.
;
use16
org 100h
;
; Al tratarse de un archivo .com se coloca el org correspondiente, y
; como se parte de modo real se debe usar segmento de 16 bits.
;
jmp inicio
;
; Se saltean las variables por costumbre, a pesar de que en este caso no
; hay.
;
inicio:
;
;
mov eax,1
;break: cmp eax,1
;
je break
;
; IMPORTANTE:
; Las anteriores líneas se colocan aquí para poder comenzar a utilizar
; el bochs como herramienta de depuración. Lo que se debe hacer es
; quitar los comentarios de las líneas y compilar el programa. Al correr
; el bochsdbg, pulsar ctrl-c en la consola de debug, una vez ejecutado
; el programa ej01.com. Del manual del bochs y del help de la línea de
; comandos se puede obtener ayuda para entender su funcionamiento.
;
; Con set $eax=0 <ENTER> se hace cierta la condición para salir del
; bucle, para luego, con s <ENTER>, ejecutar paso a paso el programa.
; Este método de depuración se puede copiar en cualquier parte del
; programa. Es conveniente colocarlo después de la parte del programa
; que no falla, para hacer el proceso de depuración lo más simple
; posible. También se lo puede utilizar para saber qué está sucediendo
; exactamente al ejecutar estos u otros ejercicios.
;
;
;
;
;
;
Se debe RECORDAR eliminar estas líneas cuando se desee correr sobre la
la PC real, ya que el programa se bloqueará indefinidamente hasta una
interrupción o, en el caso en que estén inhabilitadas, hasta que se
reinicie la PC.
cli
;
;
;
;
;
;
En modo protegido la tabla de interrupciones se maneja de forma
distinta e incompatible con la de modo real, por lo que se deben
inhabilitar las interrupciones hasta conocer y entender el mecanismo
de interrupciones (ver ej03.asm).
mov eax,cr0
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;lee el registro de control 0.
El bit PE es el bit 0 del cr0. Para mantener el resto de los bits
inalterados, antes de modificar el registro, se lee su contenido y
se setea únicamente el bit deseado. Las formas de acceder al bit PE
son:
mov cr0,exx y mov exx,cr0
lmsw reg16 y smsw reg16
A diferencia del mov al registro cr0, el segundo grupo de
instrucciones sólo accede a la parte baja del cr0 (el cual en el
80286 se denomina Machine Status Word). Por otro lado, las
instrucciones mov del cr0 no se pueden correr estando en modo
protegido en los niveles NO privilegiados, en los que se corre, por
ejemplo, el programa en una ventana bajo Windows. Por lo tanto, para
saber si el procesador se encuentra ya en modo protegido, se debe
utilizar smsw. En este ejercicio se lee directamente del cr0, por lo
que el programa se cerrará si el procesador se encuentra en modo
protegido. En ejemplos posteriores se utilizará la instrucción smsw
para indicar en la pantalla, mediante un mensaje, si el procesador se
encuentra en modo protegido.
or al,1
;setea el bit PE Protection Enable.
;
; Se coloca el bit correspondiente en 1.
;
mov cr0,eax
;pasa a modo protegido.
;
; Se pasa a modo protegido.
;
jmp short $+2
;vacía la cola de ejecución.
;
; Intel recomienda vaciar la cola de ejecución al entrar y salir de modo
; protegido. Si bien es posible que el programa funcione a pesar de no
; hacerlo, Intel no lo puede asegurar.
;
mp:in al,60h
;lee el scan-code del teclado.
;
; Se lee el contenido del puerto 60h, o sea, el scan-code del teclado.
;
dec al
;compara con esc.
;
; Se decrementa el scan-code para compararlo con 0, por ser esto más
; simple que comparar con uno.
;
jnz mp
;si es esc, se retorna a modo real.
;
; Si el scan-code leído del puerto 60h es distinto de 1, significa que
; no se pulsó la tecla escape, de modo que se sigue dentro del bucle.
;
mov eax,cr0
and al,0feh
;resetea el PE.
;
; Se lee nuevamente el valor de cr0 y se coloca en cero el bit de PE.
;
mov cr0,eax
;retorna a modo real.
jmp short $+2
;vacía la cola de ejecución.
;
; Se carga el cr0, con lo que se pasa nuevamente a modo real. Luego se
; realiza nuevamente un salto corto para vaciar la cola de ejecución.
;
sti
;habilita las interrupciones.
mov ah,4ch
;retorna al DOS mediante el
int 21h
;servicio 4ch de la int 21h.
;
; Se habilitan las interrupciones nuevamente y se retorna al sistema.
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
ej02.asm - programa que guarda el BIOS en un archivo
Protected Mode by Examples – 1era Edición - Octubre 2004
Autor:
Mariano Cerdeiro
<[email protected]>
http://www.soix.com.ar/links/mpbyexamples.html
compilar: nasm ej02.asm -o ej02.com
Este ejercicio guarda en el archivo bios.dat la parte de BIOS del
start-up. Como es sabido, los procesadores, a partir de la 80386
comienzan a ejecutar el código que encuentran en memoria en el
segmento F000 y offset FFF0. Pero este segmento, a pesar de
encontrarse en modo real, no tiene base igual a F0000h, como se podría
suponer, sino que su base es FFFF0000. A pesar de iniciarse en
modo real, el comportamiento del segmento de código no es el normal de
modo real. La primera vez que se modifique el cs, su base será cargada
normalmente (o sea, la base será 16 veces el valor del cs), en tanto
la base del segmento de código permanecerá en FFFF0000. Ya que el
procesador comienza ejecutando esta sección de la memoria, es de
suponer que en la misma debe haber memoria ROM cuyo código inicialice
a la PC, o al menos los bytes suficientes para llevar a cabo un salto.
Es por esto que las PCs tienen 64 Kbytes de ROM a partir de la
posición de memoria física FFFF0000 y hasta FFFFFFFF.
Para copiar el BIOS a un archivo es necesario entrar a modo protegido,
donde se tendrá acceso a los 4Gb de memoria, y solamente al primer
mega. De esta forma se tendrá acceso a la ROM del BIOS. Por otro lado,
se debe guardar esta información en un archivo. Lamentablemente, el
modo protegido también tiene un defecto y es que no hay rutinas de
BIOS ni DOS para guardar un archivo en disco, por lo cual se puede
hacer un driver de disco para modo protegido (tema que excede al
libro), o bien copiar el BIOS por debajo del mega de memoria,
retornar a modo real y, una vez en modo real, grabar el archivo
mediante las llamadas al sistema convencionales de DOS.
Ahora lo que se necesita conocer es cómo se direcciona la memoria en
modo protegido, para así poder leer el BIOS que se encuentra al final.
En modo protegido, al igual que en modo real, los segmentos tienen una
base y un límite, pero además tienen otros atributos, como permisos de
acceso y otros indicadores. A diferencia del modo real, donde la base
de un segmento es el valor del selector (cs,ds,es,fs,gs,ss)
multiplicado por 16, en modo protegido la misma puede ser cualquier
valor dentro de los 4Gb. Asimismo el límite, que en modo real es de
65536 bytes, puede ser de cualquier valor entre 0 y 4 Gb. Claramente
se puede intuir que toda esta información no puede ser almacenada en
16 bits que poseen cs, ds, es, fs, gs y ss.
Segmentación en modo protegido
En modo protegido los selectores cs, ds, es, fs, gs y ss son un
puntero a una entrada de una tabla, la cual almacena información sobre
cómo es el segmento: base, límite y derechos de acceso. El formato de
los selectores en modo protegido es el siguiente:
15
3 2 1 0
+-------------+---+----+
|
Índice
|TI | PL |
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
+-------------+---+----+
- ÍNDICE: Indica qué entrada de la tabla se debe utilizar.
- TI (Table Indicator): Indica qué tabla se debe utilizar, la
GDT o la LDT.
- PL (Privilege Level): Indica qué nivel de privilegio tiene este
selector.
Los selectores pueden apuntar a una de dos tablas de descriptores, a
la GDT (Global Descriptor Table) o a la LDT (Local Descriptor Table).
Estas tablas, como el nombre indica, están formadas por descriptores.
Cada descriptor ocupa 8 bytes y consta de:
31
16 15
0
+----------+-+-+-+-+-----+-+-+-+-+----+-----------+
|
Base
| | | | |Limit|P|D P|#|Tipo|
Base
| 4
|
31:23 | | | | |19:16| | L |S|
|
22:16
|
+----------+-+-+-+-+-----+-+-+-+-+----+-----------+
+------------------------+------------------------+
|
Base 15:0
|
Limit 15:0
| 0
|
|
|
+------------------------+------------------------+
31
16 15
0
- Base 31:0: Base LINEAL del segmento, puede ser cualquier
posición de memoria LINEAL dentro de los 4Gb lineales
direccionables.
- D/B: Su función es variable. Si es un segmento de código indica
si el segmento es de 16 o 32 bits. Si es un segmento de datos de
pila, indica el tamaño de registros de pila, por ejemplo, si
está seteado, a pesar de que se pushee ax, decrementará el
puntero de la pila en 4 para mantenerla alineada en 4 bytes.
Si es un segmento de datos expand down, también indica si el
límite superior es 0FFFFh ó 0FFFFFFFFh.
- AVL: A disposición del programador del sistema.
- G: Granularidad. Como se puede ver, hay solamente 20 bits para
indicar el largo del segmento. Este bit cambia las unidades del
límite de bytes a 4096 bytes. Entonces, al estar G seteado y el
límite al máximo, se obtendrá un límite de 4Gb.
- Límite 19:0: Límite del segmento en cantidad de bytes o 4096
bytes (ver G).
- P: Bit de presencia. Indica si el segmento está presente en
memoria o si está en memoria virtual. La finalidad de este bit
es poder administrar la memoria virtual en segmentación. Hoy en
día se suele utilizar la paginación para la memoria virtual,
pero existe la posibilidad de utilizar la segmentación. Para
eso, al faltar memoria, se quita el segmento resetanado el bit
de presencia y se podrán utilizar todos los campos salvo el 5
byte para indicar en qué parte del disco se ha almacenado este
segmento. De esta forma se podrá usar la sección de memoria que
el segmento ocupaba. En el caso de que alguna aplicación o el
mismo sistema desee utilizar el segmento no presente, se
generará una excepción (ver ejercicio 4) que podrá cargar el
segmento donde sea correcto y continuar la ejecución normal del
programa.
- DPL: Nivel de privilegio del descriptor (Descriptor Privilege
Level).
- #S: Indica si se trata de un segmento de sistema o no (0
significa que es de sistema).
- TIPO: Si #S es 1, se trata de un segmento, entonces el campo
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
tipo puede ser alguno de los siguientes:
Campo TIPO con #S en 1
Segmentos de datos (C/#D en 0)
C/#D
0
0
0
0
0
0
E
0
0
0
0
1
1
W
0
0
1
1
0
0
A
0
1
0
1
0
1
0
0
1
1
1
1
0
1
Descripción
sólo lectura
sólo lectura, accedido
lectura/escritura
lectura/escritura, accedido
sólo lectura, expand down
sólo lectura, expand down,
accedido
lectura/escritura, expand down
lectura/escritura, expand donw,
accedido
Segmentos de código (C/#D en 1)
C/#D
1
1
1
1
1
1
1
1
C
0
0
0
0
1
1
1
1
R
0
0
1
1
0
0
1
1
A
0
1
0
1
0
1
0
1
ejecución
ejecución, accedido
ejecución/lectura
ejecución/lectura, accedido
ejecución, conforming
ejecución, conforming, accedido
ejecución/lectura, conforming
ejecución/lectura, conforming,
accedido
NOTA: El bit denominado aquí como C/#D, en el manual de Intel no tiene
denominación. La misma se realiza aquí sólo para su mejor comprensión.
El mismo indica si se trata de un segmento de datos, en caso de estar
reseteado, o de un segmento de código, en caso de estar seteado.
Continuando con el campo tipo, el mismo, en caso de ser
el campo S=0, se tratará de un descriptor de sistema.
0
0
0
0
0
0
0
0
1
1
1
1
1
1
1
1
0
0
0
0
1
1
1
1
0
0
0
0
1
1
1
1
0
0
1
1
0
0
1
1
0
0
1
1
0
0
1
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1
Reservado
16-Bit TSS (no ocupada)
LDT
16-Bit TSS (ocupada)
16-Bit Call Gate
Task Gate
16-Bit Interrupt Gate
16-Bit Interrupt Gate
Reservado
32-Bit TSS (no ocupada)
Reservado
32-Bit TSS (ocupada)
32-Bit Call Gate
Reservado
32-Bit Interrupt Gate
32-Bit Trap Gate
Hasta aquí se trataron muchos nuevos conocimientos, que no serán tan
sencillos ni fáciles de asimilar. Por eso parece razonable hacer un
repaso. Para empezar, del 286 en adelante los registros CS,DS,ES,FS,GS
y SS ya no se denominan registros de segmento sino selectores. Los
; mismos no apuntan ya a la base física dividido 16, sino que tienen un
; campo índice y uno tabla, que hacen referencia a una de dos tablas,
; que pueden ser la GDT o la LDT, ambas de descriptores. Los
; descritpores pueden ser de segmentos del sistema, según el campo S. Si
; son descriptores de segmentos pueden ser apuntados por CS,DS,ES, FS,
; GS y SS; en caso contrario tendrán otras finalidades que veremos más
; adelante.
;
; Siendo el objetivo de este problema leer el BIOS, que se encuentra en
; los últimos 64 Kbytes de memoria, se debe armar un segmento que apunte
; a ese sector de memoria física. Para eso se debe armar un descriptor
; que apunte a ese sector de memoria y cargar el selector que direcciona
; al descriptor. De esta forma se podrá leer el BIOS y copiarlo debajo
; del mega de memoria para así grabarlo en un archivo.
;
; Para grabar un archivo bajo DOS se utilizan las funciones de DOS (int
; 21h).
;
; Crear o truncar un archivo Int 21 ah=3C.
; CX = file attributes, 0 es un archivo normal.
;
DS:DX -> ASCIZ filename
;
Return ax=handler
;
; Cerrar un archivo Int 21 ah=3E.
; bx=handler
;
; Escribir en un archivo Int 21 ah=40.
; bx=handler
; cx=cantidad de bytes
; ds:dx -> datos a escribir
;
org 100h
comienzo:jmp inicio
;
archivo
db
'bios.dat',0
;
; Aquí se define un nombre para el archivo, que debe terminar en NULL,
; porque así interpreta el DOS el fin del nombre (no se lo debe
; confundir con cómo interpreta el fin de línea para imprimir un
; string).
;
gdtr
dw 8*3-1
dd 0
;
; Hasta aquí se trataron la GDT y la LDT, que tienen descriptores (que
; son de 8 bytes) y que los selectores deben hacer referencia a algún
; descriptor. Sin embargo no se mencionó en qué posición de memoria se
; encuentran estas tablas y el procesador no lo puede adivinar. Para
; ello existe un registro llamado GDTR (Global Descriptor Table
; Register) formado por 6 bytes, que indica, en 1 word, el largo menos 1
; de la tabla y, en un double word, la base lineal de la tabla (para la
; LDT se utiliza otro método que no se estudiará aquí). Se coloca el
; largo, se indica 8 por el largo de cada descriptor, 3 porque se quiere
; utilizar 3 de ellos y –1 por la definición de límite.
;
; Al colocar la base pueden surgir dudas. ¿Dónde esta la base de la GDT?
; Se sabe dónde comienza: en gdt: (ver abajo). Pero, ¿dónde quedará
; esa posición cuando el DOS o algún loader cargue el programa? Es
; imposible saber esto de antemano, por lo cual se debe cargar la base
; en tiempo de ejecución. Como el programa comienza a ejecutarse en modo
; real, el CS es la base sobre 16 y el offset es gdt:. Para conocer la
; base se calcula CSx16+gdt:.
;
gdt:
resb 8
;
; A partir de aquí comienza la GDT. Se puede observar que la primera
; entrada se deja en blanco. La razón es que el micro NO UTILIZA el
; primer descriptor de la GDT, el cual se denomina descriptor NULO y
; puede ser referenciado por cualquier selector, con el fin de colocar
; los selectores no utilizados en algún valor.
;
;--- 08h Segmento de datos flat
dw 0ffffh
;límite 15.00
dw 00000h
;base 15.00
db 000h
;base 23.16
db 10010010b
;Presente Segmento Datos Read Write
db 10001111b
;G y limite 0Fh
db 00h
;base 31.24
;
; Para inicializar el segmento se debe conocer la base y el límite que
; dependen de lo que se quiere hacer. En este caso se coloca la base en
; 0 y el límite de 4Gb, el BIOS también se podría leer colocando la base
; en FFFF0000h y el límite en 0FFFFh. Un segmento que tiene base en 0 y
; límite 4Gb se denomina FLAT.
;
; Como el límite es mayor que el Mbyte, se debe setear la granularidad,
; entonces se expresa en unidades de 4 Kbytes: 4 Gb/4 Kbytes-1, o sea,
; 0FFFFFh. Donde justamente los 20 bits del límite están seteados. Es
; por esto que en el primer word los bits 15:0 del límite están
seteados.
; La base es 0. Luego se indica el tipo de segmento (de datos, de
; lectura o de escritura). Es indistinto como se setea el bit accedido.
; Este es automáticamente colocado en 1 por hard cada vez que se utiliza
; el descriptor. Así el SO puede llevar un control de cuánto se utiliza
; cada descriptor. Colocando en cero este bit periódicamente sabrá
; aproximadamente cada cuánto tiempo es accedido. El objetivo final es
; saber cuál es el segmento más indicado para enviar a memoria virtual.
; En el último byte se deben colocar la granularidad en 1 y el resto del
; límite también. El 8º byte, se coloca cero, que representa la parte
; alta de la base del segmento.
;
;--- 10h Segmento de datos de 64 Kb
dw 0ffffh
dw 0
db 0
db 10010010b
db 0
db 0
;
; El segundo descriptor se utiliza para mantener compatibilidad con
; el modo real. En modo real el límite es de 65535 bytes, por lo que es
; recomendable que, antes de retornar a modo real, todos los límites
; estén en este valor.
;
; En este descriptor el límite es de 0FFFFh (64 Kbytes). La base, al no
; ser conocida (ya que depende de dónde el DOS cargue el programa), se
; deja en blanco, para completarla en tiempo de ejecución con 16 veces
; el valor indicado por CS más lo que ocupe el código.
;
inicio:
mov al,0d1h
out 64h,al
mov al,0dfh
out 60h,al
;Eanble A20
;
; A20 hace referencia a una AND que funciona como habilitación de la
; línea 20 del bus de direcciones. Cuando el A20 está en 0, la línea 20
; del bus de direcciones es cero siempre; cuando A20 está en 1, la línea
; A20 del bus de direcciones es la que corresponde según la referencia a
; memoria física que se haga. Si se intenta acceder a la dirección
; 3FFFFFh con la A20 en cero, se accederá a la dirección 2FFFFFh, o sea
; que el bit 20 de las direcciones es colocado en cero por la AND.
;
; La AND en el Address Bus 20 fue colocada por compatibilidad con la
; 8086. En el 8086 el micro, al direccionar la posición de memorias
; superiores al mega (por ejemplo haciendo al segmento igual a 0FFFFh y
; el offset mayor a 0Fh), se direcciona nuevamente; la parte baja de
; memoria. Por ejemplo para direccionar el primer byte físico de la RAM
; se puede hacer con DS en cero y offset 0, o bien con DS en FFFFh y
; offset 10h. En los procesadores posteriores al 8086, estando en modo
; real y colocando DS en 0FFFFh y el offset en 10h, se accede al byte
; físico 100000h y no al 0h. Esto implica una incompatibilidad, por la
; cual se colocó la AND.
;
; La existencia de esta AND hace colocar en 1 la entrada de control,
; para poder acceder a los megas impares de memoria (todos los que
tengan
; en 1 el bit 20 del bus de direcciones). Para evitar complicaciones, al
; utilizar memoria superior al mega, se habilita
; la A20.
;
;mov al,0D1h
;out 64h,al
;mov al,0ddh
;out 60h,al
;Disable A20
;
; Éste es el procedimiento para inhabilitar la A20. A pesar de que no
; se use en este ejemplo, se lo deja planteado. Se debe tener en cuenta
; que a veces programas residentes de modo real utilizan la memoria
; entre 0FFFFh:10h y 0FFFFh:0FFFFh, por eso es recomendable no modificar
; esta sección de memoria.
;
xor eax,eax
;pone en cero eax.
mov ax,cs
;carga el cs en eax.
shl eax,4
;en eax=base lineal de cs.
mov ecx,eax
;almacena en ecx la base lineal de cs.
;
; Como se indicó anteriormente, para colocar en el GDTR y en el
; descriptor 10h se debe conocer la base lineal en la que el DOS cargó
; el código. Para eso, estando todavía en modo real, se lee el CS y se
; lo multiplica por 16 (que es lo mismo que hacer 4 veces un shift a la
; izquierda). Este valor debe ser almacenado en ecx y eax.
;
; Luego, con el valor de ecx, que es la base del segmento de código, se
; puede calcular la base de la GDT para colocar en el GDTR.
;
add ecx,gdt
;ecx=base lineal gdt
mov [gdtr+2],ecx
;carga la base de GDTR
lgdt [gdtr]
;carga el GDTR
;
; Con las primeras dos instrucciones se completó el campo de base del
; GDTR. Una vez realizado esto se debe cargar estos 6 bytes dentro del
; registro de la PC, para lo que existe la instrucción lgdt. También
; existe su función sgtr que lee el contenido, y cuya sintaxis es
; idéntica a la de lgdt.
;
; Luego se debe colocar la base al segmento de datos, que se encuentra
; debajo del mega. Para eso se necesita memoria libre que, por ser un
; .com, se encuentra desde el final del código del programa hasta los
; 640 Kbytes.
;
; IMPORTANTE: La pila se encuentra en el segmento de código en el
; desplazamiento sp=0FFFEh, posiciones de memorias que en este ejemplo
; serán utilizadas para almacenar el BIOS, lo cual es sólo posible
porque
; no se utiliza la pila. Es importante inhabilitar las interrupciones,
; evitando así el uso involuntario.
;
; Por otro lado se debe buscar un segmento de datos vacío por debajo del
; mega de memoria. Para eso se utiliza el label fin: que indica el
; final del código.
;
xor ebx,ebx
mov bx,fin+15
add eax,ebx
;eax=base lineal fin+15
;
; En eax se tiene la base lineal del segmento de código. Se suma el
; offset de fin: y 15. Como se busca un segmento, es recomendable que
; éste se encuentre alineado en 16 bytes.
;
shr eax,4
mov es,ax
;es segmento vacío de 64 Kb.
;
; Se divide por 16 y se lo carga en es.
;
cli
mov eax,cr0
or al,1
mov cr0,eax
jmp $+2
;
; Se pasa a modo protegido.
;
mov ax,8h
mov ds,ax
;
; Se carga en ds el selector que apunta al descriptor de un segmento de
; datos de FLAT, o sea que tiene la base en 0 y el límite en 4Gb.
;
mov di,0
mov esi,0FFFF0000h
mov cx,0ffffh
;
; Se inicializan las variables para copiar de ds:esi a es:di. No se
; utiliza la instrucción rep movsd, ya que el origen es esi (32 bits) y
; el destino es di (16 bits).
;
repmovsb:
mov al,[ds:esi]
mov [es:di],al
inc esi
inc di
dec cx
jnz repmovsb
;
; Se realiza el loop, copiando así 65536 bytes.
;
mov ax,10h
mov ds,ax
;se carga nuevamente un segmento de 64 Kb.
;de límite.
;
; Se carga en el selector que se utilizó un descriptor de límite de
; 64Kbytes por compatibilidad con modo real.
;
mov eax,cr0
and al,0feh
mov cr0,eax
;se pasa a modo real.
jmp $+2
;
; Se pasa a modo real y se realiza el jmp para vaciar la cola de
; instrucciones.
;
mov ax,cs
mov ds,ax
;se recupera el segmento de modo real.
;
; El selector ds apuntaba a un descriptor de segmento de datos de base 0
; y límite de 64 Kbytes. Sin embargo su valor era de 10h, por lo cual,
; en modo real, se podría interpretar que su base es de 100h. Por eso es
; que se carga nuevamente con un valor de modo real, así se acomoda la
; base correctamente.
;
mov ah,3ch
xor cx,cx
mov dx,archivo
int 21h
;se crea el archivo.
mov bx,ax
;se guarda el handler.
;
; Se crea un archivo utilizando la función de BIOS. Al handler del mismo
; se lo deja en bx.
;
mov ax,es
mov ds,ax
mov ah,40h
mov cx,0ffffh
xor dx,dx
int 21h
;se graba el archivo.
;
; Se graba el archivo. Cómo el segmento que se estaba utilizando estaba
; en ds, se lo pasa a ds.
;
mov ah,3eh
;se cierra el archivo.
int 21h
;
; Se cierra el archivo.
;
sti
mov ah,4ch
int 21h
;
; Se habilitan las interrupciones y se vuelve al DOS.
;
fin:
;
; Éste es label que se utiliza para conocer la ubicación del final del
; código.
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
ej03.asm - programa con interrupciones
Protected Mode by Examples revision 0.1 - Octubre 2004
Autor:
Mariano Cerdeiro
<[email protected]>
http://www.soix.com.ar/links/mpbyexamples.html
Compilar: nasm ej03.asm -o ej03.com
Este es un programa muy simple que muestra el uso de interrupciones en
modo protegido, también muestra cómo verificar si el microprocesador
ya se encuentra en este modo, evitando que, si lo está, el sistema
operativo cierre el programa indicando un error. El programa
intercepta la interrupción 8, es decir la IRQ0, la cual pertenece al
timer. La rutina de interrupción graba, en una variable global, el
scan-code actualmente presente en el puerto 60h (el del teclado),
mientras el programa principal espera hasta que esta variable scancode sea igual a 1 (tecla escape). Antes de comenzar a programar se
debe conocer el funcionamiento de las interrupciones en modo
protegido.
Interrupciones
En modo real se sabe que cuando sucede una interrupción el
microprocesador busca, en una tabla ubicada a partir de la posición
física 0, el vector de interrupción; cada vector ocupa 4 bytes.
Para conocer dónde se encuentra el offset y el segmento a ejecutar en
el caso de una interrupción se busca el vector lejano en la posición
interrupción x 4, a partir del comienzo de la memoria, ya que cada
vector de interrupción ocupa 4 bytes.
En modo protegido el procedimiento es similar, existe una tabla
llamada IDT (Interrupt Descriptor Table). Ésta no guarda vectores de
interrupción, sino que, como su nombre lo indica, almacena
descriptores. Sin embargo, a diferencia de las anteriores dos tablas
(GDT y LDT que aún no se vieron), esta tabla tiene únicamente 256
entradas, ya que es ésta la cantidad de interrupciones del procesador.
Las entradas de la IDT pueden ser de tres tipos y únicamente del
sistema, no se puede definir un segmento de datos en la IDT. Los
tipos son:
- Interrupt Gate
- Trap Gate
- Task Gate
La primera es una interrupción normal en la cual, para ingresar, se
inhabilitan las interrupciones. Ésta es la única característica que
la diferencia de la Trap Gate. Por otro lado, la Task Gate es un
puntero a una tarea. No se han visto tareas todavía, de modo que este
tema quedará para más adelante. Sin embargo, es bueno saber que al
llegar una interrupción se puede ejecutar directamente otra tarea.
Al igual que la GDT, la IDT tiene un registro que indica tanto su base
como su tamaño; se denomina IDTR (Interrupt Descriptor Table
Register) al cual se accede, como al GDTR, mediante dos instrucciones:
- LIDT fword ptr
- SIDT fword ptr
;
; La primera de ellas carga en IDTR el valor del puntero, mientras
; que SIDT lee el contenido del IDTR y lo coloca en la dirección del
; puntero.
;
use16
org 100h
comienzo:jmp inicio
modo_virtual_msg
db
'El procesador se encuentra en modo virtual, no'
'se puede correr la aplicación.$'
;
; El texto anterior servirá de mensaje de error cuando se ejecute el
; programa estando en modo virtual.
;
mask_real_pic1
db 0
;
; Se almacenará el valor de la máscara de interrupciones del PIC
; (Programable Interrupt Controler) en modo real, para poder restaurar
; al regresar. Se debe tener en cuenta que el programa no utilizará
; todas las interrupciones, y una forma prolija para habilitar algunas y
; otras no, es mediante el PIC.
;
real_idtr
resb 6
;
; Estos 6 bytes son para almacenar el valor del IDTR de modo real. En
; los x86 no existía este registro, sin embargo, a partir del x286, el
; mismo indica dónde se encuentra la IDT, tanto en modo protegido como
; en MODO REAL. Por eso es importante, antes de modificar el contenido
; del IDTR, guardar una copia para poder recuperar al volver a modo
; real.
;
scan_code
db
0
;
; Ésta será la variable global que almacenará el último scan-code.
;
gdtr:
dw 8*5-1
dd 0
;
; Puntero utilizado para cargar el GDTR. La base será calculada en
; tiempo de ejecución, ya que depende de donde cargue el SO el
; programa, mientras que el límite puede ya ser impuesto en 5
; descriptores (4, ya que el primero es el NULO y no se utiliza).
;
idtr
dw 8*10-1
dd 0
;
; Este puntero será el utilizado para cargar el IDTR. Al igual que con
; el GDTR la base será cargada en tiempo de ejecución, mientras que el
; límite será de 10 descriptores, ya que se necesita manejar la
; interrupción 9.
;
gdt
resb 8
;
; Se deja la primera entrada libre, ya que se trata del descriptor NULO.
;
;--- 08h Segmento de código de 64 Kb sobre el de modo real --seg_cod:dw 0ffffh
;Límite 15:0
dw 0
;Base 15:0
db 0
;Base 23:16
db 10011010b
;P PL #S Tipo
db 00000000b
db 0
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;G AVL Límite 19:16
;Base 31:24
Como segunda entrada de la GDT se coloca el segmento de código, que
está superpuesto con el de modo real. El límite es de 65536. La
base se coloca en tiempo de ejecución, de tal forma que se superponga
con el de modo real, que es donde está el código. El segmento está
presente, es de nivel de privilegio 0, se trata de un segmento, es de
código y legible. La granularidad es cero.
Ya se ha explicado explicado antes, pero siempre es bueno refrescar.
Tal vez sea llamativo, éste es el PRIMER programa en que se utiliza
segmento de código en modo protegido. ¿POR QUÉ? Al ejecutarse una
interrupción, y al retornar de ella, se modifica tanto el CS, como el
IP. En los ejemplos anteriores no se utilizaba CS de modo protegido,
ya que se mantenía el de modo real. Ahora se modificará el CS
implícitamente cuando ocurra una interrupción, por eso se debe
utilizar un selector de modo protegido válido.
Al suceder una interrupción se cargará el nuevo segmento de código y
esto se hará de forma correcta, en la pila donde se almacena la
dirección de retorno se guarda el CS de modo real. Cuando se ejecute
un iret al finalizar la interrupción, se intentará cargar el CS con el
valor del selector almacenado en la pila, que era el de modo real, sin
embargo se cargará estando en modo protegido. Esto generará una
excepción, ya que ese selector de la GDT o LDT no existirá.
;--- 10h Segmento de datos de 64 Kb sobre el de modo real.
seg_datos:dw 0ffffh
dw 0
db 0
db 10010010b
db 0b
db 0
;
; En modo protegido no se puede escribir en el CS.
; O sea, la instrucción:
;
; mov [cs:si],al
;
; No se puede ejecutar. Entonces, si se desea modificar una variable
; sobre el code segment, se debe inicializar un segmento de datos
; superpuesto con el de código. Para ello se inicializa el segmento con
; todos sus campos menos la base, ya que la misma estará superpuesta con
; la del segmento de código.
;--- 18h Segmento de datos flat.
dw 0ffffh
;límite 15.00
dw 00000h
;base 15.00
db 000h
;base 23.16
db 10010010b
;Presente Segmento Datos Read Write.
db 10001111b
;G y límite 0Fh
db 00h
;base 31.24
;
; Este segmento flat se utiliza para agregar al programa un detalle.
; Incrementar el primer byte de la pantalla sirve únicamente para poder
; asegurar que el programa se está ejecutando correctamente.
;
;--- 20h Segmento de datos de 64 Kb.
dw 0ffffh
dw 0
db 0
db 10010010b
db 0
db 0
;
; Por último se inicializa un segmento para mantener la compatibilidad
; al retornar a modo real, como recomienda Intel.
;
idt:
resb 8*8
;
; En la tabla de interrupciones se dejan las primeras 8 interrupciones
; sin inicializar, por lo cual se deben tomar las suficientes
; precauciones para que éstas no sucedan.
;
irq0: dw
0
dw
08h
db
0
db
10000110b
db
0
db
0
;
; La IRQ 0 es la del timer-tick y la que se necesita para este
; ejemplo, el offset será colocado en tiempo de ejecución, ya que no
; se sabe dónde el SO colocará el código. Si se puede indicar el
; selector de código en 8. El segmento está presente, con nivel 0, se
; trata de un descriptor de sistema del tipo interrupt gate de 16 bits.
;
irq0_han:
;
; Ésta será la rutina de antención al timer-tick, la cual incrementará
el
; primer byte de la pantalla y almacenará en la variable scan code el
; valor leído desde el teclado.
;
push ds
pushad
;
; Lo primero que se debe hacer en el servicio de interrupción es guardar
; en la pila los registros que van a ser modificados, en este caso DS y
; los registros de propósitos generales. En realidad en este ejemplo no
; es necesario, ya que el programa principal no los utiliza, pero es una
; práctica recomendable.
;
mov ax,18h
mov ds,ax
mov eax,0b8000h
inc byte [eax]
;
; En estas 4 líneas se carga el selector del segmento FLAT en ds, se
; apunta a la memoria de video en modo texto, ubicada en 0b8000h en
; todos los monitores VGA color y se incrementa el primer byte.
;
mov ax,10h
mov ds,ax
;
; Luego se carga el selector del segmento imagen del de código.
;
in al,60h
mov [ds:scan_code],al
;
; Se lee el scan code y se lo almacena en la variable global.
;
mov al,20h
out 20h,al
;
;
;
;
;
;
;
;
IMPORTANTE: Este paso es un error típico y genera muchos dolores de
cabeza. El PIC nunca ejecuta una interrupción mientras esté sucediendo
otra de mayor prioridad. Por eso es que por soft se le debe informar
cuándo la rutina de interrupción a finalizado. Para ello se debe
enviar un valor 20h al puerto 20h, de lo contrario nunca más se
ejecutará una irq0 (hasta resetear).
popad
pop ds
iret
;
; Se recuperan los registros y se retorna de la interrupción.
;
inicio:
;
; Aquí comienza el programa principal.
;
smsw ax
;
; Lo primero es verificar si se está en modo protegido, para lo que se
; lee el bit 0 del CR0. Pero leer el CR0 en nivel 3 estando en modo
; protegido genera una excepción, por eso se utiliza la instrucción smsw
; (store machine status word), que almacena los 16 bits bajos en el
; registro destino. A modo informativo: también existe una instrucción
; lmsw que cumple la función inversa, o sea, carga los 16 bits bajos de
; CR0 con el contenido del registro origen.
;
test al,1
;
; Se verifica el bit 0, que indica si se está en modo protegido o no.
;
jz en_modo_real
mov ah,09h
mov dx,modo_virtual_msg
int 21h
mov ah,4ch
int 21h
;
; Si se está en modo real, se imprime el mensaje de error y se retorna
; al DOS.
;
en_modo_real:
;--- Guardo el cs de modo real
mov [real_cs],cs
;
; En este ejemplo se utilizó un segmento de código de modo protegido.
; Por eso, al regresar a modo real, se necesitará el valor del selector
; de modo real, que se puede recuperar de varias formas. La más sencilla
; es directamente guardarlo en una variable.
;
;--- Se acomoda el CS
xor eax,eax
;eax=0
mov ax,cs
;almacena CS en eax.
shl eax,4
;en eax=base lineal de CS.
mov ebx,eax
;se guarda en ebx.
;
; Como se indica en la definición de los descriptores de los segmentos
; de datos y de códigos, se debe inicializar la base de los mismos en
; tiempo de ejecución. Para eso se multiplica por 16 el valor del
; segmento de código y se almacena una copia en ebx.
;
mov [seg_cod+2],ax
mov [seg_datos+2],ax
;
; En los bytes 2 y 3 de ambos descriptores se almacena la parte baja de
; la base.
;
shr eax,16
mov [seg_cod+4],al
mov [seg_datos+4],al
;
; Después se realiza un desplazamiento para colocar la parte alta de la
; base y cargar el 3er byte. No es necesario inicializar la parte más
; alta (últimos 8 bits), ya que la posición del CS en modo real nunca
; podrá exceder 0FFFFh, lo cual, multiplicado 16 veces, puede llegar a
; ser 0FFFF0h. Nunca un bit mayor al 19 estará seteado.
;
;--- Se inicializa el GDTR.
mov eax,ebx
add eax,gdt
mov [gdtr+2],eax
lgdt [gdtr]
;se carga el GDTR.
;
; En ebx se almacenó una copia de la base del segmento. Si a ésta se le
; suma el offset de la gdt, se obtiene la posición lineal de la misma
; para almacenar en el GDTR. Luego se carga el GDTR.
;
;--- Se inicializa la IDT.
mov ax,irq0_han
mov [irq0],ax
;
; En el descriptor de irq0 se debe colocar el offset. En este caso, el
; offset será respecto a la base del segmento de código que se utilizó.
; Como el mismo tiene una base igual a la de modo real, se utiliza el
; offset irq0_han directamente.
;
;--- Se carga la IDT.
sidt [real_idtr]
mov eax,ebx
add eax,idt
mov [idtr+2],eax
lidt [idtr]
;
; Con el IDTR se debe realizar una operación similar a la del GDTR y,
; como en ebx, se tiene todavía la base lineal del segmento de códigos.
; A este valor se le suma el offset de la idt y se almacena el valor en
; el IDTR. Luego se carga el IDTR.
;
;--- A modo protegido
cli
mov eax,cr0
or al,1
mov cr0,eax
;
; Dado que éste ya es el tercer ejemplo de modo protegido, no es
; necesario explicarlo.
;
jmp 08h:modo_protegido
;
; Aquí hay algo nuevo. Como siempre, luego de pasar a modo protegido,
; se realiza un salto. Sin embargo esta vez, como el salto es largo, la
; sintaxis es distinta. Primero se indica el selector y luego el offset.
;
modo_protegido:
;--- Se carga el segmento de datos.
mov ax,10h
mov ds,ax
;
; Se carga el selector que apunta al descriptor del segmento de datos
; imagen del de código. Para poder acceder a las variables que están
; después del jmp inicio y antes del label inicio:
;
;--- Se guarda el PIC de modo real y se carga el de modo
;protegido.
in al,21h
mov [mask_real_pic1],al
mov al,011111110b
out 21h,al
;
; Ahora se almacena el estado de la máscara del PIC en una variable,
; para luego, antes de retornar al DOS, dejar las mismas intactas. Y se
; coloca en el PIC la nueva máscara habilitando únicamente la irq0.
;
sti
;no olvidar setear las interrupciones.
esperar:
cmp byte [cs:scan_code],1
jne esperar
;
; Luego se habilitan las interrupciones y se espera un scancode igual a
; 1. Si llega dicho scancode se comienza el retorno a modo real.
;
cli
;--- Se carga en todos los selectores un segmento de 64 Kb de
; límite.
mov ax,20h
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
;--- Se retorna a modo real.
mov eax,cr0
and al,0feh
mov cr0,eax
;se pasa a modo real.
db 0eah
;código del salto.
dw modo_real
real_cs dw 0
;
;
;
;
;
;
;
;
;
;
;
El retorno a modo real no es mucho más complicado que en el ejemplo
anterior. En esta ocasión, debido a que se modificó el cs al entrar en
modo protegido, se debe realizar un salto lejano para retornar a modo
real. El salet debe ser al offset modo_real:, o sea, a la instrucción
siguiente. Como el compilador no puede interpretar un jmp a esta
dirección, se lo realiza manualmente. Para ello se debe conocer el
código de la instrucción jmp far, que es 0eah. Luego del código de la
instrucción se debe colocar el offset y el selector. Como offset se
coloca el de la siguiente instrucción, el label modo_real. Como
selector se utiliza una variable que se carga en el inicio del
; programa antes de pasar a modo protegido, con la líneas: mov
; [cs:real_cs],cs.
;
modo_real:
;--- Se carga el selector de modo real en todos los segmentos.
mov ax,cs
mov ds,ax
;se recupera el segmento de modo real
mov es,ax
mov fs,ax
mov gs,ax
;
; Una vez en modo real se recargan todos los selectores con valores
; coherentes para que su base quede en modo real.
;
;--- Se recuperan la idt y las máscaras.
lidt [real_idtr]
mov al,[mask_real_pic1]
out 21h,al
;
; Se carga el IDTR de modo real que se guardo antes de pasar a modo real
; y se recupera la máscara del PIC de modo real.
;
sti
mov ah,4ch
int 21h
;
; Por último se habilitan las interrupciones y se retorna a modo real.
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
ej04.asm - excepciones en modo protegido
Protected Mode by Examples – 1era Edición - Octubre 2004
Autor:
Mariano Cerdeiro
<[email protected]>
http://www.soix.com.ar/links/mpbyexamples.html
compilar: nasm ej04.asm -o ej04.com
Excepciones
En este ejemplo se estudiarán las excepciones. Las excepciones son
interrupciones generadas por el procesador cuando se detecta algún
tipo de violación. Las fuentes pueden ser:
- Cuando se detecta un error en el programa.
- Cuando el programa la genera.
- Por un error de chequeo del procesador.
Las excepciones se clasifican en 3 tipos:
- Fault: Son excepciones que pueden ser corregidas. Permiten que
el programa continúe su ejecución. Las fault suceden ANTES de
que se ejecute la instrucción que la genera.
- Trap: Son excepciones que permiten seguir con la ejecución
normal del programa pero que, a diferencia de las faltas,
suceden luego de la ejecución de la instrucción. O sea, el ip
apunta a la siguiente instrucción.
- Abort: No se puede conocer con certeza la instrucción que las
generó. No hay forma de regresar a la aplicación. Se trata de un
error GRAVE y la aplicación que la generó debe ser cerrada.
El procesador tiene reservadas las primeras 32 entradas de
interrupciones para las excepciones, del vector 0 al 31 decimal. Las
interrupciones del valor 32 al 255 están destinadas al usuario. Pero
las IRQ 0 a la 7 ocupan las interrupciones de la 8 a la 15, lo cual es
un inconveniente, ya que la excepción 8 está superpuesta con la IRQ0,
la 9 con la IRQ1 y sucesivamente. Es por eso que, cuando ocurre una
interrupción, como por ejemplo la 8, no se estará seguro de la
fuente. Podría haber sido generada por una excepción de doble falta o
por una IRQ del timer tick.
Para resolver este inconveniente se modifica la base del PIC 0, de
modo que la base del mismo se encuentre arriba de la interrupción 31,
a partir de donde Intel dejó disponible las interrupciones para el uso
del programador del sistema.
Las excepciones son las siguientes:
Vector Descripción
Tipo
Error
Code
No
No
0
1
Divide Error
Debug
Fault
Fault/
Trap
2
NMI Interrupt
-
-
3
4
5
Breakpoint
Overflow
BOUND
Trap
Trap
Fault
No
No
No
Fuente de la excepción
Instrucciones DIV o IDIV
Instrucción Int 1 o
Cualquier instrucción o
referencia a datos
Interrupción no
enmascarable
Instrucción INT3
Instrucción INTO
Instrucción BOUND
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
1
6
Invalid Opcode
Fault
No
7
No Math CoProcessor
Fault
No
8
Double Fault
Abort
Yes
(cero)
9
Fault
No
10
Coprocessor segment
overrun(386 only)
Invalid TSS
Fault
Yes
11
Segment no present
Fault
Yes
12
Stack Segment Fault
Fault
Yes
13
General Protection
Fault
Yes
14
Page fault
Fault
Yes
15
16
17
Reserved
FPU Error
Alignment check
Fault
Fault
No
Yes
18
19
20-31
32-255
Machine check
SIMD Exception
Reserved
User defined
Abort
Fault
-
No
No
-
Instrucción U2D o
Instrucción desconocida
Instrucciones F___ o
instrucción WAIT.
Cualquier instrucción
que pueda generar
excepciones, NMI o INTR
Instrucciones de coma
flotante
Cambio de tarea o acceso
al TSS
Cargando un selector de
segmento o al acceder a
un descriptor de sistema
Operaciones de pila y al
cargar ss
Cualquier referencia a
memoria y otros chequeos
de protección
Cualquier referencia a
memoria
FPU instructions
Cualquier referencia a
memoria
Instrucciones SSE/SSE2
-
Esta lista fue copiada del manual del Pentium 4: IA-32 Intel
Architecture Software Developer's Manual, Volumen 3: System
Programming Guide, Capítulo 5: Interrupt and Exception Handling.
Como se puede observar, hay distintos tipos de excepciones, según sea
la causa que la genere. Además se puede notar que algunas excepciones
tienen error code. Error code es un valor que se pushea en la pila,
posterior a los flags, cs y eip, y que brinda información sobre lo que
a causado la excepción. No todas las excepciones tienen código de
error, sólo en algunas de ellas.
El Código de error tiene un formato especifico para todas las
excepciones, salvo para la Page Fault. El formato común es el
siguiente1:
31
16 15
0
+------------------------+------------------+-+-+-+
|
Reservado
|
Índice del
|T|I|E|
|
|
Selector
|I|D|x|
+------------------------+------------------+-+-+-+
En este ejercicio se busca colgarse de algunas excepciones y generar
una intencionalmente. La rutina de atención a las excepciones imprime
en la pantalla el estado de todos los registros y retorna a modo real.
El ejercicio además utiliza la IRQ 1 para leer una tecla y en función
de si se trata de un ESC 1 ó 2, no generar excepción, generar una de
protección general o una de instrucción inválida respectivamente.
En el manual de Intel se habla de un error code de 32 bits. Sin embargo, cuando se trabaja en 16 bits, como
en este ejercicio, es de 16 bits, la parte baja.
;
org 100h
comienzo:jmp inicio
;
; Lo primero que se debe hacer es definir una estructura donde se
; almacenarán todos los registros que luego se imprimirán en pantalla.
;
struc registros_struc
.reg_EIP
resd 1
.reg_EFLAGS
resd 1
.reg_EAX
resd 1
.reg_ECX
resd 1
.reg_EDX
resd 1
.reg_EBX
resd 1
.reg_ESP
resd 1
.reg_EBP
resd 1
.reg_ESI
resd 1
.reg_EDI
resd 1
.reg_ES
resw 1
.reg_CS
resw 1
.reg_SS
resw 1
.reg_DS
resw 1
.reg_FS
resw 1
.reg_GS
resw 1
endstruc
;
struc excepciones_struc
.excepcion resd 1
.errorcode? resw 1
.errorcode resw 1
.registros resb registros_struc_size
endstruc
;
; Se declara la variable excepciones del tipo excepciones_struc.
;
excepciones resb excepciones_struc_size
;
; Se colocan los mensajes que se deben imprimir.
;
eax_msg
db
'EAX=',0
ebx_msg
db
'EBX=',0
ecx_msg
db
'ECX=',0
edx_msg
db
'EDX=',0
esi_msg
db
'ESI=',0
edi_msg
db
'EDI=',0
esp_msg
db
'ESP=',0
ebp_msg
db
'EBP=',0
cs_msg
db
' CS=',0
ds_msg
db
' DS=',0
es_msg
db
' ES=',0
fs_msg
db
' FS=',0
gs_msg
db
' GS=',0
ss_msg
db
' SS=',0
eip_msg
db
'EIP=',0
eflags_msg
db
'EFLAGS=',0
errorcode_msg
db
'ERRORCODE=',0
excepcion_msg
db
'EXCEPCIÓN=',0
;
; La que sigue es la función utilizada para mostrar los registros.
; Esta función llama a otras 3, clrscr, que borra la pantalla,
; mostrar_cadena, que imprime una cadena de caracteres hasta encontrar
; un NULL y mostrar_valor, que imprime un eax en hexadecimal.
;
mostrar_reg:
;
; Claramente se borra la pantalla, más adelante está la función.
;
call clrscr
;
; Se guarda el estado de los registros y el DS. En el DS se carga el
; segmento solapado con el código.
;
push ds
pushad
mov ax,10h
mov ds,ax
;
; Se imprime en pantalla la cadena de EIP, vale aclarar que ds:esi es la
; posición de la cadena a imprimir, la cual debe finalizar con NULL y
; que la posición en la pantalla es expresada en edi.
;
mov esi,eip_msg
mov edi,2+80*2
call mostrar_cadena
;
; Luego se carga el valor de eip en eax y se lo muestra en la posición
; indicada por edi.
;
mov edi,10+80*2
mov eax, [excepciones\
+excepciones_struc.registros+registros_struc.reg_EIP]
call mostrar_valor
;
; La forma de direccionar las estructuras es algo complejo en nasm. Las
; estructuras se manejan como desplazamientos, de modo que se deben
; realizar simples sumas de desplazamientos para indicar al procesador
; dónde se encuentra el valor del EIP, registro que se desea imprimir.
; "excepciones" indica el offset dentro del segmento de datos donde
; comienza la estructura. Luego, "excepciones_struc.registros" indica
; dónde comienza la estructura de registros dentro del la estructura
; excepciones. Por último "registros_struc.reg_EIP" indica dónde se
; encuentra el EIP, dentro de la estructura registros, dentro de la
; estructura excepciones.
;
; Se imprime la excepción y su mensaje correspondiente.
;
mov esi,excepcion_msg
mov edi,70
call mostrar_cadena
mov edi,90
mov eax,[excepciones+excepciones_struc.excepcion]
call mostrar_valor
;
; Se imprime EAX.
;
mov esi,eax_msg
mov edi,2+160*2
call mostrar_cadena
mov edi,2+160*2+8
mov eax,[excepciones+excepciones_struc.registros+\
registros_struc.reg_EAX]
call mostrar_valor
;
; Se imprime EBX.
;
mov esi,ebx_msg
mov edi,2+160*2+30
call mostrar_cadena
mov edi,2+160*2+8+30
mov eax,[excepciones+excepciones_struc.registros+\
registros_struc.reg_EBX]
call mostrar_valor
;
; Se imprime ECX.
;
mov esi,ecx_msg
mov edi,2+160*2+60
call mostrar_cadena
mov edi,2+160*2+8+60
mov eax,[excepciones+excepciones_struc.registros+\
registros_struc.reg_ECX]
call mostrar_valor
;
; Se imprime EDX.
;
mov esi,edx_msg
mov edi,2+160*2+90
call mostrar_cadena
mov edi,2+160*2+8+90
mov eax,[excepciones+excepciones_struc.registros+\
registros_struc.reg_EDX]
call mostrar_valor
;
; Se imprime ESI.
;
mov esi,esi_msg
mov edi,2+160*3
call mostrar_cadena
mov edi,2+160*3+8
mov eax,[excepciones+excepciones_struc.registros+\
registros_struc.reg_ESI]
call mostrar_valor
;
; Se imprime EDI.
;
mov esi,edi_msg
mov edi,2+160*3+30
call mostrar_cadena
mov edi,2+160*3+8+30
mov eax,[excepciones+excepciones_struc.registros+\
registros_struc.reg_EDI]
call mostrar_valor
;
; Se imprime ESP.
;
mov esi,esp_msg
mov edi,2+160*3+60
call mostrar_cadena
mov edi,2+160*3+8+60
mov eax,[excepciones+excepciones_struc.registros+\
registros_struc.reg_ESP]
call mostrar_valor
;
; Se imprime EBP.
;
mov esi,ebp_msg
mov edi,2+160*3+90
call mostrar_cadena
mov edi,2+160*3+8+90
mov eax,[excepciones+excepciones_struc.registros+\
registros_struc.reg_EBP]
call mostrar_valor
;
; Se imprime CS.
;
mov edi,160*4
mov eax,[excepciones+excepciones_struc.registros+\
registros_struc.reg_CS]
call mostrar_valor
mov esi,cs_msg
mov edi,160*4
call mostrar_cadena
;
; Se imprime DS.
;
mov edi,160*4+20
mov eax,[excepciones+excepciones_struc.registros+\
registros_struc.reg_DS]
call mostrar_valor
mov esi,ds_msg
mov edi,160*4+20
call mostrar_cadena
;
; Se imprime ES.
;
mov edi,160*4+40
mov eax,[excepciones+excepciones_struc.registros+\
registros_struc.reg_ES]
call mostrar_valor
mov esi,es_msg
mov edi,160*4+40
call mostrar_cadena
;
; Se imprime FS.
;
mov edi,160*4+60
mov eax,[excepciones+excepciones_struc.registros+\
registros_struc.reg_FS]
call mostrar_valor
mov esi,fs_msg
mov edi,160*4+60
call mostrar_cadena
;
; Se imprime GS.
;
mov edi,160*4+80
mov eax,[excepciones+excepciones_struc.registros+\
registros_struc.reg_GS]
call mostrar_valor
mov esi,gs_msg
mov edi,160*4+80
call mostrar_cadena
;
; Se imprime SS.
;
mov edi,160*4+100
mov eax,[excepciones+excepciones_struc.registros+\
registros_struc.reg_SS]
call mostrar_valor
mov esi,ss_msg
mov edi,160*4+100
call mostrar_cadena
;
; Se imprimen los EFLAGS.
;
mov edi,160*5+2+14
mov eax,[excepciones+excepciones_struc.registros+\
registros_struc.reg_EFLAGS]
call mostrar_valor
mov esi,eflags_msg
mov edi,160*5+2
call mostrar_cadena
;
; Por último se imprime el error code, en caso de existir alguno.
;
cmp byte [excepciones+excepciones_struc.errorcode?],1
jne no_errorcode
mov edi,160*6+2+12
mov eax,[excepciones+excepciones_struc.errorcode]
call mostrar_valor
mov esi,errorcode_msg
mov edi,160*6+2
call mostrar_cadena
no_errorcode:
;
; Se recuperan los valores de los registros y se retorna.
;
popad
pop ds
ret
;
; Esta función es la encargada de mostrar una cadena en la pantalla,
; para ello debe recibir en ds:esi la cadena finalizada en NULL, y en
; edi la posición dentro de la pantalla.
;
mostrar_cadena:
;
; Antes que nada se guardan todos los registros que se van a utilizar y
; se carga el segmento necesario, que en este caso es el flat, para
; poder acceder a la memoria de video.
;
pushad
push es
mov bx,18h
mov es,bx
;
; Se verifica que la dirección a imprimir sea par y menor a 4096. Luego
; se suma el desplazamiento, para que apunte a la memoria de video.
;
and edi,0FFEh
add edi,0b8000h
;
; Se hará un loop para imprimir en la pantalla. Se leen los caracteres
; de ds:esi mediante la instrucción lodsb y se verifica que el valor sea
; distinto de 0. Luego se lo coloca en es:edi, se incrementa edi y se
; continúa con el siguiente byte.
;
mov ah,07h
cadena:
mov al,[esi]
cmp al,0
je salir_cadena
mov [es:edi],ax
inc edi
inc edi
inc esi
jmp cadena
salir_cadena:
;
; Se recuperan los registros y se retorna.
;
pop es
popad
ret
;
; La rutina clrscr no merece demasiados comentarios. Completa la
; pantalla con espacios y con atributos de color de letras blancas y
; fondo negro. Esta rutina no recibe ningún parámetro.
;
clrscr:
push ds
mov ax,18h
mov ds,ax
mov edi,0b8000h
mov cx,80*25*2/4
mov eax,07000700h
clrscr1:
mov [edi],eax
add edi,4
dec cx
jnz clrscr1
pop ds
ret
;
; La siguiente función imprime el valor de eax en la pantalla en la
; posición indicada por edi.
;
mostrar_valor:
;
; Primero se guardan los registros que se van a utilizar. En ds se
; carga el segmento flat para poder acceder a la memoria de video.
;
push ds
pushad
mov bx,18h
mov ds,bx
;
; Se verifica que la dirección a imprimir sea par y menor a 4096. Luego
; se suma el desplazamiento, para que apunte a la memoria de video.
;
and edi,0FFEh
add edi,0b8000h
;
;
;
;
;
;
;
Y se arma una rutina que escribe el número contenido en eax en formato
hexadecimal. Para ello se separa el número en nibles (grupos de 4
bits). A cada nible se le suma 0 ascii y si es mayor a 9 se le suma la
diferencia entre 9+1 y A ascii, de modo que, de ser 10 el valor del
dígito, se vea una A.
mov cx,8
correr:
rol eax,4
mov byte [edi],'0'
mov bl,al
and bl,0fh
add [edi],bl
cmp byte [edi],':'
jb ok
add byte [edi],'A'-':'
ok:
inc edi
inc edi
dec cx
jnz correr
;
; Se recuperan las variables y se retorna.
;
popad
pop ds
ret
;
; Esta rutina es la que se ocupa de cargar los registros, que estarán
; almacenados en la pila en el orden según el cual fueron pusheados.
; Vale aclarar que el código es de 16 bits, por eso el tamaño mínimo de
; datos a pushear es de 2 bytes. Por el contrario, de estar trabajando
; con pilas de 32 bits, al realizar un push ds u otro selector, el push
; ocupa 4 bytes en la pila, dos del ds y dos que deja libres para
; mantener la pila alineada en 4 bytes.
;
cargar_reg:
mov [excepciones+excepciones_struc.registros+\
registros_struc.reg_EAX],eax
mov [excepciones+excepciones_struc.registros+\
registros_struc.reg_EBX],ebx
mov [excepciones+excepciones_struc.registros+\
registros_struc.reg_ECX],ecx
mov [excepciones+excepciones_struc.registros+\
registros_struc.reg_EDX],edx
mov [excepciones+excepciones_struc.registros+\
registros_struc.reg_ESI],esi
mov [excepciones+excepciones_struc.registros+\
registros_struc.reg_EDI],edi
mov [excepciones+excepciones_struc.registros+\
registros_struc.reg_EBP],ebp
mov [excepciones+excepciones_struc.registros+\
registros_struc.reg_ESP],esp
mov ax,[esp+2]
mov [excepciones+excepciones_struc.registros+\
registros_struc.reg_EIP],ax
mov ax,[esp+2+2]
mov [excepciones+excepciones_struc.registros+\
registros_struc.reg_CS],ax
mov [excepciones+excepciones_struc.registros+\
registros_struc.reg_DS],ds
mov [excepciones+excepciones_struc.registros+\
registros_struc.reg_ES],es
mov [excepciones+excepciones_struc.registros+\
registros_struc.reg_FS],fs
mov [excepciones+excepciones_struc.registros+\
registros_struc.reg_GS],gs
mov [excepciones+excepciones_struc.registros+\
registros_struc.reg_SS],ss
mov eax,[esp+2+2+2]
mov [excepciones+excepciones_struc.registros+\
registros_struc.reg_EFLAGS],eax
ret
;
; Ahora quedan por definir las variables del sistema.
;
modo_virtual_msg db 'El procesador se encuentra en modo virtual, no'
db 'se puede correr la aplicacion.$'
mask_real_pic1
db 0
real_idtr
resb 6
;
; Las primeras 3 serán: el mensaje para indicar que el micro ya se
; encuentra en modo protegido, la variable para almacenar las máscara
; del PIC y el IDTR de modo real.
;
scan_code
db
0
;
; scan_code es la variable global donde la rutina de atención de la irq0
; almacena el scancode leído del teclado, mientras el programa principal
; verifica si es igual a 1 (la tecla escape).
;
gdtr
dw 8*5-1
dd 0
;
idtr
dw 8*34-1
;0,1,2,3,4,5,6,7,8,9
dd 0
;
gdt
resb 8
;
; Se reservan 8 bytes para el descriptor nulo.
;
; Primero como descriptor 08h se realiza un segmento de código de
; 64 Kbytes sobre el de modo real.
;
seg_cod:dw 0ffffh
dw 0
db 0
db 10011010b
db 00000000b
db 0
;
; Como ya se realizaron algunas veces y debido a que no se puede
; escribir sobre el primer segmento de código definido, de define un
; segmento igual, pero de datos, será el 10h.
;
seg_datos:dw 0ffffh
dw 0
db 0
db 10010010b
db 0b
db 0
;
;
;
;
;
;
Por último se utilizarán dos descriptores más. Uno FLAT, para acceder,
por ejemplo, a la memoria de video, y realizar las impresiones en
pantalla. Otro de 64 Kbytes, para colocar antes de retornar a modo
real. Sus valores son:
dw
dw
db
db
db
db
0ffffh
00000h
000h
10010010b
10001111b
00h
dw
dw
db
db
db
db
0ffffh
0
0
10010010b
0
0
;limite 15.00
;base 15.00
;base 23.16
;Presente Segmento Datos Read Write
;G y limite 0Fh
;base 31.24
;
; Luego se define la IDT, en la que se deben indicar todas las
; excepciones e interrupciones que se desean recibir. En este caso se
; usan dos puertas de interrupciones, pero en otros es recomendable usar
; puertas de tarea, por ejemplo en una doble falta, o en la stack fault.
; Esto se debe a que a veces es necesario cambiar de entorno de tarea
; para no seguir generando la excepción. Por ejemplo: si se acaba la
; pila estando en una rutina de nivel 0 y se desea ejecutar una stack
; fault, se necesita pila para colocar las direcciones de retorno. En
; este caso será mejor utilizar tareas alternativas.
;
idt:
resb 8*6
;
; Invalid Opcode exception handler.
;
int6: dw
int6han
dw
08h
db
0
db
10000110b
db
0
db
0
;
; Se reserva espacio para las interrupciones 7,8,9,10,11,12 que no
; se utilizan.
;
resb 8*6
;
; General Protection exception handler.
;
int13:
dw
int13han
dw
08h
db
0
db
10000110b
db
0
db
0
;
; Se reserva espacio para las interrupciones 14, 15, 16, 17, 18, 19, 20,
; 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32.
;
resb 8*19
irq1: dw
irq1han
dw
08h
db
db
db
db
0
10000110b
0
0
;
; La rutina de irq0 handler se ocupa de incrementar el primer byte de la
; pantalla y leer el último scancode enviado por el teclado.
;
irq1han:
push ds
push eax
mov ax,10h
mov ds,ax
;
; En la interrupción se lee el scancode del puerto 60h y se lo almacena
; en la variable correspondiente.
;
in al,60h
mov [ds:scan_code],al
mov al,20h
out 20h,al
;
;
;
;
;
;
No deben olvidarse las 2 líneas anteriores, que son las que indican al
PIC que la interrupción ha terminado. De lo contrario, no ejecutará
nunca más una interrupción, ya que no habrá otra de mayor prioridad
(ésta es la 0, que es la de mayor prioridad).
pop eax
pop ds
iret
;
; Ambas excepciones hacen lo mismo. Se necesita un método para
; distinguir cuál de ellas ha sucedido, por lo que se hace una rutina de
; 3 líneas por excepción donde se almacenan los registros en la pila y
; se coloca en eax el número de la excepción. Luego se salta, claro, a
; la rutina principal de excepción, que es común a ambas.
;
int6han:
mov dword[excepciones+excepciones_struc.excepcion],6
jmp atender_excepcion
int13han:
push eax
mov word [excepciones+excepciones_struc.errorcode?],1
mov ax,[esp+1*4]
mov [excepciones+excepciones_struc.errorcode],ax
pop eax
add esp,2
mov dword [excepciones+excepciones_struc.excepcion],13
jmp atender_excepcion
;
; La rutina de excepción guarda el estado del procesador, lo muestra en
; pantalla y retorna a modo real.
;
atender_excepcion:
call cargar_reg
call mostrar_reg
jmp volver_a_modo_real
;
; Se hace una rutina (macro) para mover ambos PICs, que recibe como
; parámetros la base de ambos PIC. Para modificar la base del PIC
; se deben enviar las 4 palabras de configuración (queda al lector
; estudiar qué hace cada una de ellas).
;
%macro picmove 2
pushfd
cli
mov al,11h
out 20h,al
;ICW1
mov al,%1
out 21h,al
;ICW2
mov al,04h
out 21h,al
;ICW3
mov al,01h
out 21h,al
;ICW4
mov al,11h
out 0a0h,al
;ICW1
mov al,%2
out 0a1h,al
;ICW2
mov al,02h
out 0a1h,al
;ICW3
mov al,1
out 0a1h,al
;ICW4
popfd
%endmacro
;
; De aquí en más se trata de un programa típico de modo protegido, con
; alguna salvedad, por ejemplo la generación de la excepción, pero ya
; se llegará a eso.
;
inicio:
;
; Se verifica si se está en modo real. En caso contrario se imprime el
; mensaje y se retorna.
;
smsw ax
test al,1
jz en_modo_real
mov ah,09h
mov dx,modo_virtual_msg
int 21h
mov ah,4ch
int 21h
en_modo_real:
;
; Ya que se cambiará de segmento de código al pasar a modo protegido,
; para poder retornar se debe almacenar el de modo real. Por ello es que
; se almacena el segmento de código de modo real en la variable real_cs.
;
mov ax,cs
mov [real_cs],ax
;
; Se carga la base del code segment, como también del segmento de datos.
;
xor eax,eax
;carga eax en cero
mov ax,cs
;carga el cs en eax
shl eax,4
;en eax=base lineal de cs
mov ebx,eax
;se guarda en ebx
mov word [seg_cod+2],ax
mov word [seg_datos+2],ax
shr eax,16
mov byte [seg_cod+4],al
mov byte [seg_datos+4],al
;
; Se carga la base del GDTR.
;
mov eax,ebx
add eax,gdt
mov dword [gdtr+2],eax
lgdt [gdtr];cargo el GDTR
;
; Se guarda el IDTR de modo real en una variable y se carga el nuevo
; IDTR, no sin antes cargar la base del mismo.
;
sidt [real_idtr]
mov eax,ebx
add eax,idt
mov dword [idtr+2],eax
lidt [idtr]
;
; Luego se pasa a modo protegido.
;
cli
mov eax,cr0
or al,1
mov cr0,eax
jmp 08:modo_protegido
;
; A partir de aquí el procesador corre en modo protegido.
;
modo_protegido:
;
; Se carga el segmento de datos.
;
mov ax,10h
mov ds,ax
;
; Y se inhabilita el uso del resto de los selectores, colocándolos en
; 0, salvo la pila, porque se utilizará la de modo real.
;
mov ax,0
mov es,ax
mov fs,ax
mov gs,ax
;
; Se modificará la base de los PICs a 32 y 40.
;
picmove 32,40
;
; Se guarda el valor de la máscara del PIC de modo real y se inicializa
; la máscara a utilizar en este programa, la cual permite únicamente la
; irq0. Luego se habilitan las interrupciones.
;
in al,21h
mov [mask_real_pic1],al
mov al,011111101b
out 21h,al
;
; Se habilitan las interrupciones, en este caso la del teclado
; únicamente.
;
sti
;
; Se espera que el scancode sea igual a 1,2,3, o sea, que se presione la
; tecla escape, 1 ó 2.
;
esperar:
cmp byte [cs:scan_code],1
je volver_a_modo_real
;
; Si la tecla presionada es el escape, se sale a modo real directamente.
;
cmp byte [cs:scan_code],2
je write_cs
;
; Si la tecla presionada es el 1, se intenta escribir en el cs,
; generando una excepción de protección general.
;
cmp byte [cs:scan_code],3
je invalidopcode
;
; Si se presiona la tecla 2, se intenta ejecutar una instrucción
; inválida generando una excepción de instrucción inválida, de lo
; contrario, se sigue esperando una tecla.
;
jmp esperar
;
; Se intenta escribir un 0 en el offset de scancode del cs.
;
write_cs:
mov byte [cs:scan_code],0
;
; Para ejecutar una instrucción inexistente, se puede armar una que no
; esté, o bien ejecutar UD2 (Intel define que nunca lo utilizará como
; instrucción).
;
invalidopcode:
ud2
;
; A la siguiente instrucción se llegará por el salto que se realiza al
; terminar el análisis de la excepción. Para demostrar esto, se puede
; introducir un cli y un jmp $ y verificar que nunca se ejecutan, ya que
; se bloquearía el sistema.
;
cli
jmp $
volver_a_modo_real:
cli
;
; Se cargan todos los selectores con el valor recomendado por Intel.
;
mov ax,20h
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
;
; Se retorna modo real.
;
mov eax,cr0
and al,0feh
mov cr0,eax
;se vuelve a modo real
;
; En este caso se realiza el jmp mediante su opcode, ya que se trata de
; saltar a una variable.
;
db 0eah
;código del salto
dw modo_real
real_cs resw 1
modo_real:
;
; Se recuperan todos los selectores a un valor válido.
;
mov ax,cs
mov ds,ax
;se recupera el segmento de modo real
mov es,ax
mov fs,ax
mov gs,ax
;
; Se recupera el viejo IDTR y la vieja máscara del PIC.
;
lidt [real_idtr]
mov al,[mask_real_pic1]
out 21h,al
;
; Se recupera la base de los PICs, como se encontraba en modo real.
;
picmove 8,70h
;
; Se habilitan las interrupciones y se retorna a modo real.
;
sti
mov ah,4ch
int 21h
fin:
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
ej05.asm - programa multitarea
Protected Mode by Examples – 1era Edición - Octubre 2004
Autor:
Mariano Cerdeiro
<[email protected]>
http://www.soix.com.ar/links/mpbyexamples.html
compilar: nasm ej05.asm -o ej05.com
Este ejemplo tiene por finalidad ilustrar la ejecución de dos tareas
en forma concurrente. El programa salta de una tarea a la otra cada
vez que hay una interrupción del timertick, hasta que se pulse la
tecla escape, caso en que retornará a modo real. Las tareas imprimen,
cada una, un contador ascendente o descendente en distintas partes
del monitor.
Multitarea
Lo primero a saber respecto de la multitarea es que un procesador
siempre ejecuta una ÚNICA tarea a la vez. La multitarea implica
ejecutar varias tareas, de a una por vez, seguidas en el tiempo
por intervalos tan pequeños que aparentan, para el usuario, correr
simultáneamente. La capacidad de correr varias tareas en forma
realmente simultánea se denomina multiprocesamiento, para lo que se
necesitan varios procesadores o un procesador con esa capacidad, como
por ejemplo los HT (Hyper Threading), que en realidad internamente
funciona como 2.
A diferencia de la protección, a la multitarea se la puede realizar
por soft. El procesador provee un método por hardware para realizar
multitarea. Sin embargo, se pueden correr muchas tareas sin utilizar
las facilidades que provee el procesador, ya que se puede pasar de
ejecutar la aplicación X a ejecutar la aplicación Y, almacenando el
estado actual de todos los registros de la aplicación X y cargando
los almacenados anteriormente de la aplicación Y. Ejemplo de este
procedimiento son, para quienes tienen memoria, los programas
residentes de DOS. Estos programas corrían mientras uno realizaba otra
tarea. Hoy en día muchos sistemas operativos no utilizan las
facilidades de multitarea de los procesadores, sino que la realizan
manualmente, como por ejemplo Linux.
En este ejemplo se estudia cómo realizar multitarea mediante el
procesador.
Para almacenar el estado de cada una de las tareas, el procesador
utiliza una estructura denominada TSS (Task State Segment). Al saltar
de una tarea a otra, se guarda automáticamente el estado actual del
procesador en el TSS actual y se carga el nuevo estado del procesador
del TSS de destino.
Cada TSS almacena el estado de los registros del procesador de cada
tarea, por ejemplo eax, ebx, eip, para que cuando se desee retornar a
dicha tarea se pueda continuar ejecutando el programa del mismo lugar
donde fue dejado.
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
La estructura del TSS de 32 bits es la siguiente:
31
16 15
0
+------------------------+----------------------+-+
| I/O Map Base Address |
Reserved
|T|100
+------------------------+----------------------+-+
|
Reserved
| LDT Segment Selector | 96
+------------------------+------------------------+
|
Reserved
|
GS
| 92
+------------------------+------------------------+
|
Reserved
|
FS
| 88
+------------------------+------------------------+
|
Reserved
|
DS
| 84
+------------------------+------------------------+
|
Reserved
|
SS
| 80
+------------------------+------------------------+
|
Reserved
|
CS
| 76
+------------------------+------------------------+
|
Reserved
|
ES
| 72
+------------------------+------------------------+
|
EDI
| 68
+-------------------------------------------------+
|
ESI
| 64
+-------------------------------------------------+
|
EBP
| 60
+-------------------------------------------------+
|
ESP
| 56
+-------------------------------------------------+
|
EBX
| 52
+-------------------------------------------------+
|
EDX
| 48
+-------------------------------------------------+
|
ECX
| 44
+-------------------------------------------------+
|
EAX
| 40
+-------------------------------------------------+
|
EFLAGS
| 36
+-------------------------------------------------+
|
EIP
| 32
+-------------------------------------------------+
|
CR3
| 28
+------------------------+------------------------+
|
Reserved
|
SS2
| 24
+------------------------+------------------------+
|
ESP2
| 20
+------------------------+------------------------+
|
Reserved
|
SS1
| 16
+------------------------+------------------------+
|
ESP1
| 12
+------------------------+------------------------+
|
Reserved
|
SS0
| 8
+------------------------+------------------------+
|
ESP0
| 4
+------------------------+------------------------+
|
Reserved
|
Previous Task Link
| 0
+------------------------+------------------------+
31
16 15
0
Descargar el documento (PDF)
ejemplos_modo_protegido.pdf (PDF, 275 KB)
Documentos relacionados
Palabras claves relacionadas
valor
datos
memoria
protegido
carga
limite
instruccion
excepciones
mostrar
segmento
struc
codigo
registros
programa
puede