ELF檔案
編譯和連結
ELF代表Executable and Linkable Format,是類Unix平臺最通用的二進位制檔案格式。下面三種檔案的格式都是ELF。
- 目標檔案
.o
- 動態庫檔案
.so
.o
和.so
連結得到的二進位制可執行檔案
編譯連結與執行過程中的檔案轉換如下圖所示。
檔案結構
根據馮諾伊曼原理,程式有指令和資料構成,因此ELF檔案儲存的內容即程式碼(指令)+資料+其他元資訊。
ELF檔案是靜態程式轉換為程序的橋樑,結構概覽如下圖:
- FILE HEDER:描述整個檔案的組織結構。
- Program Header Table:描述如何將 ELF 檔案中的段對映到記憶體中,它為作業系統的載入器提供資訊,告知哪些段需要被載入到記憶體中、它們的許可權以及如何對映。
- Section Header Table:描述 ELF 檔案中的各個節,提供了每個節的詳細資訊,如名稱、大小、型別和位置等。
- Section / Segment:節從連結角度描述elf檔案,段從記憶體載入角度描述elf檔案。
ELF檔案用於連結和載入兩個階段,有兩個檢視,連結檢視和執行檢視。
檢視 | 儲存內容 | 資料結構 | 使用階段 | 檔案格式 |
---|---|---|---|---|
連結檢視 | 靜態程式,用節(Section)組織 | Section Header Table | 編譯,連結 | .o ,.so |
執行檢視 | 載入後到記憶體分佈,用段(Segment)組織 | Program Header Table | 載入 | 可執行程式 |
Section 和 Segment 是 邏輯到物理的對映關係:
- 一個Segment對應多個Section
- 一個Section只能對應一個Segment
典型的對應關係:
執行段名稱(Segments) | 包含的節(Sections) | 許可權 |
---|---|---|
PT_LOAD(程式碼段) | .text,.rodata | R E |
PT_LOAD(資料段) | .data,.bss | RW |
PT_DYNAMIC | .dynamic,.got,.plt | RW |
PT_INTERP | .interp | R |
PT_NOTE | .note | R |
PT_SHLIB | .shstrtab,.symtab | RW |
PT_TLS | .tbss,.tdata | R,RW |
載入階段,載入器會按照Segment來組織虛擬記憶體,構造一個程序的記憶體空間,完成一個靜態檔案到程序的轉換。
ELF檔案解析
基本思路:根據ELF Header中的元資訊,跳轉到對應部分進行解析。
readelf -l fileName
解析ELF檔案的檔案頭,資料結構如下,逐個解析即可:
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;
成員 | 含義 | 備註 |
---|---|---|
e_ident | 檔案資訊 | 下標: [0.3]:魔數 4:檔案類 5:資料編碼 6:檔案版本 7:補齊 |
e_type | 檔案型別 | ET_NONE,ET_REL,ER_EXEC,ET_DYN,ET_CORE |
e_machine | 機器架構 | EM_NONE,EM_M32,EM_SPARC,EM_386,EM_68K,EM_88K,EM_860,EM_MIPS |
e_version | 目標檔案版本 | EV_NONE,EV_CURRENT |
e_entry | 入口項地址 | 上文圖中的Entry Point指標 |
e_phoff | 程式頭部表偏移 | Program Header Table Offset |
e_shoff | 節頭表偏移 | Section Header Table Offset |
e_flags | 檔案中與特定處理器相關的標誌 | |
e_ehsize | ELF 檔案頭部的位元組長度 | ELF Header Size |
e_phentsize | 程式頭部表中每個表項的位元組長度 | Program Header Entry Size |
e_phnum | 程式頭部表的項數 | Program Header Entry Number |
e_shentsize | 節頭的位元組長度 | Section Header Entry Size |
e_shnum | 節頭表中的項數 | Section Header Number |
e_shstrndx | 節頭表中與節名字串表相關的表項的索引值 | Section Header Table Index Related With Section Name String Table |
readelf -S fileName
解析ELF檔案的節頭表。
依照檔案頭資訊得到節頭表:(elf_header為Elf64_Ehdr型別指標)
-
獲得節頭表地址:
elf_header + elf_header->e_shoff
-
遍歷節頭表:表大小:
elf_header->e_shnum
-
節頭表中每個元素的資料結構如下
typedef struct
{
Elf64_Word sh_name; /* Section name (string tbl index) */
Elf64_Word sh_type; /* Section type */
Elf64_Word sh_flags; /* Section flags */
Elf64_Addr sh_addr; /* Section virtual addr at execution */
Elf64_Off sh_offset; /* Section file offset */
Elf64_Word sh_size; /* Section size in bytes */
Elf64_Word sh_link; /* Link to another section */
Elf64_Word sh_info; /* Additional section information */
Elf64_Word sh_addralign; /* Section alignment */
Elf64_Word sh_entsize; /* Entry size if section holds table */
} Elf64_Shdr;
readelf -s fileName
解析ELF檔案中符號表。
符號表作為一個節儲存,遍歷所有節,根據``elf_shdr->sh_type`判斷是否為符號表,如果是則解析該節(elf_shdr為Elf64_Shdr型別指標)
-
節中元素數量:
elf_shdr->sh_size / elf_shdr->sh_entsize
-
符號表作為節中元素結構如下:
typedef struct
{
Elf64_Word st_name; /* Symbol name (string tbl index) */
Elf64_Addr st_value; /* Symbol value */
Elf64_Word st_size; /* Symbol size */
unsigned char st_info; /* Symbol type and binding */
unsigned char st_other; /* Symbol visibility */
Elf64_Section st_shndx; /* Section index */
} Elf64_Sym;
程式執行結果
編譯: gcc -o elf_reader elf_reader.c
./elf_reader -h a.out
./elf_reader -S a.out
./elf_reader -s a.out
原始碼
#include <stdio.h>
#include <stdlib.h>
#include <elf.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
// 主要函式:進行ELF檔案解析
void parse_elf_header(const Elf64_Ehdr *elf_header); //-h: 解析檔案頭
void parse_section_headers(const Elf64_Ehdr *elf_header); //-S: 解析節頭表
void parse_symbol_table(const Elf64_Ehdr *elf_header); //-s: 解析符號表
// 輔助函式:格式化輸出
const char* get_elf64_st_type_name(unsigned char info);
const char* get_elf64_st_bind_name(unsigned char info);
const char* get_elf64_st_visibility_name(unsigned char other);
const char* get_section_type_name(Elf64_Word type);
const char* get_section_flags_name(Elf64_Xword flags);
const char* get_class_name(unsigned char class_value);
const char* get_data_name(unsigned char data_value);
const char* get_version_name(unsigned char version_value);
const char* get_os_name(unsigned char os_value);
const char* get_type_name(unsigned char type_value);
const char* get_machine_name(unsigned char machine_value);
void print_symbol_table(const char *strtab, Elf64_Sym *symbols, int count, const char *symtab_name);
int main(int argc, char * argv[])
{
// 獲取程式引數
if (argc != 3) {
fprintf(stderr, "Usage: %s <elf-file> <-h|-S|-s>\n", argv[0]);
exit(EXIT_FAILURE);
}
const char *option = argv[1];
const char *filename = argv[2];
// 開啟檔案
int fd = open(filename, O_RDONLY);
if (fd == -1) {
perror("Failed to open ELF file");
exit(EXIT_FAILURE);
}
// mmap對映
off_t file_size = lseek(fd, 0, SEEK_END);
char *map = mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (map == MAP_FAILED) {
perror("Memory mapping failed");
close(fd);
exit(EXIT_FAILURE);
}
Elf64_Ehdr *elf_header = (Elf64_Ehdr *)map;
// 處理不同引數對應的情況
if(strcmp(option, "-h") == 0)
{
parse_elf_header(elf_header);
}
else if(strcmp(option, "-S") == 0)
{
parse_section_headers(elf_header);
}
else if(strcmp(option, "-s") == 0)
{
parse_symbol_table(elf_header);
}
// 關閉檔案
munmap(map, file_size);
close(fd);
return 0;
}
// -h: 解析檔案頭
void parse_elf_header(const Elf64_Ehdr *elf_header) {
printf("ELF Header:\n");
printf(" Magic: ");
for (int i = 0; i < EI_NIDENT; ++i) {
printf("%02X ", elf_header->e_ident[i]);
}
printf("\n");
printf(" Class: %s\n", get_class_name(elf_header->e_ident[EI_CLASS]));
printf(" Data: %s\n", get_data_name(elf_header->e_ident[EI_DATA]));
printf(" Version: %s\n", get_version_name(elf_header->e_ident[EI_VERSION]));
printf(" OS/ABI: %s\n", get_os_name(elf_header->e_ident[EI_OSABI]));
printf(" Type: %s\n", get_type_name(elf_header->e_type));
printf(" Machine: %s\n", get_machine_name(elf_header->e_machine));
printf(" Version: %d\n", elf_header->e_version);
printf(" Entry point address: %#lx\n", elf_header->e_entry);
printf(" Start of program headers: %ld (bytes into file)\n", elf_header->e_phoff);
printf(" Start of section headers: %ld (bytes into file)\n", elf_header->e_shoff);
printf(" Flags: %#lx\n", elf_header->e_flags);
printf(" Size of this header: %d (bytes)\n", elf_header->e_ehsize);
printf(" Size of program headers: %d (bytes)\n", elf_header->e_phentsize);
printf(" Number of program headers: %d\n", elf_header->e_phnum);
printf(" Size of section headers: %d (bytes)\n", elf_header->e_shentsize);
printf(" Number of section headers: %d\n", elf_header->e_shnum);
printf(" Section header string table index: %d\n", elf_header->e_shstrndx);
}
// -S: 解析節頭表
void parse_section_headers(const Elf64_Ehdr *elf_header) {
// 找到節頭表和字串表
Elf64_Shdr *sections = (Elf64_Shdr *)((char *)elf_header + elf_header->e_shoff);
const char *strtab = (char *)elf_header + sections[elf_header->e_shstrndx].sh_offset;
printf("Section Headers:\n");
printf(" [Nr] Name Type Address Offset\n");
printf(" Size EntSize Flags Link Info Align\n");
// 遍歷節頭
for (int i = 0; i < elf_header->e_shnum; i++) {
// 列印節號、名稱、型別、地址、偏移
printf(" [%2d] %-17s %-16s %016lx %08lx\n",
i,
&strtab[sections[i].sh_name],
get_section_type_name(sections[i].sh_type),
sections[i].sh_addr,
sections[i].sh_offset);
// 列印節大小、條目大小、標誌、連結索引、資訊、對齊
printf(" %016lx %016lx %-6s %4u %4u %5lu\n",
sections[i].sh_size,
sections[i].sh_entsize,
get_section_flags_name(sections[i].sh_flags),
sections[i].sh_link,
sections[i].sh_info,
sections[i].sh_addralign);
}
}
// -s: 解析符號表
void parse_symbol_table(const Elf64_Ehdr *elf_header) {
// 找到節頭表指標和字串表
Elf64_Shdr *sections = (Elf64_Shdr *)((char *)elf_header + elf_header->e_shoff);
const char *strtab = (char *)elf_header + sections[elf_header->e_shstrndx].sh_offset;
// 遍歷每個節
for (int i = 0; i < elf_header->e_shnum; i++) {
// 如果是符號表
if (sections[i].sh_type == SHT_SYMTAB) { // .symtab符號表
Elf64_Shdr *symtab = §ions[i];
Elf64_Sym *symbols = (Elf64_Sym *)((char *)elf_header + symtab->sh_offset);
int count = symtab->sh_size / symtab->sh_entsize; // 符號個數
const char *symstrtab = (char *)elf_header + sections[symtab->sh_link].sh_offset;
print_symbol_table(symstrtab, symbols, count, ".symtab");
}
// 如果是動態符號表
else if (sections[i].sh_type == SHT_DYNSYM) { // .dynsym符號表
Elf64_Shdr *dynsymtab = §ions[i];
Elf64_Sym *dynsymbols = (Elf64_Sym *)((char *)elf_header + dynsymtab->sh_offset);
int dynsym_count = dynsymtab->sh_size / dynsymtab->sh_entsize; // 動態符號個數
const char *dynsymstrtab = (char *)elf_header + sections[dynsymtab->sh_link].sh_offset;
print_symbol_table(dynsymstrtab, dynsymbols, dynsym_count, ".dynsym");
}
}
}
// 獲取Class欄位資訊
const char* get_class_name(unsigned char class_value) {
switch(class_value) {
case ELFCLASS32: return "ELF32";
case ELFCLASS64: return "ELF64";
default: return "Unknown";
}
}
// 獲取Data欄位資訊
const char* get_data_name(unsigned char data_value) {
switch(data_value) {
case ELFDATA2LSB: return "2's complement, little endian";
case ELFDATA2MSB: return "2's complement, big endian";
default: return "Unknown";
}
}
// 獲取Version欄位資訊
const char* get_version_name(unsigned char version_value) {
switch(version_value) {
case 0: return "Invalid Version";
case 1: return "1 (current)";
default: return "Invalid Version";
}
}
// 獲取OS欄位資訊
const char* get_os_name(unsigned char os_value) {
switch(os_value) {
case ELFOSABI_NONE: return "UNIX - System V";
case ELFOSABI_LINUX: return "Linux";
case ELFOSABI_SOLARIS: return "Solaris";
case ELFOSABI_FREEBSD: return "FreeBSD";
default: return "Others";
}
}
// 獲取Type欄位資訊
const char* get_type_name(unsigned char type_value) {
switch(type_value) {
case ET_NONE: return "NONE (None)";
case ET_REL: return "REL (Relocatable file)";
case ET_EXEC: return "EXEC (Executable file)";
case ET_DYN: return "DYN (Shared object file)";
case ET_CORE: return "CORE (Core file)";
default: return "Unknown";
}
}
// 獲取Machine欄位資訊
const char* get_machine_name(unsigned char machine_value) {
switch(machine_value) {
case EM_386: return "Intel 80386";
case EM_ARM: return "ARM";
case EM_X86_64: return "AMD x86-64";
case EM_AARCH64: return "ARM AARCH64";
default: return "Unknown";
}
}
// 解析節型別
const char* get_section_type_name(Elf64_Word type) {
switch (type) {
case SHT_NULL: return "NULL";
case SHT_PROGBITS: return "PROGBITS";
case SHT_SYMTAB: return "SYMTAB";
case SHT_STRTAB: return "STRTAB";
case SHT_RELA: return "RELA";
case SHT_HASH: return "HASH";
case SHT_DYNAMIC: return "DYNAMIC";
case SHT_NOTE: return "NOTE";
case SHT_NOBITS: return "NOBITS";
case SHT_REL: return "REL";
case SHT_SHLIB: return "SHLIB";
case SHT_DYNSYM: return "DYNSYM";
default: return "UNKNOWN";
}
}
// 解析節標誌
const char* get_section_flags_name(Elf64_Xword flags) {
static char flag_str[64];
flag_str[0] = '\0';
if (flags & SHF_WRITE) strcat(flag_str, "W");
if (flags & SHF_ALLOC) strcat(flag_str, "A");
if (flags & SHF_EXECINSTR) strcat(flag_str, "X");
if (flags & SHF_MERGE) strcat(flag_str, "M");
if (flags & SHF_STRINGS) strcat(flag_str, "S");
return flag_str[0] == '\0' ? "None" : flag_str;
}
// 獲取符號型別
const char* get_elf64_st_type_name(unsigned char info) {
switch (ELF64_ST_TYPE(info)) {
case STT_NOTYPE: return "NOTYPE";
case STT_OBJECT: return "OBJECT";
case STT_FUNC: return "FUNC";
case STT_SECTION: return "SECTION";
case STT_FILE: return "FILE";
default: return "UNKNOWN";
}
}
// 獲取符號繫結
const char* get_elf64_st_bind_name(unsigned char info) {
switch (ELF64_ST_BIND(info)) {
case STB_LOCAL: return "LOCAL";
case STB_GLOBAL: return "GLOBAL";
case STB_WEAK: return "WEAK";
default: return "UNKNOWN";
}
}
// 獲取符號可見性
const char* get_elf64_st_visibility_name(unsigned char other) {
switch (ELF64_ST_VISIBILITY(other)) {
case STV_DEFAULT: return "DEFAULT";
case STV_INTERNAL: return "INTERNAL";
case STV_HIDDEN: return "HIDDEN";
case STV_PROTECTED: return "PROTECTED";
default: return "UNKNOWN";
}
}
void print_symbol_table(const char *strtab, Elf64_Sym *symbols, int count, const char *symtab_name) {
printf("Symbol table '%s' contains %d entries:\n", symtab_name, count);
printf(" Num: Value Size Type Bind Vis Ndx Name\n");
for (int i = 0; i < count; i++) {
// 符號表內容輸出,按照readelf -s格式進行對齊
printf("%6d: %016lx %-5lu %-7s %-6s %-8s %-4d %s\n",
i,
symbols[i].st_value,
symbols[i].st_size,
// 解析符號型別
get_elf64_st_type_name(symbols[i].st_info),
// 解析符號繫結
get_elf64_st_bind_name(symbols[i].st_info),
// 解析符號可見性
get_elf64_st_visibility_name(symbols[i].st_other),
symbols[i].st_shndx,
&strtab[symbols[i].st_name]);
}
}
參考
-
相關概念
-
ELF檔案資料結構
-
程式碼實現