Documentos de Académico
Documentos de Profesional
Documentos de Cultura
2 Practica Compilador NASM
2 Practica Compilador NASM
UGR 2008-2009 1
Para poder traducir una instrucción a código máquina (o a ensamblador), hay que
conocer el nivel de lenguaje máquina con gran detalle (registros del procesador,
instrucciones máquina, acceso a memoria y dispositivos, etc) para determinar qué
instrucciones simples son necesarias para ejecutar cierta instrucción de nuestro lenguaje.
Una vez que el entorno integrado ha generado ese código (de forma transparente al
programador), se guarda en ficheros, y se llama al compilador correspondiente (C++,
2 Desarrollo de un compilador-traductor
Basic, Pascal) para que traduzca esas instrucciones de alto nivel a otro lenguaje más
cercano a la máquina (ya que el procesador no entiende más que lenguaje máquina
binario). Entre el lenguaje de alto nivel y el código máquina, se utiliza el ensamblador.
En cada paso, vemos que se va traduciendo el código fuente inicial a un lenguaje cada
vez más cercano al que entiende la máquina. En contrapartida, al traducir y bajar de
nivel, pasamos a manejar un lenguaje cada vez más complejo y difícil de entender para
un humano. Es más, desarrollar la misma aplicación en ese lenguaje de más bajo nivel,
si no se dispone del compilador adecuado, resultaría mucho más costoso en tiempo y
esfuerzo.
Veamos un programa en C++ muy sencillo que muestra una cadena de texto:
#include <iostream>
using namespace std;
char *cadena=”hola”;
int main(void) {
cout << cadena;
return 0;
}
#include <stdlib.h>
char *cadena=”hola”;
int main(void) {
printf(”%s”,cadena);
return 0;
}
Salvo las dos líneas resaltadas, el resto siempre es fijo: los #include como información
al compilador, y la función main que establece una estructura y un punto de comienzo
del programa.
El siguiente programa es una traducción directa del programa C++ anterior. En éste
vemos la declaración de la zona de datos con todas las variables que teníamos definidas
en el programa de C++ (la línea resaltada definiendo la cadena “hola”), y por último el
3 Desarrollo de un compilador-traductor
segmento de código, que contiene las instrucciones ensamblador que ejecutan cada
instrucción de C++ :
mov eax, 1
mov ebx, 0
int 80h
Este tipo de funciones, que sólo las puede proveer el sistema operativo se llaman
interrupciones software. La forma de pedirle al sistema que ejecute cierta función para
servir a nuestro programa (así nuestro programa puede escribir en pantalla, leer de
teclado, etc.) es utilizando la instrucción de ensamblador INT (seguido del número de
interrupción, que identifica “a quién” le pedimos que ejecute esa función).
Vemos que en ese programa anterior se le pide al kernel de Linux (interrupción 80h)
que ejecute la función de sacar una cadena de texto (función 4) a salida estándar
(ebx=3). Al final del programa, vemos que se le pide de nuevo al kernel (interrupción
80h) que ejecute la función de terminar el programa y salir al sistema (función 1,
especificado en EAX).
Como vemos, los valores que identifican la función que queremos ejecutar para nuestro
programa, y los parámetros para esa función se indican en los registros del
microprocesador.
Por otro lado, nuestros programas podrán hacer uso de las siguientes instrucciones:
Para delimitar el código del programa (la secuencia de instrucciones), vamos a utilizar
las palabras clave “INICIO_CUERPO” y “FIN_CUERPO”.
Supongamos que nos piden escribir un programa que muestre la cadena de caracteres
“hola”, y a continuación que muestre la cadena de caracteres “adios”.
VARIABLES
cad "hola"
cad2 "adios"
FIN_VARIABLES
INICIO_CUERPO
IMPRIMIR cad
IMPRIMIR cad2
FIN_CUERPO
Vemos las dos zonas bien diferenciadas: la primera delimita la definición de las
variables que usaremos en nuestro programa. La segunda recoge las instrucciones que
componen el código de nuestro programa. En este ejemplo tan básico, el flujo del
programa es secuencial. Más adelante, tendríamos que definir cómo se construyen
bucles y estructuras condicionales.
El programa en alto nivel que mostrábamos antes, una vez traducido a ensamblador
(sintaxis NASM), quedaría como sigue:
section .data
cad db "hola"
cadSIZE equ $ - cad
cad2 db "adios"
cad2SIZE equ $ - cad2
section .text
global _start
_start:
mov eax, 4
mov ebx, 1
mov ecx, cad
mov edx, cadSIZE
int 80h
mov eax, 4
mov ebx, 1
mov ecx, cad2
mov edx, cad2SIZE
int 80h
mov eax, 1
mov ebx, 0
int 80h
6 Desarrollo de un compilador-traductor
ld -o miprog miprog.o
./miprog
Código de ayuda
Para construir nuevas instrucciones secuenciales, será de gran ayuda el código
entregado en la parte común a todas las prácticas para escribir cadenas de texto por
pantalla, convertir números en cadenas y viceversa, acceder a ficheros en modo texto
para lectura y escritura, etc. (cada función de interrupción que ejecuta cierta función, se
puede traducir por una instrucción de más alto nivel para nuestro lenguaje).
//**************************************************************************//
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <math.h>
#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
using namespace std;
//**************************************************************************//
ifstream fichSRC;
ofstream fichASM;
//**************************************************************************//
string cabeceras_asm_1() {
string nuevo_codigo="";
nuevo_codigo = nuevo_codigo+"section .data \n";
return ((string) nuevo_codigo);
}
string cabeceras_asm_2() {
7 Desarrollo de un compilador-traductor
string nuevo_codigo="";
nuevo_codigo = nuevo_codigo+"section .text \n";
nuevo_codigo = nuevo_codigo+"global _start \n";
nuevo_codigo = nuevo_codigo+"_start: \n";
return ((string) nuevo_codigo);
}
string cabeceras_asm_3() {
string nuevo_codigo="";
nuevo_codigo = nuevo_codigo+"mov eax, 1 \n";
nuevo_codigo = nuevo_codigo+"mov ebx, 0 \n";
nuevo_codigo = nuevo_codigo+"int 80h \n\n";
return ((string) nuevo_codigo);
}
string definicion_de_variables() {
string nuevo_codigo="";
string token="";
string nombre="";
string valor="";
while( token != "VARIABLES" ) {
fichSRC >> token ;
}
bool quedan=true;
do{
fichSRC >> token ;
if( token == "FIN_VARIABLES" ) {
quedan=false;
}else{
// ya está leido el tipo de la variable.
// queda comprobar de qué tipo es y leer dos tokens: nombre y el valor.
// Parte de definición de cadenas (STR)
if( token == "STR" ) {
fichSRC >> nombre;
fichSRC >> valor;
nuevo_codigo=nuevo_codigo+"\t"+nombre+" db "+valor+" \n";
nuevo_codigo=nuevo_codigo+"\t"+nombre+"SIZE equ $- "+nombre+" \n";
}
// La parte de definición de "integers" no está terminado
if( token == "INT" ) {
fichSRC >> nombre;
fichSRC >> valor;
}
}
}while( quedan );
return ((string) nuevo_codigo);
}
string procesar_codigo() {
string nuevo_codigo="";
string token="";
while( token != "INICIO_CUERPO" ) {
fichSRC >> token ;
}
bool quedan=true;
do{
fichSRC >> token ;
if( token == "FIN_CUERPO" ) {
quedan=false;
}else{
if( token == "IMPRIMIR" ) {
fichSRC >> token ;
nuevo_codigo=nuevo_codigo+"mov eax, 4 \n";
nuevo_codigo=nuevo_codigo+"mov ebx, 1 \n";
nuevo_codigo=nuevo_codigo+"mov ecx, "+token+" \n";
nuevo_codigo=nuevo_codigo+"mov edx, "+token+"SIZE \n";
nuevo_codigo=nuevo_codigo+"int 80h \n\n";
}
if( token == "INCREMENTAR" ) {
fichSRC >> token ;
nuevo_codigo=nuevo_codigo+"mov eax, ["+token+"] \n";
8 Desarrollo de un compilador-traductor
fichSRC.open( nombre_fichSRC.c_str() );
fichASM.open( nombre_fichASM.c_str() );
fichSRC.close();
fichASM.close();
return 0;
}
//**************************************************************************//
• funciones y procedimientos
• estructura condicional del tipo if (condición) then .... else ....
• bucle del tipo do{ .... } while (condición)
• bucle del tipo for (i=0;i<limite;i++){....}
• acceso a los argumentos pasados por la línea de comando
• definición y uso de arrays unidimensionales (y/o multidimensionales)
• función GOTO etiqueta
• comprobación de errores de sintaxis y/o semántica