在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ C/ 第三部分: 系統(tǒng)
第四部分: 工具
第二部分: 高級
第三部分: 系統(tǒng)
第一部分: 語言

第三部分: 系統(tǒng)

1. ELF File Format

Executable and Linking Format,縮寫 ELF。是 Linux 系統(tǒng)目標文件 (Object File) 格式。

主要有如下三種類型:

(1) 可重定位文件 (relocatable file),可與其它目標文件一起創(chuàng)建可執(zhí)行文件或共享目標文件。

$ gcc -g -c hello.c

$ file hello.o
hello.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped

(2) 可執(zhí)行文件 (executable file)。

$ gcc -g hello.c -o hello

$ file hello
hello: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared
libs), for GNU/Linux 2.6.15, not stripped

(3) 共享目標文件 (shared object file),通常是 "函數(shù)庫",可靜態(tài)鏈接到其他 ELF 文件中,或動態(tài)鏈接共同創(chuàng)建進程映像 (類似 DLL)。

$ gcc -shared -fpic stack.c -o hello.so

$ file hello.so
hello.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, not
stripped

1.1 基本結構

我們可以從文件 (Linking) 和執(zhí)行 (Execution) 兩個角度審視 ELF 結構 (/usr/include/elf.h)。

和 Windows COFF 格式類似,ELF 也有一個特定的文件頭,包括一個特定的標志串 (Magic)。

文件頭中描述了 ELF 文件版本 (Version),目標機器型號 (Machine),程序入口地址 (Entry point Address) 等信息。緊接其后的是可選的程序頭表 (Program Header Table) 和多個段(Section),其中有我們所熟悉的存儲了執(zhí)行代碼的 .text 段。

ELF 使用段表 (Section Header Table) 存儲各段的相關信息,包括名稱、起始位置、長度、權限屬性等等。除了段表,ELF 中還有符號表 (Symbol Table)、字符串表 (String Table,段、函數(shù)等名稱) 等。

Section 和 Segment 中文翻譯雖然都是 "段",但它們并不是一個意思。Section 主要是面向目標文件連接器,而 Segment 則是面向執(zhí)行加載器,后者描述的是內存布局結構。本文主要分析 ELF 靜態(tài)文件格式,也就是說主要跟 Section 打交道,而有關 ELF 進程及內存布局模型將另文詳述。

http://wiki.jikexueyuan.com/project/c-study-notes/images/7.png" alt="" />

相關分析將使用下面這個例子,如非說明,所有生成文件都是32位。

$ cat hello.c

#include <stdio.h>

int main(int argc, char* argv[])
{
    printf("Hello, World!\n");
    return 0;
}

$ gcc -g -c hello.c

$ gcc -g hello.c -o hello

$ ls
hello.c hello.o hello

$ file hello
hello: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared
libs), for GNU/Linux 2.6.15, not stripped

附: ELF文件標準歷史

20世紀90年代,一些廠商聯(lián)合成立了一個委員會,起草并發(fā)布了一個 ELF 文件格式標準供公開使用,并且希望所有人能夠遵循這項標準并且從中獲益。1993年,委員會發(fā)布了 ELF 文件標準。當時參與該委員會的有來自于編譯器的廠商,如 Watcom 和 Borland;來自 CPU 的廠商如 IBM 和 Intel;來自操作系統(tǒng)的廠商如 IBM 和 Microsoft。1995年,委員會發(fā)布了 ELF1.2 標準,自此委員會完成了自己的使命,不久就解散了。所以 ELF 最新版本為1.2。

1.2 ELF Header

我們先看看 elf.h 中的相關定義。

typedef uint16_t Elf32_Half;
typedef uint32_t Elf32_Word;
typedef uint32_t Elf32_Addr;
typedef uint32_t Elf32_Off;

#define EI_NIDENT (16)

typedef struct
{
    unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
    Elf32_Half e_type; /* Object file type */
    Elf32_Half e_machine; /* Architecture */
    Elf32_Word e_version; /* Object file version */
    Elf32_Addr e_entry; /* Entry point virtual address */
    Elf32_Off e_phoff; /* Program header table file offset */
    Elf32_Off e_shoff; /* Section header table file offset */
    Elf32_Word e_flags; /* Processor-specific flags */
    Elf32_Half e_ehsize; /* ELF header size in bytes */
    Elf32_Half e_phentsize; /* Program header table entry size */
    Elf32_Half e_phnum; /* Program header table entry count */
    Elf32_Half e_shentsize; /* Section header table entry size */
    Elf32_Half e_shnum; /* Section header table entry count */
    Elf32_Half e_shstrndx; /* Section header string table index */
} Elf32_Ehdr;

總長度是 52 (0x34) 字節(jié)。

$ xxd -g 1 -l 0x34 hello

0000000: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 .ELF............
0000010: 02 00 03 00 01 00 00 00 30 83 04 08 34 00 00 00 ........0...4...
0000020: 80 16 00 00 00 00 00 00 34 00 20 00 08 00 28 00 ........4. ...(.
0000030: 26 00 23 00 &.#.

我們可以借助 readelf 這個工具來查看詳細信息。

$ readelf -h hello
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: EXEC (Executable file)
    Machine: Intel 80386
    Version: 0x1
    Entry point address: 0x8048330
    Start of program headers: 52 (bytes into file)
    Start of section headers: 5760 (bytes into file)
    Flags: 0x0
    Size of this header: 52 (bytes)
    Size of program headers: 32 (bytes)
    Number of program headers: 8
    Size of section headers: 40 (bytes)
    Number of section headers: 38
    Section header string table index: 35

頭信息中,我們通常關注的是 Entry point address、Start of section headers。

$ objdump -dS hello | less

08048330 <_start>:
    8048330: 31 ed xor %ebp,%ebp
    8048332: 5e pop %esi
    8048333: 89 e1 mov %esp,%ecx
    8048335: 83 e4 f0 and $0xfffffff0,%esp
    8048338: 50 push %eax

注意 Entry point address 指向 <_start> 而非 mian(),我們再看看段表信息。

$ readelf -S hello

There are 38 section headers, starting at offset 0x1680:

Section Headers:
    [Nr] Name Type Addr Off Size ES Flg Lk Inf Al
    [ 0] NULL 00000000 000000 000000 00 0 0 0
    [ 1] .interp PROGBITS 08048134 000134 000013 00 A 0 0 1
    [ 2] .note.ABI-tag NOTE 08048148 000148 000020 00 A 0 0 4
    [ 3] .note.gnu.build-i NOTE 08048168 000168 000024 00 A 0 0 4
    [ 4] .hash HASH 0804818c 00018c 000028 04 A 6 0 4
    [ 5] .gnu.hash GNU_HASH 080481b4 0001b4 000020 04 A 6 0 4
    [ 6] .dynsym DYNSYM 080481d4 0001d4 000050 10 A 7 1 4
    [ 7] .dynstr STRTAB 08048224 000224 00004a 00 A 0 0 1
    [ 8] .gnu.version VERSYM 0804826e 00026e 00000a 02 A 6 0 2
    [ 9] .gnu.version_r VERNEED 08048278 000278 000020 00 A 7 1 4
    [10] .rel.dyn REL 08048298 000298 000008 08 A 6 0 4
    [11] .rel.plt REL 080482a0 0002a0 000018 08 A 6 13 4
    [12] .init PROGBITS 080482b8 0002b8 000030 00 AX 0 0 4
    [13] .plt PROGBITS 080482e8 0002e8 000040 04 AX 0 0 4
    [14] .text PROGBITS 08048330 000330 00016c 00 AX 0 0 16
    [15] .fini PROGBITS 0804849c 00049c 00001c 00 AX 0 0 4
    [16] .rodata PROGBITS 080484b8 0004b8 000016 00 A 0 0 4
    [17] .eh_frame PROGBITS 080484d0 0004d0 000004 00 A 0 0 4
    [18] .ctors PROGBITS 08049f0c 000f0c 000008 00 WA 0 0 4
    [19] .dtors PROGBITS 08049f14 000f14 000008 00 WA 0 0 4
    [20] .jcr PROGBITS 08049f1c 000f1c 000004 00 WA 0 0 4
    [21] .dynamic DYNAMIC 08049f20 000f20 0000d0 08 WA 7 0 4
    [22] .got PROGBITS 08049ff0 000ff0 000004 04 WA 0 0 4
    [23] .got.plt PROGBITS 08049ff4 000ff4 000018 04 WA 0 0 4
    [24] .data PROGBITS 0804a00c 00100c 000008 00 WA 0 0 4
    [25] .bss NOBITS 0804a014 001014 000008 00 WA 0 0 4
    [26] .comment PROGBITS 00000000 001014 000046 01 MS 0 0 1
    [27] .debug_aranges PROGBITS 00000000 001060 000040 00 0 0 8
    [28] .debug_pubnames PROGBITS 00000000 0010a0 000040 00 0 0 1
    [29] .debug_info PROGBITS 00000000 0010e0 0001ae 00 0 0 1
    [30] .debug_abbrev PROGBITS 00000000 00128e 0000c3 00 0 0 1
    [31] .debug_line PROGBITS 00000000 001351 0000ba 00 0 0 1
    [32] .debug_frame PROGBITS 00000000 00140c 00002c 00 0 0 4
    [33] .debug_str PROGBITS 00000000 001438 0000c6 01 MS 0 0 1
    [34] .debug_loc PROGBITS 00000000 0014fe 00002c 00 0 0 1
    [35] .shstrtab STRTAB 00000000 00152a 000156 00 0 0 1
    [36] .symtab SYMTAB 00000000 001c70 0004a0 10 37 54 4
    [37] .strtab STRTAB 00000000 002110 000202 00 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)

"starting at offset 0x1680" 轉換成十進制就是5760。

1.3 Program Header

程序頭表告訴系統(tǒng)如何建立一個進程映象。

操作系統(tǒng)依據(jù)該表對進程地址空間進行分段 (Segment),并依據(jù)該表數(shù)據(jù)對進程 "內存段" 進行屬性和權限管理。

typedef struct
{
    Elf32_Word p_type; /* Segment type */
    Elf32_Off p_offset; /* Segment file offset */
    Elf32_Addr p_vaddr; /* Segment virtual address */
    Elf32_Addr p_paddr; /* Segment physical address */
    Elf32_Word p_filesz; /* Segment size in file */
    Elf32_Word p_memsz; /* Segment size in memory */
    Elf32_Word p_flags; /* Segment flags */
    Elf32_Word p_align; /* Segment alignment */
} Elf32_Phdr;

ELF 頭信息中已經給出了 Program 的相關數(shù)據(jù),起始位置 52(0x34),數(shù)量8,每個頭信息長度32(0x20) 字節(jié),總長度 256(0x100) 字節(jié)。

$ readelf -h hello

ELF Header:
    ... ...
    Start of program headers: 52 (bytes into file)
    Size of program headers: 32 (bytes)
    Number of program headers: 8
    ... ...
$ xxd -g 1 -s 0x34 -l 0x100 hello

0000034: 06 00 00 00 34 00 00 00 34 80 04 08 34 80 04 08 ....4...4...4...
0000044: 00 01 00 00 00 01 00 00 05 00 00 00 04 00 00 00 ................
0000054: 03 00 00 00 34 01 00 00 34 81 04 08 34 81 04 08 ....4...4...4...
0000064: 13 00 00 00 13 00 00 00 04 00 00 00 01 00 00 00 ................
0000074: 01 00 00 00 00 00 00 00 00 80 04 08 00 80 04 08 ................
0000084: d4 04 00 00 d4 04 00 00 05 00 00 00 00 10 00 00 ................
0000094: 01 00 00 00 0c 0f 00 00 0c 9f 04 08 0c 9f 04 08 ................
00000a4: 08 01 00 00 10 01 00 00 06 00 00 00 00 10 00 00 ................
00000b4: 02 00 00 00 20 0f 00 00 20 9f 04 08 20 9f 04 08 .... ... ... ...
00000c4: d0 00 00 00 d0 00 00 00 06 00 00 00 04 00 00 00 ................
00000d4: 04 00 00 00 48 01 00 00 48 81 04 08 48 81 04 08 ....H...H...H...
00000e4: 44 00 00 00 44 00 00 00 04 00 00 00 04 00 00 00 D...D...........
00000f4: 51 e5 74 64 00 00 00 00 00 00 00 00 00 00 00 00 Q.td............
0000104: 00 00 00 00 00 00 00 00 06 00 00 00 04 00 00 00 ................
0000114: 52 e5 74 64 0c 0f 00 00 0c 9f 04 08 0c 9f 04 08 R.td............
0000124: f4 00 00 00 f4 00 00 00 04 00 00 00 01 00 00 00 ................

從程序表數(shù)據(jù)中,我們可以從執(zhí)行角度來看操作系統(tǒng)如何映射 ELF 文件數(shù)據(jù) (Section to Segment mapping),如何確定各段 (Segment) 加載偏移量、內存虛擬地址以及內存屬性 (Flag)、對齊方式等信息。

$ readelf -l hello

Elf file type is EXEC (Executable file)
Entry point 0x8048330
There are 8 program headers, starting at offset 52

Program Headers:
    Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
    PHDR 0x000034 0x08048034 0x08048034 0x00100 0x00100 R E 0x4
    INTERP 0x000134 0x08048134 0x08048134 0x00013 0x00013 R 0x1
        [Requesting program interpreter: /lib/ld-linux.so.2]
    LOAD 0x000000 0x08048000 0x08048000 0x004d4 0x004d4 R E 0x1000
    LOAD 0x000f0c 0x08049f0c 0x08049f0c 0x00108 0x00110 RW 0x1000
    DYNAMIC 0x000f20 0x08049f20 0x08049f20 0x000d0 0x000d0 RW 0x4
    NOTE 0x000148 0x08048148 0x08048148 0x00044 0x00044 R 0x4
    GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4
    GNU_RELRO 0x000f0c 0x08049f0c 0x08049f0c 0x000f4 0x000f4 R 0x1
Section to Segment mapping:
    Segment Sections...
    00
    01 .interp
    02 .interp .note.ABI-tag .note.gnu.build-id .hash .gnu.hash .dynsym ...
    03 .ctors .dtors .jcr .dynamic .got .got.plt .data .bss
    04 .dynamic
    05 .note.ABI-tag .note.gnu.build-id
    06
    07 .ctors .dtors .jcr .dynamic .got

1.4 Section Header Table

分析 Section 之前,我們需要先了解 Section Header Table,因為我們需要通過它定位 Section,并獲知相關的屬性信息。

從 ELF Header 中我們可以獲知起始位置、單條記錄長度、總記錄數(shù)以及存儲段名稱字符串表的索引號信息。

$ readelf -h hello

ELF Header:
    Start of section headers: 5760 (bytes into file)
    Size of section headers: 40 (bytes)
    Number of section headers: 38
    Section header string table index: 35

elf.h 中對 Section Header 的數(shù)據(jù)結構定義:

typedef struct
{
    Elf32_Word sh_name; /* Section name (string tbl index) */
    Elf32_Word sh_type; /* Section type */
    Elf32_Word sh_flags; /* Section flags */
    Elf32_Addr sh_addr; /* Section virtual addr at execution */
    Elf32_Off sh_offset; /* Section file offset */
    Elf32_Word sh_size; /* Section size in bytes */
    Elf32_Word sh_link; /* Link to another section */
    Elf32_Word sh_info; /* Additional section information */
    Elf32_Word sh_addralign; /* Section alignment */
    Elf32_Word sh_entsize; /* Entry size if section holds table */
} Elf32_Shdr;

每條 Header 記錄是 40(0x28) 字節(jié)。我們對照著分析看看。

$ readelf -S hello

There are 38 section headers, starting at offset 0x1680:

Section Headers:
    [Nr] Name Type Addr Off Size ES Flg Lk Inf Al
    [ 0] NULL 00000000 000000 000000 00 0 0 0
    [ 1] .interp PROGBITS 08048134 000134 000013 00 A 0 0 1
    [ 2] .note.ABI-tag NOTE 08048148 000148 000020 00 A 0 0 4
    ... ...
    [35] .shstrtab STRTAB 00000000 00152a 000156 00 0 0 1
    [36] .symtab SYMTAB 00000000 001c70 0004a0 10 37 54 4
    [37] .strtab STRTAB 00000000 002110 000202 00 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)

$ xxd -g 1 -s 0x1680 -l 0x78 hello

0001680: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0001690: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00016a0: 00 00 00 00 00 00 00 00 1b 00 00 00 01 00 00 00 ................
00016b0: 02 00 00 00 34 81 04 08 34 01 00 00 13 00 00 00 ....4...4.......
00016c0: 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 ................
00016d0: 23 00 00 00 07 00 00 00 02 00 00 00 48 81 04 08 #...........H...
00016e0: 48 01 00 00 20 00 00 00 00 00 00 00 00 00 00 00 H... ...........
00016f0: 04 00 00 00 00 00 00 00 ........

Sections[0] 為空,我們就從 [1] .interp 開始分析,跳過40個字節(jié),從 0x1680 + 0x28 = 0x16a8 開始抓取數(shù)據(jù)。

$ xxd -g 1 -s 0x16a8 -l 0x28 hello

00016a8: 1b 00 00 00 01 00 00 00 02 00 00 00 34 81 04 08 ............4...
00016b8: 34 01 00 00 13 00 00 00 00 00 00 00 00 00 00 00 4...............
00016c8: 01 00 00 00 00 00 00 00 ........

從 elf.h 結構定義中得知,前4個字節(jié)存儲了該段名稱在字符串表中序號。

$ readelf -p .shstrtab hello ; 也可以使用索引號 "readelf -p 35 hello"

String dump of section '.shstrtab':
    [ 1] .symtab
    [ 9] .strtab
    [ 11] .shstrtab
    [ 1b] .interp
    [ 23] .note.ABI-tag
    [ 31] .note.gnu.build-id
    .. ...

很好,名稱是 "[ 1b] .interp"。

sh_type(Section type) = 0x00000001 = SHT_PROGBITS。
#define SHT_PROGBITS 1 /* Program data */

sh_flags(Section flags) = 0x00000002 = SHF_ALLOC
#define SHF_ALLOC (1 << 1) /* Occupies memory during execution */
sh_addr(virtual addr) = 0x08048134
sh_offset(Section file offset) = 0x00000134
sh_size(Section size) = 0x00000013
... ...

嗯相關信息和 readelf 輸出的都對上號了。

1.5 String Table

字符串表是以 "堆(Heap)" 的方式存儲的,也就是說 "序號" 實際上是字符串在該段的偏移位置。

$ readelf -x .shstrtab hello ; 或使用索引號 "readelf -x 35 hello"

Hex dump of section '.shstrtab':
    0x00000000 002e7379 6d746162 002e7374 72746162 ..symtab..strtab
    0x00000010 002e7368 73747274 6162002e 696e7465 ..shstrtab..inte
    0x00000020 7270002e 6e6f7465 2e414249 2d746167 rp..note.ABI-tag
    0x00000030 002e6e6f 74652e67 6e752e62 75696c64 ..note.gnu.build
    ... ...

我們數(shù)一下:

.symtab 序號是 1
.strtab 序號是 9
...

字符串以 "\0" 結尾,并以此來分割表中的多個字符串。

$ readelf -p .shstrtab hello

String dump of section '.shstrtab':
    [ 1] .symtab
    [ 9] .strtab
    [ 11] .shstrtab
    [ 1b] .interp
    ... ...

1.6 Symbol Table

符號表記錄了程序中符號的定義信息和引用信息,它是一個結構表,每條記錄對應一個符號。

記錄中存儲了符號的名稱、類型、尺寸等信息,這些記錄可能對應源代碼文件、結構類型、某個函數(shù)或者某個常變量。

當我們調試程序時,這些信息有助于我們快速定位問題所在,我們可以使用符號信息設置斷點,看到更易閱讀的反匯編代碼。

typedef uint16_t Elf32_Section;
typedef struct
{
    Elf32_Word st_name; /* Symbol name (string tbl index) */
    Elf32_Addr st_value; /* Symbol value */
    Elf32_Word st_size; /* Symbol size */
    unsigned char st_info; /* Symbol type and binding */
    unsigned char st_other; /* Symbol visibility */
    Elf32_Section st_shndx; /* Section index */
} Elf32_Sym;

每條記錄的長度是 16 (0xF) 字節(jié)。我們可以用 "readelf -s" 查看符號表詳細信息。

$ readelf -s hello

Symbol table '.dynsym' contains 5 entries:
    Num: Value Size Type Bind Vis Ndx Name
    0: 00000000 0 NOTYPE LOCAL DEFAULT UND
    1: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
    2: 00000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.0 (2)
    3: 00000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.0 (2)
    4: 080484bc 4 OBJECT GLOBAL DEFAULT 16 _IO_stdin_used

Symbol table '.symtab' contains 74 entries:
    Num: Value Size Type Bind Vis Ndx Name
    0: 00000000 0 NOTYPE LOCAL DEFAULT UND
    1: 08048134 0 SECTION LOCAL DEFAULT 1
    ... ...
    35: 00000000 0 FILE LOCAL DEFAULT ABS init.c
    36: 00000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
    37: 08049f0c 0 OBJECT LOCAL DEFAULT 18 __CTOR_LIST__
    ... ...
    49: 00000000 0 FILE LOCAL DEFAULT ABS hello.c
    50: 08049ff4 0 OBJECT LOCAL HIDDEN 23 _GLOBAL_OFFSET_TABLE_
    ... ...
    72: 080483e4 28 FUNC GLOBAL DEFAULT 14 main
    73: 080482b8 0 FUNC GLOBAL DEFAULT 12 _init

我們看看 "symtab" 段具體的數(shù)據(jù)信息。符號表所需的字符串數(shù)據(jù)存儲在 .strtab 字符串表。

$ readelf -S hello

There are 38 section headers, starting at offset 0x1680:

Section Headers:
    [Nr] Name Type Addr Off Size ES Flg Lk Inf Al
    ... ...
    [14] .text PROGBITS 08048330 000330 00016c 00 AX 0 0 16
    ... ...
    [36] .symtab SYMTAB 00000000 001c70 0004a0 10 37 54 4
    [37] .strtab STRTAB 00000000 002110 000202 00 0 0 1

我們用 "72: 080483e4 28 FUNC GLOBAL DEFAULT 14 main" 這條記錄來比對數(shù)據(jù)。

$ readelf -x .symtab hello

Hex dump of section '.symtab':
    0x00000000 00000000 00000000 00000000 00000000 ................
    0x00000010 00000000 34810408 00000000 03000100 ....4...........
    0x00000020 00000000 48810408 00000000 03000200 ....H...........
    0x00000030 00000000 68810408 00000000 03000300 ....h...........
    0x00000040 00000000 8c810408 00000000 03000400 ................
    0x00000050 00000000 b4810408 00000000 03000500 ................
    ... ...
    0x00000410 9b010000 189f0408 00000000 11021300 ................
    0x00000420 a8010000 10840408 5a000000 12000e00 ........Z.......
    0x00000430 b8010000 14a00408 00000000 1000f1ff ................
    0x00000440 c4010000 1ca00408 00000000 1000f1ff ................
    0x00000450 c9010000 00000000 00000000 12000000 ................
    0x00000460 d9010000 14a00408 00000000 1000f1ff ................
    0x00000470 e0010000 6a840408 00000000 12020e00 ....j...........
    0x00000480 f7010000 e4830408 1c000000 12000e00 ................
    0x00000490 fc010000 b8820408 00000000 12000c00 ................

記錄長度是16,整好一行,我們直接挑出所需的記錄 (72 * 16 = 0x480)。

0x00000480 f7010000 e4830408 1c000000 12000e00 ................

st_name(Symbol name) = 0x000001f7
st_value(Symbol value) = 0x080483e4
st_size(Symbol size) = 0x0000001c
st_info(Symbol type and binding) = 0x12
st_other(Symbol visibility) = 0x00
st_shndx(Section index) = 0x000e

首先從字符串表找出 Name。

$ readelf -p .strtab hello

String dump of section '.strtab':
    [ 1] init.c
    ... ...
    [ 1f7] main
    [ 1fc] _init

elf.h 中的相關定義:

#define STT_FUNC 2 /* Symbol is a code object */
#define STB_GLOBAL 1 /* Global symbol */
#define STV_DEFAULT 0 /* Default symbol visibility rules */

整理一下:

st_name(Symbol name) = 0x000001f7 -> "main"
st_value(Symbol value) = 0x080483e4
st_size(Symbol size) = 0x0000001c -> 28
st_info(Symbol type and binding) = 0x12 -> 參考 elf 中的轉換公式
st_other(Symbol visibility) = 0x00 -> STV_DEFAULT
st_shndx(Section index) = 0x000e -> "[14] .text"

嘿嘿,對上號了。

我們還可以用 strip 命令刪除符號表 .symtab,這可以縮減文件尺寸。

$ ls -l hello
-rwxr-xr-x 1 yuhen yuhen 8978 2009-12-04 00:24 hello

$ strip hello

$ ls -l hello
-rwxr-xr-x 1 yuhen yuhen 5528 2009-12-04 20:27 hello

$ readelf -s hello ; .symtab 不見了

Symbol table '.dynsym' contains 5 entries:
    Num: Value Size Type Bind Vis Ndx Name
    0: 00000000 0 NOTYPE LOCAL DEFAULT UND
    1: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
    2: 00000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.0 (2)
    3: 00000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.0 (2)
    4: 080484bc 4 OBJECT GLOBAL DEFAULT 16 _IO_stdin_used

$ readelf -S hello ; Section 也少了很多

There are 28 section headers, starting at offset 0x1138:

Section Headers:
    [Nr] Name Type Addr Off Size ES Flg Lk Inf Al
    [ 0] NULL 00000000 000000 000000 00 0 0 0
    [ 1] .interp PROGBITS 08048134 000134 000013 00 A 0 0 1
    [ 2] .note.ABI-tag NOTE 08048148 000148 000020 00 A 0 0 4
    [ 3] .note.gnu.build-i NOTE 08048168 000168 000024 00 A 0 0 4
    [ 4] .hash HASH 0804818c 00018c 000028 04 A 6 0 4
    [ 5] .gnu.hash GNU_HASH 080481b4 0001b4 000020 04 A 6 0 4
    [ 6] .dynsym DYNSYM 080481d4 0001d4 000050 10 A 7 1 4
    [ 7] .dynstr STRTAB 08048224 000224 00004a 00 A 0 0 1
    [ 8] .gnu.version VERSYM 0804826e 00026e 00000a 02 A 6 0 2
    [ 9] .gnu.version_r VERNEED 08048278 000278 000020 00 A 7 1 4
    [10] .rel.dyn REL 08048298 000298 000008 08 A 6 0 4
    [11] .rel.plt REL 080482a0 0002a0 000018 08 A 6 13 4
    [12] .init PROGBITS 080482b8 0002b8 000030 00 AX 0 0 4
    [13] .plt PROGBITS 080482e8 0002e8 000040 04 AX 0 0 4
    [14] .text PROGBITS 08048330 000330 00016c 00 AX 0 0 16
    [15] .fini PROGBITS 0804849c 00049c 00001c 00 AX 0 0 4
    [16] .rodata PROGBITS 080484b8 0004b8 000016 00 A 0 0 4
    [17] .eh_frame PROGBITS 080484d0 0004d0 000004 00 A 0 0 4
    [18] .ctors PROGBITS 08049f0c 000f0c 000008 00 WA 0 0 4
    [19] .dtors PROGBITS 08049f14 000f14 000008 00 WA 0 0 4
    [20] .jcr PROGBITS 08049f1c 000f1c 000004 00 WA 0 0 4
    [21] .dynamic DYNAMIC 08049f20 000f20 0000d0 08 WA 7 0 4
    [22] .got PROGBITS 08049ff0 000ff0 000004 04 WA 0 0 4
    [23] .got.plt PROGBITS 08049ff4 000ff4 000018 04 WA 0 0 4
    [24] .data PROGBITS 0804a00c 00100c 000008 00 WA 0 0 4
    [25] .bss NOBITS 0804a014 001014 000008 00 WA 0 0 4
    [26] .comment PROGBITS 00000000 001014 000046 01 MS 0 0 1
    [27] .shstrtab STRTAB 00000000 00105a 0000de 00 0 0 1

1.7 Section .text

.text 段中保存了所有函數(shù)的執(zhí)行代碼,我們看看 main 的反匯編代和 .text 數(shù)據(jù)對比。

$ objdump -d hello | less

080483e4 <main>:
    80483e4: 55
    80483e5: 89 e5
    80483e7: 83 e4 f0
    80483ea: 83 ec 10
    80483ed: c7 04 24 c0 84 04 08
    80483f4: e8 1f ff ff ff
    80483f9: b8 00 00 00 00
    80483fe: c9
    80483ff: c3

08048400 <__libc_csu_fini>:
    8048400: 55
    8048401: 89 e5
    8048403: 5d
    8048404: c3
    8048405: 8d 74 26 00
    8048409: 8d bc 27 00 00 00 00

$ readelf -x .text hello

Hex dump of section '.text':
    ... ...
    0x080483e0 ******** 5589e583 e4f083ec 10c70424
    0x080483f0 c0840408 e81fffff ffb80000 0000c9c3
    0x08048400 5589e55d c38d7426 008dbc27 00000000
    ... ...

通過對比數(shù)據(jù),我們會發(fā)現(xiàn) .text 段中只保存了所有函數(shù)機器碼,并沒有其他的信息,包括函數(shù)名稱、起始位置等等。那么反編譯時如何確定某個函數(shù)的名稱以及具體位置和長度呢?這其實就是我們前面提到的符號表的作用了。

$ readelf -s hello

... ...
Symbol table '.symtab' contains 74 entries:
    Num: Value Size Type Bind Vis Ndx Name
    ... ...
    72: 080483e4 28 FUNC GLOBAL DEFAULT 14 main
    ... ...

Type = FUNC 表明該記錄是個函數(shù),起始位置就是 Value:080483e4,代碼長度 28(0x1c) 字節(jié),存儲在索引號為14的段中。怎么樣?這回對上了吧。不過有個問題,很顯然反編譯和符號表中給出的都是虛擬地址,我們如何確定代碼在文件中的實際位置呢?

公式:VA - Section Address + Offset = 實際文件中的位置

$ readelf -S hello

There are 38 section headers, starting at offset 0x1680:

Section Headers:
    [Nr] Name Type Addr Off Size ES Flg Lk Inf Al
    ... ...
    [14] .text PROGBITS 08048330 000330 00016c 00 AX 0 0 16
    ... ...

0x080483e4 - 0x08048330 + 0x000330 = 0x3E4

驗證一下。

$ xxd -g 1 -s 0x3e4 -l 0x1c hello

00003e4: 55 89 e5 83 e4 f0 83 ec 10 c7 04 24 c0 84 04 08
00003f4: e8 1f ff ff ff b8 00 00 00 00 c9 c3

如果用 strip 命令刪除了符號表,反匯編效果就比較悲慘了,都擠到 .text 段,正經的入門級匯編編碼風格啊。

$ strip hello

$ objdump -d hello | less

Disassembly of section .text:

... ...

08048330 <.text>:
    8048330: 31 ed
    8048332: 5e
    8048333: 89 e1
    ... ...
    80483e4: 55 ! ! ; 可憐的 <main> 就是從這嘎達開始的
    80483e5: 89 e5
    80483e7: 83 e4 f0
    80483ea: 83 ec 10
    80483ed: c7 04 24 c0 84 04 08
    80483f4: e8 1f ff ff ff
    80483f9: b8 00 00 00 00
    80483fe: c9
    80483ff: c3
    8048400: 55 ! ! ; 這家伙是 <__libc_csu_fini>
    8048401: 89 e5
    8048403: 5d
    8048404: c3
    8048405: 8d 74 26 00
    8048409: 8d bc 27 00 00 00 00
    ... ...

1.8 Section .rodata .data

.data 段包含了諸如全局變量、常量、靜態(tài)局部變量之類的需要初始化的數(shù)據(jù),而 .rodata 段則包含代碼中的常量字符串(注意不是函數(shù)名這些符號名)等只讀數(shù)據(jù)。

.data 段是可寫的,實際內容是指針或常量值,而 .rodata 則是個只讀段。

為了查看實際效果,需要修改一下演示程序。

#include <stdio.h>

const char* format = "Hello, %s !\n";
char* name = "Q.yuhen";

int main(int argc, char* argv[])
{
    printf(format, name);
    return 0;
}

我們開始分析編譯后的程序文件。

$ objdump -dS -M intel hello | less

... ...

080483e4 <main>:

const char* format = "Hello, %s !\n";
char* name = "Q.yuhen";

int main(int argc, char* argv[])
{
    80483e4: push ebp
    80483e5: mov ebp,esp
    80483e7: and esp,0xfffffff0
    80483ea: sub esp,0x10
        printf(format, name);
    80483ed: mov edx,DWORD PTR ds:0x804a018 ! ! ; 變量 name
    80483f3: mov eax,ds:0x804a014 ! ! ! ; 常量 format
    80483f8: mov DWORD PTR [esp+0x4],edx
    80483fc: mov DWORD PTR [esp],eax
    80483ff: call 804831c <printf@plt>
        return 0;
    8048404: mov eax,0x0
}
    8048409: leave
    804840a: ret
    804840b: nop
    804840c: nop
    804840d: nop
    804840e: nop
... ...

我們可以從符號表中找出相關的定義信息。通過對比,就可以知道匯編代碼中的虛擬地址的實際含義。

$ readelf -s hello

... ...

Symbol table '.symtab' contains 76 entries:
    Num: Value Size Type Bind Vis Ndx Name
    ... ...
    57: 0804a014 4 OBJECT GLOBAL DEFAULT 24 format
    70: 0804a018 4 OBJECT GLOBAL DEFAULT 24 name
    74: 080483e4 39 FUNC GLOBAL DEFAULT 14 main
    ... ...

$ readelf -S hello

There are 38 section headers, starting at offset 0x16f0:

Section Headers:
    ... ...
    [Nr] Name Type Addr Off Size ES Flg Lk Inf Al
    [16] .rodata PROGBITS 080484c8 0004c8 00001d 00 A 0 0 4
    [24] .data PROGBITS 0804a00c 00100c 000010 00 WA 0 0 4
    ... ...
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)

format 和 name 都存儲在 .data 段中,且該段是可寫的,這表明該變量是多個棧共享。我們繼續(xù)看看 .data 段中具體內容。

$ readelf -x .data hello

Hex dump of section '.data':
    0x0804a00c 00000000 00000000 d0840408 dd840408 ................

從符號表的 Value 值我們可以看到:

[format] = 0x080484d0
[name] = 0x080484dd

.data 中存儲的指針顯然指向 .rodata 段 (0x080484c8 ~ 0x080484e5)。

$ readelf -x .rodata hello

Hex dump of section '.rodata':
    0x080484c8 03000000 01000200 48656c6c 6f2c2025 ........Hello, %
    0x080484d8 7320210a 00512e79 7568656e 00 s !..Q.yuhen.

.rodata 段果然也就是這些變量所指向的字符串。

1.9 Section .bss

.bss 段實際是執(zhí)行后才會啟用,并不占用文件空間 (下面 .bss 和 .comment Offset 相同),相關細節(jié)可參考 Linux/ELF 內存布局分析之類文章。

$ readelf -S hello

Section Headers:
    [Nr] Name Type Addr Off Size ES Flg Lk Inf Al
    ... ...
    [25] .bss NOBITS 0804a01c 00101c 000008 00 WA 0 0 4
    [26] .comment PROGBITS 00000000 00101c 000046 01 MS 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)

$ readelf -x .bss hello

    Section '.bss' has no data to dump.

ELF 中我們常打交道的幾個段:

  • .bss : 存儲未初始化的全局和靜態(tài)局部變量等。程序運行時初始為零,不占文件空間。
  • .data : 包含占據(jù)內存映像的初始化數(shù)據(jù) (包括已初始化的全局變量、靜態(tài)局部變量等)。
  • .rodata : 包含程序映像中的只讀數(shù)據(jù),通常是代碼中的常量字符串 (非符號名)。
  • .shstrtab : 字符串表,包含 Section 名稱。
  • .strtab : 字符串表,包含符號表記錄 (Symbol Table Entry) 名稱。
  • .symtab : 符號表,包含定位或重定位程序符號定義和引用時所需要的信息。
  • .text : 包含程序的可執(zhí)行指令。

2. Linux Process Model

下圖是一個簡易的內存模型示意圖。其中某些段 (Segment) 是從可執(zhí)行文件加載的,有關 ELFSection 和 Segment 的映射關系,我們可以從 ELF Program Headers 中獲取相關信息。

http://wiki.jikexueyuan.com/project/c-study-notes/images/8.png" alt="" />

$ readelf -l hello

Elf file type is EXEC (Executable file)
Entry point 0x8048410
There are 8 program headers, starting at offset 52

Program Headers:
    Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
    PHDR 0x000034 0x08048034 0x08048034 0x00100 0x00100 R E 0x4
    INTERP 0x000134 0x08048134 0x08048134 0x00013 0x00013 R 0x1
    LOAD 0x000000 0x08048000 0x08048000 0x0064c 0x0064c R E 0x1000
    LOAD 0x000f0c 0x08049f0c 0x08049f0c 0x0011c 0x00128 RW 0x1000
    DYNAMIC 0x000f20 0x08049f20 0x08049f20 0x000d0 0x000d0 RW 0x4
    NOTE 0x000148 0x08048148 0x08048148 0x00044 0x00044 R 0x4
    GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4
    GNU_RELRO 0x000f0c 0x08049f0c 0x08049f0c 0x000f4 0x000f4 R 0x1

Section to Segment mapping:
    Segment Sections...
    00
    01 .interp
    02 ... .init .plt .text .fini .rodata
    03 ... .data .bss
    04 .dynamic
    05 .note.ABI-tag .note.gnu.build-id
    06
    07 .ctors .dtors .jcr .dynamic .got

對照示意圖,我們可以看到 .text, .rodata, .data, .bss 被加載到 0x08048000 之后,也就是序號02, 03兩個 LOAD Segemtn 段中。ELF Section 信息中的 Virtual Address 也是一個參考。

$ readelf -S hello

There are 38 section headers, starting at offset 0x1a10:

Section Headers:
    [Nr] Name Type Addr Off Size ES Flg Lk Inf Al
    ... ...
    [14] .text PROGBITS 08048410 000410 0001ec 00 AX 0 0 16
    [16] .rodata PROGBITS 08048618 000618 000030 00 A 0 0 4
    [24] .data PROGBITS 0804a018 001018 000010 00 WA 0 0 4
    [25] .bss NOBITS 0804a028 001028 00000c 00 WA 0 0 4
    [35] .shstrtab STRTAB 00000000 0018b8 000156 00 0 0 1
    [36] .symtab SYMTAB 00000000 002000 000540 10 37 56 4
    [37] .strtab STRTAB 00000000 002540 000263 00 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)

注意不是所有的 Section 都會被加載到進程內存空間。

查看進程運行時內存信息:

(1) pmap

$ ps aux | grep hello | grep -v grep

yuhen 6649 0.0 1.6 39692 8404 pts/0 Sl+ Dec10 0:13 vim hello.c
yuhen 12787 0.0 0.0 1664 396 pts/1 S+ 08:24 0:00 ./hello

$ pmap -x 12787

12787: ./hello
Address Kbytes RSS Anon Locked Mode Mapping
00110000 1272 - - - r-x-- libc-2.10.1.so
0024e000 8 - - - r---- libc-2.10.1.so
00250000 4 - - - rw--- libc-2.10.1.so
00251000 12 - - - rw--- [ anon ]
002b2000 108 - - - r-x-- ld-2.10.1.so
002cd000 4 - - - r---- ld-2.10.1.so
002ce000 4 - - - rw--- ld-2.10.1.so
00c4d000 4 - - - r-x-- [ anon ]
08048000 4 - - - r-x-- hello
08049000 4 - - - r---- hello
94
0804a000 4 - - - rw--- hello
09f89000 132 - - - rw--- [ anon ]
b7848000 4 - - - rw--- [ anon ]
b7855000 16 - - - rw--- [ anon ]
bfc40000 84 - - - rw--- [ stack ]
-------- ------- ------- ------- -------
total kB 1664 - - -

(2) maps

$ cat /proc/12787/maps

00110000-0024e000 r-xp 00000000 08:01 5231 /lib/tls/i686/cmov/libc-2.10.1.so
0024e000-00250000 r--p 0013e000 08:01 5231 /lib/tls/i686/cmov/libc-2.10.1.so
00250000-00251000 rw-p 00140000 08:01 5231 /lib/tls/i686/cmov/libc-2.10.1.so
00251000-00254000 rw-p 00000000 00:00 0
002b2000-002cd000 r-xp 00000000 08:01 1809 /lib/ld-2.10.1.so
002cd000-002ce000 r--p 0001a000 08:01 1809 /lib/ld-2.10.1.so
002ce000-002cf000 rw-p 0001b000 08:01 1809 /lib/ld-2.10.1.so
00c4d000-00c4e000 r-xp 00000000 00:00 0 [vdso]
08048000-08049000 r-xp 00000000 08:01 135411 /home/yuhen/Projects/Learn.C/hello
08049000-0804a000 r--p 00000000 08:01 135411 /home/yuhen/Projects/Learn.C/hello
0804a000-0804b000 rw-p 00001000 08:01 135411 /home/yuhen/Projects/Learn.C/hello
09f89000-09faa000 rw-p 00000000 00:00 0 [heap]
b7848000-b7849000 rw-p 00000000 00:00 0
b7855000-b7859000 rw-p 00000000 00:00 0
bfc40000-bfc55000 rw-p 00000000 00:00 0 [stack]

(3) gdb

$ gdb --pid=12787

(gdb) info proc mappings

process 12619
cmdline = '/home/yuhen/Projects/Learn.C/hello'
cwd = '/home/yuhen/Projects/Learn.C'
exe = '/home/yuhen/Projects/Learn.C/hello'
Mapped address spaces:
    Start Addr End Addr Size Offset objfile
    ... ...
    0x8048000 0x8049000 0x1000 0 /home/yuhen/Projects/Learn.C/hello
    0x8049000 0x804a000 0x1000 0 /home/yuhen/Projects/Learn.C/hello
    0x804a000 0x804b000 0x1000 0x10