Operaciones con bits 3.1 Operaciones de desplazamientos
4.7. Interfazando ensamblador con C
39 ret
sub4.asm
El ejemplo anterior solo tiene etiquetas de c´odigo global sin embargo las etiquetas de datos global trabajan exactamente de la misma manera.
4.7.
Interfazando ensamblador con C
Hoy d´ıa, pocos programas est´an escritos completamente en ensamblador. Los compiladores son muy buenos en convertir c´odigo de alto nivel en un c´odigo de m´aquina eficiente. Ya que es mucho m´as f´acil escribir c´odigo en un lenguaje de alto nivel, es m´as popular. Adem´as, el c´odigo de alto nivel es mucho m´as port´atil que el ensamblador.
Cuando se usa ensamblador, se usa a menudo solo para peque˜nas partes de c´odigo. Esto se puede hacer de dos maneras: llamando rutinas de ensam- blador desde C o ensamblado en l´ınea. El ensamblado en l´ınea le permite al programador colocar instrucciones de ensamblador directamente en el c´odi- go de C. Esto puede ser muy conveniente; sin embargo hay desventajas del ensamblado en l´ınea. El c´odigo en ensamblador se debe escribir en el formato que usa el compilador. No hay compilador que en el momento soporte el for- mato de NASM. Los diferentes compiladores requieren diferentes formatos. Borland y Microsoft requieren el formato NASM . DJGPP y el gcc de Linux requieren el formato GAS3. La t´ecnica de llamar una rutina en ensamblador est´a mucho m´as generalizada en el PC.
Las rutinas de ensamblador com´unmente se usan con C por las siguientes razones:
Se necesita acceso directo a caracter´ısticas del hardware del compu- tador que es imposible o dif´ıcil acceder desde C.
La rutina debe ser lo m´as r´apida posible y el programador puede op- timizar a mano el c´odigo mejor que el compilador
La ´ultima raz´on no es tan v´alida como una vez lo fue. La tecnolog´ıa de los compiladores se ha mejorado con los a˜nos y a menudo generan un c´odigo muy eficiente. (Especialmente si se activan las optimizaciones del compilador). Las desventajas de las rutinas en ensamblador son: portabilidad reducida y lo poca legibilidad.
La mayor´ıa de las convenciones de llamado ya se han especificado. Sin embargo hay algunas caracter´ısticas adicionales que necesitan ser descritas.
3
GAS es el ensamblador que usan todos los compiladores GNV. Usa la sintaxis AT&T que es muy diferente de la sintaxis relativamente similares de MASM, TASM y NASM.
1 segment .data 2 x dd 0 3 format db "x = %d\n", 0 4 5 segment .text 6 ...
7 push dword [x] ; empuja el valor de x
8 push dword format ; empuja la direcci´on de la cadena
9 ; con formato
10 call _printf ; observe el gui´on bajo
11 add esp, 8 ; quita los par´ametros de la pila
Figura 4.11: Llamado aprintf
EBP + 12 valor dex
EBP + 8 direcci´on de la cadena con formato EBP + 4 Direcci´on de retorno
EBP EBP guardado
Figura 4.12: Pila dentro de printf
4.7.1. Ahorrando registros
Primero, C asume que una subrutina conserva los valores de los siguientes
La palabra reservada
register se puede usar en una declaraci´on de una variable de C para sugerirle al compilador que use un registro para esta variable en vez de un lugar en memoria. Ellas se conocen como variables register. Los compiladores modernos hacen esto au- tom´aticamente sin requerir ninguna sugerencia.
registros: EBX, ESI, EDI, EBP, CS, DS, SS, ES. Esto no significa que la subrutina no pueda cambiarlos internamente. En vez de ello, significa que si se hace un cambio en sus valores, deben restablecer los valores originales antes que la subrutina retorne. Los valores en EBX, ESI y EDI no se deben modificar porque C usa esos registros paravariables register. Normalmente la pila se usa para guardar los valores originales de estos registros.
4.7.2. Etiquetas de funciones
La mayor´ıa de compiladores anteponen un gui´on bajo ( ) al inicio de los nombres de funciones y variables globales o static. Por ejemplo una funci´on llamada f se le asignar´a la etiqueta f. As´ı, si ´esta es una rutina en en- samblador se debe llamar fnof. El compilador gcc de Linuxno antepone ning´un car´acter. Bajo los ejecutables ELF de Linux, uno simplemente usar´ıa la etiqueta f para la funci´on de C f. Sin embargo el gcc de DJGPP ante- pone un gui´on bajo. Observe que en el programa esqueleto de ensamblador (Figura 1.7) la etiqueta de la rutina principal es asm main.
4.7. INTERFAZANDO ENSAMBLADOR CON C 85
4.7.3. Pasando par´ametros
Bajo la convenci´on de llamado de C, los argumentos de una funci´on se empujan en la pila en el orden inverso que aparecen en el llamado a la funci´on.
Considere la siguiente instrucci´on en C:printf ("x=%d\n",x);la Figu- ra 4.11 muestra como se compilar´ıa esto (mostrado en el formato equivalente de NASM). La Figura 4.12 muestra c´omo se ve la pila luego del pr´ologo den- tro de la funci´on printf. La funci´on printfes una de las funciones de la biblioteca de C que puede tomar cualquier n´umero de argumentos. Las re- glas de la convenci´on de llamado de C fueron escritas espec´ıficamente para
permitir este tipo de funciones. Ya que la direcci´on de la cadena con formato No es necesario usar en- samblador para procesar un n´umero arbitrario de par´ametros en C. El archi- vo de cabecera stdarg.h
define marcos que se pue- den usar para procesarlos con portabilidad. Vea cual- quier buen libro de C para los detalles
se empuja de ´ultimo, ´este lugar en la pila ser´a siempre EBP + 8no importa cuantos par´ametros se le pasan a la funci´on. El c´odigo printf puede ver en la cadena con formato cu´antos par´ametros se le debieron haber pasado y verlos en la en la pila.
Claro est´a, si se comete un error, printf ("x=%d\n"); el c´odigo de printfesperar´a imprimir una palabra doble en[EBP+12]. Sin embargo este no ser´a el valor dex.
4.7.4. Calculando las direcciones de las variables locales
Hallar la direcci´on de una variable local definida en el segmento data o bsses sencillo, b´asicamente el encadenador hace esto. Sin embargo calcular la direcci´on de una variable local (o par´ametro) en la pila no es directo. Sin embargo, es una necesidad muy com´un cuando se llaman subrutinas. Considere el caso de pasar la direcci´on de una variable (la llamaremos x) a una funci´on (que llamaremos foo). Si x est´a en EBP−8 en la pila, uno no puede usar:
mov eax, ebp - 8
¿Por qu´e? El valor que MOV almacena en EAX debe ser calculado por el ensamblador (esto es, debe ser una constante). Sin embargo, hay una ins- trucci´on que hace el c´alculo deseado. Es llamadaLEA(Load Efective Adress). Lo siguiente calcular´ıa la direcci´on de xy la almacena en EAX:
lea eax, [ebp - 8]
Ahora EAX almacena la direcci´on de x y podr´ıa ser empujada en la pila cuando se llame la funci´onfoo. No se confunda, parece como si esta instruc- ci´on estuviera leyendo el dato en [EBP−8]; sin embargo esto no es verdad. ¡La instrucci´onLEAnunca lee la memoria! Solo calcula la direcci´on que ser´ıa le´ıda por otra instrucci´on y almacena esta direcci´on en el primer operando de registro. Ya que no se hace ninguna lectura a memoria, no hay necesidad, ni est´a permitido definir el tama˜no de la memoria (dword u otros).
4.7.5. Retornando valores
Las funciones diferentes a void retornan un valor. La convenci´on de lla- mado de C especifica c´omo se hace esto. Los valores de retorno se pasan a trav´es de registros. Todos los tipos enteros (char,int,enum, etc.) se retor- nan en el registro EAX. Si son m´as peque˜nos que 32 bits, ellos son extendidos a 32 bits cuando se almacenan en EAX. (el c´omo se extienden depende de si ellos tipos son con o sin signo.) Los valores de 64 bits se retornan en el par de registros EDX:EAX. Los valores tipo apuntador tambi´en se almacenan en EAX. Los valores de punto flotante son almacenados en el registro ST0 del coprocesador matem´atico. (Este registro se discute en el cap´ıtulo de punto flotante).
4.7.6. Otras convenciones de llamado
Las reglas anteriores describen la convenci´on de llamado est´andar de C que es soportada por todos los compiladores de C para 80x86. A menudo los compiladores soportan otras convenciones de llamado tambi´en. Cuan- do se interfaza con lenguaje ensamblador es muy importante conocer que convenci´on de llamado est´a usando el compilador cuando llama su funci´on. Normalmente, por omisi´on se usa la convenci´on de llamado est´andar; sin embargo no siempre es este el caso4. Los compiladores que usan varias con- venciones a menudo tienen opciones de la l´ınea de ´ordenes que se pueden usar para cambiar la convenci´on por omisi´on. Ellos tambi´en le suministran extensiones a la sintaxis de C para asignar expl´ıcitamente convenciones de llamado a funciones individuales. Sin embargo, estas extensiones no est´an normalizadas y pueden variar de un compilador a otro.
El compilador GCC permite diferentes convenciones de llamado. La con- venci´on de una funci´on se puede declarar expl´ıcitamente usando la extensi´on attribute . Por ejemplo para declarar una funci´on void que usa la con- venci´on de llamado est´andar llamada fque toma un par´ametro int, use la siguiente sintaxis para su prototipo:
void f ( int ) attribute ((cdecl));
GCC tambi´en soporta la convenci´on de llamado est´andar. La funci´on de arriba se podr´ıa declarar para usar esta convenci´on reemplazando cdecl constdcall. La diferencia entrestdcall ycdecles questdcall requiere que la subrutina quite los par´ametros de la pila (como lo hace la convenci´on de llamado de Pascal). As´ı, la convenci´on stdcall s´olo se puede usar con 4El compilador de C de Watcom es un ejemplo de uno que no usa la convenci´on de
llamado est´andar por omisi´on. Vea el c´odigo fuente de ejemplo para Watcom para los detalles
4.7. INTERFAZANDO ENSAMBLADOR CON C 87