2010-05-06 49 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.這意味着調用函數在adresss 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()添加正確的地址。這是我寫的一個「你好,世界」程序的一個簡單例子。

首先,目標文件的反彙編:

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的評論? – 2010-05-06 23:24:00

5

調用在x86中是相對的,如果你有e8,則調用位置是addr + 5。

e1 fe ff ff a是小端編碼的相對跳轉。這實際上意味着fffffee1

現在添加這個到呼叫指令的地址+ 5: (0xfffffee1 + 0x4004da + 5) % 2**32 = 0x4003c0

+1

+5是因爲它在調用後相對於* next *指令,並且該調用長度爲5個字節。 – caf 2010-05-06 23:01:53

+0

x86上的調用可以是相對的也可以是絕對的。只是'E8'是一個相對的呼叫。 – AnT 2010-05-06 23:26:45

+0

是的,我忘記了也有絕對的目的地,但它們要麼由segment:selector指定,要麼指向要跳轉到的地址的指針。 – 2010-05-07 00:46:10

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。因此,機器指令將包含一個等於0x4003C0 - 0x4004DF的32位偏移值,該值爲負值-287。所以,你在代碼中看到什麼是真正

call -287 

-2870xFFFFFEE1是二進制。這正是你在機器代碼中看到的。只是您使用的工具向後顯示。