2010-09-06 16 views
14

Estoy tratando de comprender el código de nivel de ensamblaje para un simple programa C inspecándolo con el desensamblador de gdb.Comprenda el código de ensamblado generado por un simple programa en C

A continuación se presenta el código C:

#include <stdio.h> 

void function(int a, int b, int c) { 
    char buffer1[5]; 
    char buffer2[10]; 
} 

void main() { 
    function(1,2,3); 
} 

siguiente es el código de desmontaje para ambos main y function

gdb) disass main 
Dump of assembler code for function main: 
0x08048428 <main+0>: push %ebp 
0x08048429 <main+1>: mov %esp,%ebp 
0x0804842b <main+3>: and $0xfffffff0,%esp 
0x0804842e <main+6>: sub $0x10,%esp 
0x08048431 <main+9>: movl $0x3,0x8(%esp) 
0x08048439 <main+17>: movl $0x2,0x4(%esp) 
0x08048441 <main+25>: movl $0x1,(%esp) 
0x08048448 <main+32>: call 0x8048404 <function> 
0x0804844d <main+37>: leave 
0x0804844e <main+38>: ret 
End of assembler dump. 

(gdb) disass function 
Dump of assembler code for function function: 
0x08048404 <function+0>: push %ebp 
0x08048405 <function+1>: mov %esp,%ebp 
0x08048407 <function+3>: sub $0x28,%esp 
0x0804840a <function+6>: mov %gs:0x14,%eax 
0x08048410 <function+12>: mov %eax,-0xc(%ebp) 
0x08048413 <function+15>: xor %eax,%eax 
0x08048415 <function+17>: mov -0xc(%ebp),%eax 
0x08048418 <function+20>: xor %gs:0x14,%eax 
0x0804841f <function+27>: je  0x8048426 <function+34> 
0x08048421 <function+29>: call 0x8048340 <[email protected]> 
0x08048426 <function+34>: leave 
0x08048427 <function+35>: ret  
End of assembler dump. 

estoy buscando respuestas para siguientes cosas:

  1. cómo el direccionamiento está funcionando, quiero decir (principal + 0), (principal + 1), (principal + 3)
  2. En general, ¿por qué se usa $ 0xfffffff0?% Esp
  3. En la función, ¿por qué se utilizan% gs: 0x14,% eax,% eax, -0xc (% ebp)?
  4. Si alguien puede explicar, paso a paso, será muy apreciado.
+4

Olvidó la etiqueta de la tarea. –

+0

Para cada uno de estos puntos, es posible que desee explicar cuál cree que es la respuesta, y luego, si está fuera, puede aclararse. –

+3

no, esta no es una tarea .. – Adi

Respuesta

40

La razón de las direcciones "extrañas", como main+0, main+1, main+3, main+6 y así sucesivamente, es porque cada instrucción ocupa un número variable de bytes. Por ejemplo:

main+0: push %ebp 

es una instrucción de un byte por lo que la próxima instrucción está en main+1. Por otro lado,

main+3: and $0xfffffff0,%esp 

es una instrucción de tres bytes por lo que la siguiente instrucción después de eso está en main+6.

Y, como usted pregunta en los comentarios por qué movl parece tomar un número variable de bytes, la explicación es la siguiente.

Longitud de instrucción no sólo depende de la código de operación (como movl), sino también los modos de direccionamiento de los operandos (así las cosas el código de operación están operando en). No he comprobado específicamente para su código, pero sospecho que la instrucción

movl $0x1,(%esp) 

es probablemente más corto porque no hay desplazamiento involucrados - sólo se utiliza como dirección de esp. Mientras que algo como:

movl $0x2,0x4(%esp) 

requiere todo lo que hace movl $0x1,(%esp), además un byte adicional para la 0x4 offset.

De hecho, aquí es una sesión de depuración que muestra lo que quiero decir:

Microsoft Windows XP [Version 5.1.2600] 
(C) Copyright 1985-2001 Microsoft Corp. 

c:\pax> debug 
-a 
0B52:0100 mov word ptr [di],7 
0B52:0104 mov word ptr [di+2],8 
0B52:0109 mov word ptr [di+0],7 
0B52:010E 
-u100,10d 
0B52:0100 C7050700  MOV  WORD PTR [DI],0007 
0B52:0104 C745020800 MOV  WORD PTR [DI+02],0008 
0B52:0109 C745000700 MOV  WORD PTR [DI+00],0007 
-q 
c:\pax> _ 

se puede ver que la segunda instrucción con un desplazamiento es en realidad diferente a la primera sin ella. Es un byte más largo (5 bytes en lugar de 4, para contener el desplazamiento) y en realidad tiene una codificación diferente c745 en lugar de c705.

También puede ver que puede codificar la primera y la tercera instrucción de dos maneras diferentes, pero básicamente hacen lo mismo.


La instrucción and $0xfffffff0,%esp es una manera de forzar esp estar en un límite específico. Esto se usa para asegurar la alineación adecuada de las variables. Muchos accesos a memoria en procesadores modernos serán más eficientes si siguen las reglas de alineación (como un valor de 4 bytes que debe alinearse con un límite de 4 bytes). Algunos procesadores modernos incluso presentarán un error si no sigues estas reglas.

Después de esta instrucción, tiene la garantía de que esp es tanto menor o igual a su valor anterior y alineado con un límite de 16 bytes.


El prefijo gs: significa simplemente utilizar el registro gs segmento de acceder a la memoria en lugar de la forma predeterminada.

La instrucción mov %eax,-0xc(%ebp) medios para trazar el contenido del registro ebp, restar 12 (0xc) y luego poner el valor de eax en esa posición de memoria.


Consulte la explicación del código. Su función function es básicamente una gran no operación. El ensamblaje generado se limita a la configuración y al desmontaje del marco de la pila, junto con la comprobación de corrupción del marco de pila que utiliza la ubicación de memoria %gs:14 antes mencionada.

Carga el valor desde esa ubicación (probablemente algo así como 0xdeadbeef) en el marco de la pila, hace su trabajo, luego verifica la pila para asegurarse de que no esté dañada.

Su trabajo, en este caso, no es nada. Entonces, todo lo que ves es la administración de funciones.

La configuración de la pila se produce entre function+0 y function+12. Todo lo que sigue es configurar el código de retorno en eax y derribar el marco de la pila, incluido el control de corrupción.

De forma similar, main consiste en la configuración del marco de la pila, presionando los parámetros para function, llamando al function, derribando el marco de la pila y saliendo.

Los comentarios han sido insertados en el código de abajo:

0x08048428 <main+0>: push %ebp     ; save previous value. 
0x08048429 <main+1>: mov %esp,%ebp   ; create new stack frame. 
0x0804842b <main+3>: and $0xfffffff0,%esp  ; align to boundary. 
0x0804842e <main+6>: sub $0x10,%esp   ; make space on stack. 

0x08048431 <main+9>: movl $0x3,0x8(%esp)  ; push values for function. 
0x08048439 <main+17>: movl $0x2,0x4(%esp) 
0x08048441 <main+25>: movl $0x1,(%esp) 
0x08048448 <main+32>: call 0x8048404 <function> ; and call it. 

0x0804844d <main+37>: leave      ; tear down frame. 
0x0804844e <main+38>: ret       ; and exit. 

0x08048404 <func+0>: push %ebp     ; save previous value. 
0x08048405 <func+1>: mov %esp,%ebp   ; create new stack frame. 
0x08048407 <func+3>: sub $0x28,%esp   ; make space on stack. 
0x0804840a <func+6>: mov %gs:0x14,%eax  ; get sentinel value. 
0x08048410 <func+12>: mov %eax,-0xc(%ebp)  ; put on stack. 

0x08048413 <func+15>: xor %eax,%eax   ; set return code 0. 

0x08048415 <func+17>: mov -0xc(%ebp),%eax  ; get sentinel from stack. 
0x08048418 <func+20>: xor %gs:0x14,%eax  ; compare with actual. 
0x0804841f <func+27>: je  <func+34>   ; jump if okay. 
0x08048421 <func+29>: call <_stk_chk_fl>  ; otherwise corrupted stack. 
0x08048426 <func+34>: leave      ; tear down frame. 
0x08048427 <func+35>: ret       ; and exit. 

creo que la razón de la %gs:0x14 puede ser evidente desde arriba, pero, por si acaso, voy a elaborar aquí.

Utiliza este valor (un centinela) para poner en el marco de pila actual para que algo en la función haga algo tonto como escribir 1024 bytes en una matriz de 20 bytes creada en la pila o, en su caso:

char buffer1[5]; 
strcpy (buffer1, "Hello there, my name is Pax."); 

entonces el centinela se sobrescribirán y el cheque al final de la función detectará que, llamando a la función de fallo para hacerle saber, y luego probablemente abortar el fin de evitar cualquier otro problema.

Si se coloca 0xdeadbeef en la pila y esto fue cambiado a otra cosa, a continuación, un xor con 0xdeadbeef produciría un valor distinto de cero que se detecta en el código con la instrucción je.

El bit correspondiente se parafrasea aquí:

  mov %gs:0x14,%eax  ; get sentinel value. 
      mov %eax,-0xc(%ebp) ; put on stack. 

      ;; Weave your function 
      ;; magic here. 

      mov -0xc(%ebp),%eax ; get sentinel back from stack. 
      xor %gs:0x14,%eax  ; compare with original value. 
      je  stack_ok   ; zero/equal means no corruption. 
      call stack_bad   ; otherwise corrupted stack. 
stack_ok: leave     ; tear down frame. 
+13

+1: impresionante respuesta detallada :) –

+0

Gracias pax por la respuesta detallada. Todavía no entendía la lógica detrás, el incremento en la dirección, es decir, cómo se está produciendo el incremento de la dirección, como main + 0, main +1, main + 3, main + 6, main + 9, main + 17 y etc. – Adi

+0

Por lo tanto, refiriéndose a su punto anterior, strcpy (buffer1, "Hola, mi nombre es Pax."); ¿Quiere decir que es un tipo de verificación de flujo de buffer, que está haciendo el sistema? Entonces, eso significa que la vulnerabilidad de desbordamiento del búfer no puede explotarse aquí. – Adi

3

Pax ha producido una respuesta definitiva. Sin embargo, para completar, pensé en agregar una nota sobre cómo obtener GCC para que le muestre el ensamblaje que genera.

La opción -S de GCC le indica que detenga la compilación y escriba el conjunto en un archivo. Normalmente, pasa ese archivo al ensamblador o, para algunos destinos, escribe directamente el archivo de objeto.

Para el código de ejemplo en la cuestión:

#include <stdio.h> 

void function(int a, int b, int c) { 
    char buffer1[5]; 
    char buffer2[10]; 
} 

void main() { 
    function(1,2,3); 
} 

el comando gcc -S q3654898.c crea un archivo denominado q3654898.s:

 
     .file "q3654898.c" 
     .text 
.globl _function 
     .def _function;  .scl 2;  .type 32;  .endef 
_function: 
     pushl %ebp 
     movl %esp, %ebp 
     subl $40, %esp 
     leave 
     ret 
     .def ___main;  .scl 2;  .type 32;  .endef 
.globl _main 
     .def _main; .scl 2;  .type 32;  .endef 
_main: 
     pushl %ebp 
     movl %esp, %ebp 
     subl $24, %esp 
     andl $-16, %esp 
     movl $0, %eax 
     addl $15, %eax 
     addl $15, %eax 
     shrl $4, %eax 
     sall $4, %eax 
     movl %eax, -4(%ebp) 
     movl -4(%ebp), %eax 
     call __alloca 
     call ___main 
     movl $3, 8(%esp) 
     movl $2, 4(%esp) 
     movl $1, (%esp) 
     call _function 
     leave 
     ret 

Una cosa que es evidente es que mi GCC (GCC (GCC) 3.4.5 (mingw-vista special r3)) no incluye el código de comprobación de pila por defecto. Me imagino que hay una opción de línea de comando, o que si alguna vez llegué a empujar mi instalación MinGW a un GCC más actual que podría.

Edit: Empujado a hacer tan por Pax, aquí está otra manera de conseguir que GCC haga la mayor parte del trabajo.

 
C:\Documents and Settings\Ross\My Documents\testing>gcc -Wa,-al q3654898.c 
q3654898.c: In function `main': 
q3654898.c:8: warning: return type of 'main' is not `int' 
GAS LISTING C:\DOCUME~1\Ross\LOCALS~1\Temp/ccLg8pWC.s     page 1 


    1       .file "q3654898.c" 
    2       .text 
    3     .globl _function 
    4       .def _function;  .scl 2;  .type 
32;  .endef 
    5     _function: 
    6 0000 55     pushl %ebp 
    7 0001 89E5     movl %esp, %ebp 
    8 0003 83EC28    subl $40, %esp 
    9 0006 C9     leave 
    10 0007 C3     ret 
    11       .def ___main;  .scl 2;  .type 
32;  .endef 
    12     .globl _main 
    13       .def _main; .scl 2;  .type 32; 
.endef 
    14     _main: 
    15 0008 55     pushl %ebp 
    16 0009 89E5     movl %esp, %ebp 
    17 000b 83EC18    subl $24, %esp 
    18 000e 83E4F0    andl $-16, %esp 
    19 0011 B8000000    movl $0, %eax 
    19  00 
    20 0016 83C00F    addl $15, %eax 
    21 0019 83C00F    addl $15, %eax 
    22 001c C1E804    shrl $4, %eax 
    23 001f C1E004    sall $4, %eax 
    24 0022 8945FC    movl %eax, -4(%ebp) 
    25 0025 8B45FC    movl -4(%ebp), %eax 
    26 0028 E8000000    call __alloca 
    26  00 
    27 002d E8000000    call ___main 
    27  00 
    28 0032 C7442408    movl $3, 8(%esp) 
    28  03000000 
    29 003a C7442404    movl $2, 4(%esp) 
    29  02000000 
    30 0042 C7042401    movl $1, (%esp) 
    30  000000 
    31 0049 E8B2FFFF    call _function 
    31  FF 
    32 004e C9     leave 
    33 004f C3     ret 

C:\Documents and Settings\Ross\My Documents\testing> 

Aquí vemos un listado de salida producido por el ensamblador. (Su nombre es GAS, ya que es la versión de GNU de la clásica * ensamblador nix as Hay humor en alguna parte..)

Cada línea tiene más de los siguientes campos: un número de línea, una dirección en la sección actual, bytes almacenado en esa dirección, y el texto fuente del archivo fuente del ensamblaje. Las direcciones son compensaciones en esa parte de cada sección proporcionada por este módulo. Este módulo en particular solo tiene contenido en la sección .text que almacena el código ejecutable. Por lo general, encontrará menciones de las secciones llamadas .data y .bss también. Se usan muchos otros nombres y algunos tienen propósitos especiales. Lea el manual del vinculador si realmente desea saberlo.

+0

'-fstack-protector', creo. Algunas distribuciones de Linux lo activan por defecto. – zwol

+0

+1 solo para "Pax ha producido una respuesta definitiva" :-) También puede agregar el hecho de que puede usar 'gcc -Wa, -al ...' para que el ensamblador produzca un listado que incluya el bytes generados, así como la fuente. – paxdiablo

+0

@Pax, ;-). Trataré de afirmar con franqueza que iba a ir, pero MinGW estropeó mi producción y luego la cena estuvo lista ... la comida casera tiene prioridad, naturalmente. – RBerteig

2

Me gustaría agregar que para cosas simples, la salida de ensamblaje de GCC es a menudo más fácil de leer si se activa un poco la optimización. Aquí está el código de ejemplo de nuevo ...

void function(int a, int b, int c) { 
    char buffer1[5]; 
    char buffer2[10]; 
} 

/* corrected calling convention of main() */ 
int main() { 
    function(1,2,3); 
    return 0; 
} 

esto es lo que me pasa sin optimización (OSX 10.6, gcc 4.2.1 + parches de Apple)

.globl _function 
_function: 
    pushl %ebp 
    movl %esp, %ebp 
    pushl %ebx 
    subl $36, %esp 
    call L4 
"L00000000001$pb": 
L4: 
    popl %ebx 
    leal L___stack_chk_guard$non_lazy_ptr-"L00000000001$pb"(%ebx), %eax 
    movl (%eax), %eax 
    movl (%eax), %edx 
    movl %edx, -12(%ebp) 
    xorl %edx, %edx 
    leal L___stack_chk_guard$non_lazy_ptr-"L00000000001$pb"(%ebx), %eax 
    movl (%eax), %eax 
    movl -12(%ebp), %edx 
    xorl (%eax), %edx 
    je  L3 
    call ___stack_chk_fail 
L3: 
    addl $36, %esp 
    popl %ebx 
    leave 
    ret 
.globl _main 
_main: 
    pushl %ebp 
    movl %esp, %ebp 
    subl $24, %esp 
    movl $3, 8(%esp) 
    movl $2, 4(%esp) 
    movl $1, (%esp) 
    call _function 
    movl $0, %eax 
    leave 
    ret 

¡Qué diablos, qué diablos! Pero mira lo que sucede con -O en la línea de comandos ...

.text 
.globl _function 
_function: 
    pushl %ebp 
    movl %esp, %ebp 
    leave 
    ret 
.globl _main 
_main: 
    pushl %ebp 
    movl %esp, %ebp 
    movl $0, %eax 
    leave 
    ret 

Por supuesto, usted corre el riesgo de su código que se queden totalmente irreconocible, especialmente en los niveles más altos de optimización y con cosas más complicadas. Incluso aquí, vemos que la llamada a function ha sido descartada como inútil. Pero me parece que no tener que leer docenas de derrames de pila innecesarios en general es más que vale la pena un poco más rascándome la cabeza sobre el flujo de control.

+0

Si ni siquiera está llamando 'function', me pregunto por qué lo incluye en el ejecutable? – Hassan

+0

Si 'function' se declaraba' static', o si se compiló con '-fwhole-program', lo habría descartado. De lo contrario, se supone que el código fuera de la unidad de traducción actual puede llamar a 'función'. – zwol

+0

Oh, claro. Pero entonces, ¿no podría el enlazador soltarlo? Si está vinculando un archivo ejecutable (no una biblioteca), sabe a qué funciones se llama desde dónde. ¿El enlazador tiene la capacidad de omitir el código? – Hassan

3

Será mejor probar el indicador -fno-stack-protector con gcc para desactivar el canario y ver los resultados.

+0

gracias berkay .. funciona .. – Adi

Cuestiones relacionadas