2010-05-06 3 views
7

Я написал простую программу Hello World.Сомнения в исполняемом и перемещаемом объектном файле

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

Я хотел бы понять, как выглядит файл перемещаемого объекта и исполняемый файл. Файл объект, соответствующий основной функции

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 

Здесь вызов функции для Printf является callq 13. Одна вещь, которую я не понимаю, почему это 13. Это означает, что вызов функции в 13 адрес;, право ??. 13 имеет следующую инструкцию, правильно? Пожалуйста, объясните мне, что это значит?

Исполняемый код, соответствующий главной является

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 

Здесь callq 4003c0. Но двоичная инструкция e8 e1 fe ff ff. Нет ничего, что соответствовало бы 4003c0. Что это значит, что я ошибаюсь?

Спасибо. Bala

ответ

7

В первом случае ознакомьтесь с инструкцией по кодированию - это все нули, куда будет идти адрес функции. Это потому, что объект еще не был связан, поэтому адреса для внешних символов еще не подключены. Когда вы делаете окончательную ссылку в исполняемом формате, система вставляет туда еще один местозаполнитель, а затем динамический компоновщик, наконец, добавит правильный адрес для printf() во время выполнения. Вот краткий пример для программы «Hello, world», которую я написал.

Во-первых, разборке объектного файла:

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  

Тогда переезды:

main.o:  file format pe-i386 

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

Как вы можете видеть, что есть переселение там _puts, что и вызов printf повернулся в. Это перемещение будет замечено во время связи и исправлено. В случае динамической компоновки библиотек перемещение и исправления могут не полностью разрешиться до тех пор, пока программа не будет запущена, но вы получите идею из этого примера, я надеюсь.

+0

Любые комментарии от downvoter? –

5

Звонки относительно x86, IIRC, если у вас есть e8, местоположение вызова - addr + 5.

e1 fe ff ff a является небольшим эндианским кодированным относительным прыжком. Это действительно означает fffffee1.

Теперь добавьте в адрес инструкции вызова + 5: (0xfffffee1 + 0x4004da + 5) % 2**32 = 0x4003c0

+1

+5 - это потому, что это связано с инструкцией * next * после вызова, а вызов имеет длину 5 байтов. – caf

+0

Вызовы x86 могут быть как относительными, так и абсолютными. Просто «E8» является относительным вызовом. – AnT

+0

Да, я забыл, что есть также абсолютные адресаты, но они либо указаны сегментом: селектор, либо указатель на адрес для перехода. –

7

Цель вызова в E8 инструкции (call) определяются, как относительного смещения от указателя текущей команды (IP) стоимость.

В вашем первом примере кода смещение, очевидно, 0x00000000. Это в основном говорит

call +0 

Фактический адрес printf пока не известно, так что компилятор просто поставить 32-битное значение 0x00000000 там в качестве заполнителя.

Такой неполный вызов с нулевым смещением, естественно, будет интерпретироваться как вызов текущего значения IP. На вашей платформе IP предварительно увеличивается, что означает, что при выполнении какой-либо инструкции IP содержит адрес следующей команды. То есть при выполнении инструкции по адресу 0xE IP-адрес содержит значение 0x13. И call +0 естественно интерпретируется как вызов инструкции 0x13. Вот почему вы видите, что 0x13 при разборке неполного кода.

После того, как код будет завершен, замещающий 0x00000000 смещение заменяется на фактическое смещение функции printf в коде. Смещение может быть положительным (вперед) или отрицательным (назад). В вашем случае IP в момент вызова 0x4004DF, а адрес printf - 0x4003C0. По этой причине машинная инструкция будет содержать 32-битное значение смещения, равное 0x4003C0 - 0x4004DF, что является отрицательным значением -287. Так что вы видите в коде на самом деле

call -287 

-287 является 0xFFFFFEE1 в двоичной системе. Это именно то, что вы видите в своем машинных кодах. Просто инструмент, который вы используете, отображает его назад.

 Смежные вопросы

  • Нет связанных вопросов^_^