La práctica consistirá en hacer en C la parte de interfaz de usuario ( menús, presentaciones de datos de forma gráfica, ventanas de color, preguntas, etc) de la práctica anterior, utilizando las rutinas ya escritas en ensamblador en las prácticas anteriores. Es decir, se utilizará el driver diseñado en la práctica 2 y se utilizarán la mayoría de las funciones escritas para la práctica 1. Estas funciones estarán agrupadas en varias librerías (obligatoriamente más de una) y estarán preparadas para ser compiladas en el modelo largo del C.
Se propone una presentación gráfica de los valores entregados
por el hardware del analizador lógico, de forma que se pueda representar en la pantalla la forma
de la señal digital de la entrada sobre unos ejes coordenados.
En el eje X irá el tiempo y en el eje Y el valor lógico. El sistema de
representación incluirá un scrooll tanto a la izquierda como a la derecha. En cualquier caso,
se deja a la elección del alumno la forma de la presentación
final.
Se deberá incluir un fichero "makefile" que compile todo el proyecto.
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.
Para lograr el objetivo:
Re-escribir la interfaz en C, utilizando el driver de la práctica
2 y utilizando solo llamadas a funciones para comunicar la parte en C y el
driver.
Que hay que hacer:
1. Escribir una librería de conexión entre
la parte C y la parte ASM (el dirver). Esta librería incluye una función
para cada servicio del driver (cada AH distinto).
2. Escribir la interfaz en C (descrita en el inicia de
este documento).
3. Link del programa en C y la librería en ASM
en un programa.
4. Hacer un makefile que compila y haga el link y la compilación.
Escribir una librería de conexión entre el driver y el programa
C:
Hay que aprender:
1. Como se hace un prototipo (esqueleto) de un programa ASM que va a utilizarse
como parte de un programa C. Lo más fácil -- con
el mismo compilador de C:
-- escribimos funciones (stubs o dummy) con el prototipo deseado.
-- Compilamos con opción que genera ficheros .asm (casi
siempre -S).
-- Utilizamos los ficheros generados del compilador.
2. Como se pasan parámetros de un programa C a un procedimiento ASM.
3. Conocimiento general sobre la estructura de los segmentos que utiliza
un programa en C. Conocimiento general sobre contexto de ejecución
de un programa.
Independiente de SO y el compilador, casi siempre la memoria
de un programa en C está segmentada en los siguientes segmentos:
TEXT : el código ejecutable del programa.
BSS: datos (variables globales) con inicialización.
DATA: datos (variables globales) sin inicialización.
CONST: constantes (variables globales o estáticas) que
no se pueden cambiar.
STACK: la pila del compilador.
Es normal que tengan los siguientes permisos:
READ WRITE EXECUTE
------------------------
TEXT no no si
BSS si si no
CONST si no no
STACK si si si
DATA si si no
------------------------
Según el modelo de memoria pueden existir varios segmentos de cada
tipo. Según la plataforma puede ser que se permiten mas operaciones
que los indicados en la tabla. También es posible juntar varios segmentos
en un grupo. Por ejemplo en DOS todos los permisos están en 'si' y
es normal que se junten
BSS+CONST+DATA -> DGROUP
Escribir la interfaz en C:
Las explicaciones del programa en C no forma parte de esta asignatura. El
programa tendrá la misma funcionalidad que la práctica 1, pero
será escrita en C.
Unir la parte de C y de ASM:
1. Cómo llamar a un procedimiento en ensamblador
desde un programa en C. La llamada tiene la misma estructura que la llamada
a una función en C.
2. Conocimiento de transformación de nombres en
ASM y C. El nombre de la función se transforma según las reglas
de la plataforma correspondiente. En DOS y en C (no C++) se añade el
símbolo '_' en el principio del nombre. Además es necesario
de declarar el nombre de la función como 'public' en el programa .asm.
La declaración en C se puede escribir en un fichero .h.
3. Case-sensitive ASM y link. El lenguaje C es un lenguaje
que distingue letras minúscula y mayúscula. Lo mismo tiene que
hacer el ensamblador. La opción del ensamblador que hace esto se consigue
pidiendo la ayuda de línea de comando para el ensamblador correspondiente
(en DOS al ejecutar TASM sin argumentos).
Hacer un makefile que compila y hace el link.
1. Que es un makefile:
El makefile sirve para hacer programas de muchas partes (módulos).
Define el orden en que se compilan los módulos del programa y en que
casos se compilan.
Ejemplo:
Un programa que esta compuesto de los siguientes ficheros:
calcula.c
util.c
util.h
Suponemos que util.h contiene las declaraciones necesarias
para utiliza las funciones del util.c. calcula.c utiliza las funciones de
calcula.c, pero util.c no utiliza las funciones de calcula.c.
Si vamos a compilar el programa calcula.c desde cero hay que
hacer:
(1) tcc /c util.c
(2) tcc /c calcula.c
(3) tlink calcula.obj util.obj
Está claro que hay que compilar util.c a util.obj sólo
si cambia util.c.
Está claro que hay que compilar calcula.c sólo
si cambia calcula.c o util.h.
(Pregunta: ¿Por qué no hay que compilar calcula.c
si cambia util.c sin que cambia util.h?)
Está claro que hay que hacer el link si cambia calcula.obj
o util.obj.
Si el programa contiene solo 2 módulos estos pasos son
simples de hacerles a mano. Si el programa contiene 80 módulos ya es
prácticamente imposible de hacer a mano los pasos necesarios. Esto
se hace desde el make.
El programador declara en el fichero make que fichero de que depende y cómo
construir el fichero dependiente. El programa make a partir de esta información
genera el plan de ejecución y ejecuta los programas correspondientes.
En el caso del ejemplo al make es:
# makefile para calcula.exe:
calcula.exe: calcula.obj util.obj
tlink calcula.obj util.obj
# el fichero calcula.obj depende del fichero calcula.c y util.h:
calcula.obj: calcula.c util.h
tcc /c calcula.c
# el fichero util.obj se hace a partir de util.c
util.obj: util.c
tcc /c util.c
El de arriba es el contenido del makefile que hace calcula.exe a partir
de
calcula.c, util.h y util.h.
Se hace con el comando:
A:> make -f makefile
El make va a hacer solo los pasos necesarios para construir calcula.exe.
Como make sabe que hay que hacer un paso del programa:
Si makefile contiene la línea
R: A B C
X
El make ejecuta el paso X solo si una de las fechas de modificación
de A B o C es posterior a la fecha de R. Las consecuencias prácticas
de esto son:
· La fecha de todos los sistemas donde se hacen
los programas tienen que estar correctos.
· Al cambiar el ordenador hay que comprobar si
la fecha es correcta.
· Al copiar los ficheros hay que ver si la fecha
de los ficheros no cambia.
En el caso de que una de las condiciones no se cumpla, lo más seguro
es borrar todos los ficheros intermedios (los *.obj en el caso del ejemplo)
y compilar todo de nuevo.
2. Cómo se hace.
El make contiene comandos del usuario. Por
tanto al compilar una vez a mano la compilación se escribe en el makefile
toda la línea de ejecución y la línea de dependencias.
La salida de cada paso es el fichero dependiente. Los ficheros de que dependen
estos ficheros contienen en todos casos los argumentos del comando y los ‘includes’
en el programa.
Ejemplo:
(calcula.c util.c util.h)
Hay que ejecutar:
tcc -c calcuca.c
Esta ejecución tiene como salida calcula.obj, como entrada
calcula.c. La línea en makefile será:
calcula.obj: calcula.c
tcc -c calcula.c
Si calcula.c contiene util.h hay que escribir:
calcula.obj: calcula.c util.h
tcc -c calcula.c
4. Errores comunes al hacer un makefile.
n Incluir dependencias no directas, por ejemplo
# !!! ERROR:
calcula.exe: calcula.obj util.obj util.c .
En esta manera se pueden incluir bucles de dependencias o múltiples
compilaciones con opciones que no se controlan del make.
n Cambiar las fechas al copiar. No incluir dependencias
de ficheros importantes. Un fallo frecuente es no incluir los includes.
n La línea de acción no empieza con tabulador
sino con espacios.
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
bcc -S mul_div.c
3. Escribimos el programa en ensamblador, utilizando el esqueleto generado
por bcc 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:
bcc muldiv.c
(mensaje de error por falta de la función mul_div)
bcc 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
bcc 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
bcc 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
9. 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: Utilización
de múltiples lenguajes en un programa (by Kostadin).
El Universo Digital del
PC: El ensamblador y el lenguaje C. Capítulo 13 de un buen
libro digital