目標檔案和ELF格式詳解
Linux生成的目標檔案是標準的ELF檔案格式,使用objdump工具和readelf工具可以檢視分析elf檔案的格式
gcc -c 選項只編譯不連線生成目標檔案
# c語言原始碼
[root@localhost simple-secion-linux-elf]# cat SimpleSection.c
#include <stdio.h>
int global_init_var = 84;
int global_uninit_var;
void func1(int i) {
printf("%d\n", i);
}
int main(int argc, char const *argv[])
{
static int static_var = 82;
static int static_var2;
int a = 1;
int b;
func1(static_var + static_var2 + a + b);
return 0;
}
[root@localhost simple-secion-linux-elf]# gcc -c SimpleSection.c
[root@localhost simple-secion-linux-elf]# ll
總用量 40
-rwxr-xr-x. 1 root root 6740 2月 23 18:20 a.out
-rw-r--r--. 1 root root 286 2月 23 18:17 SimpleSection.c
-rw-r--r--. 1 root root 16968 2月 23 18:17 SimpleSection.i
-rw-r--r--. 1 root root 1904 2月 23 18:20 SimpleSection.o
-rwxr-xr-x. 1 root root 1343 2月 23 18:19 SimpleSection.s
[root@localhost simple-secion-linux-elf]# ./a.out
複製程式碼
初探目標檔案
objdump工具可以檢視ELF檔案的段的資訊,包含
- 檢視目標檔案各個段的資訊(-h -x)
- 以十六進位制的方式檢視段的內容(-s)
- 檢視反彙編之後的程式碼(-d)
ELF檔案各個段的基本資訊
objdump -h 選項檢視ELF檔案各個段的基本資訊
[root@localhost simple-secion-linux-elf]# objdump -h SimpleSection.o
SimpleSection.o: file format elf64-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 0000005d 0000000000000000 0000000000000000 00000040 2**2
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 00000008 0000000000000000 0000000000000000 000000a0 2**2
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000004 0000000000000000 0000000000000000 000000a8 2**2
ALLOC
3 .rodata 00000004 0000000000000000 0000000000000000 000000a8 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .comment 0000002d 0000000000000000 0000000000000000 000000ac 2**0
CONTENTS, READONLY
5 .note.GNU-stack 00000000 0000000000000000 0000000000000000 000000d9 2**0
CONTENTS, READONLY
6 .eh_frame 00000058 0000000000000000 0000000000000000 000000e0 2**3
CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
複製程式碼
size工具可以檢視ELF檔案的各個段的大小,.data段和.bss段和objdump工具檢視的一致,而.text段不一致,這個地方暫時還不懂為什麼??
[root@localhost simple-secion-linux-elf]# size SimpleSection.o
text data bss dec hex filename
185 8 4 197 c5 SimpleSection.o
複製程式碼
目標檔案程式碼段
objdump -s 選項把ELF檔案的所有資訊以十六進位制的方式列印出來;-d選項把程式碼段反彙編,可以看到程式碼段的地址從0-5c一共為5d個位元組,和使用objdump -h
檢視的一致
[root@localhost simple-secion-linux-elf]# objdump -s -d SimpleSection.o
SimpleSection.o: file format elf64-x86-64
Contents of section .text:
0000 554889e5 4883ec10 897dfcb8 00000000 UH..H....}......
0010 8b55fc89 d64889c7 b8000000 00e80000 .U...H..........
0020 0000c9c3 554889e5 4883ec20 897dec48 ....UH..H.. .}.H
0030 8975e0c7 45f80100 00008b15 00000000 .u..E...........
0040 8b050000 00008d04 020345f8 0345fc89 ..........E..E..
0050 c7e80000 0000b800 000000c9 c3 .............
Contents of section .data:
0000 54000000 52000000 T...R...
Contents of section .rodata:
0000 25640a00 %d..
Contents of section .comment:
0000 00474343 3a202847 4e552920 342e342e .GCC: (GNU) 4.4.
0010 37203230 31323033 31332028 52656420 7 20120313 (Red
0020 48617420 342e342e 372d3429 00 Hat 4.4.7-4).
Contents of section .eh_frame:
0000 14000000 00000000 017a5200 01781001 .........zR..x..
0010 1b0c0708 90010000 1c000000 1c000000 ................
0020 00000000 24000000 00410e10 8602430d ....$....A....C.
0030 065f0c07 08000000 1c000000 3c000000 ._..........<...
0040 00000000 39000000 00410e10 8602430d ....9....A....C.
0050 06740c07 08000000 .t......
Disassembly of section .text:
0000000000000000 <func1>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 83 ec 10 sub $0x10,%rsp
8: 89 7d fc mov %edi,-0x4(%rbp)
b: b8 00 00 00 00 mov $0x0,%eax
10: 8b 55 fc mov -0x4(%rbp),%edx
13: 89 d6 mov %edx,%esi
15: 48 89 c7 mov %rax,%rdi
18: b8 00 00 00 00 mov $0x0,%eax
1d: e8 00 00 00 00 callq 22 <func1+0x22>
22: c9 leaveq
23: c3 retq
0000000000000024 <main>:
24: 55 push %rbp
25: 48 89 e5 mov %rsp,%rbp
28: 48 83 ec 20 sub $0x20,%rsp
2c: 89 7d ec mov %edi,-0x14(%rbp)
2f: 48 89 75 e0 mov %rsi,-0x20(%rbp)
33: c7 45 f8 01 00 00 00 movl $0x1,-0x8(%rbp)
3a: 8b 15 00 00 00 00 mov 0x0(%rip),%edx # 40 <main+0x1c>
40: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # 46 <main+0x22>
46: 8d 04 02 lea (%rdx,%rax,1),%eax
49: 03 45 f8 add -0x8(%rbp),%eax
4c: 03 45 fc add -0x4(%rbp),%eax
4f: 89 c7 mov %eax,%edi
51: e8 00 00 00 00 callq 56 <main+0x32>
56: b8 00 00 00 00 mov $0x0,%eax
5b: c9 leaveq
5c: c3 retq
複製程式碼
目標檔案資料段
資料段包含了讀寫資料段.data和只讀資料段.rodata,objdump工具提供的-x選項可以檢視段的內容
[root@localhost simple-secion-linux-elf]# objdump -s -x SimpleSection.o
...
Contents of section .data:
0000 54000000 52000000 T...R...
Contents of section .rodata:
0000 25640a00 %d..
...
複製程式碼
-
.data段儲存已初始化的全域性變數和區域性靜態變數
54000000 對應的是int global_init_var = 84;
52000000 對應的是static int static_var = 82;;
-
.rodata段儲存的是隻讀資料,一般是程式中的只讀變數(const)和字串常亮
25640a00 %d..
儲存的是printf函式用到的字串常量%d\n
(25640a)
目標檔案BSS段
BSS段儲存的是未初始化的全域性變數和區域性靜態變數
[root@localhost simple-secion-linux-elf]# objdump -h SimpleSection.o
...
2 .bss 00000004 0000000000000000 0000000000000000 000000a8 2**2
ALLOC
...
複製程式碼
ELF檔案結構解析

主要包含:
- ELF檔案頭(ELF Header)包含了ELF檔案版本,目標機器型號、程式入口地址
- ELF檔案各個段(程式碼段,資料段、BSS段)
- 段表(Section Header Table),ELF檔案中所有段的資訊,比如段名、段長度、段在檔案中的偏移、段的讀寫許可權和其他屬性
- 其他的輔助結構,比如字串表、符號表等
readelf工具提供的以下選項可以檢視elf檔案的資訊:
Usage: readelf <option(s)> elf-file(s)
Display information about the contents of ELF format files
Options are:
-a --all Equivalent to: -h -l -S -s -r -d -V -A -I
-h --file-header Display the ELF file header
-l --program-headers Display the program headers
--segments An alias for --program-headers
-S --section-headers Display the sections' header
--sections An alias for --section-headers
-g --section-groups Display the section groups
-t --section-details Display the section details
-e --headers Equivalent to: -h -l -S
-s --syms Display the symbol table
--symbols An alias for --syms
-n --notes Display the core notes (if present)
-r --relocs Display the relocations (if present)
-u --unwind Display the unwind info (if present)
-d --dynamic Display the dynamic section (if present)
-V --version-info Display the version sections (if present)
-A --arch-specific Display architecture specific information (if any).
-c --archive-index Display the symbol/file index in an archive
-D --use-dynamic Use the dynamic section info when displaying symbols
-x --hex-dump=<number|name>
Dump the contents of section <number|name> as bytes
-p --string-dump=<number|name>
Dump the contents of section <number|name> as strings
-R --relocated-dump=<number|name>
Dump the contents of section <number|name> as relocated bytes
-w[lLiaprmfFsoR] or
--debug-dump[=rawline,=decodedline,=info,=abbrev,=pubnames,=aranges,=macro,=frames,=str,=loc,=Ranges]
Display the contents of DWARF2 debug sections
-I --histogram Display histogram of bucket list lengths
-W --wide Allow output width to exceed 80 characters
@<file> Read options from <file>
-H --help Display this information
-v --version Display the version number of readelf
複製程式碼
ELF 檔案頭
使用readelf工具的-h選項檢視ELF檔案頭部資訊
[root@localhost simple-secion-linux-elf]# readelf -h SimpleSection.o
ELF Header:
Magic(ELF魔數): 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class(檔案機器位元組長度): ELF64
Data(資料儲存方式): 2's complement, little endian
Version(版本): 1 (current)
OS/ABI(執行平臺): UNIX - System V
ABI Version(ABI版本): 0
Type(ELF重定位型別): REL (Relocatable file)
Machine(硬體平臺): Advanced Micro Devices X86-64
Version(硬體平臺版本): 0x1
Entry point address(入口地址): 0x0
Start of program headers(程式頭入口): 0 (bytes into file)
Start of section headers(段表入口): 416 (bytes into file)
Flags: 0x0
Size of this header(ELF頭自身的大小): 64 (bytes)
Size of program headers(程式頭長度): 0 (bytes)
Number of program headers: 0
Size of section headers(段表的長度): 64 (bytes)
Number of section headers(段的數量): 13
Section header string table index(段表字串表在段中的位置): 10
複製程式碼
ELF 檔案頭結構對應的結構體,位於/usr/include/elf.h裡,包含了32位和64位的版本,下面是64位版本的頭資訊結構體
/* The ELF file header. This appears at the start of every ELF file. */
#define EI_NIDENT (16)
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf64_Half e_type; /* Object file type */
Elf64_Half e_machine; /* Architecture */
Elf64_Word e_version; /* Object file version */
Elf64_Addr e_entry; /* Entry point virtual address */
Elf64_Off e_phoff; /* Program header table file offset */
Elf64_Off e_shoff; /* Section header table file offset */
Elf64_Word e_flags; /* Processor-specific flags */
Elf64_Half e_ehsize; /* ELF header size in bytes */
Elf64_Half e_phentsize; /* Program header table entry size */
Elf64_Half e_phnum; /* Program header table entry count */
Elf64_Half e_shentsize; /* Section header table entry size */
Elf64_Half e_shnum; /* Section header table entry count */
Elf64_Half e_shstrndx; /* Section header string table index */
} Elf64_Ehdr;
複製程式碼
型別描述以及長度資訊如下所示: 
對應關係如下表所示: 
ELF魔數
標識ELF檔案的平臺屬性,比如ELF字長、位元組序、ELF檔案版本,如下圖所示。

ELF 檔案型別
ELF機器型別 
ELF 段表
ELF檔案中有很多段,段表(Section Header Table)就是儲存這些段的基本資訊的結構,包括了段名、段長度、段在檔案中的偏移位置、讀寫許可權和其他段屬性。
objdump工具可以檢視ELF檔案基本的段結構
[root@localhost simple-secion-linux-elf]# objdump -h SimpleSection.o
SimpleSection.o: file format elf64-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 0000005d 0000000000000000 0000000000000000 00000040 2**2
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 00000008 0000000000000000 0000000000000000 000000a0 2**2
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000004 0000000000000000 0000000000000000 000000a8 2**2
ALLOC
3 .rodata 00000004 0000000000000000 0000000000000000 000000a8 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .comment 0000002d 0000000000000000 0000000000000000 000000ac 2**0
CONTENTS, READONLY
5 .note.GNU-stack 00000000 0000000000000000 0000000000000000 000000d9 2**0
CONTENTS, READONLY
6 .eh_frame 00000058 0000000000000000 0000000000000000 000000e0 2**3
CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
複製程式碼
使用readelf可以看到ELF檔案全部的段結構
[root@localhost simple-secion-linux-elf]# readelf -S SimpleSection.o
There are 13 section headers, starting at offset 0x1a0:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
000000000000005d 0000000000000000 AX 0 0 4
[ 2] .rela.text RELA 0000000000000000 000006c8
0000000000000078 0000000000000018 11 1 8
[ 3] .data PROGBITS 0000000000000000 000000a0
0000000000000008 0000000000000000 WA 0 0 4
[ 4] .bss NOBITS 0000000000000000 000000a8
0000000000000004 0000000000000000 WA 0 0 4
[ 5] .rodata PROGBITS 0000000000000000 000000a8
0000000000000004 0000000000000000 A 0 0 1
[ 6] .comment PROGBITS 0000000000000000 000000ac
000000000000002d 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 000000d9
0000000000000000 0000000000000000 0 0 1
[ 8] .eh_frame PROGBITS 0000000000000000 000000e0
0000000000000058 0000000000000000 A 0 0 8
[ 9] .rela.eh_frame RELA 0000000000000000 00000740
0000000000000030 0000000000000018 11 8 8
[10] .shstrtab STRTAB 0000000000000000 00000138
0000000000000061 0000000000000000 0 0 1
[11] .symtab SYMTAB 0000000000000000 000004e0
0000000000000180 0000000000000018 12 11 8
[12] .strtab STRTAB 0000000000000000 00000660
0000000000000066 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
複製程式碼
可以看到一共有13個段,每個段對應一個Section Header資料結構,下面是64位的Section Header資料結構。
Elf64_Shdr的定義位於“/usr/include/elf.h”檔案
typedef struct
{
Elf64_Word sh_name; /* Section name (string tbl index) */
Elf64_Word sh_type; /* Section type */
Elf64_Xword sh_flags; /* Section flags */
Elf64_Addr sh_addr; /* Section virtual addr at execution */
Elf64_Off sh_offset; /* Section file offset */
Elf64_Xword sh_size; /* Section size in bytes */
Elf64_Word sh_link; /* Link to another section */
Elf64_Word sh_info; /* Additional section information */
Elf64_Xword sh_addralign; /* Section alignment */
Elf64_Xword sh_entsize; /* Entry size if section holds table */
} Elf64_Shdr;
複製程式碼
段表成員含義描述: 
段型別(sh_type)

段的標誌位(sh_flag) 
** 段的連結資訊(sh_link、sh_info)** 
``