好吧,我假設你正在使用一些基於unix的系統,因爲這看起來像objdump命令的輸出,但我知道這與ELF和PE文件都是相關的。我們首先在使用c時將一些模型編譯到目標文件中,並最終將它們鏈接在一起,如前所述。例如:
m1.c
- >m1.o
m2.c
- >m2.o
main.c
+ m2.o
+ m1.o
- >main.exe
我們有一個名爲m1.c
/m2.c
一些C程序,定義了一些功能,由main.c調用,最終全部鏈接並編譯到main.exe中ich完全可執行。
現在,讓我們潛入並看看引擎蓋下發生了什麼。首先我想從一個非常重要的開始開始,在最後的可執行文件中,在我們的例子(main.exe)中,所有地址都是完全分辨的虛擬地址(這不一定是真的,因爲某些概念稱爲PIE/PIC,但是現在讓我們不要進去)
因此內你的可執行文件,函數內m1.o
foo
會有些解決地址(例如0x400100),MAIN.EXE內當調用函數foo你拆卸一些諸如內看到as
call 0x400100
現在這就是概念上發生的事情,現在讓我們進入實際發生的事情。當獲取指令時,例如jmp
或call
指令某些地址作爲操作數給出,然後處理器的指令寄存器更改爲作爲操作數給出的地址,因此,如果鏈接器按指令執行指令,找到哪個指令需要改變並改變它?好,不,鏈接器根本就沒有這樣做,它比這更聰明。
首先,編譯時,編譯器生成的跳轉和調用,以內部模塊(例如jmp
一些地址應該在我們的例子中m1.o
已經屬於)相對於當前指令執行。那是什麼意思? 假設我們有一些if語句,它將被編譯爲跳轉到某些地址,編譯器足夠聰明,可以使用相對跳轉操作數並在命令之間放置偏移量,因此鏈接器連接時甚至不必更改它們,因爲調用與當前指令相關,並且某個目標文件的命令之間的偏移量通過鏈接階段保持靜態,所以與加載代碼的哪個地址無關。
現在這裏的事情變得稍微複雜一點,我們已經介紹了鏈接器如何避免內m1.o
改變地址,現在,如果在m1.o
定義m2.o
調用函數都是可執行文件,有沒有辦法在地球上編譯器可以假設他們之間的偏移,因爲他們都不知道他們會鏈接多少其他模型,這是如何解決的?引入符號和重定位表。
- 符號表 - 包含在模型中的所有符號表 - 一個 符號的東西,其他型號可能需要通過名稱來識別,如函數和全局變量, 。
- 重新定位表 - 一個表,其中包含某些模型中 符號的所有「出現」。
你以前可能聽說過這些,但現在我會向你解釋這些。 在進入之前,我需要警告我對ELF格式文件更加熟悉,但據我所知,概念上PE文件的工作方式是一樣的。
讓我們看看這個例子的代碼
#include <stdio.h>
/** file: m1.c **/
extern void goo();
void foo()
{
printf("I am foo()!\n");
goo();
}
和
#include <stdio.h>
/** file: m2.c **/
void goo()
{
printf("I am goo()!\n");
}
編譯對象文件內m1.o
時,會有一些表說,這樣的事情
符號:foo
- >在文件中的偏移X處,goo
- >未定義 RELOCATION:goo
- >在文件中的偏移Y處,
現在這意味着編譯器會生成一個表,收集模型使用的所有函數並確定它們是否被定義 - 它給出函數在其內定義的偏移量如果它沒有定義它會說明它,
它也會聲明,在這個模型中,goo被稱爲偏移量X,它需要被重新定位(我們會得到我的觀點,這是答案你的問題!)
當鏈接到一個可執行文件時,鏈接程序將獲取所有目標文件的所有符號,解析其中的某個地址,然後遍歷每個目標文件的每個符號表,查找並確定哪些符號尚未定義,然後它通過重定位表並查看哪些調用是針對未定義的符號進行的,將該位置放在文件中,然後簡單地將調用的地址重新寫入解析的地址,所以如果在我們之前有這樣的東西m1.o
call 0x000000 ;undefined goo address
符號解析後,鏈接器將可能對重定位表一些入門說你需要的線X搬遷咕地址,我們將會導致
call 0x400100 ;actual goo address
僅供參考,有一個未定義的引用鏈接錯誤時,它意味着你有你的符號表中的一些不確定的符號和連接器不能解析匹配功能的定義,它也...如果我沒有說清楚,這對於全局變量和靜態變量的作用完全相同,它們也被認爲是符號
不確定這是否是您要查找的內容,但該過程在' - ['man gcc']中的fpic'相關部分(http://linux.die.net/man/1/gcc)。 – CristiFati
真正的問題是什麼? – Art
直到鏈接器創建它時才存在機器代碼,所以我不明白你的意思。 「從哪裏閱讀關於文本部分的指示」? – Lundin