Ensamblador y lenguaje C Lenguaje C: lenguaje de alto nivel z z Ensamblador Intel x86 y lenguaje C Sistemas Computacionales Mario Medina C. [email protected] z Lenguaje de ensamblador: lenguaje de bajo nivel z z Código C y ensamblador x86 z z z Saltos directos e indirectos Condicionales e incondicionales A direcciones absolutas y relativas Lenguaje ensamblador no posee estructuras de control complejas z Pero, éstas se pueden implementar utilizando saltos Correspondencia 1-a-1 con código de máquina Usado para optimizar código Veremos cómo traducir código C a código ensamblador equivalente z Lenguaje ensamblador Intel x86 posee gran variedad de saltos Fácil de compilar a código de máquina Usado para escribir sistemas operativos El lenguaje de alto nivel más cercano a la máquina Ayuda a entender cómo funciona un programa Código C y ensamblador x86 Lenguaje de programación C posee estructuras de control complejas z z z z z z if-else do-while while for goto Cómo traducir estas estructuras a lenguaje de máquina? Utilizando sólo instrucciones goto Transformando if-else Código C original z Calcula valor absoluto de la diferencia entre dos números int absdiff(int x, int y) { if (x < y) return y – x; else return x – y; } ©2013 Mario Medina C. Transformando if-else Función recibe 2 argumentos z z Tiene 2 valores de retorno posibles Flujo de control puede tomar 2 caminos Reescribir usando sólo instrucciones goto No son recomendados en C, pero son muy usados en ensamblador! z Fáciles de traducir a ensamblador 1 Transformando if-else Código C modificado int gotodiff(int x, int y) { int rval; if (x < y) goto menor; rval = x – y; goto listo; menor: rval = y – x; listo: return rval; } Transformando if-else Nueva versión del código utiliza sólo instrucción goto z z movl 8(%ebp), %edx movl 12(%ebp), %eax cmpl %eax, %edx jl .menor subl %eax, %edx movl %edx, %eax jmp .listo .menor: subl %edx, %eax .listo: // // // // // // // // // // Lee x Lee y Compara x e y Si <, ir a menor: Calcula x-y Valor de retorno Ir a listo: menor: Calcula y-x listo: if-else con doble condición Código C modificado Evalúa primero condición de la izquierda y luego la de la derecha Cumple condición de cortocircuito void cond(int a, int *p) { if (p != 0) { if(a > 0) { *p = *p + a; } } } ©2013 Mario Medina C. Valor a retornar en rval Un punto de entrada a la función Un punto de salida de la función if-else con doble condición Transformando if-else •Código ensamblador x86 equivalente Requiere 2 goto y 2 rótulos, menor y listo El flujo de control de las instrucciones está en forma explícita Un solo valor de retorno posible Código C original void cond(int a, int *p) { if (p && a > 0) *p += a; } z if-else con condición doble Lenguaje C evalúa primero la condición de la izquierda Descomponer condición doble en condiciones simples if-else con doble condición Código ensamblador equivalente movl 8(%ebp), %edx movl 12(%ebp), %eax testl %eax, %eax jz .Fin testl %edx, %edx jle .Fin addl %edx, (%eax) .Fin: // // // // // // // Copia a en %edx Copia p en %eax Calcula p&p Salta si p es 0 Calcula a&a Salta si a <= 0 *p = *p + a 2 Lazos do-while Tipo de lazo muy usado en lenguaje de máquina z No tan utilizado en lenguaje C do { nucleo del lazo; } while(condicion); Se convierte en código C equivalente que usa sólo ifs y gotos Ejemplo: Fibonacci con do-while Secuencia de Fibonacci z z rotulo: { nucleo del lazo; } if (condicion) goto rotulo; z F0 = 0 F1 = 1 Fn = Fn-1 + Fn-2, n≥2 Implementación en C con ciclo do-while z z z z val almacena Fo = 0 nval almacena F1 = 1 t almacena F2 = val + nval Función retorna Fp, donde p es el argumento Ejemplo: Fibonacci con do-while int fib_dowhile(int p) { int i = 0, t; int val = 0; int nval = 1; do { t = val + nval; val = nval; nval = t; i++; } while (i < p); return val; // Inicializa con F_0 // Inicializa con F_1 Ejemplo: Fibonacci Tabla de registros y variables // Calcula F_n // Actualiza F_n-2 // Actualiza F_n-1 Registro Variable Valor inicial %ecx i 0 %esi p p %ebx val 0 %edx nval 1 // Retorna F_n-2 %eax t - } Lazos while Puede implementarse como do-while Lazo while Lazo do-while while(condicion) { nucleo del lazo; } if (!condicion) goto listo; do { nucleo del lazo; } while(condicion); listo: Sacar la primera verificación de la condicion del lazo ©2013 Mario Medina C. Código ensamblador que implementa Fibonacci .L1: leal (%edx,%ebx),%eax movl %edx, %ebx movl %eax, %edx incl %ecx cmpl %esi, %ecx jl .L1 movl %ebx, %eax Ejemplo: Fibonacci con while int fib_while(int p) { int i = 1, t; int val = 1; int nval = 1; while (i < p) { t = val + nval; val = nval; nval = t; i++; } return val; } // Inicializa con F_1 // Inicializa con F_2 // Calcula F_n // Actualiza F_n-2 // Actualiza F_n-1 // Retorna F_n-2 3 Ejemplo: Fibonacci con while Ciclo while verifica condición antes de ejecutar el núcleo del lazo z z Si condición no se cumple, el lazo no se ejecuta A diferencia de ciclo do_while Condiciones iniciales comienzan con F1 y F2 z Valor inicial de i es 1 Para pasarlo a ensamblador, primero debemos reescribirlo para usar comandos goto Ejemplo: Fibonacci con while int fib_while_goto(int n) { int i = 1; int val = 1; // Inicializa con F_1 int nmi = n - 1, t; if (val >= n) goto listo; lazo: t = val + nval; // Calcula F_n val = nval; // Actualiza F_n-2 nval = t; // Actualiza F_n-1 nmi--; // Calcula n - i if (nmi) goto lazo; listo: return val; // Inicializa con F_n-2 } Ejemplo: Fibonacci con while Versión de ciclo while utilizando instrucciones goto Código calcula n – i y usa valor nmi como índice del lazo z z z Se detiene cuando nmi es 0 Usa 1 registro menos Elimina variable i Ejemplo: Fibonacci con while Registro Variable Valor inicial %edx nmi n %ebx val 1 %ecx nval 1 movl 8(%ebp), %eax movl $1, %ebx movl $1, %ecx cmpl %eax, %ebx jge .Listo leal -1(%eax), %edx .Lazo: leal (%ecx,%ebx), %eax movl %ecx, %ebx movl %eax, %ecx decl %edx jnz .Lazo .Listo - 1 •Optimizaciones •Código usa 1 registro menos •Elimina variable i Lazos for Forma general de lazo iterativo z Equivalente a lazo while Lazo for Lazo while for (expr1; expr2; expr3) { nucleo del lazo; } expr1; while(expr2) { nucleo del lazo; expr3; } Ejemplo: Fibonacci con for Lazo for con goto expr1; if (!expr2) goto listo; lazo: { nucleo del lazo; expr3; } if (expr2) goto lazo; listo: int fib_for(int n) { int i = 1, t; int val = 1; int nval = 1; for (i = 0; i < n; i++) { t = val + nval; val = nval; nval = t; } return val; } Código x86 es similar a while ©2013 Mario Medina C. 4 Estructura de control switch Estructuras switch(i) implementan saltos multivías z z Puede ser modelado como series de if-else Generalmente se implementa como tablas de saltos Ejemplo: switch int switch_s(int x) { switch(x) { case 100: x *= 13; break; case 104: case 106: x *= x; break; default: x = 0; Vector de direcciones indexado por índice i z case 102: x += 10; case 103: x += 11; break; Más eficiente que varios if-else Ejemplo: switch int switch_tabla(int x) { unsigned xi = x – 100; if (xi > 6) goto dir_default; /* C invalido */ goto tabla[xi]; dir_A: x *= 13; goto listo; dir_B: x += 10; Estructura de control switch se transforma en saltos incondicionales dir_C: x += 11; goto listo; dir_D: x *= x; goto listo; dir_default: x = 0; listo: return x; } z Direcciones de destino están almacenadas en tabla en memoria Variable x es la variable del switch z Variable xi es un índice a la tabla de salto z Se calcula xi como x - 100 Variable es de tipo unsigned Si xi > 6, ir a dir_default /* Esto no es C válido */ Tabla contiene direcciones de salto que dependen de x ©2013 Mario Medina C. } Ejemplo: switch Ejemplo: switch tabla[7] = { dir_A, dir_default, dir_B, dir_C, dir_D, dir_default, dir_D; } return x; tabla[0] dir_A tabla[1] dir_default tabla[2] dir_B tabla[3] dir_C tabla[4] dir_D tabla[5] dir_default tabla[6] dir_D Ejemplo: switch leal -100(%edx), %eax cmpl $6, %eax ja .Ldef jmp *.Ltbl(, %eax, 4) .L103: // Caso 103 addl $11, %edx jmp .Lret .L104: // Casos 104 y 106 imull %edx, %edx .L100: // Caso 100 jmp .Lret leal (%edx, %edx, 2), %eax leal (%edx, %eax, 4), %edx .Ldef // Caso default jmp .Lret xorl %edx, %edx .L102: // Caso 102 .Lret // retorno addl $10, %edx movl %edx, %eax 5 Ejemplo: switch .Ltbl: // Tabla de salto .long .L100 // Caso 100 .long .Ldef // Caso 101 .long .L102 // Caso 102 .long .L103 // Caso 103 .long .L104 // Caso 104 .long .Ldef // Caso 105 .long .L104 // Caso 106 Compilador genera una tabla estática en memoria Entradas son direcciones a dónde saltar z Suponiendo un procesador de 32 bits, cada entrada son 4 bytes Ejercicio: switch movl 8(%ebp), %eax addl $2, %eax cmpl $6, %eax ja .L10 jmp *.L11, (, %eax, 4) Ejercicio: z Sea el siguiente switch() incompleto y el código ensamblador equivalente, complete el código C .L11: // Tabla de salto .long .L4 .long .L10 .long .L5 .long .L6 .long .L8 .long .L8 .long .L9 int switch2(int x) { int result = 0; switch(x) { /* Completar */ } return result; } Vectores Vectores Vectores y matrices en C son almacenados primero por filas y luego por columnas z z Fácil acceso a elementos del vector Modos de direccionamiento indexado escalado Ejemplo: tipo Vector[N]; z Reserva una área contigua de memoria de L*N bytes apuntada por Vector L: tamaño de tipo en bytes Sean los vectores (proc. 32 bits) char A[12]; char *B[8]; double C[6]; double *D[5]; int E[10]; Vector Tamaño elemento Tamaño Dir. total Inicial Elemento i A 1 12 xA xA+i B 4 32 xB xB+4i C 8 48 xC xC+8i D 4 20 xD xD+4i E 4 40 xE xE+4i Aritmética de punteros Dada la declaración int E[10], calcule las siguientes expresiones en código ensamblador z Suponga que %edx contiene la dirección inicial de E, %ecx contiene el índice i y %eax debe contener la expresión Expresión Tipo Valor Código E int * xE movl %edx,%eax E[0] int M[xE] movl (%edx),%eax E[i] int M[xE+4i] movl (%edx,%ecx,4),%eax &E[2] int * xE+8 leal 8(%edx),%eax E+i-1 int * xE+4i-4 leal -4(%edx,%ecx,4),%eax *(&E[i]+i) int M[xE+4i+4i] movl (%edx,%ecx,8),%eax &E[i] - E i movl %ecx,%eax int ©2013 Mario Medina C. Lazos y vectores Patrones muy regulares z Usa aritmética de punteros para recorrer vector Comparación con dirección final int decimal5(int *x) { int i; int val = 0; for (i = 0; i < 5; i++) val = 10*val + x[i]; return val; } Función convierte un vector de 5 enteros en el entero equivalente x 1 2 3 4 5 12345 6 Lazos y vectores int decimal5_dw(int *x) { int i, val = 0; int *xend = x + 4; do { val = 10*val + *x; x++; while(x <= xend); return val; } Código ensamblador Puntero xend apunta a último elemento del vector Puntero x avanza hasta que sea igual a xend movl 8(%ebp),%ecx xorl %eax,%eax leal 16(%ecx),%ebx .Lazo: leal (%eax,%eax,4),%edx movl (%ecx),%eax leal (%eax,%edx,2),%eax addl $4,%ecx cmpl %ebx,%ecx jbe .Lazo Matrices en C Declaración Elemento int A[4][3]; La matriz A contiene 4 elementos z Cada elemento es un vector de 3 enteros Memoria total: 4*3*4=48 bytes tipo D[F][C]; z A[0][2] xA+8 A[1][0] xA+12 A[1][2] xA+20 z A[2][0] xA+24 sall leal leal movl A[2][2] xA+32 A[3][0] xA+36 A[3][2] xA+44 Matrices de tamaño fijo z Acceder directamente a elementos de la matriz mediante punteros a filas y/o columnas Calcular direcciones de elementos en forma estática Acceso a elemento Ai,j en matriz anterior xA en %eax, i en %edx, j en %ecx, tipo es int A[2][1] xA+28 A[3][1] xA+40 z reserva una área contigua de memoria de L*F*C bytes apuntada por D L: tamaño de tipo en bytes A[1][1] xA+16 Si las matrices a usar son de tamaño fijo, compilador puede optimizar acceso a elementos %edx = 5*val lee *x lee *x + 10*val x++ compara x y xend Si <=, ir a Lazo Declaración de matriz A[0][1] xA+4 typedef int fila3_t[3]; fila3_t A[4]; // // // // // // Acceso a elementos de matrices A[0][0] xA Es equivalente a z Dirección // Lee dir. vector X // val = 0 // xend = x + 4 $2,%ecx (%edx,%edx,2),%edx (%ecx,edx,4), %edx (%eax,%edx),%eax // // // // j*4 i*3 j*4 + i*12 lee M[A + j*4 + i*12] Optimizaciones y matrices #define N 16 typedef int mat16[N][N]; int prodMatriz16(mat16 A, mat16 B, int i, int k) { int j; int result = 0; for (j = 0; j < N; j++) result += A[i][j]*B[j][k]; return result; Usando desplazamientos y sumas } ©2013 Mario Medina C. 7 Cálculo del elemento C[i][k] Matriz A Matriz B Fila A[i][ ] Columna B[ ][k] Optimizaciones y matrices int prodMatriz16_opt(mat16 A, mat16 B, int i, int k) { int *Aptr = &A[i][0]; int *Bptr = &B[0][k]; int count = N – 1; int result = 0; do { result += (*Aptr)*(*Bptr); Aptr += 1; Bptr += N; count--; } while (count >= 0); return result; } Optimizaciones y matrices .Lazo: movl (%edx), %eax imull (%ecx), %eax addl %eax, %esi addl $64, %ecx addl $4, %edx decl %ebx jns .Lazo // // // // // // // Lee *Aptr calcula (*Bptr)*(*Aptr) Suma A[i]*B[i] a result Incrementa Bptr Incrementa Aptr Decrementa count Si >=, ir a Lazo Suponemos Aptr en %edx, Bptr en %ecx, result en %esi, count en %ebx Código ejemplo Código C original z Inicializa los elementos diagonales de A movl 12(%ebp),%edx movl 8(%ebp),%eax void initDiag(mat16 A, int val) movl $15,%ecx { .Lazo: int i; movl %edx,(%eax) for (i = 0; i < N; i++) addl $68,%eax A[i][i] = val; decl %ecx } jns .Lazo Memoria dinámica Asignación dinámica de memoria z z Obtenida via malloc() o calloc() Liberada via free() Vectores y/o matrices cuyo tamaño se determinan en tiempo de ejecución z z Matrices se simulan como vectores Si A es una matriz de nxm, se asigna como A = calloc(sizeof(int), n*m); ©2013 Mario Medina C. Código ensamblador Acceso a elementos de matrices Acceso a elemento Ai,j en matriz anterior z Cálculo del índice al vector debe usar multiplicaciones A[i*m + j] z Ejemplo de acceso a matriz Suponemos que A, i, m y j están en la pila movl 8(%ebp),%edx movl 12(%ebp),%eax imull 20(%ebp),%eax addl 16(%ebp),%eax movl (%edx,%eax,4),%eax // // // // // Lee A Lee i Calcula m*i Calcula m*i + j Lee A[m*i + j] 8 Ejemplo: prodMatriz() typedef int *varMat; int prodMatriz(varMat A, varMat B, int i, int k, int n) { int j; int result = 0; for (j = 0; j < N; j++) result += A[i*n + j]*B[j*n + k]; return result; } Suponemos que A y B son punteros a vectores de int Convertir ahora a ciclo do-while Ejemplo: prodMatriz() int prodMatOpt(varMat A, varMat B, int i, int k, int n) { int *Aptr = &A[i*n] int temp = n; int count = n; int result = 0; if (n <= 0) return result; do { result += (*Aptr) * B[temp]; Aptr += 1; temp += n; count --; } while (count != 0); return result; } Ejemplo: prodMatriz() .L10: movl 12(%ebp),%eax movl (%ebx),%edi addl $4, %ebx imull (%eax,%ecx,4),%edi addl %edi,%esi addl 24(%ebp),%ecx decl %edx jns .L10 z // // // // // // // // Lee B Lee *Aptr Incrementa Aptr Multiplica por B[temp] Acumula resultado Sumar n a temp Incrementa count Si count != 0, ir a L10 Suponemos Aptr en %ebx, temp en %ecx, result en %esi, count en %edx Estructuras de datos struct crea un tipo de dato compuesto que agrupa múltiples objetos de tipos diferentes z z z Componentes de estructura referenciados por nombre Componentes de estructura se almacenan en memoria en forma contigua Puntero a estructura es dirección del primer byte de la estructura temp calcula n*j+k B y n se leen desde memoria porque no alcanzan los registros Estructuras de datos struct rec { int i; int j; int a[3]; int *p; }; struct rec r; struct rec *sr = r; r.i = 30; (*sr).j = 10; sr->a[0] = r.i; ©2013 Mario Medina C. sr struct rec r Estructuras de datos Supongamos que sr está en %edx, i en %esi Copiar sr->i en sr->j movl (%edx),%eax i movl %eax,4(%edx) Calcular &(sr->a[i]) j a leal 8(%edx, %esi, 4),%ecx sr->p = &sr->a[sr->i + sr->j]; p 4 bytes movl addl leal movl 4(%edx), %eax (%edx), %eax 8(%edx, %eax, 4), %eax %eax, 20(%edx) 9 Ejemplo de estructura struct prob { Calcule el tamaño de la estructura int *p; Cuáles son los offsets de los campos struct { p, s.x, s.y, next? int x; Código ASM inicialización int y; movl 8(%ebp), %eax } s; movl 8(%eax), %edx struct prob *next; movl %edx, 4(%eax) }; leal 4(%eax), %edx Inicializada por movl %edx, (%eax) void init(*sp) { movl %eax, 12(%eax) sp->s.x = ??? Complete ahora el código C anterior sp->p = ??? sp->next = ??? } Uniones Permiten referencias de diferentes tipos a un mismo objeto union U3 { char c; int i[2]; double v; } Ejemplo ocupa 8 bytes z Tamaño de la unión es el tamaño máximo de sus miembros Ejemplo de union union ele { struct { int *p; int y; } e1; struct { int x; union ele *next; } e2; }; void proc(union ele *up) { up->? = *(up->?) – up->?; } Calcule el tamaño de la union Cuáles son los offsets de los campos e1.p, e1.y, e2.x, e2.next? Código ASM inicialización movl movl movl movl movl subl movl 8(%ebp), %eax 4(%eax), %edx (%edx), %ecx (%eax), %eax (%ecx), %ecx %eax, %ecx %ecx, 4(%edx) Complete el código C anterior Funciones Son la base de la programación estructurada z Encapsular el código frecuentemente usado en una función que z z z z Funciones z Similar a instrucción jmp Pero, es necesario saber adónde debe continuar la ejecución después de ejecutada la función Es decir, cuál es la dirección de retorno z Dirección en memoria de la siguiente instrucción a ejecutar en la función invocadora Esta dirección se preserva almacenándola en memoria ©2013 Mario Medina C. Se escribe y depura una vez Se invoca muchas veces Desde diversas partes de un programa Generalmente, tiene z Una llamada a una función involucra un cambio en la secuencia de ejecución de instrucciones Programa es más claro, conciso y mantenible Un punto de entrada Un valor de retorno Instrucciones call y ret Llamada a función en dirección de memoria rotulo se realiza mediante instrucción especial call rotulo z Instrucción call *operando permite llamar a una función especificada en forma indirecta Instrucciones almacenan automáticamente la dirección de retorno en la pila z CPU ejecuta código a partir de la dirección destino Instrucción ret extrae dirección de la pila y salta a ella 10 Funciones y la pila Funciones utilizan la pila para almacenar la dirección de retorno La pila puede ser usada también para z z z z z Pasar argumentos a la función invocada Almacenar variables locales asociadas a la función invocada Almacenar variables temporales Almacenar copias de los registros utilizados en la función invocada Retornar un valor a la función invocadora desde la función invocada Marco de activación Estructura utilizada para permitar anidar llamadas a funciones z z Para cada llamada a función, almacena en forma ordenada los datos ya mencionados Utiliza el registro %ebp como puntero base al marco de activación Queda fijo durante la ejecución de la función z z z Registro %esp es el puntero a la pila Varía según instrucciones pushl y popl Dirección de retorno queda en 4(%ebp) Marcos de activación • • • +4+4n Argumento n Marco del invocador • • • Direcciones de memoria Puntero a marco %ebp Puntero a pila %esp +8 Argumento 1 +4 Dir. de retorno %ebp guardado –4 Registros, variables locales, y temporales Marco de la función en ejecución Marcos de activación Marco es área entre registros %ebp (puntero a marco) y %esp (puntero a pila) Registro %esp (Stack Pointer) apunta a último dato en la pila Registro %ebp (Base Pointer) sirve de referencia para el marco de activación z Puntero a marco fijo durante ejecución de la función Argumentos en (%ebp + constante) Variables locales en (%ebp – constante) Área de argumentos Comienzo de pila Instrucción leave Llamada a función Función P llama a función Q z z Argumentos de llamada a Q se almacenan en el marco de activación de P Dirección de retorno de función P se almacena en el marco de activación de P Último elemento del marco de activación de P z Se almacena el valor actual de %ebp en la pila Nuevo valor del registro %ebp apunta a esta dirección de memoria z Función Q usa la pila para variables locales, copias de registros y temporales ©2013 Mario Medina C. Prepara la pila para retorno de función z z Puntero a pila debe apuntar a dirección de memoria que almacena puntero a marco Recupera puntero a marco anterior Equivalente a movl %ebp, %esp popl %ebp z Puede hacerse lo mismo con movl y/o pop Generalmente registro %eax retorna valores de retorno 11 Convención de uso de registros Registros del procesador compartidos entre todas las funciones z Convención de uso de registros Quién salva los registros? z Necesario salvar los registros antes de llamar a una función int P(int x) { int y = x*x; int z = Q(y); // y debe existir aqui return y + z; } z Función invocadora (caller save), o Función invocada (callee save)? Convención de Intel x86 z z Registros %edx, %eax y %ecx salvados por función invocadora Registros %ebx, %esi y %edi salvados por función invocada (si es que los usa!) Convención de uso de registros int p(int x) { int y = x*x; int z = Q(y); // y debe // existir // aqui return y + z; } Cómo salvar el valor de y? Función invocadora puede z z Almacenar y en registro %edx, %eax o %ecx y guardarlo en la pila Almacenar y en registro %esi, %edi o %ebx y dejar que Q lo guarde en la pila (sólo si Q modifica estos registros) Ejemplo paso de parámetros int suma(int *xp, int *yp) { int x = *xp; int y = *yp; } } Puntero a pila %esp 0 %ebp guardado –4 arg2 –8 arg1 –12 &arg2 –16 &arg1 %ebp guardado Marco de activación para ejemplo Puntero a marco %ebp Puntero a pila %esp ©2013 Mario Medina C. Código ejemplo int ejemplo(void) { int diff; int arg1 = 534; int arg2 = 1057; En ejecución de suma Antes de llamar a suma arg2 arg1 +12 int sum = suma(&arg1, &arg2); diff = arg1 – arg2; return sum*diff; &arg2 +8 &arg1 +4 Dir. retorno 0 %ebp guardado –4 %ebx guardado Marco para suma int sum = suma(&arg1, &arg2); diff = arg1 – arg2; return sum*diff; *xp = y; *yp = x; return x + y; Ejemplo paso de parámetros Puntero a marco %ebp int ejemplo(void) { int diff; int arg1 = 534; int arg2 = 1057; } Código ensamblador leal -4(%ebp), %eax pushl %eax leal -8(%ebp), %eax pushl %eax call suma Almacena &arg2 en posición -4(%ebp) Almacena &arg1 en posición -8(%ebp) 12 Comienzo de suma suma: pushl %ebp // Guarda %ebp movl %esp, %ebp // Nuevo puntero pushl %ebx // Guarda ebx La función suma utiliza el registro %ebx para almacenar valores temporales z Registro %ebx salvado por rutina invocada (callee save) Cuerpo de suma movl movl movl movl movl movl addl 8(%ebp), edx 12(%ebp), %ecx (%edx), %ebx (%ecx), %eax %eax, (%edx) %ebx, (%ecx) %ebx, %eax z Direcciones referidas a nuevo %ebp Funciones recursivas Estructuras de pila y marcos de activación útiles para llamadas recursivas Cada instancia de la función maneja su propio marco de activación z Instrucciones en rojo pueden ser reemplazadas por instrucción leave Valor de retorno de la función está en registro %eax Ejemplo: fibonacci() Ojo: no es una implementación eficiente! int fib_rec(int n) { int valorPrev, val; if (n <= 2) return 1; valorPrev = fib_rec(n – 2); val = fib_rec(n – 1); return valorPrev + val; } ©2013 Mario Medina C. Lee xp Lee yp Lee x Lee y Guarda y Guarda x x + y Código obtiene argumentos de la pila Final de suma popl %ebx // Recupera %ebx movl %ebp, %esp // Recupera %esp popl %ebp // Recupera ebp ret // retorno // // // // // // // z z z Argumentos Variables locales Puntero a la pila Puntero a marco de activación Ejemplo: fibonacci en ASM fib_rec: pushl %ebp movl %esp, %ebp pushl %esi pushl %ebx movl 8(%ebp), %ebx cmpl $2, %ebx jle .Listo leal -2(%ebx), %eax pushl %eax call fib_rec // // // // // // // // // // Guarda %ebp viejo Define nuevo %ebp Guarda %esi Guarda %ebx Lee n Compara n y 2 Si <=, ir a Listo: Calcula n – 2 Graba argumento fib_rec(n – 2) 13 Ejemplo: fibonacci en ASM movl %eax, %esi leal -1(%ebx), %eax pushl %eax call fib_rec addl %esi, %eax jmp .L1 .Listo: movl $1, %eax .L1: popl %ebx popl %esi leave ret // // // // // Resultado en %esi Calcula n – 1 Graba argumento fib_rec(n – 1) valorPrev + val // Retorna 1 // // // // Recupera %ebx Recupera %esi Recupera %ebp Retorno Estado de marco de activación Puntero a marco %ebp Puntero a pila %esp +8 n +4 Dir retorno 0 %ebp guardado –4 %esi guardado –8 %ebx guardado Marco de activación para fib_rec(n) Estado del marco de activación después de guardar %ebx de fib_rec(n) en la pila Estado de marco de activación Convención de paso de argumentos Lenguaje C establece la siguiente convención para paso de argumentos n Dir retorno %ebp guardado %esi guardado z Marco de activación para fib_rec(n) %ebx guardado +8 n-1 Puntero a marco %ebp +4 0 %ebp guardado Puntero a pila %esp –4 %esi guardado –8 %ebx guardado Dir retorno Marco de activación para fib_rec(n-1) Estado del marco de activación después de guardar %ebx de fib_rec(n-1) en la pila z Lenguaje PASCAL establece otra convención z z Ejemplo Dada función fun(int A, float B, int *C) push push push call A B C fun En función fun ret 12 En C push C push B push A call fun Add $12, %esp En función fun ret ©2013 Mario Medina C. Argumentos se pasan de izquierda a derecha Función invocada debe ajustar el puntero a la pila antes de retornar para saltarse los argumentos Convención de paso de argumentos Convención stdcall es similar a PASCAL, pero z En PASCAL Argumentos se pasan de derecha a izquierda Función invocadora debe ajustar el puntero a la pila al recuperar el control z z Argumentos se pasan de derecha a izquierda Función invocada debe ajustar el puntero a la pila antes de retornar para saltarse los argumentos Estándar en Win32 API Convención fastcall de Microsoft z z z Primeros 2 argumentos se pasan en %ecx y %edx Otros argumentos se pasan de derecha a izquierda Función invocada debe ajustar el puntero a la pila antes de retornar para saltarse los argumentos 14