2010-05-06 11 views
7

Ho scritto un semplice programma Hello World.Dubbi nel file oggetto eseguibile e rilocabile

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

Volevo capire come appaiono il file oggetto riposizionabile e il file eseguibile. Il file oggetto corrispondente alla funzione principale è

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 

Qui la chiamata di funzione per printf è callq 13. Una cosa che non capisco è perché è 13. Ciò significa chiamare la funzione in adresss 13, a destra ??. 13 ha l'istruzione successiva, giusto ?? Per favore, spiegami cosa significa?

Il codice eseguibile corrispondente al principale è

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 

Qui è callq 4003c0. Ma l'istruzione binaria è e8 e1 fe ff ff. Non c'è nulla che corrisponda a 4003c0. Che cosa sto sbagliando?

Grazie. Bala

risposta

7

Nel primo caso, dare un'occhiata alla codifica delle istruzioni - sono tutti zeri in cui l'indirizzo della funzione sarebbe andato. Questo perché l'oggetto non è ancora stato collegato, quindi gli indirizzi per i simboli esterni non sono ancora stati collegati. Quando si esegue il collegamento finale nel formato eseguibile, il sistema inserisce in esso un altro segnaposto, quindi il linker dinamico aggiungerà infine l'indirizzo corretto per printf() in fase di runtime. Ecco un rapido esempio di un programma "Hello, world" che ho scritto.

In primo luogo, lo smontaggio del file oggetto:

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  

Poi le delocalizzazioni:

main.o:  file format pe-i386 

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

Come potete vedere c'è un trasferimento lì per _puts, che è ciò che la chiamata a printf trasformato in. Il trasferimento verrà notato al momento del collegamento e risolto. Nel caso del collegamento dinamico alle librerie, le delocalizzazioni e le correzioni potrebbero non essere completamente risolte fino a quando il programma è in esecuzione, ma spero che l'idea venga presa da questo esempio.

+0

Qualche commento dal downvoter? –

5

Le chiamate sono relative in x86, IIRC se si dispone di e8, la posizione della chiamata è + 5.

e1 fe ff ff a è un salto relativo codificato in little endian. Significa davvero fffffee1.

Ora aggiungere questo al indirizzo dell'istruzione chiamata + 5: (0xfffffee1 + 0x4004da + 5) % 2**32 = 0x4003c0

+1

Il +5 è perché è relativo all'istruzione * next * dopo la chiamata e la chiamata è lunga 5 byte. – caf

+0

Le chiamate su x86 possono essere relative o assolute. È solo che "E8" è una chiamata relativa. – AnT

+0

Sì, ho dimenticato che ci sono anche destinazioni assolute, ma sono specificate per segmento: selettore o puntatore a un indirizzo per saltare a. –

7

L'obiettivo della chiamata nell'istruzione E8 (call) è specificato come relativa compensato dal puntatore all'istruzione corrente (IP) valore.

Nel primo esempio di codice l'offset è ovviamente 0x00000000. Si dice in sostanza

call +0 

L'indirizzo effettivo del printf non è ancora noto, in modo che il compilatore appena messo il valore a 32 bit 0x00000000 lì come un segnaposto.

Tale chiamata incompleta con spostamento dello zero verrà naturalmente interpretata come la chiamata al valore IP corrente. Sulla tua piattaforma, l'IP è pre-incrementato, il che significa che quando alcune istruzioni vengono eseguite, l'IP contiene l'indirizzo dell'istruzione successiva. Cioè quando viene eseguita l'istruzione all'indirizzo 0xE, l'IP contiene il valore 0x13. E lo call +0 è naturalmente interpretato come la chiamata all'istruzione 0x13. Questo è il motivo per cui vedi lo 0x13 nello smontaggio del codice incompleto.

Una volta che il codice è completo, l'offset del segnaposto 0x00000000 viene sostituito con l'offset effettivo della funzione printf nel codice. L'offset può essere positivo (avanti) o negativo (indietro). Nel tuo caso l'IP al momento della chiamata è 0x4004DF, mentre l'indirizzo della funzione printf è 0x4003C0. Per questo motivo, l'istruzione della macchina conterrà un valore di offset a 32 bit uguale a 0x4003C0 - 0x4004DF, che è il valore negativo -287. Quindi, quello che si vede nel codice è in realtà

call -287 

-287 è 0xFFFFFEE1 in binario. Questo è esattamente ciò che vedi nel tuo codice macchina. È solo che lo strumento che stai usando lo ha mostrato all'indietro.