Tema I. Hardware y Lenguaje Ensamblador para Intel x86
Cualquier sistema operativo tiene que administrar los recursos de la computadora. El código que aplica dichas operaciones
es dependiente de la arquitectura de la computadora y es escrito en lenguaje ensamblador.
Un sistema operativo con un diseño modular, aisla las rutinas en ensamblador para que apliquen las operaciones de
bajo nivel (entrada/salida, cambios de contexto, manejo de interrupciones)
1. Unidad central de procesamiento Intel, modelo de memoria y sus Registros.
El centro de una computadora es el CPU. En el caso de una PC es un CPU Intel. La arquitectura de computadora de 16
bits está basada en la arquitectura 8088 y las arquitecturas como 8086,80186, 80286 y 80386 difieren en esta por aspectos
de velocidad, precio y manejo de hardware.
La unidades básicas son el byte (8 bits) y una palabra o word (16 bits o 2 bytes)
El modelo de memoria del 8088 es un espacio de direcciones de 1 MegaByte (1 MB), donde cada byte tiene una dirección
unica de 0 a 2^20-1. Las variables tipicamente ocupan una palabra y pueden estar localizados en cualquier area de memoria,
pero es practica comun almacenarlo a partir de un byte par y el siguiente byte impar, es decir, se usa alineacion par
de memoria.
Por ejemplo, la variable que tiene valor 0x7705 en memoria se almacena como (suponiendo que esta en la direccion 0x2030
Direccion: 0x2030 0x2031
Contenido: 0x05 0x77
(la regla es que el contenido se almacena al reves de como se lee)
Por ejemplo una palabra de 2 words o double word con valor 0x12243648 y a partir de la direccion 0x1040
Direccion: 0x103D 0x103E 0x103F 0x1040
0x48 0x36 0x24 0x12
El 8088 tiene 12 registros, los cuales se clasifican en 3 grupos
Uso generico
AX (1 word) o AH (1 byte) y AL (1 byte)
BX (1 word) o BH (1 byte) y BL (1 byte)
CX (1 word) o CH (1 byte) y CL (1 byte)
DX (1 word) o DH (1 byte) y DL (1 byte)
De apuntadores
IP - siguiente instruccion a ejecutar (1 word)
SP - apuntador de la pila (1 word)
BP - apuntador para manejar parametros de una funcion (1 word)
SI - source index (1 word)
DI - data index (1 word)
Los registros SI y DI son utilizados para operaciones de desplazamiento de bloques y cadenas, y de comparacion. Algunos
compiladores utilizan estos registros para variables de registro, guardandolas y recolaconadolas cuando se requieran.
De Banderas
De segmento
CS - codigo (1 word)
DS - dato (1 word)
SS - stack o pila (1 word)
ES - (1 Word)
Estos registros se pueden considerar de 20 bits, pero los cuatro de bajo orden son siempre 0x0, por lo que siempre
se almacenan 16 bits. Estos registros sirven para localizar en que area de memoria está el codigo maquina que se
ejecuta, los datos y la pila
La siguiente figura muestra la organización en memoria de un programa (con el sistema operativo incluido)
Memoria Baja
0 100K 120K 184K 210K 1MB
--------------------------------------------------------
|Sistema operativo|Texto|Datos|Pila|Texto|Datos|Pila|....|
--------------------------------------------------------
Programa 1 Programa 2
Basicamente en memoria de la computadora se aloja al sistema operativo y varios programas.
Para que se pueda utilizar el direccionamiento de los distintos componentes de un programa se usan los registros de
segmento, donde los datos se hacen relativos a los segmentos de código, datos o pila segun aplique.
Por ejemplo, si el programa 1 está en ejecución, CS tiene la dirección de 100K (0x19000), pero los ultimos 4 bits
son cortados, se lee como 0x1900) y todas las direcciones del programa son relativas a 0x19000, entonces un
salto (JMP) a 0x0400 (1K) se interpreta como
Direccion real = 0x01900*0x10 + 0x0400 = 0x19400 (101 K)
En si cualquier codigo que se ejecuta es
Direccion Real = CS*0x10 + Direccion Relativa dada por IP
Para datos
Direccion Real = DS*0x10 + Direccion Relativa de variable
por ejemplo, mover el contenido de la dirección 0x2500 al registro AX, si DS es 0x1E000 (120 K), la direccion real
es
0x1E000*0x10 + 0x2500 = 0x1E2500
Para pila
Direccion Real = SS*0x10 + Direccion Relativa dada por SP
El registro ES es un segmento extra y es usado de manera auxiliar.
FLAGS (1 word)
El CPU contiene un registro denominado FLAGS o palabra de condicion del programa (Program Status Word). Contiene los
bits del código de condición (colocados en instrucciones de comparación), donde un bit indica si las interrupciones están
activadas o desactivadas y algunos otros bits. En una maquinas con un modo de kernel y uno de usuario, un bit en la PSW
indica qué modo es el actual.
El 8088 tiene sólo un modo (en esencia, el modo del kernel). El PSW se usa principalmente para el procesamiento de
interrupciones.
2. Estructura de un programa ensamblador
Un programa ensamblador queda dividido en 3 secciones, datos, pila y código.
En cada sección se indican una serie de instrucciones, con el siguiente formato
IDENTIFICADOR OPERACION OPERANDO COMENTARIO
El identificaro es una etiqueta
La operación define datos o instrucciones ensamblador
El operando son los valores con los que opera la operacion
Comentario, dado con un ;
La mayoria de las instrucciones tiene un código de operación de 1 byte seguido de 1 byte que especifica los modos
de direccionamiento y los registros. Los modos de direccionamiento son de registro a registro, memoria a registro,
indirecto e indizado. El direccionamiento directo se utiliza para variables locales.
3. Primer programa en ensamblador
Solo suma una unidad al registro AX y lo almacena en BX
;primer programa en ensamblador que suma una unidad a AX y guarda el
;resultado en BX
;IDENTIFICADOR OPERACION OPERANDO COMENTARIO
;Segmento de codigo
CODESEG SEGMENT PARA ;define un segmento en una direccion
;divisible entre 16 o 10H
MOV AX,0 ;mover un 0 a AX
INC AX ;incrementar una unidad a AX
MOV BX,AX
CODESEG ENDS ;fin de segmento de codigo
END
4. Instrucciones básicas en ensamblador.
ADD AX, var ; sumar el contenido de una palabra de la memoria var al registro AX
AND BX,[BP+6] ; hacer un AND BX con la palabra en memoria en la direccion BP+6
CMP AX,1 ; comparar AX con 1
DEC [BP-4] ; decrementar una unidad a palabra en memoria en la direccion BP-4
INC SI ; incrementar una unidad al contenido del registro SI
JE etiqueta ; saltar a etiqueta si la operacion de comparacion es igual (cero en FLAG:SF)
JL etiqueta ; saltar a etiqueta si la comparacion anterior fue menor (JG,JNE existen)
LOOP etiqueta ; restar 1 de CX; si es distinto de cero saltar a etiqueta
MOV AX,var ; mover el contenido de var a AX
MOVB AX,VAR ; pasar el contenido del byte de la memoria var a AX
POP var ; descargar una palabra de la pila y colocarla en var
PUSH BX ; meter el registro BX en la pila
REP ; repetir siguiendo la instruccion hasta que CX sea 0
CLI ; desactivar interrupciones (eliminar las señales de interrupcion)
STI ; activar las interrupciones (fija la señal de interrupcion)
IRET ; regresa desde la interrupcion, descargando PC,CS,PSW
CALL _rutina ; llamada al procedimiento _rutina
RET ; retornar de la llamada al procedimiento (descarga la direccion de retorno en IP y por tanto pasa a
ese sitio)
5. Invocando una rutina ensamblador desde lenguaje C.
Es posible que C use rutinas en ensamblador.
Solo que se debe tener en cuenta las siguientes reglas
+) Se debe definir una funcion en ensamblador. Si el nombre en C es X, en ensamblador es
_X
+) Los registros BP, SI y DI deben ser guardados en la pila. En el caso de SI y DI se usen, se
debe aplicar esta regla. BP SIEMPRE se debe salvar. Primero se debe guardar BP, luego SI y DI.
Esto se debe a que los procedimientos en C asumen que BP, DI y SI no cambian.
+) El registro BP es la referencia de parametros y variables locales dentro de la función.
En la entrada de la función, el valor actual de BP es guardado en la pila (via un push)
y el valor de SP se mueve a BP. Se conoce que BP es el apuntador al "stack frame" o "contexto
de la pila" y la organización de memoria se ve de la siguiente manera (suponiendo que
se guardo DI y SI)
-----------
SP---> DI guardado Memoria Baja
-----------
SI guardado
-----------
variables
locales
-----------
BP guardado
BP---> -----------
Dirección retorno
-----------
parametros
----------- Memoria alta
Como regla
+ La direccion de retorno esta en BP+2 (BP corrido 2 bytes)
+ Para accesar los parametros se debe tomar desde BP+4. C guarda los parametros en orden inverso, el ultimo parametro se apila primero y el primero
hasta el final (esto permite implantar funciones de parametros variables). Es decir los parametros
se toman con BP mas un corrimiento (offset) positivo
+ Para accesar las variables locales debe tomar bp-x,donde x queda definido segun el tipo de dato
y el orden de declaracion de las variables; es decir BP mas un offset negativo
+ El valor de retorno se deposita en el registro AX (cuando es de 1 o 2 bytes) o en AX y DX
(cuando es de 4 bytes)
Suponiendo una funcion f que recibe un parametro char y otra funcion g que recibe dos parametros
char.
La siguiente relación muestra el manejo de la pila para el paso de parametros, suponiendo las
direcciones asignadas en el texto.
En main, los registros SP y BP son
SP FFF6
BP FFF6
Cuando se invoca a f
SP FFF2 (4 bytes, dado que en sp+2 está la direccion de retorno y en dx el parametro de entrada)
BP FFF6
Se introduce sp al stack
SP FFF0 (se introducen 2 bytes en la pila)
BP FFF0
Se resta 4 al SP
SP FFEC
BP FFF0
Al iniciar la logica de la rutina
SP FFEC
BP FFF0
Al preparar los dos parametros para g
SP FFE8 (se introducen 4 bytes en el stack, los dos parametros)
BP FFF0
Al invocar a g
SP FFE6 (se introducen 2 bytes donde esta la direccion de retorno
BP FFF0
Se introduce sp al stack
SP FFE4
BP FFE4
Se resta 2 a la pila
SP FFE2
BP FFE4
El siguiente diagrama muestra la estructura de la memoria
En la rutina main
Memoria Alta Memoria Baja
---------------------------------------------------------------
| | Datos | Texto |
---------------------------------------------------------------
^
|
SP (Pila)
Al invocar la funcion f
Memoria Alta Memoria Baja
---------------------------------------------------------------
| |par1f|retorno|BP Anterior|local1| ... | Datos | Texto |
---------------------------------------------------------------
^
|
SP (Pila)
Al invocar la funcion g
Memoria Alta Memoria Baja
--------------------------------------------------------------------------------------------------------
| |par1f|retorno|BP Anterior|local1|par2g|par1g|retorno|BP Anterior|local1|local2|... | Datos | Texto |
--------------------------------------------------------------------------------------------------------
^
|
SP (Pila)
Se reproduce el código C y ensamblador
ifndef ??version
?debug macro
endm
$comm macro name,dist,size,count
comm dist name:BYTE:count*size
endm
else
$comm macro name,dist,size,count
comm dist name[size]:BYTE:count
endm
endif
?debug S "func.c"
?debug C E916A61F2F0666756E632E63
_TEXT segment byte public 'CODE'
_TEXT ends
DGROUP group _DATA,_BSS
assume cs:_TEXT,ds:DGROUP
_DATA segment word public 'DATA'
d@ label byte
d@w label word
_DATA ends
_BSS segment word public 'BSS'
b@ label byte
b@w label word
_BSS ends
_TEXT segment byte public 'CODE'
;
; char g(char p1,char p2)
;
assume cs:_TEXT
_g proc near
push bp
mov bp,sp
mov cx,word ptr [bp+2]
mov bl,byte ptr [bp+4]
mov bh,byte ptr [bp+6]
sub sp,2
;
; {
;
; char y = p1+p2;
;
mov al,byte ptr [bp+4]
add al,byte ptr [bp+6]
mov byte ptr [bp-1],al
;
; return y;
;
mov al,byte ptr [bp-1]
jmp short @1@58
@1@58:
;
; }
;
mov sp,bp
pop bp
ret
_g endp
;
; char f(char p)
;
assume cs:_TEXT
_f proc near
push bp
mov bp,sp
mov cx,word ptr [bp+2]
mov bl,byte ptr [bp+4]
sub sp,4
;
; {
; char a=2+p,b=3+p ;
;
mov al,byte ptr [bp+4]
add al,2
mov byte ptr [bp-1],al
mov al,byte ptr [bp+4]
add al,3
mov byte ptr [bp-2],al
;
; char x=g(a,b);
;
mov al,byte ptr [bp-2]
push ax
mov al,byte ptr [bp-1]
push ax
call near ptr _g
pop cx
pop cx
mov byte ptr [bp-3],al
;
; return x;
;
mov al,byte ptr [bp-3]
jmp short @2@58
@2@58:
;
; }
;
mov sp,bp
pop bp
ret
_f endp
?debug C E9
_TEXT ends
_DATA segment word public 'DATA'
s@ label byte
_DATA ends
_TEXT segment byte public 'CODE'
_TEXT ends
public _f
public _g
_s@ equ s@
end
extern int f(char);
extern int g(int,int);
main() {
f(16);
printf("\n%p %p %p\n",main,g,f);
}
6. Lenguaje ensamblador para interactuar con el Hardware
Además del CPU, Intel consta de hardware para controlar memoria, teclado, pantalla de despliegue, unidades de disco
y otros perifericos.
La CPU se comunica con dichos componentes a través de un conjunto de 62 conexiones paralelas conocidos como el bus del
sistema. Algunas conexiones se utilizan para direcciones, algunos datos y otros para el control.
La CPU emite comandos a los dispositivos de E/S mediante la ejecución de la instrucción ensamblador OUT, la cual produce
un byte para el controlador del dispositivo. El controlador contiene la electrónica que convierte los comandos digitales
de la CPU en señales de control analógicas para activar el dispositivo de E/S. Cada controlador tiene un conjunto de
direcciones E/S asignado a él. El controlador del floppy, por ejemplo, tiene la dirección E/S 0x3F0 a 0x3F7, y el
video tiene dirección de E/S 0x3C0 a 0x3CF. Estas direcciones de E/S pertenecen a un espacio de de dirección de E/S
diferentes y no están relacionadas con las direcciones ordinarias de memoria.
De esta manera, la CPU manda comandos a los distintos dispositvos. Por ejemplo
OUT 3F2H ; mandar un byte a la tarjeta controladora del floppy
Cuando un dispositivo de E/S termina de ejecutar su comando, éste le puede indicar a la CPU enviando una señal de
interrupción por el bus. La señal de interrupción hace que la CPU introduzca tres palabras en la pila actual: primero
el registro de FLAGS o PSW, despues el registro CS y por último el Program Counter (PC). Después se desactiva el bit
de interrupción del registro PSW, para desactivar interrupciones adicionales y finalmente se carga un nuevo Program
Counter (PC) y registro CS. Los cuatro bytes que contienen los nuevos PC y CS se denominan vectores de interrupción;
y esta carga oblica a que la CPU salte a una dirección en alguna parte de dirección de código. Como cada controlador
tiene su vector de interrupción, cada uno tiene la dirección del código que sirve a las interrupciones de ese controlador.
El sistema PC tiene en ROM una serie de rutinas para controlar el hardware del sistema, denominado BIOS (Basic Input
Output System, sistema básico de entrada y salida). Contiene un conjunto de procedimientos de lectura y escritura de bloques
del disco, escritura de caracteres en la pantalla y otra E/S.
Ninguno de estos procedimientos del BIOS es impulsado por interrupciones. Todos espera a que la E/S termine anstes de
devolver el control al usuario. Por tanto, si un programa está esperando la entrada del teclado, todo el sistema, incluso
todos los procesos de fondo, hacen un alto total. En un sistema de tiempo compartido, la detención de todo el sistema debido
a que un programa necesite entrada no es óptimo.
En resumen, para manejar el hardware de una computadora, se puede hacer por medio de dos filosofías, escribiendo al
bus de sistema o por medio del BIOS.
7. Uso del BIOS
Para poder comunicarse con el BIOS se necesita usar una interrupción de software. EXisten distintos números de
interrupciones para distintos propósitos y a continuación se listan
número Nombre Descripción
0x08 CLK_INT Interrupción del reloj
0x09 KBD_INT Interrupción del teclado
0x10 VID Servicio de desplegado de video
0x0E FLPY Control del disco flexible (floppy)
0x16 KBD Servicios de teclado
Por cada interrupción se deben en algunos casos llenar los registros de uso genérico (AX, BX, DX)
Se muestra a continuación la tabla para algunos servicios
INTERRUPCION SubFuncion Registros entrada Registros de salida
16H Leer teclado AH=00H AX
10H Escribir a video AH=0EH AL=caracter
13H Estado de floppy AH=02H DL=00H AX
Se muestra codigo ensamblador para leer del teclado un caracter, escribir un caracter a video y probar la existencia
de un disco flexible
El código ensamblador que maneja los servicios de BIOS
ifndef ??version
?debug macro
endm
$comm macro name,dist,size,count
comm dist name:BYTE:count*size
endm
else
$comm macro name,dist,size,count
comm dist name[size]:BYTE:count
endm
endif
?debug S "bios.c"
?debug C E97406212F0662696F732E63
_TEXT segment byte public 'CODE'
_TEXT ends
DGROUP group _DATA,_BSS
assume cs:_TEXT,ds:DGROUP
_DATA segment word public 'DATA'
d@ label byte
d@w label word
_DATA ends
_BSS segment word public 'BSS'
b@ label byte
b@w label word
_BSS ends
_TEXT segment byte public 'CODE'
;
; int kbdgetc()
;
assume cs:_TEXT
_kbdgetc proc near
push bp ;guardar bp en la pila
mov bp,sp ;poner bp relativo a sp
mov ah,00H ;peticion de lectura de un caracter del teclado
int 16H ;invocar la interrupcion 16 del BIOS, en AX se queda
xor ah,ah ;eliminar byte para caracteres especiales
pop bp ;desapilar bp
ret
_kbdgetc endp
;
; void ttyw(int c)
;
assume cs:_TEXT
_ttyw proc near
push bp
mov bp,sp
mov al,[bp+4] ;tomar el caracter a escribir
mov ah,0EH ;la subfuncion OEH es escribir a video
mov bl,07H ;color de background
mov bh,0 ;usar pagina de video actual
int 10H ;invocar la interrupcion 10H del BIOS
pop bp
ret
_ttyw endp
;
; int hasFloppy()
;
assume cs:_TEXT
_hasFloppy proc near
push bp
mov bp,sp
mov ah,02H ;peticion para reestablecer el disco
xor al,al ;limpiar al
mov dl,00H ;pedir unidad de disco flexible
xor dh,dh ;limipiar dh
int 13H ;invocar interrupcion 13H del BIOS, disco
mov al,ah
xor ah,ah
pop bp
ret
_hasFloppy endp
?debug C E9
_TEXT ends
_DATA segment word public 'DATA'
s@ label byte
_DATA ends
_TEXT segment byte public 'CODE'
_TEXT ends
public _hasFloppy
public _ttyw
public _kbdgetc
_s@ equ s@
end
El archivo de encabezado es
/*leer un caracter del teclado usando el BIOS*/
extern int kbdgetc(void);
/*desplegar un caracter en video, usando el BIOS*/
extern void ttyw(int);
#define HASFLPY 0x0009
#define NOFLPY 0x0080
/*checa si la computadora tiene una unidad de disco flexible*/
extern int hasFloppy();
El programa que prueba es
#include "bios.h"
void main()
{
int ch=0;
int floppy=0;
/*leer un caracter*/
ch=kbdgetc();
ttyw(ch);
ttyw('\n');
floppy=hasFloppy();
if (floppy==HASFLPY)
ttyw('f');
else if (floppy==NOFLPY)
ttyw('n');
else
ttyw('?');
ttyw('\n');
}
               (
geocities.com/gusdelact)