問題引入:
Linux的elf文件一開始理解起來的確很難,有的人可能會去看《linkers and loaders》,這書的確好,但是沒有詳細的解釋很多細節,尤其是從匯編語言視角。我讀了這本書很多地方後還是不明白,然後我又讀了IBM360計算機的匯編器設計文檔,裡面詳解了二次掃描匯編器的設計原理,以及relocation概念。但這些依然解決不了我的疑惑,因為困擾我的是一個選項,即ld -Ttext=org,我不明白這個org偏移會對程序產生什麼影響,一開始我以為這選項只不過更改了elf的文件頭信息,但是我錯了,這裡面涉及到很復雜的機制。於是我經過很多次試驗,終於得出了現在的個人見解,之所以說是個人見解,是因為可能在某些細節我的理解依然不准確,所以希望各位讀者能給我指出。在此謝過。如果我的文章能對大家有所幫助,我將倍感榮幸。
首先讓我們看看一個簡單的匯編程序,
.section .data
a:
.int 2222
.section .text
.globl _start
_start:
mov a,%eax
mov $1,%eax
mov $250,%ebx
int $0x80
這程序很簡單,當然,這是用AT&T語法寫的。程序簡單的把2222存入eax,然後退出,並返回退出碼250.
讓我們匯編它。
as -o testelf.o testelf.s
下面即將開始鏈接,注意,我們為了以後看代碼更清晰,所以要用到ld的很多選項,-x和-s都用於去掉不必要的符號信息,因為我們這篇博文重點是函數入口點和重定位。
好了,運行命令
ld -o testelf testelf.o -s -x
下面我們用
readelf testelf -a
看看elf可執行文件的詳細信息。【為了簡潔,我不截圖了,直接復制必要的輸出部分】
Entry point address: 0x8048074
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x08048000 0x08048000 0x00085 0x00085 R E 0x1000
LOAD 0x000088 0x08049088 0x08049088 0x00004 0x00004 RW 0x1000
Section to Segment mapping:
Segment Sections...
00 .text
01 .data
解釋一下,我不翻譯了,更准確
Offset:This member gives the offset from the beginning of the file at which the first byte of the segment resides.
VirtAddr:This member gives the virtual address at which the first byte of the segment resides in memory.
PhysAddr:On systems for which physical addressing is relevant, this member is reserved for the segment’s physical address. Because System V ignores physical addressing for application
programs, this member has unspecified contents for executable files and shared objects.
FileSiz:This member gives the number of bytes in the file image of the segment; it may be zero.
MemSiz:This member gives the number of bytes in the memory image of the segment; it may be zero.
【發現再詳細的寫下去太麻煩了,都是十六進制代碼沒法寫。。我自己當成記筆記算了,各位對不住】
總之就是我用-Ttext可以影響VirtAddr的值,-Ttext 0x22,VirtAddr就變成0x22(不是完全對等,後文有描述),但是entry是0x24,因為有對齊的因素存在。
如果-Ttext 0,那麼你會發現出現了頁對齊,也就是elf文件以4k的頁面對齊,在第一頁4k字節內,出了開頭是文件頭和program header,其余全0,這是為了填補第0和1頁之間的空隙。ld一看你要把text段放到文件內第0頁偏移0x22處,這會覆蓋文件頭,於是就把text搞到第1頁去了,故而會出現空隙。text在第1頁的偏移還是0x22。
Entry point address: 0x24
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x001022 0x00000022 0x00000022 0x00013 0x00013 R E 0x1000
LOAD 0x001038 0x00001038 0x00001038 0x00004 0x00004 RW 0x1000
看,這就是變化,原先是把text和文件頭一起加載,所以從偏移0開始,現在只是加載text,所以從第1頁偏移0x22開始。當然為了對齊,真正的代碼退後了兩字節,故入口在0x24.把代碼0x001022--0x001034載入內存0x00000022位置,代碼入口正好是0x00000024
要是-Ttext 0x128,這樣即使text放在第0頁也不會覆蓋文件頭,所以這下ld就把text放在第0頁,和文件頭之間的空隙幾百字節用0補全。program header內容隨之調整。
Entry point address: 0x128
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x00000000 0x00000000 0x00139 0x00139 R E 0x1000
LOAD 0x00013c 0x0000113c 0x0000113c 0x00004 0x00004 RW 0x1000
這次是從偏移0開始加載text,連同文件頭一起。加載到0x00000000,entry是0x128正好訪問到代碼開頭。
如果用-n禁止ld分頁,那麼你-Ttext 0會強制ld把text放到第0頁的開頭,為了避免覆蓋文件頭,ld會把text強制放到文件頭後面。但是data依然在第1頁,沒有跟在text後面,原因是-Ttext 0沒有影響data,除非你用-Tdata 0
基本就是這樣,這個選項會影響elf的很多信息,包括文件格局,因為ld會根據參數不同做出調整。我只是留個筆記,寫完了,我也暈了。各位再見。