Сомнения в исполняемом и перемещаемом объектном файле - PullRequest
7 голосов
/ 07 мая 2010

Я написал простую программу 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 имеет следующую инструкцию, верно? Пожалуйста, объясните мне, что это значит ??

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

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 <printf@plt>
  4004df:       b8 00 00 00 00          mov    $0x0,%eax
  4004e4:       c9                      leaveq 
  4004e5:       c3                      retq 

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

Спасибо. Bala

Ответы [ 3 ]

7 голосов
/ 07 мая 2010

Цель вызова в инструкции 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 в двоичном формате. Это именно то, что вы видите в своем машинном коде. Просто используемый вами инструмент отображал его в обратном направлении.

7 голосов
/ 07 мая 2010

В первом случае взгляните на кодировку инструкций - все это нули, куда направляется адрес функции. Это потому, что объект еще не был связан, поэтому адреса для внешних символов еще не подключены. Когда вы делаете последнюю ссылку в исполняемом формате, система вставляет туда другой заполнитель, а затем динамический компоновщик, наконец, добавит правильный адрес для 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. Это перемещение будет замечено во время соединения и исправлено. В случае динамического связывания библиотек перемещения и исправления могут не быть полностью решены, пока программа не будет запущена, но я надеюсь, что вы поймете идею из этого примера.

5 голосов

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

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

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

...