1. 程式人生 > >ELF格式的重定位原理分析

ELF格式的重定位原理分析

前面有篇文章分析了ELF格式,也只是讓我們對目標檔案有了一個大概的瞭解,並沒有說明一個十分重要的問題:重定位,今天重新看了下重定位的資料,終於弄懂了重定位的過程,下面來做一個分析。

我們將使用下面兩個原始碼中的檔案a.c和b.c展開分析:

//a.c
extern int shared;
int main()
{
	int a=100;
	swap(&a,&shared);
}
//b.c
int shared=1;
void swap(int *a,int *b)
{
	*a^=*b^=*a^=*b;
}

使用命令:gcc -c a.c b.c生成兩個可重定位目標檔案a.o和b.o,下面就要使用連結器將a.o和b.o連結起來生成執行檔案ab,我們分析的就是這個過程。

1.連結的過程

現在的連結器一般都是兩步連結,也就是說整個連結過程分兩步。

1)符號解析,主要使用ELF裡面的符號表節來完成,不做描述。

2)重定位:一旦連結器完成符號解析這一步,它就把程式碼中的每個符號引用和定義聯絡起來。在此時,連結器就知道它的輸入目標檔案中的程式碼節和資料節的確切大小。現在就可以重定位了,在這個步驟中將合併輸入模組,併為每個符號分配執行時地址。重定位由兩步組成:

  • 重定位節和符號定義:在這一步中,連結器將所有相同型別的節合併為同一型別的新的聚合節。例如輸入檔案a.o的.text節和b.o中的.text節合併為可執行檔案ab中的.text節。然後連結器將執行時儲存器地址賦值給新的聚合節。當這一步完成時,程式中的每個指令和全域性變數都有唯一的執行時儲存器地址。
  • 重定位節中的符號引用:在這一步中,連結器修改程式碼節和資料節中對每個符號的引用,試的他們指向正確的執行時地址。為了執行這一步,連結器依賴於重定位條目。稍後我們來重點分析這一步驟的具體實現過程。

我們看到上面是a.o的各個段的分佈情況,看到VMA列全都是0,VMA就是虛擬地址的意思,說明a.o中的節確實沒有分配儲存器執行時地址,根據上面描述,可執行檔案ab中應該分配了虛擬地址,事實確實證明了這點:
我們發現VMA不再是0。可執行檔案的程式碼段對映到08048094,資料段對映到08049108。 2.重定位的具體實現分析 先來看下重定位條目的內容: RELOCATION RECORDS FOR [.text]:
OFFSET   TYPE              VALUE 
0000001c R_386_32          shared
00000027 R_386_PC32        swap
OFFSET的意義:要修改的位置在.text節的偏移量 TYPE:重定位型別 VALUE:重定位符號的名稱 R_386_32(絕對定址修正):  objdump -d a.o   18:   c7 44 24 04 00 00 00    movl   $0x0,0x4(%esp)
  1f:   00 
movl指令要將shared的地址送到esp+4的位置,但是此時並不知道shared的地址,所以就先放00000000。在重定位條目中的第一項偏移是1c,剛好對應00000000。所以重定位的過程就是修改這個偏移處的內容。其實這個決定定址修正很簡單,只是書上各種符號公式看的讓人迷惑。 修正過程: 假設shared符號的執行時地址是0x8049108,即ADDR(shared)=0x8049108。 要該修的位置的地址:就將00000000替換為ADDR(shared)即可。 R_386_PC32(相對定址xiuzheng):ojbdump -d a.o 26:   e8 fc ff ff ff          call   27 <main+0x27>
 相對定址的意思就是相對當前IP位置跳轉,call實現近轉移,當前指令的ip+跳轉位移=目標地址 我們先看可執行檔案ab的反彙編程式碼:
也就是說0x8048bf+x=0x80480c8,算得x=9,正好是指令的運算元。 假設P是待修改的位置的虛擬地址,S是符號實際虛擬地址,那麼P+4+x=S,所以x=S-P-4,即x=0x8048c8-4-0x8048bb=9.