和JavaScript這樣的解釋執(zhí)行語言不同,編譯執(zhí)行的語言通過編譯、鏈接最終生成可執(zhí)行文件。
ELF(Executable and Linkable Format),指.o文件和可執(zhí)行文件最铁,本文分析.o文件格式。
1. 示例程序
demo.s
#understanding .obj sections and .elf segments
.section .data
mydata1:
.ascii "This is my data1 xxx."
mydata2:
.ascii "This is my data2 xxx."
.section .text
.globl _start
_start:
movl $mydata1, %edi #replace "xxx" with "1st"
movb $49, 17(%edi) #"1st"
movb $115, 18(%edi)
movb $116, 19(%edi)
movl $mydata2, %edi #replace "xxx" with "2nd"
movb $50, 17(%edi) #"2nd"
movb $110, 18(%edi)
movb $100, 19(%edi)
movl $1, %eax #syscall exit
movl $0, %ebx #argument 0, ie. exit(0)
int $0x80
上述程序的功能是將兩段字符串中"XXX"分別替換為"1st"和"2nd"
編譯
as -o demo.o demo.s
得到demo.o文件
鏈接
ld -o demo demo.o
得到demo可執(zhí)行文件垮兑。
2. 調試
加載到調試器中
gdb demo
設置斷點
break *_start
讓程序在_start處暫停冷尉。
查看當前指令
display/2i $eip
查看eip指針指向的兩條指令。顯示格式為I (instruction)
運行
r
運行系枪,顯示輸出:
1: x/2i $eip
=> 0x8048074 <_start>: mov $0x80490a4,%edi 0x8048079 <_start+5>:
movb $0x31,0x11(%edi)
0x80490a4即為mydata1在內存中的起始地址雀哨。
查看mydata
display/5s 0x80490a4
顯示地址0x80490a4開始的5條字符串。輸出格式為s(string)
執(zhí)行下一條指令
ni
輸入ni,執(zhí)行next instruction嗤无。然后一直按回車震束,重復執(zhí)行ni怜庸。
最終顯示輸出:
2: x/5s 0x80490a4
0x80490a4 <mydata1>: "This is my data1 1st.This is my data2 2nd."
0x80490cf: ".symtab"
0x80490d7: ".strtab"
0x80490df: ".shstrtab"
0x80490e9: ".text"
"This is…."存放.data節(jié)中,".sysmtab",".strtab"等字符串存放在.shstrtab 節(jié)中垢村。最終加載到內存中割疾,.shstrtab剛好位于.data節(jié)后,所以將.shstrtab中的內容也打印出來了嘉栓。(結論:.shstrtab也是會被加載到內存中的宏榕。)
3. 分析.o文件的結構。
輸出elf文件內容
readelf -a demo.o > elf.demo.o
用readelf工具將 demo3.o的內容輸出到elf.demo3.o中侵佃。
反匯編.text段
objdump -d demo.o > objdump.demo.o
用objdump工具將.text段的代碼反匯編麻昼,輸出到objdump.demo.o中
說明:下面內容中中括[]號內表示文件中的起止位置(包括首尾),單位為byte馋辈,計數(shù)進制為16進制抚芦, 圓括號()表示不包含首尾字節(jié)。
(1) ELF Header [0,33](共52 bytes)
文件開始的52byte為ELF Header迈螟,這52字節(jié)按如下結構進行解釋
elf32_hdr結構體
typedef struct elf32_hdr{
unsigned char e_ident[EI_NIDENT];
Elf32_Half e_type;
Elf32_Half e_machine;
Elf32_Word e_version;
Elf32_Addr e_entry; /* Entry point */
Elf32_Off e_phoff;
Elf32_Off e_shoff;
Elf32_Word e_flags;
Elf32_Half e_ehsize;
Elf32_Half e_phentsize;
Elf32_Half e_phnum;
Elf32_Half e_shentsize;
Elf32_Half e_shnum;
Elf32_Half e_shstrndx;
} Elf32_Ehdr;
本實例顯示內容為:
ELF Header
(2) .text [34,61](共46 bytes)
.text存放的是匯編代碼叉抡,共2e(即十進制的46) 字節(jié)。
.text
0: bf 00 00 00 00 mov $0x0,%edi
5: c6 47 11 31 movb $0x31,0x11(%edi)
9: c6 47 12 73 movb $0x73,0x12(%edi)
d: c6 47 13 74 movb $0x74,0x13(%edi)
11: bf 15 00 00 00 mov $0x15,%edi
16: c6 47 11 32 movb $0x32,0x11(%edi)
1a: c6 47 12 6e movb $0x6e,0x12(%edi)
1e: c6 47 13 64 movb $0x64,0x13(%edi)
22: b8 01 00 00 00 mov $0x1,%eax
27: bb 00 00 00 00 mov $0x0,%ebx
2c: cd 80 int $0x80
(3) 填充字符 [62,63]
用00 00填充答毫。
(4) .data [64,8D](共42 bytes)
.data段對應匯編語言中的.data段褥民,存放了兩個字符串,共2a (即十進制的42)字節(jié)洗搂。
.text段后有兩個空字符 00 00消返,然后才是.data段。
.data
This is my data1 xxx.This is my data2 xxx.
(5) 填充字符 [62,63]
用 00 00填充耘拇。
(6) .bss (90,90)(共0bytes)
.bss段撵颊,只有section header,沒有對應section驼鞭。所以它對應的section大小設置為0字節(jié)秦驯。.bss段不占用.o文件中的空間,當進程被加載到內存中時挣棕,才為.bss分配空間,并用0填充該空間亲桥。
(7) .shstrtab [90,BF](共48 bytes)
.shstrtab 即 section header string table,存放的是各個section名稱字符串洛心。其中第一個字符串為'\0'. 此處為30 (即十進制的48)字節(jié)。
.shstrtab
""
".symtab"
".strtab"
".shstrtab"
".rel.text"
".data"
".bss"
上面字符串按照標準C語言格式表示的题篷,即 “.symtab”實際由: '.' 's' 'y' 'm' 't' 'a' 'b' '\0' 字符組成词身。每個字符串用'\0'結尾,在文件視圖中顯示為一個點“.”(16進制00)番枚。另外法严,名稱
注意:這些字符串之間并沒有用換行分隔损敷,這里只是為了顯示清晰,每個字符串單獨占一行深啤。
(8) Section Headers [C0,1FF](共320 bytes)
section headers 處存放的是用來描述各個section的section header,每個section header占40 bytes, 這里共有8個section header拗馒。
每個占用40個字節(jié)的section header根據(jù)如下結構體解釋:
Elf32_Shdr
typedef struct {
Elf32_Word sh_name;
Elf32_Word sh_type;
Elf32_Word sh_flags;
Elf32_Addr sh_addr;
Elf32_Off sh_offset;
Elf32_Word sh_size;
Elf32_Word sh_link;
Elf32_Word sh_info;
Elf32_Word sh_addralign;
Elf32_Word sh_entsize;
} Elf32_Shdr;
本示例中,各個section header的有效信息如下:
section headers
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00000000 000034 00002e 00 AX 0 0 4
[ 2] .rel.text REL 00000000 000288 000010 08 6 1 4
[ 3] .data PROGBITS 00000000 000064 00002a 00 WA 0 0 4
[ 4] .bss NOBITS 00000000 000090 000000 00 WA 0 0 4
[ 5] .shstrtab STRTAB 00000000 000090 000030 00 0 0 1
[ 6] .symtab SYMTAB 00000000 000200 000070 10 7 6 4
[ 7] .strtab STRTAB 00000000 000270 000018 00 0 0 1
第0條對應ELF Header.
(9) .symtab [200,26F](共112 bytes)
.symtab 存放的是代碼中使用到的符號字符串索引以及它對應的地址溯街、屬性(local,global,weak global)等信息诱桂。在這里共占用0x70(即十進制的112)字節(jié)。
其中每一項占用16個字節(jié)呈昔,這里共有7項挥等,所以占用112字節(jié)。每項都通過如下結構體進行解釋
Elf32_Sym
typedef struct elf32_sym{
Elf32_Word st_name;
Elf32_Addr st_value;
Elf32_Word st_size;
unsigned char st_info;
unsigned char st_other;
Elf32_Half st_shndx;
} Elf32_Sym;
在本示例中堤尾,.symtab中7項內容的有效信息如下:
Symbol table '.symtab' contains 7 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 SECTION LOCAL DEFAULT 1
2: 00000000 0 SECTION LOCAL DEFAULT 3
3: 00000000 0 SECTION LOCAL DEFAULT 4
4: 00000000 0 NOTYPE LOCAL DEFAULT 3 mydata1
5: 00000015 0 NOTYPE LOCAL DEFAULT 3 mydata2
6: 00000000 0 NOTYPE GLOBAL DEFAULT 1 _start
1~3條分別表示索引號為1肝劲、3、4的section的名稱郭宝,即.text,.data和.bss涡相。第0條默認為STN_UNDEF(undefined)類型
其中,后三項對應代碼中的三個符號剩蟀。
(10) .strtab [270,287](共24 bytes)
.strtab即 string table 用來存放在代碼中定義的符號的字符串催蝗。
和.shstrtab一樣,第一個字符串為'\0'育特,每個字符串以'\0'結尾丙号。
.strtab
""?"mydata1"?"mydata2"?"_start"
(11) .rel.text [288,297](共16 bytes)
.rel.text存放的是.text節(jié)的重定位信息,缰冤。其中每一項占8個字節(jié)犬缨,這里共兩項所以占16字節(jié)。
PS: .rel.XXX節(jié)為.XXX節(jié)的重定位表棉浸,如:.data段的重定位表為.rel..data
每一項通過如下結構解釋:
Elf32_Rel
typedef struct elf32_rel {
Elf32_Addr r_offset;
Elf32_Word r_info;
} Elf32_Rel;
本示例中怀薛,有效信息如下:
.rel.text
Relocation section '.rel.text' at offset 0x288 contains 2 entries:
Offset Info Type Sym.Value Sym. Name
00000001 00000201 R_386_32 00000000 .data
00000012 00000201 R_386_32 00000000 .data
(12) 重定位
在源碼中定義的符號代碼一個地址,在.o文件中迷郑,該地址指向對應符號所在文件中的相對位置枝恋。鏈接過程中需要將這個相對位置替換為虛擬內存地址,才能使得程序正常運行嗡害。
匯編代碼
movl $mydata1, %edi
movb $49, 17(%edi)
movb $115, 18(%edi)
movb $116, 19(%edi)
movl $mydata2, %edi
movb $50, 17(%edi)
movb $110, 18(%edi)
movb $100, 19(%edi)
movl $1, %eax #syscall exit
movl $0, %ebx #argument 0, ie. exit(0)
int $0x80
.o文件中的.text段
00000000 <_start>:
0: bf 00 00 00 00 mov $0x0,%edi
5: c6 47 11 31 movb $0x31,0x11(%edi)
9: c6 47 12 73 movb $0x73,0x12(%edi)
d: c6 47 13 74 movb $0x74,0x13(%edi)
11: bf 15 00 00 00 mov $0x15,%edi
16: c6 47 11 32 movb $0x32,0x11(%edi)
1a: c6 47 12 6e movb $0x6e,0x12(%edi)
1e: c6 47 13 64 movb $0x64,0x13(%edi)
22: b8 01 00 00 00 mov $0x1,%eax
27: bb 00 00 00 00 mov $0x0,%ebx
2c: cd 80 int $0x80
可執(zhí)行文件中的.text段
08048074 <_start>:
8048074: bf a4 90 04 08 mov $0x80490a4,%edi
8048079: c6 47 11 31 movb $0x31,0x11(%edi)
804807d: c6 47 12 73 movb $0x73,0x12(%edi)
8048081: c6 47 13 74 movb $0x74,0x13(%edi)
8048085: bf b9 90 04 08 mov $0x80490b9,%edi
804808a: c6 47 11 32 movb $0x32,0x11(%edi)
804808e: c6 47 12 6e movb $0x6e,0x12(%edi)
8048092: c6 47 13 64 movb $0x64,0x13(%edi)
8048096: b8 01 00 00 00 mov $0x1,%eax
804809b: bb 00 00 00 00 mov $0x0,%ebx
80480a0: cd 80 int $0x80
觀察上面代碼中綠色陰影標記部分焚碌,在.o文件中,使用的是mydata1和mydata2在.data段中的相對位置霸妹。在可執(zhí)行文件中十电,使用的是mydata1和mydata2的虛擬內存地址。
在.rel.text中可以看到,.o文件中將匯編源碼中使用了mydata1和mydata2的地方標記為需要重定位鹃骂,重定位類型為:R_386_32
台盯。
一個符號就代表一個地址,這兩個符號代表的值是它們在.data段中的偏移量畏线,分別為0和15(即十進制 的21)静盅。所以.o中兩條mov指令使用的分別是0x1和0x15。
已經初始化的數(shù)據(jù)存放在.data section中象踊。這些數(shù)據(jù)實際上引用的是.data的地址温亲。例如,定義了一個變量static int x = 10. 我們知道符號實際上代表一個地址而已杯矩,在這里引用x時,實際上是這樣的:.symtab中記錄了x在.data section中的偏移量(假設是12)栈虚,其他地方要用到x變量時,實際上是通過 .data地址+偏移量 來進行引用的史隆。
對于上面的 movl $mydata1, %edi
指令也是如此魂务。因此,在重定位表中記錄的需要重定位的項不是mydata1和mydata2而是.data泌射。
Offset Info Type Sym.Value Sym. Name
00000001 00000201 R_386_32 00000000 .data
00000012 00000201 R_386_32 00000000 .data
offset 表示要更改的地方在.text section中起始地址粘姜。要更改的內容默認為為該處的4個字節(jié);
info
1. 表示更改的方式為:0X01 即:R_386_32
2. 被引用的符號在.symtab中的索引為: 0x000002熔酷,即.data
需要重定位的地方如上表所示孤紧,offset為需要重定位的字段(四字節(jié))在.text section中的偏移量(.rel.text是.text section的重定位信息表)。被引用的symbol為.data(因為它是已經初始化的數(shù)據(jù)拒秘,因此被引用符號為.data而不是mydata1和mydata2). info字段中号显,低八位表示重定位類型(上面顯示的是十六進制),因此這里是01即R_386_32
. 高24位表示的是被引用字符在.symtab中的索引,這里高24位為2,查找上面的.symtab可以發(fā)現(xiàn)它對應第三條(粗體顯示):
Symbol table '.symtab' contains 7 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 SECTION LOCAL DEFAULT 1
2: 00000000 0 SECTION LOCAL DEFAULT 3
3: 00000000 0 SECTION LOCAL DEFAULT 4
4: 00000000 0 NOTYPE LOCAL DEFAULT 3 mydata1
5: 00000015 0 NOTYPE LOCAL DEFAULT 3 mydata2
6: 00000000 0 NOTYPE GLOBAL DEFAULT 1 _start
1~3條分別表示索引號為1躺酒、3押蚤、4的section的名稱,即.text,.data和.bss羹应。第0條默認為STN_UNDEF(undefined)類型 第三條中的Ndx字段為3,表示它是第三節(jié)(即.data)中的符號揽碘。
R_386_32
重定位方式:
.text section中偏移量為0x1和偏移量為0x12處的四字節(jié)需要更改為.data section在內存中的起始地址+它原來的值(分別為0x0和0x15)。(生成的.o文件中园匹。匯編器(as命令)已經將我們匯編代碼中引用到的mydata1和mydata2分別替換為了這兩個符號在.data中的偏移地址了)
細節(jié)情況如下:
R_386_32
:重定位一個使用32位絕對地址的引用雳刺。其地址計算方法為.symtab中對應的value值加上原始值。鏈接過程中先將.symtab中索引為2的項(即.data)的value設置為它的虛擬地址(本示例是:80490a4)偎肃。用該虛擬地址加上原值煞烫,便得到了新的值,這也就是之前所說的那個過程累颂。
重定位的方式還有很多種,這里只討論其中之一,也是最常用的一種紊馏。注意:當被引用的符號是未初始化的變量時料饥,它和上面的過程一樣,整個過程僅僅是把上面涉及的.data符號改為被引用的符號而已朱监。
總結:
編譯器將程序的入口地址設置為標號_start的地址岸啡。所以匯編程序必須提供_start符號,C程序由glibc自動提供赫编,因此在C程序中不能定義新的_start全局符號巡蘸。
附錄
輸出文件
elf.demo.o
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: REL (Relocatable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x0
Start of program headers: 0 (bytes into file)
Start of section headers: 192 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 40 (bytes)
Number of section headers: 8
Section header string table index: 5
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00000000 000034 00002e 00 AX 0 0 4
[ 2] .rel.text REL 00000000 000288 000010 08 6 1 4
[ 3] .data PROGBITS 00000000 000064 00002a 00 WA 0 0 4
[ 4] .bss NOBITS 00000000 000090 000000 00 WA 0 0 4
[ 5] .shstrtab STRTAB 00000000 000090 000030 00 0 0 1
[ 6] .symtab SYMTAB 00000000 000200 000070 10 7 6 4
[ 7] .strtab STRTAB 00000000 000270 000018 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
There are no section groups in this file.
There are no program headers in this file.
Relocation section '.rel.text' at offset 0x288 contains 2 entries:
Offset Info Type Sym.Value Sym. Name
00000001 00000201 R_386_32 00000000 .data
00000012 00000201 R_386_32 00000000 .data
There are no unwind sections in this file.
Symbol table '.symtab' contains 7 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 SECTION LOCAL DEFAULT 1
2: 00000000 0 SECTION LOCAL DEFAULT 3
3: 00000000 0 SECTION LOCAL DEFAULT 4
4: 00000000 0 NOTYPE LOCAL DEFAULT 3 mydata1
5: 00000015 0 NOTYPE LOCAL DEFAULT 3 mydata2
6: 00000000 0 NOTYPE GLOBAL DEFAULT 1 _start
No version information found in this file.
objdump.demo.o
demo.o: file format elf32-i386
Disassembly of section .text:
00000000 <_start>:
0: bf 00 00 00 00 mov $0x0,%edi
5: c6 47 11 31 movb $0x31,0x11(%edi)
9: c6 47 12 73 movb $0x73,0x12(%edi)
d: c6 47 13 74 movb $0x74,0x13(%edi)
11: bf 15 00 00 00 mov $0x15,%edi
16: c6 47 11 32 movb $0x32,0x11(%edi)
1a: c6 47 12 6e movb $0x6e,0x12(%edi)
1e: c6 47 13 64 movb $0x64,0x13(%edi)
22: b8 01 00 00 00 mov $0x1,%eax
27: bb 00 00 00 00 mov $0x0,%ebx
2c: cd 80 int $0x80