Práctica 3. Interfaz con el lenguaje C



Objetivos

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 librerias (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 conversor, de forma que se pueda representar en la pantalla la forma de la señal analógica de la entrada sobre unos ejes coordenados. En el eje X irá el tiempo y en el eje Y el valor en tensión. Cuando la señal llegue a la parte derecha de la pantalla se empezá de nuevo por la izquierda. En este caso no hará falta presentar ni el valor actual ni el valor medio de la señal. 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



Plazo de entrega : La presentación y evaluación de esta práctica será según consta en el calendario. El nombre del fichero o ficheros a entregar será de la forma pareja_p3x.asm ó .c ó .h. Si hay más de un fichero, nombrarlo con números sucesivos.