Programación en Lenguaje Ensamblador

-El Verdadero Lenguaje de las Máquinas-

CALL. Una Llamada Perdida (Parte 3)

–Llamada a Funciones Con Argumentos y Variables Locales–

De acuerdo, esta es la última parte de la explicación sobre como trabaja el sistema de llamadas a función. Hasta ahora, no espero que hayan escrito una sola linea de código, cuando mucho unos cuantos valientes habrán hecho copy paste y ejecutado los códigos con F9 en el FASM. Esto tan solo es para echar un poco de luz y que comprendan el último código. Comencemos.

Existe una tercera forma de llamar funciones con la instrucción de ensamblador CALL. Y es la que echa mano de un Stack Frame completo con argumentos de entrada y variables locales. Veamos de nuevo el viejo ejemplo de sumar dos números:

-sección de datos:

A dd 2

B dd 3

C dd ?

-Sección de código, llamada a función:

push dword [B]

push dword [A]

call sumar

-sección de código, la función que suma:

sumar:

push ebp

mov ebp, esp

sub esp, 4

virtual at ebp – 4

.resultado dd ?

.ebp_anterior dd ?

.retorno dd ?

.local_A dd ?

.local_B dd ?

end virtual

mov eax, [.local_A]

add eax, [.local_B]

mov [.resultado], eax

leave

ret 8

Veamos, la única diferencia que tiene este código respecto al anterior es la parte de ‘virtual at ebp – 4′. Como ya se explicón en la entrada anterior, Virtual crea un nuevo Location Counter y le da un nuevo valor a las etiquetas. No se desesperen si no entienden esto por ahora, ya que el ‘Memory Addressing’ es la parte mas dificil de esto de la programación en Ensamblador.

Veamos un poco de esto de la directiva Virtual y el Stack Frame. El Stack Frame se divide en 3 partes:

*La parte central, que es donde apunta EBP directamente y el valor que le sigue a este, EBP + 4 que guarda la dirección de retorno.

*La Parte Positiva.- Es donde van los argumentos de entrada los offsets de la memoria son positivos. Todos los valores se direccionan como [EBP + 8], [EBP +0Ch], etc. Aunque en teoría es posible usar una cantidad indeterminada de argumentos de entrada, para cantidades de datos muy grandes es mejor pasar la posición de memoria en donde están.

*La Parte Negativa.- Aquí van las variables locales. La parte que dice SUB EBP, 4 aparta espacio en el stack para estas variables. Todos los datos locales se referencian con offsets negativos. Veamos como la computadora ve el Stack Frame:

[ebp-4] = [.resultado]

[ebp] = [.ebp_anterior]

[ebp+4] = [.retorno]

[ebp + 8] = [.local_A]

[ebp + 0ch] = [.local_B]

Si esto no es suficiente para explicarlo, puede que sea mejor un dibujo. Lo interesante de esto es que podemos tener multitud de Stack Frames no solo de diferentes procesos. ¡Incluso es posible tener muchos Stack Frames del mismo proceso! Este es el secreto de la mentada recursión a la que los matados de las universidades temen y rinden culto. Ahora rematemos con un algunos mitos y realidades sobre las llamadas a función:

*Las variables locales son ineficientes (verdadero).- Usar variables locales es seguro y estructurado, pero cada vez que se llama a una función es necesario cargar los argumentos por pila para luego hacer y deshacer el Stack Frame. Con variables globales solo hay que usarlas y ya. Sin embargo hay serias limitaciones en su uso. Sin mencionar que las variables locales ayudan a mantener una buena estructura de código. No hay que abusar de las variables locales a menos que se usen para intercambiar información entre procesos o por razones de muy alta eficiencia.

*En Ensamblador no se pueden estructurar las funciones (falso).- Creo que lo visto en las últimas 3 notas es explicación suficiente.

*Directivas como PROC, RETURN y LOCAL son indispensables(falso).- En MASM y en otros ensambladores se utilizan macros o directivas de precompilación para administrar los llamados a función. Sin embargo, en estas notas se ha demostrado que ninguna de esas no solo son por completo prescindibles, sino que incluso estorban a los principiantes en su camino a entender el ensamblador. Como nota adicional, debo comentar que una de las cosas que mas me detuvieron a la hora de aprender a programar en Ensamblador fue precisamente este tipo de pseudo directivas lamers. Solo el uso de un editor hexadecimal y un profundo conocimiento del código máquina me permitieron entender los llamados a funciones. Aprecien estas notas, pues a mi me tomó mucho tiempo y esfuerzo entender esto.

About these ads

enero 21, 2009 - Publicado por | Uncategorized

8 comentarios »

  1. Eit, el contenido de [ebp-4](.resultado) nunca lo recogiste?

    Comentario por blackpig | junio 16, 2009 | Responder

    • Dependiendo de si se usa o no un Stack Frame, en [ebp - 4] puede estar la posicion de retorno o alguna cosa peor. Para evitar fastidiar el sistema es mejor aprender a usar la directiva “virtual”

      Comentario por asm86 | junio 16, 2009 | Responder

  2. Hola,
    tengo un problema con la directiva VIRTUAL, te agradezco si me puedes ayudar.

    Tengo una cadena ASCII-Z definida en el segmento de datos de nombre cadena1, quiero ejecutar una función sobre ésta
    y hago un llamado de la forma:

    lea edi,[cadena1]
    push edi
    call Función

    …..
    …..

    Te muestro un fragmento de código de la función (solo para mostrarte cual es el problema)

    Funcion:
    push ebp
    mov ebp,esp
    sub esp,04h ;reserva 4 bytes para variables locales (2 words)

    virtual at ebp-04h
    .entero dw ? ;[ebp-04h]
    .pot10 dw ? ;[ebp-02h]
    .ebp_anterior dd ? ;[ebp]
    .retorno dd ? ;[ebp+04h]
    .pCadenaAscii dd ? ;[ebp+08h]
    end virtual

    push ebx
    push ecx

    mov word [.pot10],01h ;mov word [ebp-2],01h
    mov word [.entero],00h ;mov word [ebp-4],0h

    ;hasta aqui no hay ningún problema, puedo usar las etiquetas a memoria definidas con VIRTUAL

    Funcion_ciclo:
    xor al,al
    mov bx,[.pot10] ;aqui se produce un error. Dice que Funcion_ciclo.pot10 es un simbolo desconocido
    mov al,[edi]

    loop Funcion_ciclo

    pop ecx
    pop ebx
    add esp,04h
    pop ebp
    ret 4

    Mas o menos puedo imaginarme porque se produce el error, pero aún no se como corregirlo.

    Gracias por tu ayuda

    Comentario por Ricar2 | abril 9, 2010 | Responder

    • Problema resuelto, solo debo escribir

      mov bx,[Funcion.pot10]

      ;)

      Comentario por Ricar2 | abril 9, 2010 | Responder

      • Por si te quedan dudas, la directiva virtual lo que hace es crear un nuevo “location counter” para un grupo de simbolos. El location counter es el que dice a que posición de memoria corresponde cada identificador. VIRTUAL puede hacer un location counter relativo a cualquier cosa, por ejemplo si tenemos los valores de byte var0,var, var2:

        Virtual at ebp:
        var0 = ebp
        var1 = ebp + 1
        var2 = ebp + 2

        Virtual at
        var0 = 0
        var1 = 1
        var2 = 2

        No olvides que no es lo mismo var0 que [var0] ni la importancia de la instrucción LEA que es mas importante de lo que muchos creen a la hora de manejar posiciones de memoria absolutas y relativas.

        Comentario por asm86 | abril 9, 2010

    • Primero, el argumento que le pasas a la función es un entero de 32 bits que indica la posición de memoria de la cadena y este queda en ebp -4. Pero en ese espacio pusiste las variables “locales” .entero y pot10 que ademas son de 16 bits.

      Si te fijas, primero se meten al stack los argumentos de la función en reversa, luego entra la posición de retorno de CALL y luego el ebp antiguo. El ebp antiguo corresponde a la posición cero del stack frame. Las variables locales corresponden a la parte positiva del stack frame que es donde deberias poner pot10 y entero. Que son ebp +2 y ebp + 4 respectivamente. Aunque si fueran de 32 bits (mas eficiente en windows) serían ebp +4 y ebp + 8.

      La instrucción LOOP repite el codigo tantas veces como el contenido del registro ECX y no veo donde se inicializa el ciclo. Y ten cuidado con PUSH y POP porque puedes desbalancear el stack y retornar a una posición de memoria desconocida.

      Comentario por asm86 | abril 9, 2010 | Responder

      • Gracias por tus respuestas,
        como te había comentado, el código está incompleto, solo puse las partes que consideré necesarias para explicar el problema, por eso no está la inicialización (de dice asi???) del registro ECX para el LOOP.
        Sin embargo, se me generan un par de dudas: tu dices que las variables locales corresponden a la parte positiva del Stack Frame, hasta donde había entendido la parte positiva corresponde a los argumentos pasados a la función, pues en [EBP] está el EBP anterior, la dirección de retorno está en [EBP + 4] y el último argumento pasado a la función siempre estaba en [EBP+8], de ahi en adelante se tenían los demás argumentos. Por otro lado, había entendido (o eso creí) que las “variables locales” estaban en la parte negativa del Stack.
        Además en tu entrada mencionas que la parte positiva es para argumentos y la negativa para variables locales.

        Comentario por Ricar2 | abril 10, 2010

      • Creo que tienes razon. me confundi yo tambien con el orden de los argumentos en el stack, es que hace tanto que la escribi que ya no me acordaba de ese detalle.

        Comentario por asm86 | abril 10, 2010


Deja un comentario

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

Seguir

Recibe cada nueva publicación en tu buzón de correo electrónico.

Únete a otros 42 seguidores

%d personas les gusta esto: