2010-05-06 9 views
7

He escrito un sencillo programa Hello World.Dudas en el archivo de objeto ejecutable y reubicable

#include <stdio.h> 
    int main() { 
    printf("Hello World"); 
    return 0; 
    } 

Quería saber cómo son el archivo de objeto reubicable y el archivo ejecutable. El archivo objeto que corresponde a la función principal es

0000000000000000 <main>: 
    0: 55      push %rbp 
    1: 48 89 e5    mov %rsp,%rbp 
    4: bf 00 00 00 00   mov $0x0,%edi 
    9: b8 00 00 00 00   mov $0x0,%eax 
    e: e8 00 00 00 00   callq 13 <main+0x13> 
    13: b8 00 00 00 00   mov $0x0,%eax 
    18: c9      leaveq 
    19: c3      retq 

Aquí, la llamada a la función de printf es callq 13. Una cosa que no entiendo es por qué es 13. Eso significa llamar a la función en adresss 13, a la derecha ?? 13 tiene la siguiente instrucción, ¿verdad? Por favor, explícame qué significa esto?

El código ejecutable correspondiente a principal es

00000000004004cc <main>: 
    4004cc:  55      push %rbp 
    4004cd:  48 89 e5    mov %rsp,%rbp 
    4004d0:  bf dc 05 40 00   mov $0x4005dc,%edi 
    4004d5:  b8 00 00 00 00   mov $0x0,%eax 
    4004da:  e8 e1 fe ff ff   callq 4003c0 <[email protected]> 
    4004df:  b8 00 00 00 00   mov $0x0,%eax 
    4004e4:  c9      leaveq 
    4004e5:  c3      retq 

Aquí es callq 4003c0. Pero la instrucción binaria es e8 e1 feff ff. No hay nada que corresponda a 4003c0. ¿Qué es lo que me estoy equivocando?

Gracias. Bala

Respuesta

7

En el primer caso, eche un vistazo a la codificación de la instrucción: son todos los ceros donde iría la dirección de la función. Esto se debe a que el objeto aún no se ha vinculado, por lo que las direcciones de símbolos externos aún no se han conectado. Cuando realiza el enlace final en el formato ejecutable, el sistema pega otro marcador de posición allí, y luego el enlazador dinámico finalmente agregará la dirección correcta para printf() en tiempo de ejecución. Aquí hay un ejemplo rápido de un programa de "Hola, mundo" que escribí.

En primer lugar, el desmontaje del archivo de objeto:

00000000 <_main>: 
    0: 8d 4c 24 04    lea 0x4(%esp),%ecx 
    4: 83 e4 f0    and $0xfffffff0,%esp 
    7: ff 71 fc    pushl -0x4(%ecx) 
    a: 55      push %ebp 
    b: 89 e5     mov %esp,%ebp 
    d: 51      push %ecx 
    e: 83 ec 04    sub $0x4,%esp 
    11: e8 00 00 00 00   call 16 <_main+0x16> 
    16: c7 04 24 00 00 00 00 movl $0x0,(%esp) 
    1d: e8 00 00 00 00   call 22 <_main+0x22> 
    22: b8 00 00 00 00   mov $0x0,%eax 
    27: 83 c4 04    add $0x4,%esp 
    2a: 59      pop %ecx 
    2b: 5d      pop %ebp 
    2c: 8d 61 fc    lea -0x4(%ecx),%esp 
    2f: c3      ret  

A continuación, las reubicaciones:

main.o:  file format pe-i386 

RELOCATION RECORDS FOR [.text]: 
OFFSET TYPE    VALUE 
00000012 DISP32   ___main 
00000019 dir32    .rdata 
0000001e DISP32   _puts 

Como se puede ver que hay una reubicación allí para _puts, que es lo que la llamada a printf volvió dentro. Esa reubicación se notará en el momento del enlace y se arreglará. En el caso de la vinculación dinámica de bibliotecas, las reubicaciones y correcciones pueden no resolverse completamente hasta que se ejecute el programa, pero obtendrá la idea de este ejemplo, espero.

+0

Cualquier comentario de la downvoter? –

5

Las llamadas son relativas en x86, IIRC si tiene e8, la ubicación de la llamada es addr + 5.

e1 fe ff ff a es pequeño endian encoded relative jump. Realmente significa fffffee1.

Ahora añadir esto a la dirección de la instrucción de llamada + 5: (0xfffffee1 + 0x4004da + 5) % 2**32 = 0x4003c0

+1

El +5 se debe a que es relativo a la * siguiente * instrucción después de la llamada y la llamada tiene una longitud de 5 bytes. – caf

+0

Las llamadas a x86 pueden ser relativas o absolutas. Es solo que 'E8' es una llamada relativa. – AnT

+0

Sí, olvidé que también hay destinos absolutos, pero están especificados por segmento: selector, o un puntero a una dirección a la que saltar. –

7

El objetivo de la llamada en la instrucción E8 (call) se especifica como relativa a compensar del puntero de instrucción actual (IP) valor.

En el primer ejemplo de código, el desplazamiento es obviamente 0x00000000. Básicamente dice

call +0 

La dirección real de printf no se conoce todavía, por lo que el compilador sólo hay que poner el valor de 32 bits 0x00000000 allí como un marcador de posición.

Tal llamada incompleta con desplazamiento cero, naturalmente, ser interpretada como la llamada al valor IP actual. En su plataforma, la IP se pre-incrementa, lo que significa que cuando se ejecuta alguna instrucción, la IP contiene la dirección de la siguiente instrucción. Es decir. cuando se ejecuta la instrucción en la dirección de la 0xE IP contiene el valor 0x13. Y el call +0 se interpreta naturalmente como la llamada a la instrucción 0x13. Es por eso que ve que 0x13 en el desmontaje del código incompleto.

Una vez que se completa el código, el marcador de posición 0x00000000 se reemplaza con la compensación real de la función printf en el código. El desplazamiento puede ser positivo (avance) o negativo (hacia atrás). En su caso, la IP en el momento de la llamada es 0x4004DF, mientras que la dirección de printf función es 0x4003C0. Por este motivo, la instrucción de la máquina contendrá un valor de compensación de 32 bits igual a 0x4003C0 - 0x4004DF, que es el valor negativo -287. Así que lo que se ve en el código es en realidad

call -287 

-287 es 0xFFFFFEE1 en binario. Esto es exactamente lo que ves en tu código de máquina. Es solo que la herramienta que está usando lo muestra hacia atrás.

Cuestiones relacionadas