Uso del debugger y técnicas de rastreo de código.
Conocer la metodología de trabajo al utilizar el lenguaje ensamblador desde lenguajes de alto nivel, en este caso el C, mediante llamadas a rutinas escritas en ensamblador.
Crear librerías de rutinas en ensamblador que puedan ser utilizadas tanto desde otro programa en ensamblador como desde lenguajes de alto nivel.
La práctica consiste en la realización de la siguiente tarea:
Interfaz C-Ensamblador. Utilizando el driver diseñado en la práctica 2 (*), se realizará un programador/lector de memorias utilizando lenguaje C. Tras una presentación, diseñada por cada pareja, a través de una menú preguntará si se va a leer o a programar una memoria.
Si se elige la opción de lectura, el programa presentará en pantalla el contenido de la memoria, tanto en hexadecimal como en ASCII (en tablas separadas). En el caso de querer programarla pedirá el fichero a programar, programará la memoria y realizará una comprobación de dicha programación informando al usuario del resultado. Este programador se divide a su vez en varias partes:
Interfaz de usuario. Estará escrito totalmente en C. Tras una presentación, pedirá el fichero que contiene los datos a programar. Dicho fichero podrá estar en formato binario (el utilizado en las prácticas 1 y 2) o de manera opcional, en formato .s19 ( el utilizado en ETC1 para programar la EEPROM de la práctica del semáforo). La decodificación del fichero .s19 se hará desde C. También, desde este módulo, se gestionarán todas las llamadas a las funciones escritas en ensamblador.
Interfaz con el driver. Deberá estar escrito en
ensamblador. Contiene las llamadas a las funciones del driver.
Básicamente, lo único que tiene son funciones que obtienen los parámetro
pasados desde el C, inicializan los registros que corresponda y hacen
las llamadas a la Int 61h.
La práctica se valorará en función de la interacción entre el lenguaje C y el ensamblador, más que por un gran desarrollo en C.
No se permite el uso de código ensamblador insertado en funciones de C.
Es obligatorio el uso de paso de parámetros entre C y ensamblador.
Se recomienda dejar la implementación del formato .s19 para el final, una vez que ya esté todo funcionando para el formato binario.
(*) También se puede usar el driver entregado en la primera práctica. De esta manera se facilitan las pruebas y permite que si a algún grupo no le ha funcionado correctamente la P2 pueda seguir trabajando sin problemas con la práctica 3.
De todas formas, se valorará positivamente el uso del driver propio.
Qué hay que hacer:
1. Escribir en ensamblador un programa aparte o una librería de conexión entre
la parte C y el driver. Este programa aparte o librería deberá incluir una
llamada a cada función
correspondiente a cada servicio del driver (cada valor de AH distinto).
2. Escribir la interfaz en C (descrita en el inicio de
este documento).
3. "Linkar" el programa en C y la librería en ASM
en un programa. Para ello se deberá usar la herramienta tcc (Turbo C Compiler)
4. Hacer un makefile que compile y haga el "linkado".
Artículo muy interesante para entender como se maneja la pila desde C y ensamblador
Smashing the stack for fun and profit, por Aleph One
Ejemplo completo de programación mixta (ASM+C) paso a paso:
En muchas ocasiones se necesita el calculo a*b/c con números enteros. El
problema consiste en el posible desbordamiento del a*b incluso si el resultado
a*b/c cabe en un int. Por tanto, dicho calculo hay que hacerlo en ensamblador
o con tipo long, que es mas costoso. Se necesita hacer una función
en ensamblador, que calcula a*b/c.
1. Elegimos el prototipo de la función:
int mul_div(int a,int b,int c);
Escribimos dicho prototipo en el fichero mul_div.h
2. Necesitamos el esqueleto de esta función en ASM:
Escribimos en un fichero llamado mul_div.c:
int mul_div(int a, int b, int c) {
return a+b-c; // notar la diferencia en el func. Es un
stub
}
compilamos, generando ASM
tcc -S mul_div.c
3. Escribimos el programa en ensamblador, utilizando el esqueleto generado
por tcc en el paso previo:
edit mul_div.asm
En el cuerpo del programa cambiamos las instrucciones necesarias:
mul_div proc near
push bp
mov bp,sp
...
mov ...,[bp+4] ; el a
add ...,[bp+6] ; el b
sub ...,[bp+8] ; el c
...
pop bp
ret
mul_div endp
...
cambiamos a:
mul_div proc near
push bp
mov bp,sp
mov ax,[bp+4] ; el a
cwd
imul [bp+6] ; el b
idiv [bp+8] ; el c
pop bp
ret
mul_div endp
4. Compilamos el modulo ASM con opciones 'case sensitive'
4.1. Encontramos la opción:
tasm
4.2. Compilamos (los ... es la opción):
tasm -m... mul_div.asm
5. Escribimos un programa en C (muldiv.c) que comprueba la función:
#include <stdio.h>
#include "mul_div.h"
int main() {
for(;scanf("%d%d%d",&a,&b,&c)==3;) {
printf("%d %d\n",mul_div(a,b,c),a*b/c);
}
return 0;
}
5. Compilamos:
tcc muldiv.c
(mensaje de error por falta de la función mul_div)
tcc muldiv.c mul_div.obj
6. Comprobamos.
A>muldiv.exe
Comprobar con las entradas 2 3 2 y 512 512 10.
7. Hacer makefile:
Hemos ejecutado los comandos
tasm /mx mul_div
con entrada mul_div.asm y salida mul_div.obj y
tcc muldic.c mul_div.obj
con entrada muldiv.c, mul_div.obj y salida muldiv.exe.
muldiv.c contiene el ‘include’ de mul_div.h. Por tanto el makefile seria:
----------------------------------------------------------
muldiv.exe: muldiv.c mul_div.obj mul_div.h
tcc muldiv.c mul_div.obj
mul_div.obj: mul_div.asm
tasm /mx mul_div
----------------------------------------------------------
escribimos el makefile (edit makefile) y comprobamos:
del *.obj
make -f makefile
Ejercicios:
1. Quitar todo lo no necesario en el fichero mul_div.asm.
2. Hacer el link separado de la compilación en
el ejemplo.
3. Comprobar el programa con entrada 512 512 5 .
4. Hacer el programa mul_div con tipo unsigned. Comprobar
512 512 5.
5. Hacer mul_div en modelo de memoria large.
Notas de ayuda: El Universo Digital del PC: El ensamblador y el lenguaje C. Capítulo 13 de un buen libro digital