elf header - ceragon/LinuxDoc GitHub Wiki

elf 文件结构

header

#define EI_NIDENT	16
typedef struct elf64_hdr {
    // 魔数占用 16字节
    unsigned char	e_ident[EI_NIDENT];	/* ELF "magic number" */
//    Elf64_Half e_type;
    __u16   e_type;
//    Elf64_Half e_machine;
    __u16   e_machine;
//    Elf64_Word e_version;
    __u32   e_version;
//    Elf64_Addr e_entry;		
    __u64   e_entry;		/* Entry point virtual address */
//    Elf64_Off e_phoff;		
    __u64   e_phoff;		/* Program header table file offset */
//    Elf64_Off e_shoff;		
    __u64   e_shoff;		/* Section header table file offset */
//    Elf64_Word e_flags;
    __u32   e_flags;
//    Elf64_Half e_ehsize;
    __u32   e_ehsize;
//    Elf64_Half e_phentsize;
    __u16   e_phentsize;
//    Elf64_Half e_phnum;
    __u16   e_phnum;
//    Elf64_Half e_shentsize;
    __u16   e_shentsize;
//    Elf64_Half e_shnum;
    __u16   e_shnum;
//    Elf64_Half e_shstrndx;
    __u16   e_shstrndx;
} Elf64_Ehdr;

合计头部占用 66 字节

phdata

typedef struct elf64_phdr {
//    Elf64_Word p_type;
    __u32 p_type;
//    Elf64_Word p_flags;
    __u32 p_flags;
//    Elf64_Off p_offset;		/* Segment file offset */
    __u64 p_offset;		/* Segment file offset */
//    Elf64_Addr p_vaddr;		/* Segment virtual address */
    __u64 p_vaddr;		/* Segment virtual address */
//    Elf64_Addr p_paddr;		/* Segment physical address */
    __u64 p_paddr;		/* Segment physical address */
//    Elf64_Xword p_filesz;		/* Segment size in file */
    __u64 p_filesz;		/* Segment size in file */
//    Elf64_Xword p_memsz;		/* Segment size in memory */
    __u64 p_memsz;		/* Segment size in memory */
//    Elf64_Xword p_align;		/* Segment alignment, file & memory */
    __u64 p_align;		/* Segment alignment, file & memory */
} Elf64_Phdr;

size = 4 + 4 + 8 + 8 + 8 + 8 + 8 + 8= 56 字节

我理解的 elf 文件结构

header 信息

大小 名称 含义
16 e_ident elf文件的魔数 \177ELF
2 e_type 类型必须是 ET_EXEC 或者 ET_DYN ET_EXEC
2 e_machine 指令集架构,例如 x86_64 EM_X86_64
4 e_version 未知
4 e_phoff header 占用的偏移量,或者说header占用的字节数
2 e_phentsize elf_phdr 结构体的大小,起到版本校验的作用
2 e_phnum elf_phdr 结构的个数

PT_INTERP 段信息 (interpreter)

大小 名称 含义
=== === ===== 段描述信息 PT_INTERP 开始 ======= ===
4 p_type 类型 PT_INTERP
8 p_offset interpreter 文件路径在 elf 文件中的偏移量
8 p_filesz interpreter 文件路径的大小

PT_GNU_STACK 段信息

大小 名称 含义
4 p_flags PF_X 表示是否可执行

PT_LOAD 段信息

大小 名称 含义
4 p_flags 是否可读,可写,可执行 PF_X , PF_W , PF_X
8 p_vaddr 段数据的虚拟地址
大小 名称 含义
4 p_type 类型
8 p_offset 对应段文件在文件中的偏移量
8 p_filesz 对应段文件在文件中的大小
=== === ===== 段文件地址信息 1开始 ======= ===
p_filesz elf_interpreter 段文件地址和名称
=== === ===== 段文件地址信息 2开始 ======= ===
p_filesz elf_interpreter 段文件地址和名称

加载 elf

#define elfhdr		elf64_hdr
#define	ELFMAG		"\177ELF"
#define	SELFMAG		4
#define EM_X86_64	62	/* AMD x86-64 architecture */
#define PT_INTERP  3
#define PATH_MAX        4096

#define PF_R		0x4
#define PF_W		0x2
#define PF_X		0x1

#define PT_LOOS    0x60000000
#define PT_LOPROC  0x70000000
#define PT_HIPROC  0x7fffffff
#define PT_GNU_STACK	(PT_LOOS + 0x474e551)

/* Stack area protections */
#define EXSTACK_DEFAULT   0	/* Whatever the arch defaults to */
#define EXSTACK_DISABLE_X 1	/* Disable executable stacks */
#define EXSTACK_ENABLE_X  2	/* Enable executable stacks */

static int load_elf_binary(struct linux_binprm *bprm) {
    struct file *interpreter = NULL;
    struct elf_phdr *elf_ppnt, *elf_phdata, *interp_elf_phdata = NULL;
    unsigned long elf_bss, elf_brk;
    char * elf_interpreter = NULL;
    int executable_stack = EXSTACK_DEFAULT;
    unsigned long start_code, end_code, start_data, end_data;
    struct {
		struct elfhdr elf_ex;
		struct elfhdr interp_elf_ex;
	} *loc;
    // 比较 elf 文件 header 信息中的魔数是否是 \177ELF,
    if (memcmp(loc->elf_ex.e_ident, ELFMAG, SELFMAG) != 0)
		goto out;
    // #define ET_EXEC   2
    // #define ET_DYN    3
    // type 必须是 2 或者 3
    if (loc->elf_ex.e_type != ET_EXEC && loc->elf_ex.e_type != ET_DYN)
		goto out;
    // 检查 loc->elf_ex.e_machine == 
//    if (!elf_check_arch(&loc->elf_ex))
    if ((&loc->elf_ex)->e_machine != EM_X86_64	)
		goto out;
    elf_phdata = load_elf_phdrs(&loc->elf_ex, bprm->file);
    elf_ppnt = elf_phdata;
    
    elf_bss = 0;
	elf_brk = 0;
    
	start_code = ~0UL;
	end_code = 0;
	start_data = 0;
	end_data = 0;
    // e_phnum 就是 elf_phdr 数组的个数
    // ================ 这一大段逻辑只为了处理 PT_INTERP 类型的段信息 =======
    for (i = 0; i < loc->elf_ex.e_phnum; i++) {
        if (elf_ppnt->p_type == PT_INTERP) {
            /* This is the program interpreter used for
			 * shared libraries - for now assume that this
			 * is an a.out format binary
			 */
            // p_fileez 是 segmentSize
            if (elf_ppnt->p_filesz > PATH_MAX || 
			    elf_ppnt->p_filesz < 2)
				goto out_free_ph;
            // 临时数据
            elf_interpreter = kmalloc(elf_ppnt->p_filesz, GFP_KERNEL);
            // 数据的偏移
            pos = elf_ppnt->p_offset;
            retval = kernel_read(bprm->file, elf_interpreter, elf_ppnt->p_filesz, &pos);
            // interpreter 是一个文件
            interpreter = open_exec(elf_interpreter);
            pos = 0;
            // 加载 interpreter 的 header
			retval = kernel_read(interpreter, &loc->interp_elf_ex, sizeof(loc->interp_elf_ex), &pos);
            break;
        }
        elf_ppnt++;
    }
    // =========================================================
    // =========== 由于 arch_elf_pt_proc 在 x86架构下什么也不做, ========
    // =========== 所以主要作用是获取 PT_GNU_STACK 段的信息 ==============
    // 设为数组首部,重新开始遍历
    elf_ppnt = elf_phdata;
    // e_phnum 是段描述的个数
    for (i = 0; i < loc->elf_ex.e_phnum; i++, elf_ppnt++) {
        switch (elf_ppnt->p_type) {
        case PT_GNU_STACK:
			if (elf_ppnt->p_flags & PF_X)
				executable_stack = EXSTACK_ENABLE_X;
			else
				executable_stack = EXSTACK_DISABLE_X;
			break;
        case PT_LOPROC ... PT_HIPROC:
			retval = arch_elf_pt_proc(&loc->elf_ex, elf_ppnt,
						  bprm->file, false,
						  &arch_state);
			break;
        }
    }
    // ============================================================
    // ======================= interpreter 文件的校验 ===============
    if (elf_interpreter) {
        // 校验 elf 文件的魔数
        if (memcmp(loc->interp_elf_ex.e_ident, ELFMAG, SELFMAG) != 0)
            goto out_free_dentry;
        // 校验指令集架构是不是 x86_64
        if (!elf_check_arch(&loc->interp_elf_ex) ||
		    elf_check_fdpic(&loc->interp_elf_ex))
			goto out_free_dentry;
        // 加载 interpreter 文件的段描述信息部分
        interp_elf_phdata = load_elf_phdrs(&loc->interp_elf_ex, interpreter);
        // 遇到给 elf_ppnt 赋值,就说明要开始遍历所有的段描述信息了
        elf_ppnt = interp_elf_phdata;
        for (i = 0; i < loc->interp_elf_ex.e_phnum; i++, elf_ppnt++) {
			switch (elf_ppnt->p_type) {
            case PT_LOPROC ... PT_HIPROC:
                // 目前什么也不做
                retval = arch_elf_pt_proc(&loc->interp_elf_ex,
							  elf_ppnt, interpreter, true, &arch_state);
                break;
            }
        }
    }
    // ============================================================
    // 目前 x86_64 什么也不会做
    retval = arch_check_elf(&loc->elf_ex, !!interpreter, &loc->interp_elf_ex, &arch_state);
    // 刷掉旧的信息
    retval = flush_old_exec(bprm);
    // 设置 personality
    SET_PERSONALITY2(loc->elf_ex, &arch_state);
    if (elf_read_implies_exec(loc->elf_ex, executable_stack)) {
        // 修改进程的 personality
		current->personality |= READ_IMPLIES_EXEC;
    }
    // 随机的虚拟空间地址
    if (!(current->personality & ADDR_NO_RANDOMIZE) && randomize_va_space)
		current->flags |= PF_RANDOMIZE;
    // 设置新的信息
    setup_new_exec(bprm);
    install_exec_creds(bprm);
    // TODO:
    retval = setup_arg_pages(bprm, randomize_stack_top(STACK_TOP), executable_stack);
    // 栈的起始地址
    current->mm->start_stack = bprm->p;
    
    // ===================== 处理 PT_LOAD 类型的段信息 ==================
    // 再一次遍历段信息
    unsigned long load_addr = 0, load_bias = 0;
    unsigned long elf_bss, elf_brk;
    elf_bss = 0;
	elf_brk = 0;
    start_code = ~0UL;
	end_code = 0;
	start_data = 0;
	end_data = 0;
    for(i = 0, elf_ppnt = elf_phdata; i < loc->elf_ex.e_phnum; i++, elf_ppnt++) {
        int elf_prot = 0, elf_flags, elf_fixed = MAP_FIXED_NOREPLACE;
        unsigned long k, vaddr;
        unsigned long total_size = 0;
        // 表明只处理 PT_LOAD 类型的段
        if (elf_ppnt->p_type != PT_LOAD)
			continue;
        if (elf_brk > elf_bss) { 
            // brk 是在内存中最后一个段的结束位置,bss 是在 elf 文件中的最后一个段的结束位置
			unsigned long nbyte;
        }
        
        if (elf_ppnt->p_flags & PF_R) {
            // 该段对应的信息可读
			elf_prot |= PROT_READ;
        }
        if (elf_ppnt->p_flags & PF_W) {
            // 该段对应的信息可写
			elf_prot |= PROT_WRITE;
        }
        if (elf_ppnt->p_flags & PF_X) {
            // 该段对应的信息可执行
			elf_prot |= PROT_EXEC;
        }
        // 默认情况下,一个段的信息是私有的,禁止写的,可执行的
        elf_flags = MAP_PRIVATE | MAP_DENYWRITE | MAP_EXECUTABLE;
        vaddr = elf_ppnt->p_vaddr;
        if (loc->elf_ex.e_type == ET_EXEC || load_addr_set) {
            // header 的类型是 ET_EXEC,可执行程序
			elf_flags |= elf_fixed;
        } else if (loc->elf_ex.e_type == ET_DYN) {
            // ET_DYN 表示共享库
            // TODO: 暂时先跳过吧
        }
        // 将段信息映射到内存中
        error = elf_map(bprm->file, load_bias + vaddr, elf_ppnt,
				elf_prot, elf_flags, total_size);
        // 如果 load_addr_set == 0
        if (!load_addr_set) {
			load_addr_set = 1;
            // 段数据的虚拟地址 - 段数据在文件中偏移
			load_addr = (elf_ppnt->p_vaddr - elf_ppnt->p_offset);
			if (loc->elf_ex.e_type == ET_DYN) {
                // 共享库
				load_bias += error -
				             ELF_PAGESTART(load_bias + vaddr);
				load_addr += load_bias;
				reloc_func_desc = load_bias;
			}
		}
        k = elf_ppnt->p_vaddr;
        if (k < start_code) 
			start_code = k; // 说明 start_code 是所有段数据里最靠前的那个
		if (start_data < k)
			start_data = k; // 说明 start_data 是所有段数据里最靠后的那个
        // 现在 k 是段数据的末尾位置    
        k = elf_ppnt->p_vaddr + elf_ppnt->p_filesz;    
        if (k > elf_bss)
			elf_bss = k;   // 说明 elf_bss 也是非常靠后的
		if ((elf_ppnt->p_flags & PF_X) && end_code < k) // 当前段是可执行的,且段数据的结束位置更靠后则满足
			end_code = k; // 也就是说 end_code 指向最靠后的那个可执行段的结束
		if (end_data < k)
			end_data = k; // data 就是最后一个段信息的结束
        // 虚拟地址 + 段数据在内存中的大小    
		k = elf_ppnt->p_vaddr + elf_ppnt->p_memsz;
		if (k > elf_brk) {
			bss_prot = elf_prot; // bss 的权限由在内存中最后一个段的权限控制
			elf_brk = k; // brk 指向在内存中最后一个段的结束位置
		}
    }
    // e_entry 是入口指令或者说 main 函数的位置
    loc->elf_ex.e_entry += load_bias; 
    elf_bss += load_bias;
	elf_brk += load_bias;
	start_code += load_bias;
	end_code += load_bias;
	start_data += load_bias;
	end_data += load_bias;
    
    
}

load_elf_phdrs

static struct elf_phdr *load_elf_phdrs(struct elfhdr *elf_ex,
				       struct file *elf_file) {
    struct elf_phdr *elf_phdata = NULL;
    int retval, size, err = -1;
    // Segment file offset
    loff_t pos = elf_ex->e_phoff;
    // phdata 大小 == e_phentsize,也就是 56 字节
    if (elf_ex->e_phentsize != sizeof(struct elf_phdr))
		goto out;
    // phnum 范围检查
    if (elf_ex->e_phnum < 1 ||
		elf_ex->e_phnum > 65536U / sizeof(struct elf_phdr))
		goto out;
    // 也就是说 e_phnum 是 phdata 的个数
    size = sizeof(struct elf_phdr) * elf_ex->e_phnum;
    // ELF_MIN_ALIGN == 1 << 12 = 4096 字节 = 4kb
    // 也就是 phdata 的大小需要满足 一个 page 的大小
    if (size > ELF_MIN_ALIGN)
		goto out;
    // 分配空间,由此可知 elf_phdata 是一个 elf_phdr 数组
    elf_phdata = kmalloc(size, GFP_KERNEL);
    retval = kernel_read(elf_file, elf_phdata, size, &pos);
    return elf_phdata;
}
ssize_t kernel_read(struct file *file, void *buf, size_t count, loff_t *pos) {
    ssize_t result;
    result = vfs_read(file, (void __user *)buf, count, pos);
    return result;
}

ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos) {
    ssize_t ret;
    ret = __vfs_read(file, buf, count, pos);
    return ret;
}

ssize_t __vfs_read(struct file *file, char __user *buf, size_t count,
		   loff_t *pos) {
    if (file->f_op->read)
		return file->f_op->read(file, buf, count, pos);
}

arch_elf_pt_proc

static inline int arch_elf_pt_proc(struct elfhdr *ehdr,
				   struct elf_phdr *phdr,
				   struct file *elf, bool is_interp,
				   struct arch_elf_state *state) {
    return 0;
}

arch_check_elf

static inline int arch_check_elf(struct elfhdr *ehdr, bool has_interp,
				 struct elfhdr *interp_ehdr,
				 struct arch_elf_state *state) {
	return 0;
}

randomize_stack_top

随机栈顶

// 最大偏移量
#define
STACK_RND_MASK 0x3fffff
static unsigned long randomize_stack_top(unsigned long stack_top) {
unsigned long random_variable = 0;
// 当前配置了随机栈顶
if (current->flags & PF_RANDOMIZE) {
// 随机long值
random_variable = get_random_long();
// 相当于取模
random_variable &= STACK_RND_MASK;
// 栈顶的单位是 字节,而随机值随的是页数
// PAGE_SHIFT 是一页的偏移,1页默认是 4KB
random_variable <<= PAGE_SHIFT;
}
// 默认栈是自上而下的
return PAGE_ALIGN(stack_top) - random_variable;
}

elf_map

static unsigned long elf_map(struct file *filep, // elf 文件
                            unsigned long addr, // 段数据的起始地址
                            struct elf_phdr *eppnt, // 段描述信息
                            int prot, // 访问权限
                            int type, // 映射属性
                            unsigned long total_size) {
    unsigned long map_addr;
    // 段数据在文件中的大小 + 段数据的起始虚拟地址
    unsigned long size = eppnt->p_filesz + ELF_PAGEOFFSET(eppnt->p_vaddr);
    // 段数据在文件中的偏移 - 段的起始虚拟地址
    unsigned long off = eppnt->p_offset - ELF_PAGEOFFSET(eppnt->p_vaddr);
    // 页对齐
    addr = ELF_PAGESTART(addr);
	size = ELF_PAGEALIGN(size);
    if (!size)
		return addr;
    if (total_size) {
        
    } else {
        map_addr = vm_mmap(filep, addr, size, prot, type, off);
    }
    // 实际映射的起始位置
    return(map_addr);
}
⚠️ **GitHub.com Fallback** ⚠️