[go: up one dir, main page]

0% encontró este documento útil (0 votos)
4K vistas9 páginas

Programación Híbrida en C y Ensamblador

Este documento trata sobre programación híbrida, específicamente sobre directivas para compilación híbrida entre C y ensamblador. Explica que C es apropiado para programación de sistemas pero a veces se necesita usar ensamblador por razones de rendimiento o necesidad. Luego describe cómo integrar ensamblador y C usando sentencias asm, y diferentes modelos de memoria y sus características.

Cargado por

Yossi G. Nuñez
Derechos de autor
© Attribution Non-Commercial (BY-NC)
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como DOCX, PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
4K vistas9 páginas

Programación Híbrida en C y Ensamblador

Este documento trata sobre programación híbrida, específicamente sobre directivas para compilación híbrida entre C y ensamblador. Explica que C es apropiado para programación de sistemas pero a veces se necesita usar ensamblador por razones de rendimiento o necesidad. Luego describe cómo integrar ensamblador y C usando sentencias asm, y diferentes modelos de memoria y sus características.

Cargado por

Yossi G. Nuñez
Derechos de autor
© Attribution Non-Commercial (BY-NC)
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como DOCX, PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 9

UNIDAD 4 PROGRAMACION HIBRIDA

4.1 DIRECTIVAS PARA COMPILACION HIBRIDA

Directivas de Programación Híbrida

EL ENSAMBLADOR Y EL LENGUAJE C

El lenguaje C es sin duda el más apropiado para la programación de sistemas,


pudiendo sustituir al ensamblador en muchos casos. Sin embargo, hay ocasiones
en que es necesario acceder a un nivel más bajo por razones de operatividad e
incluso de necesidad (programas residentes que economicen memoria, algoritmos
rápidos para operaciones críticas, etc.). Es entonces cuando resulta evidente la
necesidad de poder emplear el ensamblador y el C a la vez.

Para comprender este capítulo, basta tener unos conocimientos razonables de C


estándar. Aquí se explicarán las funciones de librería necesarias para acceder al
más bajo nivel, así como la manera de integrar el ensamblador y el C.

Sentencias ensamblador

C++ dispone de la opción de poder escribir directamente instrucciones en lenguaje


ensamblador junto con el resto del código fuente. Para esto dispone de una
palabra clave específica: asm.

La palabra asm indica que la cadena literal que sigue será incluida en el código


objeto en la posición indicada.

La sintaxis empleada depende del compilador. En el caso de C++Builder, es la


siguiente:

asm <opcode> <operandos> <; o NL>

Ejemplo

asm pop dx;


asm push ebx;
asm call @@std@%basic_ostream$c19std@%char_traits$c%%@put$qc;
asm add esp,8;
asm push ebx;

Se permite que varias instrucciones ensamblador pueden ser agrupadas en un


solo bloque precedido por el indicador asm. Ejemplo:

asm {
  mov ax, 0x0e07;
  xor bx, bx;
  int 0x10;
}

La sintaxis empleada por el compilador C++ GNU para Windows utilizado por


Dev-C++ no utiliza la sintaxis de Intel, sino la de AT&T.

Ejemplo [2]:

int AdrIO;            // variable global


static char ValIO;    // variable global

void foo() {
__asm("mov _AdrIO, %dx") ; // cargar un registro de 16 bits
  __asm("mov _ValIO, %al") ; // cargar un registro de 8 bits
  __asm("mov %ax,%dx") ; // mover contenido registro AX a DX

Nota: no olvidar incluir el guión bajo ("underscore") precediendo los nombres


de variables globales.

Ni que decir tiene que cualquier código ensamblador insertado mediante asm, es


específico, y por tanto dependiente, de la plataforma sobre la que se ejecutará el
programa. Este tipo de sentencias deben reducirse al máximo si queremos facilitar
la portabilidad del código.
4.3 BLOQUES EN LENGUAJE ENSAMBLADOR

Bloques en ensamblador

MODELOS DE MEMORIA.

Los modelos de memoria constituyen las diversas maneras de acceder a la


memoria por parte de los compiladores de C. En el caso del Turbo C se pueden
distinguir los siguientes:

TINY: Se emplea en los programas donde es preciso apurar el consumo de


memoria hasta el último byte. Los 4 registros de segmento (CS, DS, ES, SS) están
asignados a la misma dirección, por lo que existe un total de 64 Kb donde se
mezclan código, datos y pila. Los programas de este tipo pueden convertirse a
formato COM.

SMALL: Se utiliza en aplicaciones pequeñas. Los segmentos de código y datos


son diferentes y no se solapan. Por ello, hay 64 kb para código y otros 64 Kb a
repartir entre datos y pila.

Segmentos Punteros

Modelo Código Datos Pila Código Datos

Tiny 64 Kb near near

Small 64 Kb 64 Kb near near

Medium 1 Mb 64 Kb far near

Compact 64 Kb 1 Mb near far

Large 1 Mb 1 Mb far far

Huge 1 Mb 1 Mb

(Bloques > 64 Kb) far far

MEDIUM: Este modelo es ideal para programas largos que no manejan


demasiados datos. Se utilizan punteros largos para el código (que puede
extenderse hasta 1 Mb) y cortos para los datos: la pila y los datos juntos no
pueden exceder de 64 Kb.

COMPACT: Al contrario que el anterior, este modelo es el apropiado para los


programas pequeños que emplean muchos datos. Por ello, el programa no puede
exceder de 64 Kb aunque los datos que controla pueden alcanzar el Mb, ya que
los punteros de datos son de tipo far por defecto.

LARGE: Empleado en las aplicaciones grandes y también por los programadores


de sistemas que no tienen paciencia para andar forzando continuamente el tipo de
los punteros (para rebasar el límite de 64 Kb). Tanto los datos como el código
pueden alcanzar el Mb, aunque no se admite que los datos estáticos ocupen más
de 64 Kb. Este modo es el que menos problemas da para manejar la memoria, no
siendo quizá tan lento y pesado como indica el fabricante.

HUGE: Similar al anterior, pero con algunas ventajas: por un lado, todos los
punteros son normalizados automáticamente y se admiten datos estáticos de más
de 64 Kb. Por otro, y gracias a esto último, es factible manipular bloques de datos
de más de 64 Kb cada uno, ya que los segmentos de los punteros se actualizan
correctamente. Sin embargo, este modelo es el más costoso en tiempo de
ejecución de los programas.

Usando la pila

Una sección de la memoria del programa es reservado para el uso de una pila. La
Intel 80386 y procesadores superiores contienen un registro llamado puntero a la
pila, esp, el cual almacena la dirección del tope de la pila, la figura 1 de abajo
muestra 3 valores enteros, 49, 30 y 72, almacenados en la pila(cada entero
ocupando 4 bytes) con el registro esp apuntando a la dirección del tope de la pila.

A diferencia de una pila creciendo hacia arriba, en las máquinas intel crecen hacia
abajo. En la Figura 2 muestra las capas de la pila después de la ejecución pushl
$15.

El punter de la pila es decrementado de cuatro en cuatro y el número 15 es


almacenando como lugares de 4 bytes, 1988, 1989, 1990 y 199
La instrucción popl %eax copia el valor del tope de la pila(4 bytes) a eax e
incrementa esp en 4. Qué sucede si no quieres copiar el valor del tope de la pila a
un registro?. Puedes ejecutar la instrucción addl $4, %esp el cual simplemente
incrementa el puntero de la pila.

#Listing 3
.globl main
main:
movl $10, %eax
call foo
ret
foo:
addl $5, %eax

ret

En Listing 3, la instrucción call foo pone la dirección de la instrucción después de


call en la llamada al programa sobre la pila y va hacia foo. La subrutina termina
con ret, el cual transfiere el control a la instrucción cuya dirección se toma desde el
tope de la pila. Obviamente el tope de la pila debe contener una dirección válida.
4.4 OPERADORES LENGUAJE ENSAMBLADOR
Operadores

VARIABLES GLOBALES PREDEFINIDAS INTERESANTES.

 _version    /* devuelve la versión del DOS de manera completa */


 _osmajor    /* devuelve el número principal de versión del DOS: ej., 5 en el DOS
5.0 */
 _osminor    /* devuelve el número secundario de versión del DOS: ej., 0 en el
DOS 5.0 */
 _psp        /* segmento del PSP */
 _stklen     /* contiene el tamaño de la pila, en bytes */
 _heaplen    /* almacena el tamaño inicial del heap, en bytes (0 para maximizarlo)
*/

     De estas variables predefinidas, las más útiles son quizá las que devuelven la
versión del DOS, lo que ahorra el esfuerzo que supone averiguarlo llamando al
DOS o empleando la función de librería correspondiente. También es útil _psp,
que permite un acceso a este área del programa de manera inmediata.
Operadores Aritméticas, de Comparación y Ciclos

El siguiente programa realize un factorial de un número almacenado en eax. El


factorial es almacenado en ebx.

#Listing 2
.globl main
main:
movl $5, %eax
movl $1, %ebx
L1: cmpl $0, %eax //compare 0 with value in eax
je L2 //jump to L2 if 0==eax (je - jump if equal)
imull %eax, %ebx // ebx = ebx*eax
decl %eax //decrement eax
jmp L1 // unconditional jump to L1
L2: ret
L1 and L2 son etiquetas. Cuando el flujo de control llega a L2, ebx contendrá el
factorial de un número almacenado en eax.

INSERCIÓN DE CÓDIGO EN LÍNEA.

 void _ _emit_ _ (argumento,...);


 void geninterrupt (int interrupción);
     Por medio de _ _emit_ _() se puede colocar código máquina de manera directa
dentro del programa en C. No es conveniente hacerlo así porque así, ya que
alterar directamente los registros de la CPU acabará alterando el funcionamiento
esperado del compilador y haciendo fallar el programa. Sin embargo, en un
procedimiento dedicado exclusivamente a almacenar código inline (en línea), es
seguro este método, sobre todo si se tiene cuidado de no alterar los registros SI y
DI (empleados muy a menudo por el compilador como variables de tipo register).
Por medio de geninterrupt() se puede llamar directamente a una interrupción:
geninterrupt (interr) es exactamente lo mismo que _ _emit_ _(0xCD, interr) ya que
0xCD es el código de operación de INT. Por ejemplo, para volcar la pantalla por
impresora se puede ejecutar geninterrupt(5). Con los símbolos _AX, _AL, _AH,
_BX, _BL, _BH, _CX, _CL, _CH, _DX, _DL, _DH, _SI, _DI, _BP, _SP, _CS, _DS,
_ES, _SS y _FLAGS se puede acceder directamente a los registros de la CPU.
Hay que tomar también precauciones para evitar efectos laterales (una asignación
tipo _DS=0x40 no afectará sólo a DS).

El GNU C soporta la arquitectura x86 muy bien, e incluye la habilidad de insertar


código en programas C, tal como los espacios de registros. Por supuesto, las
instrucciones son dependientes de la arquitectura.

La instrucción asm permite insertar instrucciones dentro de C y C++, como las


siguientes:

asm ("fsin" : "=t" (answer) : "0" (angle));

que es una manera de codificar en C la siguiente instrucción:

answer = sin(angle);

Se puede ver que a diferencia de ensamblador común se permite especificar


entradas y salidas usando la sintaxis de C. Pero como no se deben usar
indiscriminadamente por qué razones debemos usarlas?:

 Sentencias Asm permiten acceder al hardware de la computadora


directamente. Esto puede generar que los programas se ejecuten más
rápidamente. Se puede usar cuando se escriben códigos de sistema
operativo que necesita directamente interactuar con el hardware.
 Instrucciones inline también aumentan la velocidad del recorrido de ciertos
ciclos de los programas. Por ejemplo, seno y coseno de los mismos
ángulos pueden encontrarse desde fsincos x86. Probablemente, los
siguientes ejemplos ayuden a entender mejor lo expuesto en estos dos
puntos.

#Listing 11
#Name : bit-pos-loop.c
#Description : Find bit position using a loop
#include <stdio.h>
#include <stdlib.h>

int main (int argc, char *argv[])


{
long max = atoi (argv[1]);
long number;
long i;
unsigned position;
volatile unsigned result;

for (number = 1; number <= max; ; ++number) {


for (i=(number>>1), position=0; i!=0; ++position)
i >>= 1;
result = position;
}
return 0;
}
#Listing 12
#Name : bit-pos-asm.c
#Description : Find bit position using bsrl

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])


{
long max = atoi(argv[1]);
long number;
unsigned position;
volatile unsigned result;

for (number = 1; number <= max; ; ++number) {


asm("bsrl %1, %0" : "=r" (position) : "r" (number));
result = position;
}
return 0;
}

Compile the two versions with full optimizations as given below:

$ cc -O2 -o bit-pos-loop bit-pos-loop.c


$ cc -O2 -o bit-pos-asm bit-pos-asm.c

Mide el tiempo de ejecución de cada versión usando el comando time y


especificando el valor longitud en los argumentos de la línea de comandos para
asegurarte que cada versión toma al menos unos segundos ejecutarse.
$ time ./bit-pos-loop 250000000

and

$ time ./bit-pos-asm 250000000

El resultado puede variar en diferentes máquinas. Sin embargo se notará que la


versión que usa funciones inline se ejecuta más rápido que la otra.

Nota: El optimizador de GCC intenta reorganizar y escribir el código del programa


para minimizar el tiempo de ejecución incluso en presencia de expresiones asm.
Si el optimizador determina que la salida de un asm no es usada, la instrucción
será omitida a menos que la palabra volatile esté entre asm y sus argumentos.
Cualquier asm puede ser movido de manera que sea difícil de predecir, incluso en
saltos cruzados. La única manera de garantizar ensamble en particular con un
orden de instrucción es incluir todas las instrucciones en el mismo asm.

También podría gustarte