Pregunta

A menos que lo copie mal, el código anterior fue escrito en la pizarra en una clase por un estudiante con la ayuda / correcciones del maestro:

int array[100], sum, i;

void ini() {
  for(i = 0; i < 100; i++)
    array[i] = i;
}

int main() {
  ini();

  sum = 0;

  for(i = 0; i < 100; i++)
    sum += array[i];
}

.pos 0
  irmovl Stack, %esp
  rrmovl Stack, %ebp

  jmp main

array:
.pos 430

sum: .long 0
i: .long 0

main:
  call ini                     //

  irmovl <*>, %eax              // %eax = 0
  irmovl sum, %esi             // %esi = 0xsum
  rmmovl %eax, 0(%esi)         // 0(%esi) = %eax <=> 0(0xsum) = 0 [sum = 0]
  rmmovl %eax, 4(%esi)         // 4(%esi) = %eax <=> 4(0xsum) = 0 [i = 0]

compare:
  irmovl $100, %ebx            // %ebx = 100
  subl %eax, %ebx              // %ebx = %ebx - %eax <=> %ebx = 100 - i
  jle finish                   // Jumps to "finish" if SF=1 pr ZF=0

  mrmovl 0(%esi), %edx         // %edx = 0(%esi) <=> %edx = 0(0xsum) = sum
  addl %eax, %edx              // %edx = %edx + %eax <=> %edx = sum + i => sum
  rmmovl %edx, 0($esi)         // 0(%esi) = %edx <=> 0(0xsum) = sum

  irmovl $1, %ecx              // %ecx = 1
  addl %ecx, %eax              // %eax = %eax + %ecx <=> %eax = i + 1 => i
  rmmovl %eax, 4(%esi)         // 4($esi) = %eax <=> 4(0xsum) = i

  jmp compare                  // Jumps unconditionally to "compare"

ini:
  pushl %ebp                   //
  rrmovl %esp, %ebp            //
  pushl %ebx                   //
  pushl %eax                   //

  irmovl <*>, %eax              // %eax = 0
  rmmovl %eax, -8(%ebp)        //

ini_compare:
  irmovl $100, %ecx            // %ecx = 100
  subl %eax, %ecx              // %ecx = %ecx - %eax <=> %ecx = 100 - i
  jle ini_finish               // Jumps to "ini_finish" if SF=1 pr ZF=0

  rrmovl %eax, %ebx            // %ebx = %eax <=> %ebx = i
  addl %eax, $ebx              // %ebx = %ebx + %eax <=> %ebx = i + i = 2i
  addl %ebx, %ebx              // %ebx = %ebx + %ebx <=> %ecx = 2i + 2i = 4i
  rmmovl %eax, array(%ebx)     // array(%ebx) = %eax <=> array(0x4i) = i

  irmovl %1, %ecx              // %ecx = 1
  addl %ecx, %eax              // %eax = %eax + %ecx <=> %eax = i + 1 => i
  rmmovl %eax, -8(%ebp)        //

  jmp ini_compare              // Jumps unconditionally to "ini_compare"

ini_finish:
  irmovl $4, %ebx              //
  addl %ebx, %esp              //
  popl %ebx                    //
  popl %ebp                    //

  ret                          //

.pos 600
  Stack .long 0

Como puede ver, hay un montón de comentarios en todas las instrucciones y recibí (creo) la mayoría de ellos, lo que me confunde son las instrucciones de llamada, pushl / popl y ret. No los entiendo del todo y tampoco entiendo qué está sucediendo con la pila y hacia dónde apuntan todos los registros. Básicamente, las líneas con comentarios (//) que no tienen nada escrito en ellas.

Es realmente importante, entiendo cómo funciona todo esto, con suerte, algunos de ustedes pueden arrojar algo de luz sobre todo este desastre.

Algunas notas sobre mis comentarios:

  • 0xsum: Esto no significa que la dirección sea " sum " ;, sería imposible. Es solo un medio para entender de lo que estoy hablando sin usar la dirección de memoria exacta.
  • [sum = 0]: Esto significa que en nuestro código C, la suma variable se establecerá como 0 en este punto.
  • i + 1 = > i: Esto significa que estamos incrementando el valor de 'i' en uno y que en la siguiente línea 'i' representará ese valor incrementado.
¿Fue útil?

Solución

Veamos algunos de los códigos:

main:
  call ini

Esto empujará el valor del puntero de instrucción a la pila (para que luego pueda volver a esta posición en el código) y salte a la dirección de la etiqueta ini. La instrucción 'ret' usa el valor almacenado en la pila para regresar de la subrutina.

La siguiente es la secuencia de inicialización de una subrutina. Guarda los valores de algunos registros en la pila y configura un marco de pila copiando el puntero de pila (esp) en el registro de puntero base (ebp). Si la subrutina tiene variables locales, el puntero de la pila se disminuye para dejar espacio para las variables en la pila, y el puntero base se usa para acceder a las variables locales en el marco de la pila. En el ejemplo, la única variable local es el valor de retorno (no utilizado).

La instrucción push disminuye el puntero de pila (esp) con el tamaño de datos de lo que se va a empujar, luego almacena el valor en esa dirección. La instrucción pop hace lo contrario, primero obtiene el valor, luego incrementa el puntero de la pila. (Tenga en cuenta que la pila crece hacia abajo, por lo que la dirección del puntero de la pila disminuye cuando la pila crece).

ini:
  pushl %ebp             // save ebp on the stack
  rrmovl %esp, %ebp      // ebp = esp (create stack frame)
  pushl %ebx             // save ebx on the stack
  pushl %eax             // push eax on the stack (only to decrement stack pointer)
  irmovl 
ini_finish:
   irmovl $4, %ebx   // ebx = 4
   addl %ebx, %esp   // esp += ebx (remove stack frame)
   popl %ebx         // restore ebx from stack
   popl %ebp         // restore ebp from stack
   ret               // get return address from stack and jump there
, %eax // eax = 0 rmmovl %eax, -8(%ebp) // store eax at ebp-8 (clear return value)

El código sigue un patrón estándar, por lo que se ve un poco incómodo cuando no hay variables locales y hay un valor de retorno no utilizado. Si hay variables locales, se usaría una resta para disminuir el puntero de la pila en lugar de presionar eax.

La siguiente es la secuencia de salida de una subrutina. Restaura la pila a la posición anterior a la creación del marco de la pila, luego regresa al código que llamó a la subrutina.

<*>

En respuesta a sus comentarios:

El registro ebx se empuja y se abre para preservar su valor. Aparentemente, el compilador siempre coloca este código allí, probablemente porque el registro se usa con mucha frecuencia, pero no en este código. Del mismo modo, siempre se crea un marco de pila copiando esp a ebp, incluso si realmente no es necesario.

La instrucción que empuja eax solo está allí para disminuir el puntero de la pila. Se hace de esa manera para pequeños decrementos, ya que es más corto y más rápido que restar el puntero de la pila. El espacio que reserva es para el valor de retorno, de nuevo el compilador aparentemente siempre hace esto incluso si no se usa el valor de retorno.

En su diagrama, el registro esp apunta constantemente cuatro bytes demasiado alto en la memoria. Recuerde que el puntero de la pila disminuye después de presionar un valor, por lo que apuntará al valor empujado, no al siguiente valor. (Las direcciones de memoria también están muy alejadas, es algo así como 0x600 en lugar de 0x20, ya que ahí es donde se declara la etiqueta Stack).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top