了解系統行為的研究方法,我認為有效的步驟應分成三個階段來進行。
初次入門:一開始進行研究時,因為對於系統的基本觀念還不夠完備,因此「學中做、做中學」成了最有效率的入門方式,透過「概念的實作」與「實作讀到的概念」的過程,最可以幫助我們在短時間內掌握重要的核心知識。以 ELF 專欄為例,在入門時期,我用了 loader 0.1~0.5 共 5 個小範例來陳述 ELF 的格式以及 section 的觀念。
掌握觀念:首先發表一個自己的看法。「將所有理論或概念全部動手實作一遍」,在我看來,並不是很有效率的辦法,這種做法應當很有幫助,但是卻會延緩學習速度,因此,這個時 期的重點如果是在「通盤掌握整體的重要關鍵」,那麼「善用工具來做分析」自然是最有成 效的方式。以「工具來操作並驗證觀念」是建議的做法,另外一個理由是,這種做法比較能貼近實務面。在「ELF 之 Program Loading 教學文件」的日記裡,我將會以此做法來分享教學文件。
思考與研究:這個階段有點像是「實驗室」的做法,在這裡不再贅述。
Program Header Table
這裡有幾個重要的觀念:
1. Program header table 是程式要能執行的重要資訊,program header table 紀錄 ELF image 裡的 'segment' 分佈,請參考 Jollen's Blog「ELF 之 Program Loading 教學文件, #1: Segment 的觀念」的說明。
2. 對 dynamic loader/linker 來說,ELF 的 'section' 是通透性的(transparent),也就是在整個載入的過程裡,dynamic loader/linker 並不會知道他所載入的 'segment' 與實際的 section 有何關係。整個載入過程只讀取 program header table 所紀錄的 'segment'。
3. 也就是說,section header table 在此時期是用不到的,就算沒有 section header table 也不影響程式載入與執行;這個觀念可參考 Jollen's Blog「「Truncate It」小技倆的原始碼與原理」。
使用 readelf 來觀察執行檔的 program header table:
# readelf -l hello Elf file type is EXEC (Executable file) Entry point 0x8048278 There are 6 program headers, starting at offset 52 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000034 0x08048034 0x08048034 0x000c0 0x000c0 R E 0x4 INTERP 0x0000f4 0x080480f4 0x080480f4 0x00013 0x00013 R 0x1 [Requesting program interpreter: /lib/ld-linux.so.2] LOAD 0x000000 0x08048000 0x08048000 0x00408 0x00408 R E 0x1000 LOAD 0x000408 0x08049408 0x08049408 0x00100 0x00104 RW 0x1000 DYNAMIC 0x000414 0x08049414 0x08049414 0x000c8 0x000c8 RW 0x4 NOTE 0x000108 0x08048108 0x08048108 0x00020 0x00020 R 0x4 Section to Segment mapping: Segment Sections... 00 01 .interp 02 .interp .note.ABI-tag .hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame 03 .data .dynamic .ctors .dtors .jcr .got .bss 04 .dynamic 05 .note.ABI-tag
Program header table 的 data structure 如下(/usr/include/elf.h):
/* Program segment header. */ 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;
'readelf' 的列表與 program header table 的 data structure 相符,每個 field 所紀錄的資訊可參考註解說明,或是參照完整的 System V ABI 文件。
幾個重要資訊說明如下。
1. Segment 的個數與大小紀錄在 ELF 檔頭裡:
# 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: 0x8048278 Start of program headers: 52 (bytes into file) Start of section headers: 1784 (bytes into file) Flags: 0x0 Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 6 Size of section headers: 40 (bytes) Number of section headers: 25 Section header string table index: 24
2. Program header table 的 p_type 欄位必須先進行研究(/usr/include/elf.h):
/* Legal values for p_type (segment type). */ #define PT_NULL 0 /* Program header table entry unused */ #define PT_LOAD 1 /* Loadable program segment */ #define PT_DYNAMIC 2 /* Dynamic linking information */ #define PT_INTERP 3 /* Program interpreter */ #define PT_NOTE 4 /* Auxiliary information */ #define PT_SHLIB 5 /* Reserved */ #define PT_PHDR 6 /* Entry for header table itself */ #define PT_TLS 7 /* Thread-local storage segment */ #define PT_NUM 8 /* Number of defined types */ #define PT_LOOS 0x60000000 /* Start of OS-specific */ #define PT_GNU_EH_FRAME 0x6474e550 /* GCC .eh_frame_hdr segment */ #define PT_LOSUNW 0x6ffffffa #define PT_SUNWBSS 0x6ffffffa /* Sun Specific segment */ #define PT_SUNWSTACK 0x6ffffffb /* Stack segment */ #define PT_HISUNW 0x6fffffff #define PT_HIOS 0x6fffffff /* End of OS-specific */ #define PT_LOPROC 0x70000000 /* Start of processor-specific */ #define PT_HIPROC 0x7fffffff /* End of processor-specific */
標示紅色的部份是我們在 'readelf -l' 列表中所看到的 type。
3. p_flags 的意義也要先行了解(/usr/include/elf.h):
/* Legal values for p_flags (segment flags). */ #define PF_X (1 << 0) /* Segment is executable */ #define PF_W (1 << 1) /* Segment is writable */ #define PF_R (1 << 2) /* Segment is readable */ #define PF_MASKOS 0x0ff00000 /* OS-specific */ #define PF_MASKPROC 0xf0000000 /* Processor-specific */
Segment Permissions
Segment permission 是 p_flags 的組合,在 'readelf -l' 列表中則是相對應於 Flg 一欄。
Segment Contents
Segment 由哪些 section 所構成,同樣可由 'readelf -l' 列表的「Section to Segment mapping」部份觀察。
小結
以下表格整理目前為止所得到的結論。
Segment | Sections | Type | Flg |
00 | PHDR | PF_R + PF_X | |
01 | .interp | INTERP | PF_R |
02 | .interp .note.ABI-tag .hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame | LOAD | PF_R + PF_X |
03 | .data .dynamic .ctors .dtors .jcr .got .bss | LOAD | PF_R + PF_W |
04 | .dynamic | DYNAMIC | PF_R + PF_W |
05 | .note.ABI-tag | NOTE | PF_R |
下則日記將會針對 segment type 所討論。
Jollen's Blog 使用 Github issues 與讀者交流討論。請點擊上方的文章專屬 issue,或 open a new issue
您可透過電子郵件 jollen@jollen.org,或是 Linkedin 與我連絡。更歡迎使用微信,請搜尋 WeChat ID:jollentw