重定位
- "main"的起始地址爲0x00000000,這是由於在未進行空間分配以前,目標文件代碼段中的起始地址以0x00000000開始,等到空間分配完成之後,各個函數纔會肯定本身在虛擬地址空間中的位置。
- 偏移爲0x18的地址上是一條mov指令,總共8個字節,它的做用是將「shared」的地址賦值到esp寄存器+4的偏移地址中去,前面4個字節「c7442404」是mov的指令碼,後面4個字節是「shared」的地址。
- 偏移爲0x26的地址上是一條調用指令,它表示對swap函數的調用。這條指令共5個字節,前面的0xe8是操做碼,這是一條近址相對位移調用指令,後面4個字節就是被調用函數的相對於調用指令的下一條指令的偏移量。在沒有重定位以前,相對偏移被置爲0xFFFFFFFC(小端),它是常量「-4」的補碼形式。
重定位表
- 對於可重定位的ELF文件來講,它必須包含有重定位表,用來描述如何修改相應的段裏的內容。對於每一個要重定位的ELF段都有一個對應的重定位表,而一個重定位表每每就是ELF文件中的一個段,因此其實重定位表也能夠叫重定位段。
- 經過命令能夠查看目標文件的重定位表。
- OFFSET是重定位的入口偏移,表示該入口在要被重定位的段中的位置。「.text」表示這個重定位表示代碼段的重定位表,因此偏移表示代碼段中須要被調整的位置。這裏的0x1c和0x27分別就是代碼段中「mov」指令和「call」指令的地址部分。
符號解析
- 重定位過程也伴隨着符號的解析過程,每一個目標文件均可能定義一些符號,也可能引用到定義在其餘目標文件的符號。重定位的過程當中,每一個重定位的入口都是對一個符號的引用,那麼當連接器須要對某個符號的引用進行重定位時,它就要肯定這個符號的目標地址。這時候連接器就會去查找由全部輸入目標文件的符號表組成的全局符號表,找到相應的符號後進行重定位。
- 經過命令查看「a.o」的符號表。
- 能夠看到shared和swap的類型都是「UND」,即「undefined」未定義類型,在連接器掃描完全部的輸入目標文件後,全部這些未定義的符號都應該可以在全局符號表中找到,不然連接器就報符號未定義錯誤。這種通常都是連接時缺乏了某些庫,或者輸入目標文件路徑不正確或符號的聲明與定義不同。
指令修改方式
- 這條相對位移調用指令的調用地址是該指令下一條指令的起始地址加上偏移量,即:0x102b+0xfd5=0x2000,恰好是swap函數的地址。
- 從這兩個例子能夠看出來,絕對尋址修正和相對尋址修正的區別就是絕對尋址修正後的地址爲該符號的實際地址;相對尋址修正後的地址爲符號距離被修正位置的地址差。
one more thing!
C語言標準庫中的變長參數
- 變長參數是C語言的特殊參數形式,好比printf的聲明:
int printf(const char* format, ...);
- printf函數除了第一個參數類型爲const char*以外,其後能夠追加任意數量、任意類型的參數。
- 變長參數的實現得益於C語言默認的cdecl調用慣例的自右向左壓棧傳遞方式。
- 首先,看這樣一個函數。
// 第一個參數傳遞一個整數num,緊接着後面會傳遞num個整數,返回num個整數的和。
int sum(int num, ...);
- 當咱們調用:」int n = sum(3, 16, 38, 53);「時,參數在棧上的佈局會是這樣的。
- 在函數內部,函數可使用名稱num來訪問數字3,當沒法使用任何名稱訪問其餘的幾個不定參數。當此時因爲棧上其餘的幾個參數實際剛好依序排列在參數num的高地址方向,所以能夠簡單地經過num的地址計算出其餘參數的地址。
// sum的實現
int sum(int num, ...) {
int *p = &num + 1;
int ret = 0;
while (num--)
ret += *p++;
return ret;
}
- printf的不定參數比sum要複雜不少,由於printf的參數不只數量不定,並且類型也不定。因此printf須要在格式字符串中註明參數類型。printf裏的格式字符串若是將類型描述錯誤,由於不一樣參數的大小不一樣,不只可能致使這個參數的輸出出錯,還有可能致使其後的一系列參數錯誤。