ELF entry point和裝載地址

爲了研究ELF文件裝載到內存的哪裏,以及從哪裏開始運行程序,環境:ubuntu12.04 64位,gcc4.6.3。

使用的源代碼是:

#include <stdlib.h>

void hello(void)
{
        exit(42);
}

int main(void)
{
        return 24;
}

程序並不是從main函數開始執行的,gcc -o main main.c時,默認會連接libc.so(可以指定-nodefaultlib, -nostdlib取消連接),並且會添加一個啓動代碼_start函數(可以指定-nodefaultlib, -nostdlib不添加啓動代碼),用於初始化,並提供main函數的argc, argv等參數,_start函數中會調用main函數。

readelf -d main可以看到,程序鏈接了libc.so.6:

$ readelf -d main

Dynamic section at offset 0xe50 contains 20 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x000000000000000c (INIT)               0x400390
 0x000000000000000d (FINI)               0x4005c8

objdump -d main可以看到,程序中有個_start函數,_start的地址是4003d0:

$ objdump -d main | grep _start
  400394:	e8 63 00 00 00       	callq  4003fc <call_gmon_start>
00000000004003b0 <__libc_start_main@plt-0x10>:
00000000004003c0 <__libc_start_main@plt>:
00000000004003d0 <_start>:
  4003f4:	e8 c7 ff ff ff       	callq  4003c0 <__libc_start_main@plt>
readelf -h main | grep Entry可以看到:

  Entry point address:               0x4003d0
程序的入口地址是0x4003d0,也就是_start函數的地址,程序裝載到內存後,從0x4003d0(_start)開始執行。


那麼,我不想執行_start函數呢,可以通過ld的參數-e指定入口函數,使用gcc -o main mian.c -Wl,-ehello編譯,-Wl用於指定後面的參數是給ld的,-e指定入口函數是hello。

注意hello不能直接return,因爲返回地址是錯誤的,會導致coredump;hello應當使用exit()函數退出程序。./main得到:

$ ./main
$ echo $?
42
objdump -d main | grep hello查看hello的地址:

$ objdump -d main | grep hello
00000000004004f4 <hello>:
readelf -h main | grep Entry查看程序的入口地址:

$ readelf -h main | grep Entry
  Entry point address:               0x4004f4
證明了-Wl,-ehello可以指定程序的入口函數爲hello。



那麼程序的入口地址0x4004f4是怎麼來的呢?

0x4f4其實是hello函數在ELF文件中的偏移量,可以通過hexdump -C main | grep -A5 -B5 4f0查看到

$ hexdump -C main | grep -A5 -B5 4f0
000004a0  0b 20 00 ff 14 c5 38 0e  60 00 48 8b 05 77 0b 20  |. ....8.`.H..w. |
000004b0  00 48 39 d8 72 e2 c6 05  63 0b 20 00 01 48 83 c4  |.H9.r...c. ..H..|
000004c0  08 5b 5d c3 66 66 66 2e  0f 1f 84 00 00 00 00 00  |.[].fff.........|
000004d0  48 83 3d 70 09 20 00 00  55 48 89 e5 74 12 b8 00  |H.=p. ..UH..t...|
000004e0  00 00 00 48 85 c0 74 08  5d bf 48 0e 60 00 ff e0  |...H..t.].H.`...|
000004f0  5d c3 90 90 <span style="color:#ff0000;">55 48 89 e5  bf 2a 00 00 00 e8 fe fe</span>  |]...UH...*......|
00000500  <span style="color:#ff0000;">ff ff</span> 55 48 89 e5 b8 18  00 00 00 5d c3 90 90 90  |..UH.......]....|
00000510  48 89 6c 24 d8 4c 89 64  24 e0 48 8d 2d 03 09 20  |H.l$.L.d$.H.-.. |
00000520  00 4c 8d 25 fc 08 20 00  4c 89 6c 24 e8 4c 89 74  |.L.%.. .L.l$.L.t|
00000530  24 f0 4c 89 7c 24 f8 48  89 5c 24 d0 48 83 ec 38  |$.L.|$.H.\$.H..8|
00000540  4c 29 e5 41 89 fd 49 89  f6 48 c1 fd 03 49 89 d7  |L).A..I..H...I..|
然後objdump -d main | grep -A5 -B5 hello查看hello對應的機器碼:

$ objdump -d main | grep -A5 -B5 hello
  4004f0:	5d                   	pop    %rbp
  4004f1:	c3                   	retq
  4004f2:	90                   	nop
  4004f3:	90                   	nop

00000000004004f4 <hello>:
  4004f4:	<span style="color:#ff0000;">55</span>                   	push   %rbp
  4004f5:	<span style="color:#ff0000;">48 89 e5</span>             	mov    %rsp,%rbp
  4004f8:	<span style="color:#ff0000;">bf 2a 00 00 00</span>       	mov    $0x2a,%edi
  4004fd:	<span style="color:#ff0000;">e8 fe fe ff ff</span>       	callq  400400 <exit@plt>
證明了這一點。

然後0x400000是什麼呢?

這個是ELF裝載到內存時的起始位置,可以通過ld的參數-Wl,-Ttext-segment,0x400000指定,比如我們gcc -o main main.c -Wl,-ehello -Wl,-Ttext-segment,0x4200000編譯程序,給程序換一個裝載地址,然後readelf -h main | grep Entry得到:

Entry point address:               0x42004f4

hello在文件中的偏移依然爲0x4f4,然後加上裝載地址0x4200000就可以得到程序執行的入口地址:0x42004f4。

objdump -d main | grep hello -A 10可以看到裝載地址變了,程序的PC指針位置也都改變了(因爲我們編譯的main是地址相關的ELF):

$ objdump -d main | grep hello -A 10
00000000042004f4 <hello>:
 <span style="color:#ff0000;background-color: rgb(204, 204, 204);">42004f4</span>:	55                   	push   %rbp
 <span style="color:#ff0000;">42004f5</span>:	48 89 e5             	mov    %rsp,%rbp
 <span style="color:#ff0000;">42004f8</span>:	bf 2a 00 00 00       	mov    $0x2a,%edi
 <span style="color:#ff0000;">42004fd</span>:	e8 fe fe ff ff       	callq  4200400 <exit@plt>

0000000004200502 <main>:
 4200502:	55                   	push   %rbp
 4200503:	48 89 e5             	mov    %rsp,%rbp
 4200506:	b8 18 00 00 00       	mov    $0x18,%eax
 420050b:	5d                   	pop    %rbp


注意

1、-Ttext-segment指定的必需是一個頁對齊的地址。

2、動態庫的裝載位置不是固定的,一般可以認爲動態庫的-Ttext-segment=0,沒有使用;如果想直接運行.so的話(glibc裏面很多.so都可以直接運行),需要爲.so指定ld-linux.so(.so默認沒有指定):
gcc -shared -fPIC -o libx.so x.c -Wl,--dynamic-linker=some_ld_linux.so.x
或者代碼裏面寫:const char my_interp[] __attribute__((section(".interp"))) = "/lib/ld-linux.so.3"; //需要保證這個ld-linux.so存在且能用

可以使用readelf -l查看.inter段,也就是指定的ld-linux.so

3、-Ttext-segment並不是指定.text段的加載位置,而是指定整個elf的加載位置。

如果想指定.text段的加載位置,可以:
       -Tbss=org
       -Tdata=org
       -Ttext=org
           Same as --section-start, with ".bss", ".data" or ".text" as the
           sectionname.
4、gcc -Wl,-Ttext-segment=0x400000 -Wl,-Ttext=0x800000  -o x x.c這樣是可以的,但是如果-Ttext指定的比較小,要麼程序無法運行,要麼可能和其他段衝突。加完-Ttext之後,.text段以及後面的段,在文件中的偏移會變大不少,中間填充的0是爲了加載到內存後的頁對齊,在內存中的加載位置都會從-Ttext指定的位置開始。

發佈了31 篇原創文章 · 獲贊 11 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章