2010-05-06 17 views
7

Ich habe ein einfaches Hello World-Programm geschrieben.Zweifel in der ausführbaren und verschiebbaren Objektdatei

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

Ich wollte verstehen, wie die verschiebbare Objektdatei und die ausführbare Datei aussehen. Die Objektdatei mit der Hauptfunktion entspricht, ist

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 

Hier ist der Funktionsaufruf für printf ist callq 13. Eine Sache, ich verstehe nicht, warum es 13 ist Das bedeutet, 13 die Funktion bei adresss nennen, rechts ??. 13 hat die nächste Anweisung, oder? Bitte erkläre mir, was das bedeutet ??

Der ausführbare Code zur Haupt entsprechende

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 

Hier callq 4003c0 ist. Aber die binäre Anweisung ist e8 e1 fe ff ff. Es gibt nichts, was 4003c0 entspricht. Was ist das, dass ich falsch liege?

Danke. Bala

Antwort

7

Im ersten Fall, schauen Sie sich die Befehlskodierung an - es ist alles Nullen, wo die Funktionsadresse gehen würde. Das liegt daran, dass das Objekt noch nicht verknüpft wurde, also wurden die Adressen für externe Symbole noch nicht verknüpft. Wenn Sie den letzten Link in das ausführbare Format einfügen, klebt das System einen anderen Platzhalter hinein, und dann fügt der dynamische Linker schließlich zur Laufzeit die korrekte Adresse für printf() hinzu. Hier ist ein schnelles Beispiel für ein "Hello, world" -Programm, das ich geschrieben habe.

Zunächst wird die Zerlegung der Objektdatei:

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  

Dann werden die Verlagerungen:

main.o:  file format pe-i386 

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

Wie Sie für _puts sehen eine Verlagerung da ist, das ist, was der Anruf an printf gedreht in. Diese Verlagerung wird bei der Verbindungszeit bemerkt und behoben. Im Fall der dynamischen Bibliotheksverknüpfung werden die Umsetzungen und Korrekturen möglicherweise erst vollständig aufgelöst, wenn das Programm ausgeführt wird. Sie erhalten jedoch die Idee aus diesem Beispiel, hoffe ich.

+0

Irgendein Kommentar vom Downvoter? –

5

Anrufe sind relativ in x86, IIRC, wenn Sie e8 haben, ist die Anrufstelle addr + 5.

e1 fe ff ff ist ein kleiner endian-codierter relativer Sprung. Es bedeutet wirklich fffffee1.

Nun fügen diese zu der Adresse des Aufrufbefehls + 5: (0xfffffee1 + 0x4004da + 5) % 2**32 = 0x4003c0

+1

Die +5 ist, weil sie nach dem Aufruf relativ zur Anweisung * next * ist und der Aufruf 5 Byte lang ist. – caf

+0

Aufrufe von x86 können entweder relativ oder absolut sein. Es ist nur so, dass "E8" ein relativer Aufruf ist. – AnT

+0

Ja, ich habe vergessen, es gibt auch absolute Ziele, aber sie sind entweder durch Segment angegeben: Selektor, oder ein Zeiger auf eine Adresse zu springen. –

7

Das Ziel des Aufrufs in dem E8 Befehl (call) als relativen aus dem aktuellen Befehlszeiger (IP) Offset angegeben ist Wert. In Ihrem ersten Codebeispiel ist der Offset offensichtlich 0x00000000. Er sagt im Grunde

call +0 

Die tatsächliche Adresse von printf noch nicht bekannt ist, so dass nur der Compiler 0x00000000 dort als Platzhalter den 32-Bit-Wert setzen.

Ein solcher unvollständiger Aufruf mit Nullpunktverschiebung wird natürlich als Aufruf des aktuellen IP-Werts interpretiert. Auf Ihrer Plattform wird die IP-Adresse vorinkrementiert, dh wenn eine Anweisung ausgeführt wird, enthält die IP die Adresse der nächsten Anweisung. I.e. Wenn die Anweisung an der Adresse 0xE ausgeführt wird, enthält die IP den Wert 0x13. Und die call +0 wird natürlich als der Aufruf der Anweisung 0x13 interpretiert. Deshalb sehen Sie das 0x13 in der Demontage des unvollständigen Codes.

Sobald der Code abgeschlossen ist, wird der Platzhalter 0x00000000 Offset durch den tatsächlichen Offset von printf Funktion im Code ersetzt. Der Offset kann positiv (vorwärts) oder negativ (rückwärts) sein. In Ihrem Fall ist die IP zum Zeitpunkt des Anrufs 0x4004DF, während die Adresse printf Funktion 0x4003C0 ist. Aus diesem Grund enthält der Maschinenbefehl einen 32-Bit-Offset-Wert gleich 0x4003C0 - 0x4004DF, der ein negativer Wert -287 ist. Also, was Sie in den Code zu sehen ist eigentlich

call -287 

-287 ist 0xFFFFFEE1 in binär. Genau das sehen Sie in Ihrem Maschinencode. Es ist nur so, dass das von Ihnen verwendete Werkzeug es rückwärts anzeigt.