ELF文件的加载和动态链接过程
Intel平台下Linux中ELF文件动态链接的加载、解析及实例分析(一):加载
Intel 平台下Linux 中ELF ⽂件动态链接的加载、解析及实例分析(⼀):加载Intel 平台下Linux 中 ELF ⽂件动态链接的加载、解析及实例分析(⼀): 加载转载⾃:IBM developerWorks 中国⽹站()2003 年 10 ⽉动态链接,⼀个经常被⼈提起的话题。
但在这⽅⾯很少有⽂章来阐明这个重要的软件运⾏机制,只有⼀些关于动态链接库编程的⽂章。
本系列⽂章就是要从动态链接库源代码的层次来探讨这个问题。
当然从⽂章的题⽬就可以看出,intel 平台下的linux ELF ⽂件的动态链接。
⼀则是因为这⼀⽅⾯的资料查找⽐较⽅便,⼆则也是这个讨论的意思⽐其它的动态链接要更为重要(毕竟现在是intel 的天下)。
当然,有了这么⼀个例⼦,其它的平台下的ELF ⽂件的动态链接也就⼤同⼩异。
你可以在阅读完了本⽂之后"举⼀隅,⽽反三隅"了。
由于这是⼀个系列的⽂章,我计划分三部分来写,第⼀部分主要分析加载,涉及dl_open 这个函数的内容,但由于这个函数所包含的内容实在太多。
这⾥主要是它的_dl_map_object 与_dl_init 这两个部分,因为这⾥是把动态链接⽂件通过在ELF ⽂件中的得到信息映射到内存空间中,⽽_dl_init 中是⼀个特殊的初始化。
这是对⾯向对象的函数实现的。
第⼆部分我将分析函数解析与卸载,这⾥要讲的内容会⽐较多,但每⼀个内容都不会多。
⾸先是在前⼀篇中没有说完的dl_open 中的涉及的_dl_map_object_deps 和_dl_relocate_object 两个函数内容,因为这些都与函数解析的内容直接相关,所以安排在这⾥。
⽽下⾯的函数解析过程_dl_runtime_resolve 是在程序运⾏中的动态解析过程。
这⾥从本质上来讲没有太多的代码,但它的精巧程度却是最多的(正是我这三篇⽂章的核⼼之处)。
最后是⼀个dl_close 的实现。
elf使用方法
elf使用方法:
elf(可执行和可链接格式)是一种常见的二进制文件格式,用于存储程序或库中的代码和数据。
以下是ELF文件的使用方法:1.加载和运行:使用操作系统或解释器加载ELF文件到内存中,并执行其中的代码。
这通常涉及将ELF文件中的代码
和数据加载到内存中的适当位置,然后开始执行程序的入口点。
2.调试:ELF文件可以使用调试器进行调试。
调试器可以读取ELF文件中的符号表和其他调试信息,以便在程序执行
期间进行断点、单步执行、查看变量值等操作。
3.反汇编:使用反汇编器可以将ELF文件中的机器代码转换为汇编语言代码。
这有助于理解程序的执行流程和代码结
构。
4.修改和重新编译:如果拥有源代码,可以修改ELF文件中的代码和数据,然后重新编译以生成新的ELF文件。
分析ELF的加载过程
分析ELF的加载过程对于可执行文件来说,段的加载位置是固定的,程序段表中如实反映了段的加载地址.对于共享库来?段的加载位置是浮动的,位置无关的,程序段表反映的是以0作为基准地址的相对加载地址.尽管共享库的连接是不充分的,为了便于测试动态链接器,Linux允许直接加载共享库运行.如果应用程序具有动态链接器的描述段,内核在完成程序段加载后,紧接着加载动态链接器,并且启动动态链接器的ELF的可执行文件与共享库在结构上非常类似,它们具有一张程序段表,用来描述这些段如何映射到?br>?炭占?对于可执行文件来说,段的加载位置是固定的,程序段表中如实反映了段的加载地址.对于共享库来?br>?段的加载位置是浮动的,位置无关的,程序段表反映的是以0作为基准地址的相对加载地址.尽管共享库的连接是不充分的,为了便于测试动态链接器,Linux允许直接加载共享库运行.如果应用程序具有动态链接器的描述段,内核在完成程序段加载后,紧接着加载动态链接器,并且启动动态链接器的?br>肟?typedef struct elf32_hdr{unsigned char e_ident[EI_NIDENT];Elf32_Half e_type; /* ET_EXEC ET_DYN 等 */Elf32_Half e_machine;Elf32_Word e_version;Elf32_Addr e_entry; /* Entry point */Elf32_Off e_phoff; 程序段描述表的位置Elf32_Off e_shoff; 一般段描述表的位置Elf32_Word e_flags;Elf32_Half e_ehsize; ELF头的大小Elf32_Half e_phentsize; 程序段描述表单元的大小Elf32_Half e_phnum; 程序段描述表单元的个数Elf32_Half e_shentsize; 一般段描述表的单元大小Elf32_Half e_shnum; 一般段描述表单元的个数Elf32_Half e_shstrndx;} Elf32_Ehdr;typedef struct elf32_phdr{Elf32_Word p_type; PT_INTERP,PT_LOAD,PT_DYNAMIC等Elf32_Off p_offset; 该程序段在ELF文件中的位置Elf32_Addr p_vaddr; 该程序段被映射到进程的虚拟地址Elf32_Addr p_paddr;Elf32_Word p_filesz; 该程序段的文件尺寸Elf32_Word p_memsz; 该程序段的内存尺寸Elf32_Word p_flags; 该程序段的映射属性Elf32_Word p_align;} Elf32_Phdr;struct linux_binprm{char buf[BINPRM_BUF_SIZE]; 预先读入的ELF文件头struct page *page[MAX_ARG_PAGES];unsigned long p; /* current top of mem */int sh_bang;struct file * file;int e_uid, e_gid;kernel_cap_t cap_inheritable, cap_permitted, cap_effective;int argc, envc;char * filename; /* Name of binary */unsigned long loader, exec;};static int load_elf_binary(struct linux_binprm * bprm, struct pt_regs * regs){struct file *interpreter = NULL; /* to shut gcc up */unsigned long load_addr = 0, load_bias;int load_addr_set = 0;char * elf_interpreter = NULL;unsigned int interpreter_type = INTERPRETER_NONE; unsigned char ibcs2_interpreter = 0;mm_segment_t old_fs;unsigned long error;struct elf_phdr * elf_ppnt, *elf_phdata;unsigned long elf_bss, k, elf_brk;int elf_exec_fileno;int retval, size, i;unsigned long elf_entry, interp_load_addr = 0;unsigned long start_code, end_code, start_data, end_data; struct elfhdr elf_ex;struct elfhdr interp_elf_ex;struct exec interp_ex;char passed_fileno[6];/* Get the exec-header */elf_ex = *((struct elfhdr *) bprm->buf);retval = -ENOEXEC;/* First of all, some simple consistency checks */if (memcmp(elf_ex.e_ident, ELFMAG, SELFMAG) != 0)goto out; 文件头标记是否匹配if (elf_ex.e_type != ET_EXEC && elf_ex.e_type != ET_DYN) goto out; 文件类型是否为可执行文件或共享库if (!elf_check_arch(&elf_ex))goto out;if (!bprm->file->f_op||!bprm->file->f_op->mmap)goto out; 所在的文件系统是否具有文件映射功能/* Now read in all of the header information */retval = -ENOMEM;size = elf_ex.e_phentsize * elf_ex.e_phnum; 求程序段表总长度if (size > 65536)goto out;elf_phdata = (struct elf_phdr *) kmalloc(size, GFP_KERNEL); 分配程序段表空间if (!elf_phdata)goto out;; 读入程序段表retval = kernel_read(bprm->file, elf_ex.e_phoff, (char *) elf_phdata, size);if (retval < 0)goto out_free_ph;retval = get_unused_fd(); 取可用进程文件表的自由槽位if (retval < 0)goto out_free_ph;get_file(bprm->file);fd_install(elf_exec_fileno = retval, bprm->file); 将打开的文件安装到进程文件表elf_ppnt = elf_phdata; 指向程序段表elf_bss = 0; bss段的起始地址elf_brk = 0; bss段的终止地址start_code = ~0UL; 代码段的开始end_code = 0; 代码段的终止start_data = 0; 数据段的开始end_data = 0; 数据段的终止; 扫描ELF程序段表,搜寻动态链接器定义for (i = 0; i < elf_ex.e_phnum; i++) {if (elf_ppnt->p_type == PT_INTERP) {retval = -EINVAL;if (elf_interpreter)goto out_free_dentry; 如果包含多个动态链接器描述项/* This is the program interpreter used for* shared libraries - for now assume that this* is an a.out format binary*/retval = -ENOMEM; 为动态链接器名称字符串分配空间elf_interpreter = (char *) kmalloc(elf_ppnt->p_filesz,GFP_KERNEL);if (!elf_interpreter)goto out_free_file;; 将动态链接器的文件名读入内存retval = kernel_read(bprm->file, elf_ppnt->p_offset,elf_interpreter,elf_ppnt->p_filesz);if (retval < 0)goto out_free_interp;/* If the program interpreter is one of these two,* then assume an iBCS2 image. Otherwise assume* a native linux image.*/if (strcmp(elf_interpreter,"/usr/lib/libc.so.1") == 0 ||strcmp(elf_interpreter,"/usr/lib/ld.so.1") == 0)ibcs2_interpreter = 1; 说明应用程序是IBCS2仿真代码interpreter= open_exec(elf_interpreter); 打开动态链接器文件retval = PTR_ERR(interpreter);if (IS_ERR(interpreter))goto out_free_interp;retval = kernel_read(interpreter, 0, bprm->buf, BINPRM_BUF_SIZE);; 读入动态链接器文件头if (retval < 0)goto out_free_dentry;/* Get the exec headers */interp_ex = *((struct exec *) bprm->buf); 假定为aout格式的文件头结构interp_elf_ex = *((struct elfhdr *) bprm->buf); 假定为ELF文件头结构}elf_ppnt++; 下一片段目录项}/* Some simple consistency checks for the interpreter */if (elf_interpreter) {; 如果定义了动态链接器,分析其格式类型interpreter_type = INTERPRETER_ELF| INTERPRETER_AOUT;/* Now figure out which format our binary is */if ((N_MAGIC(interp_ex) != OMAGIC) &&(N_MAGIC(interp_ex) != ZMAGIC) &&(N_MAGIC(interp_ex) != QMAGIC))interpreter_type = INTERPRETER_ELF; 如果不是AOUT标识if (memcmp(interp_elf_ex.e_ident, ELFMAG, SELFMAG) != 0) interpreter_type &= ~INTERPRETER_ELF; 如果没有ELF标识retval = -ELIBBAD;if (!interpreter_type) 不能识别动态链接器类型goto out_free_dentry;/* Make sure only one type was selected */if ((interpreter_type & INTERPRETER_ELF) &&interpreter_type != INTERPRETER_ELF) {printk(KERN_WARNING "ELF: Ambiguous type, using ELF ");interpreter_type = INTERPRETER_ELF;}}/* OK, we are done with that, now set up the arg stuff,and then start this sucker up */if (!bprm->sh_bang) {char * passed_p;if (interpreter_type == INTERPRETER_AOUT) {sprintf(passed_fileno, "%d", elf_exec_fileno);passed_p = passed_fileno;if (elf_interpreter) {retval = copy_strings_kernel(1,&passed_p,bprm);; 将程序的文件描述符压入参数堆栈,准备传递给aout格式的动态链接器if (retval)goto out_free_dentry;bprm->argc++; bprm->page[]中参数的数目}}}/* Flush all traces of the currently running executable */retval = flush_old_exec(bprm);if (retval)goto out_free_dentry;/* OK, This is the point of no return */current->mm->start_data = 0;current->mm->end_data = 0;current->mm->end_code = 0;current->mm->mmap = NULL;current->flags &= ~PF_FORKNOEXEC;elf_entry = (unsigned long) elf_ex.e_entry; 应用程序的入口地址/* Do this immediately, since STACK_TOP as used in setup_arg_pagesmay depend on the personality. */SET_PERSONALITY(elf_ex, ibcs2_interpreter);; 如果是ibcs2_interpreter非0,置PER_SVR4代码个性,否则置PER_LINUX代码个性/* Do this so that we can load the interpreter, if need be. We willchange some of these later */current->mm->rss = 0;setup_arg_pages(bprm); /* XXX: check error */; 建立描述堆栈参数页的初始虚存范围结构current->mm->start_stack = bprm->p;/* Try and get dynamic programs out of the way of the default mmapbase, as well as whatever program they might try to exec. Thisis because the brk will follow the loader, and is not movable. */load_bias = ELF_PAGESTART(elf_ex.e_type==ET_DYN ? ELF_ET_DYN_BASE : 0);; 如果需要加载的是共享库,则设置共享库的加载偏置为ELF_ET_DYN_BASE/* Now we do a little grungy work by mmaping the ELF image intothe correct location in memory. At this point, we assume that the image should be loaded at fixed address, not at a variable address. */old_fs = get_fs();set_fs(get_ds());; 再次扫描程序段描述表,映射其中的程序段到进程空间for(i = 0, elf_ppnt = elf_phdata; i < elf_ex.e_phnum; i++, elf_ppnt++) {int elf_prot = 0, elf_flags;unsigned long vaddr;if (elf_ppnt->p_type != PT_LOAD)continue; 如果程序段不可加载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;; 根据程序段描述设置相应的mmap参数vaddr = elf_ppnt->p_vaddr; 段起始地址if (elf_ex.e_type == ET_EXEC || load_addr_set) {; 对于可执行程序,使用固定映射elf_flags |= MAP_FIXED;}error = elf_map(bprm->file, load_bias + vaddr, elf_ppnt, elf_prot, elf_flags);; 将该程序段[eppnt->p_offset,eppnt->p_filesz]映射到虚存(load_bias+vaddr)开始的区域if (!load_addr_set) {load_addr_set = 1;load_addr = (elf_ppnt->p_vaddr - elf_ppnt->p_offset);; 求出该ELF文件在用户虚存中的起始地址if (elf_ex.e_type == ET_DYN) {load_bias += error -ELF_PAGESTART(load_bias + vaddr);load_addr += error;}}k = elf_ppnt->p_vaddr;if (k < start_code) start_code = k; 取最小的段地址作为代码段起始if (start_data < k) start_data = k; 取最大的段地址作为数据段起始k = elf_ppnt->p_vaddr + elf_ppnt->p_filesz; 这时k指向文件段尾if (k > elf_bss)elf_bss = k; 取最大文件段尾作为BSS段起始if ((elf_ppnt->p_flags & PF_X) && end_code < k)end_code = k; 取最大可执行的文件段尾作为可执行段的终止if (end_data < k)end_data = k; 取最大的文件段尾作为数据段的终止k = elf_ppnt->p_vaddr + elf_ppnt->p_memsz; 这时k指向内存段尾if (k > elf_brk)elf_brk = k; 取最大的内存段尾作为BSS段的终止}set_fs(old_fs);elf_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;if (elf_interpreter) {if (interpreter_type == INTERPRETER_AOUT)elf_entry = load_aout_interp(&interp_ex,interpreter);elseelf_entry = load_elf_interp(&interp_elf_ex, 动态链接器的文件头interpreter, 动态链接器打开的文件结构&interp_load_addr); 输出链接器的加载地址; 可执行程序的入口变为动态链接器的入口allow_write_access(interpreter);fput(interpreter);kfree(elf_interpreter);if (elf_entry == ~0UL) {printk(KERN_ERR "Unable to load interpreter ");kfree(elf_phdata);send_sig(SIGSEGV, current, 0);return 0;}}kfree(elf_phdata); 释放程序段表if (interpreter_type != INTERPRETER_AOUT)sys_close(elf_exec_fileno);set_binfmt(&elf_format); 增加ELF内核模块的引用计数compute_creds(bprm);current->flags &= ~PF_FORKNOEXEC;bprm->p = (unsigned long) 建立入口函数参数表create_elf_tables((char *)bprm->p,bprm->argc,bprm->envc,(interpreter_type == INTERPRETER_ELF ? &elf_ex : NULL), load_addr, load_bias,interp_load_addr,(interpreter_type == INTERPRETER_AOUT ? 0 : 1));/* N.B. passed_fileno might not be initialized? */if (interpreter_type == INTERPRETER_AOUT)current->mm->arg_start += strlen(passed_fileno) + 1;current->mm->start_brk = current->mm->brk = elf_brk;current->mm->end_code = end_code;current->mm->start_code = start_code;current->mm->start_data = start_data;current->mm->end_data = end_data;current->mm->start_stack = bprm->p;/* Calling set_brk effectively mmaps the pages that we need * for the bss and break sections*/set_brk(elf_bss, elf_brk); 建立bss的虚存映射,elf_bss是bss的开始,elf_brk是bss的结束padzero(elf_bss); 如果bss不起始于页连界上,说明与data段有重叠,则将该页bss区域清0#if 0printk("(start_brk) %lx " , (long) current->mm->start_brk);printk("(end_code) %lx " , (long) current->mm->end_code);printk("(start_code) %lx " , (long) current->mm->start_code);printk("(start_data) %lx " , (long) current->mm->start_data);printk("(end_data) %lx " , (long) current->mm->end_data);printk("(start_stack) %lx " , (long) current->mm->start_stack);printk("(brk) %lx " , (long) current->mm->brk);#endifif ( current->personality == PER_SVR4 ){/* Why this, you ask Well SVr4 maps page 0 as read-only, and some applications "depend" upon this behavior.Since we do not have the power to recompile these, weemulate the SVr4 behavior. Sigh. *//* N.B. Shouldn't the size here be PAGE_SIZE?? */down(¤t->mm->mmap_sem);error = do_mmap(NULL, 0, 4096, PROT_READ | PROT_EXEC, MAP_FIXED | MAP_PRIVATE, 0);up(¤t->mm->mmap_sem);}#ifdef ELF_PLAT_INIT/** The ABI may specify that certain registers be set up in special* ways (on i386 %edx is the address of a T_FINI function, for * example. This macro performs whatever initialization to* the regs structure is required.*/ELF_PLAT_INIT(regs);#endifstart_thread(regs, elf_entry, bprm->p); 将返回的eip设为elf_entry,esp设为bprm->pif (current->ptrace & PT_PTRACED)send_sig(SIGTRAP, current, 0); 如果进程处于跟踪状态,则生成SIGTRAP信号retval = 0;out:return retval;/* error cleanup */out_free_dentry:allow_write_access(interpreter);fput(interpreter);out_free_interp:if (elf_interpreter)kfree(elf_interpreter);out_free_file:sys_close(elf_exec_fileno);out_free_ph:kfree(elf_phdata);goto out;}static unsigned long load_elf_interp(struct elfhdr * interp_elf_ex,struct file * interpreter,unsigned long *interp_load_addr){struct elf_phdr *elf_phdata;struct elf_phdr *eppnt;unsigned long load_addr = 0;int load_addr_set = 0;unsigned long last_bss = 0, elf_bss = 0;unsigned long error = ~0UL;int retval, i, size;/* First of all, some simple consistency checks */if (interp_elf_ex->e_type != ET_EXEC &&interp_elf_ex->e_type != ET_DYN)goto out;if (!elf_check_arch(interp_elf_ex))goto out;if (!interpreter->f_op || !interpreter->f_op->mmap)goto out;/** If the size of this structure has changed, then punt, since * we will be doing the wrong thing.*/if (interp_elf_ex->e_phentsize != sizeof(struct elf_phdr))goto out;/* Now read in all of the header information */size = sizeof(struct elf_phdr) * interp_elf_ex->e_phnum;if (size > ELF_MIN_ALIGN)goto out; 如果动态链接器的程序段表的尺寸大于1个页面elf_phdata = (struct elf_phdr *) kmalloc(size, GFP_KERNEL);if (!elf_phdata)goto out;retval = kernel_read(interpreter,interp_elf_ex->e_phoff,(char *)elf_phdata,size);error = retval;if (retval < 0)goto out_close;eppnt = elf_phdata;for (i=0; ie_phnum; i++, eppnt++) {if (eppnt->p_type == PT_LOAD) {int elf_type = MAP_PRIVATE | MAP_DENYWRITE;int elf_prot = 0;unsigned long vaddr = 0;unsigned long k, map_addr;if (eppnt->p_flags & PF_R) elf_prot = PROT_READ;if (eppnt->p_flags & PF_W) elf_prot |= PROT_WRITE;if (eppnt->p_flags & PF_X) elf_prot |= PROT_EXEC;vaddr = eppnt->p_vaddr;if (interp_elf_ex->e_type == ET_EXEC || load_addr_set)elf_type |= MAP_FIXED;map_addr = elf_map(interpreter, load_addr + vaddr, eppnt, elf_prot, elf_type);if (!load_addr_set && interp_elf_ex->e_type == ET_DYN) { load_addr = map_addr - ELF_PAGESTART(vaddr);load_addr_set = 1;}/** Find the end of the file mapping for this phdr, and keep* track of the largest address we see for this.*/k = load_addr + eppnt->p_vaddr + eppnt->p_filesz;if (k > elf_bss)elf_bss = k;/** Do the same thing for the memory mapping - between* elf_bss and last_bss is the bss section.*/k = load_addr + eppnt->p_memsz + eppnt->p_vaddr;if (k > last_bss)last_bss = k;}}/* Now use mmap to map the library into memory. *//** Now fill out the bss section. First pad the last page up* to the page boundary, and then perform a mmap to make sure* that there are zero-mapped pages up to and including the * last bss page.*/padzero(elf_bss);elf_bss = ELF_PAGESTART(elf_bss + ELF_MIN_ALIGN - 1); /* What we have mapped so far*//* Map the last of the bss segment */if (last_bss > elf_bss)do_brk(elf_bss, last_bss - elf_bss);*interp_load_addr = load_addr;error = ((unsigned long) interp_elf_ex->e_entry) + load_addr;out_close:kfree(elf_phdata);out:return error;}static inline unsigned longelf_map (struct file *filep, unsigned long addr, struct elf_phdr *eppnt, int prot, inttype){unsigned long map_addr;down(¤t->mm->mmap_sem);map_addr = do_mmap(filep, ELF_PAGESTART(addr),eppnt->p_filesz + ELF_PAGEOFFSET(eppnt->p_vaddr), prot, type,eppnt->p_offset - ELF_PAGEOFFSET(eppnt->p_vaddr));up(¤t->mm->mmap_sem);return(map_addr);}void set_binfmt(struct linux_binfmt *new){struct linux_binfmt *old = current->binfmt;if (new && new->module)__MOD_INC_USE_COUNT(new->module);current->binfmt = new;if (old && old->module)__MOD_DEC_USE_COUNT(old->module);}static void set_brk(unsigned long start, unsigned long end) {start = ELF_PAGEALIGN(start);end = ELF_PAGEALIGN(end);if (end <= start)return;do_brk(start, end - start);}static void padzero(unsigned long elf_bss){unsigned long nbyte;nbyte = ELF_PAGEOFFSET(elf_bss);if (nbyte) {nbyte = ELF_MIN_ALIGN - nbyte;clear_user((void *) elf_bss, nbyte);}}。
ELF分析
4.2.1可执行文件ELF分析1.在Linux下ELF文件主要有三种类型阴:(1)可重定位文件(RelocatableFile)包含适合于与其他目标文件链接来创建可执行文件或者共享目标文件的代码和数据"(2)可执行文件(ExecutableFile)包含适合于执行的一个程序,此文件规定了exec()如何创建一个程序的进程映像"(3)共享目标文件(SharedObjectFile)包含可在两种上下文中链接的代码和数据"首先链接编辑器可以将它和其它可重定位文件和共享目标文件一起处理,生成另外一个目标文件"其次,动态链接器(DynamicLinker)可能将它与某个可执行文件以及其它共享目标一起组合,创建进程映像"2.ELF文件的组织结构文件ELF参与程序的连接(建立一个程序)和程序的执行(运行一个程序),编译器和链接器将其视为节头表(seetionheadertable)描述的一些节(seetion)的集合,而加载器则将其视为程序头表(programheadertable)描述的段(segmellt)的集合,通常一个段可以包含多个一节"可亚定位文件都包含一个节头表,可执行文件都包含一个程序头表"共享文件两者都包含有"为此,ELF文件格式同时提供了两种看待文件内容的方式,反映了不同行为的不同要求"下图4.1显示了ELF文件的组织结构"Elf头在程序的开始部位,作为引路表描述整个ELF的文件结构,其信扁!大致分为四部分:一是系统相关信息, 二是目标文件类型, 三是加载相关信息, 四是链接相关信息其中系统相关信息包括elf文件魔数(标识elf文件),平台位数,数据编码方式,elf头部版本,硬件平台emaehine,目标文件版本eversion,处理器特定标志e_ftags:这些信息的引入极大增强了elf文件的可移植性,使交叉编译成为可能"目标文件类型用e一yPe的值表示, 可重定位文件为1, 可执行文件为2, 共享文件为3; 加载相关信息有:程序入口e_entry,程序头表偏移量e夕hoff,elf头部长度e_ehsize,程序头表中一个条目的长度e夕hentsize,程序头表条目数目e一hnum;链接相关信息有:节头表偏移量e_shoff,节头表中一个条目的长度eShentsize,节头表条目个数eShnum,节头表字符索引eShstmdx"可使用readelf一hfilename来察看文件头的内容"程序头表(Programheadertable)告诉系统如何建立一个进程映像,它是从加载执行的角度来看待elf文件"从它的角度来看,elf文件被分成许多段,elf文件中的代码!链接信息和注释都以段的形式存放"每个段都在程序头表中有一个表项描述,包含以下属性:段的类型,段的驻留位置相对于文件开始处的偏移,段在内存中的首字节地址,段的物理地址,段在文件映像中的字节数,段在内存映像中的字节数,段在内存和文件中的对齐标记"可用readelf一1filename察看程序头表中的内容"节头表(sectionheadertable)描述程序节,为编译器和链接器服务"它把elf文件分成了许多节,每个节保存着用于不同目的的数据,这些数据可能被前面的程序头重复使用,完成一次任务所需的信息往往被分散到不同的节里"由于节中数据的用途不同,节被分成不同的类型,每种类型的节都有自己组织数据的方式"每一个节在节头表中都有一个表项描述该节的属性,节的属性包括小节名在字符表中的索引,类型,属性,运行时的虚拟地址,文件偏移,以字节为单位的大小,小节的对齐等信息,可使用:eadelf一5filename来察看节头表的内容"3.ELF文件的加载过程相对可执行文件有三个重要的概念:编译(comPile)!连接(1ink,也可称为链接!联接)!加载(load)"源程序文件被编译成目标文件,多个目标文件被连接成一个最终的可执行文件,可执行文件被加载到内存中运行"因为本文重点是讨沦可执行文件格式,因此加载过程也相对重点讨论"下面是LINUX平台下ELF文件加载过程的一个简单描述[39]"(1)内核首先读ELF文件的头部,然后根据头部的数据指示分别读入各种数据结构,找到标记为可加载(loadable)的段,并调用函数mmapo把段内容加载到内存中"在加载之前,内核把段的标记直接传递给mmapo,段的标记指示该段在内存中是否可读!可写,可执行"显然,文本段是只读可执行,而数据段是可读可写"这种方式是利用了现代操作系统和处理器对内存的保护功能"著名的Shdlcodc的编写技巧则是突破此保护功能的一个实际例子"(2)内核分析出ELF文件标记为PT--INTERP的段中所对应的动态连接器名称,并加载动态连接器"现代L州UX系统的动态连接器通常是/lib/ld一linux.so.2, 相关细节在后面有一详细描述"(3)内核在新进程的堆栈中设置一些标记一值对,以指示动态连接器的相关操作"(4)内核把控制传递给动态连接器"(5)动态连接器检查程序对外部文件(共享库)的依赖性,并在需要时对其进行加载"(6)动态连接器对程序的外部引用进行重定位,通俗的讲,就是告诉程序其引用的外部变量/函数的地址,此地址位于共享库被加载在内存的区间内"动态连接还有一个延迟定位的特性,即只在真正需要引用符号时才重定位,这对提高程序运行效率有极大帮助"(7)动态连接器执行在ELF文件中标记为.init的节的代码,进行程序运行的初始化"在早期系统中,初始化代码对应函数_init(Void)(函数名强制固定),在现代系统中,则对应形式为!oid_attribute((eonstruetor))ini仁funetion(void){,,}其中函数名为任意"(8)动态连接器把控制传递给程序,从ELF文件头部中定义的程序进入点开始执行"在a.out格式和ELF格式中,程序进入点的值是显式存在的,在COFF格式中则是由规范隐含定义"4.2.3传统加壳方法分析从上面的分析可以看出,在ELF文件的加载过程中,首先将文件读入内存,然后根据ELF头部表查找文件的入口地址,找到入口地址然后执行程序,传统的加壳方式是:将程序的入口地址修改成另外一个地址,一般是ELF文件的某些空闲节区,然后执行反跟踪分析处理,如果发现可执行程序被跟踪调试则退出,否则继续执行代码段解密工作,解密完成后转入正常的程序入口地址执行"下图4.3 是传统加壳方法的简单示意图"当系统将加壳后ELF文件读入内存中,加壳后的ELF文件的处理流程如下: (1)读取程序的入口地址,该入口地址是修改后的入口地址(处于B位置),真正的入口地址被放置在D位置位置"(2)程序跳转到B位置执行"(3)执行后面的常用处理例程,例如反跟踪检测和代码混淆等,如果这些检测通过,则解密程序的主体(A部分),接着顺序执行到D位置"(4)根据D位置存放的真正程序入口地址,然后跳转到程序主体部分,运行解密后的真正程序"传统的方法有很大的局限性,首先外壳程序(就是常用处理例程)是存放在ELF文件空闲区域,但并不是每个文件的空闲区域都是相同的,如果外壳程序功能较多,那就意味着需要更多的空闲区域,很有可能会出现空闲区域不足的情况"其次在对ELF文件进行加壳处理过程中,必须对ELF格式非常熟悉且谨慎处理,微小的疏忽就将导致加壳程序或者加壳后的程序无法运行的情况"同时传统加壳方式的扩展性不好,很难具有通用性"4.3一种新的ELF加壳方法针对上面对于加壳原理的分析和常用加壳技术的了解,在这里提出了一种新的加壳方法即:SRELF(ShellingandRe一eonstruetedExeeutableandLinkil:9Forlnat)" SRELF方法的最大特点就是R(重新构造)"该方法是将目标elf文件中的核心部分提取出来,再和准备好的解密或解压缩程序,反静态分析和反动态跟踪等程序结合在一起,按照e!f文件标难格式一重新构造一个全新的clf可执行文件"它将加壳的各项功能程序和目标程序很好的融合为一体"4.3.1SRELF方法的设计在这里SRELF加壳程序将采用多层次加密!强密码保护机制和内存中可执行文件解密的方式来确保ELF文件的安全性"SRELF加壳方法需要实现下而的功能: (l)防止系统调用跟踪(SystemCallTraeing)"(2)防止库调用跟踪(LibraryCallTraeing)"(3)防1卜程序执行路径跟踪(ExeeutionpathTraeing)"(4)防止应用级调试跟踪(ApplieationLevelDebugging)"(5)防止内存镜像(Memo仃Dumping)"(6)关键数据加密(Ene仃ptimportantcode)"从上面可以看出SRELF程序需要解决6类问题,上述问题的解决思路如下:(l)针对功能1到4"在Linux下实现跟踪的原理都是基于基于Ptrace()调试的API,所以只要使得这个API函数对于加密的二进制文件无效,这样就将会有效阻止对于程序的跟踪分析,采用这些工具将很难攻击加壳的ELF程序"(2)针对内存镜像是基于这样的设想,对程序进行内存镜像的前提条件是:在内存中运行的程序必须是明文方式,所以可采用加壳程序使程序运行时部分解密来实现"(3)如何解决关键数据加密"系统采用加密算法对于ELF的核心部分进行加毖1.加壳程序的文件结构设计采用SRELF加密后的文件结构如下所示:如上图所示,S旺LF的结构其实是:=SRELF头8=加密后的程序>"其中S贬LF头由ELF头!程序头和SRELF常用处理例程这三部分组成"在编译SRELF命令时,生成了一个很大的数组s灿ea走binll,其实就是一个通用的ELF头十程序头十SRELF常用处理例程,当加密程序的时候,只需要对数组里的某些字段稍作修改即可"其中SR卫LF的常用处理例程采用分层结构,分别由代码混淆层!反跟踪分析层和块加密层组成"2.SRELF的加解密过程(l)加密过程sRELF首先以加密的文件作为对象,将S灿ead_bin[1作为加密后文件的ELF头十程序头十SRELF常用例程,然后按照SRELF的命令选项,对文件进行逐层的处理,分别是代码混淆!反跟踪分析和块加密处理"对于每层的处理,都会在SRELF头中保存相应的标志和信息(例如加密时的随机序列,shal哈希值),然后将结果放到最后一部分中"在这里采用主机的某些特征(fillgerprillt)和密码作为密钥KEY,首先算出密钥的SHAI哈希值,然后从随机发生序列器中获得160位的随机数,并与哈希值异或得到KEYZ,然后用KEYZ对-需要程序进行RC4加密"将加密之前的明文SHAI 哈希值保存到Srhead头的相应部分,这样做密码验证时就能够判断密码的正确性"(2)解密过程首先去掉第一层保护,接下来处理运行程序的栈结构,并将系统控制权交给SRELF的处理函数"处理函数会对反跟踪分析进行处理,如果通过的话接下来是fingcrprint的检测,如果通过检验就将加密的程序解密,然后调用s贬LF中自带的Loader,将解密后的程序映射一到内存中,如果解密的程序需要inte甲rcter,loader 也会负责将interp咒ter装入到内存中,最后将控制权交给illterpreter或者解密后的FIF"3.SRELF的加载器(Loader)由于解密以后的程序与SRELF的例程函数是位于同一进程空间的,因此如果想要执行解密后的程序,例程就需要自己装载程序,在一般情况下程序是由内核来完成装载任务的"SRELF是通过bejemap来实现的,如果加密之前的程序是动态链接的,还需调用be皿apinte印reter来装载解释器(inte印reter)#4.SRELF的实现流程图下图4.5是SRELF的实现流程图"图4.5SRELF方法实现程序框图4.3.2SRELF方法性能分析1.安全性首先SRELF的方法理论上可以使用任意一种加密或压缩算法来对程序.text段的二进制代码进行加密或压缩"从加解密角度来说,SRELF的安全性比其他方法要高"其次SRELF方法没有对加壳功能程序的大小进行限制,所以在二进制代码中可以插入足够的花指令,并加上高复杂度的加密或压缩算法,想要进行静态的分析的难度会极大"再次SRELF方法是通过重构elf文件,完全改变了elf文件中的内容,在Start位置上的内容将不再是原来的内容,这对想要通过静态分析代码来破解软件的破解者来说又增加了难度"SRELF对反动态跟踪也给予了充分的支持"和反静态跟踪一样,SRELF在其中加入了充分的反动态跟踪指令,想当与对elf文件又增加了一层保护措施"2.透明性当加壳的elf文件运行时,由于解码的过程是在内存中完成的,所以对于用户来说是感觉不到程序被加了保护壳,这个过程对用户来说是透明的"3.伪装性一般加壳后的可执行文件在结构和大小上是有一定程度变化的"经过SRELF方法加壳后的elf文件仍然保持了elf文件默认的文件结构,所以大多数人都是无法看出加壳前后"1f文件的区别"而且SRELF不需要改变程序入口地址,这样即便是使用特定工具也分辨不出来"就像elf文件没有经过特殊处理,这样给加壳后的可执行文件增加了伪装"4.扩展性由于SRELF理论上没有对壳程序大小进行限制,这使得它具备了良好的扩展性"因此随着技术的史新,可以同步更新SRELF中的加密方式!反静态分析和反动态跟踪等方法,这样SRELF将具有良好的发展性,不会迅速被淘汰"5.3!5软件保护模块第四章提出了SRELF加壳方法,该方法重点在于/提取目标文件中的核心部分0和/将目标文件的核心部分和加壳部分整合0这两个部分功能的实现"所以在此,主要阐述这两部分的实现方法"1.提取}8标文件中的核心部分核心部分包括可执行指令,动态链接表等,段或节的信息"提取这些部分也就是将这些部分单独复制出来,用以后面代码整合中使用"这里以.text节为例,说明如何提取核心部分"该节保存着程序的币文或者说是可执行指令,可以说它是这部分的核心"根据elf文件头中的信息,首先定位到程序头表"在程序头表中再定位到代码段,整个代码段都是需要的核心部分"这时再定位到.text位置,将其中的数据代码复制出来"其他段或节都以同样的方法获得"这里只给出定位.text 的程序代码"V oidGetElfCore(FILE*fP)/*获取程序头表和节头表的起始地址*/Phdr=(EI仍2_Phdr*)((unsignedlong)ehdr+ehdr一>e夕ho均:shdr=(EI招2_Shdr*)((unsignedlong)ehdr+ehdr一>屯sho均;/*定位文本段*/while(l){if(判断是否是文本段){不是文本段,则将指针指向下一个段}Break;刀若是文本段,跳出循环2.将目标文件的核心部分和加壳部分整合这部分功能是整个SRELF的核心"它不是简单的将各个程序段或节一一对应相加"它需要进行精确的计算,来正确的设置elf文件头,程序头表,节头表等的数据"这样新生成的elf文件才能正常运行"这里还是以.text节为例"首先要分别计算出加壳部分.text节占物理文件的大小和载入内存所占内存的大小,目标文件核心部分.text节占物理文件的大小和载入内存所占内存的大小"然后将目标文件核心部分.text节内容续写到加壳部分.text节的后面"再将程序头表,节头表中.text 属性中与大小相关的参数对应设置成新计算出的大小"到此为止,.text部分的整合完成"其他段或节都以同样的方法整合"这里只给出.text的整合伪代码" VOidIntegrate(char*sre,ehar*des){刀通过GetElfCore函数将目标文件的.text段的地址赋值给Src//将准备好的壳程序的.text段的地址赋值给desxntegrate_segment(ehar*sre.ehar*des);//将目标程序的.text段续写在壳程序的.text段之后Set_EI仁Attribute(ehar*sre,ehar*des);//重新设置elf头和程序段表和节表中的参数.............}。
CMake:ELF文件加载动态库的位置
CMake:ELF⽂件加载动态库的位置简单⼯程⽰例1. world.c#include<stdio.h>void world(void){printf("world.\n");}2. hello.c#include <stdio.h>void world(void);void hello(void){printf("hello\n");world();}3. main.cvoid main(void){hello();}2. 编译动态库gcc -c -fPIC hello.c world.cgcc -shared -o libworld.so world.ogcc -shared -o libhello.so hello.o -lworld -L .可见动态库libhello.so依赖于libworld.so3. CMakeLists.txtcmake_minimum_required(VERSION 3.2)PROJECT(pro)SET(CMAKE_BUILD_TYPE Release)SET(LINK_PATH .)SET(LINK_PATH . )INCLUDE_DIRECTORIES(${INCLUDE_PATH})LINK_DIRECTORIES(${LINK_PATH})ADD_EXECUTABLE(main main.c)TARGET_LINK_LIBRARIES(main hello world)这种⽅式⽣成的main ELF⽂件的默认动态库搜索路径是当前⽂件夹 ".";⼀旦当前⽂件夹下动态库不存在则找不到动态库,⽆法执⾏。
此时可以通过设置 LD_LIBRARY_PATH ⽅式帮助ELF⽂件在相对应路径下查找动态库或者cmake_minimum_required(VERSION 3.2)PROJECT(pro)SET(CMAKE_BUILD_TYPE Release)SET(LINK_PATH .)SET(LINK_PATH . )INCLUDE_DIRECTORIES(${INCLUDE_PATH})LINK_DIRECTORIES(${LINK_PATH})LINK_LIBRARIES(hello world -Wl,-rpath=/usr/local/lib)ADD_EXECUTABLE(main main.c)或者⽅案⼆cmake_minimum_required(VERSION 3.2)PROJECT(pro)SET(CMAKE_BUILD_TYPE Release)SET(LINK_PATH .)SET(LINK_PATH . /usr/local/lib)INCLUDE_DIRECTORIES(${INCLUDE_PATH})LINK_DIRECTORIES(${LINK_PATH})ADD_EXECUTABLE(main main.c)TARGET_LINK_LIBRARIES(main hello world)或者⽅案三cmake_minimum_required(VERSION 3.2)PROJECT(pro)SET(CMAKE_BUILD_TYPE Release)SET(LINK_PATH .)SET(LINK_PATH . )INCLUDE_DIRECTORIES(${INCLUDE_PATH})LINK_DIRECTORIES(${LINK_PATH})SET(CMAKE_EXE_LINKER_FLAGS '-Wl,-rpath=/usr/local/lib')LINK_LIBRARIES(hello world)ADD_EXECUTABLE(main main.c)通过设置连接参数,将/usr/local/lib和当前⽂件夹路径写⼊到ELF⽂件内,则每次ELF将在/usr/local/lib路径下查找可执⾏⽂件。
elf文件格式(中文版)
序言1. OBJECT文件导言ELF头(ELF Header)SectionsString表(String Table)Symbol表(Symbol Table)重定位(Relocation)2. 程序装载与动态连接导言Program头(Program Header)Program装载(Program Loading)Dynamic连接(Dynamic Linking)3. C LIBRARYC Library________________________________________________________________导言________________________________________________________________ ELF: 可执行连接格式可执行连接格式是UNIX系统实验室(USL)作为应用程序二进制接口(Application Binary Interface(ABI)而开发和发布的。
工具接口标准委员会(TIS)选择了正在发展中的ELF标准作为工作在32位INTEL体系上不同操作系统之间可移植的二进制文件格式。
假定开发者定义了一个二进制接口集合,ELF标准用它来支持流线型的软件发展。
应该减少不同执行接口的数量。
因此可以减少重新编程重新编译的代码。
关于这篇文档这篇文档是为那些想创建目标文件或者在不同的操作系统上执行文件的开发着准备的。
它分以下三个部分:* 第一部分, “目标文件Object Files”描述了ELF目标文件格式三种主要的类型。
* 第二部分, “程序转载和动态连接”描述了目标文件的信息和系统在创建运行时程序的行为。
* 第三部分, “C 语言库”列出了所有包含在libsys中的符号,标准的ANSI C和libc的运行程序,还有libc运行程序所需的全局的数据符号。
注意: 参考的X86体系已经被改成了Intel体系。
________________________________________________________________1. 目标文件(Object file)________________________________________________________________========================= 序言=========================第一部分描述了iABI的object文件的格式, 被称为ELF(Executableand Linking Format). 在object文件中有三种主要的类型。
动态链接库dll的静态加载与动态加载
动态链接库dll的静态加载与动态加载动态链接是指在⽣成可执⾏⽂件时不将所有程序⽤到的函数链接到⼀个⽂件,因为有许多函数在操作系统带的dll⽂件中,当程序运⾏时直接从操作系统中找。
⽽静态链接就是把所有⽤到的函数全部链接到exe⽂件中。
动态链接是只建⽴⼀个引⽤的接⼝,⽽真正的代码和数据存放在另外的可执⾏模块中,在运⾏时再装⼊;⽽静态链接是把所有的代码和数据都复制到本模块中,运⾏时就不再需要库了。
1.⽣成静态链接库 newdll) win32项⽬ -> dll添加.h⽂件betabinlib.h[cpp]1. #ifndef BETABINLIB_H2. #define BETABINLIB_H3.4. #ifdef NEWDLL_EXPORTS //⾃动添加的宏右键⼯程-属性-配置属性-预处理器-..定义5. #define MYDLL_API extern "C" __declspec(dllexport)6. #else7. #define MYDLL_API extern "C" __declspec(dllimport)8. #endif9.10. MYDLL_API int add(int x, int y); // 必须加前缀11. #endif#ifndef BETABINLIB_H#define BETABINLIB_H#ifdef NEWDLL_EXPORTS //⾃动添加的宏右键⼯程-属性-配置属性-预处理器-..定义#define MYDLL_API extern "C" __declspec(dllexport)#else#define MYDLL_API extern "C" __declspec(dllimport)#endifMYDLL_API int add(int x, int y); // 必须加前缀#endif添加.cpp⽂件 betabinlib.cpp[cpp]1. #include "stdafx.h"2. #include "betabinlib.h"3.4. int add(int x, int y)5. {6. return x + y;7. }#include "stdafx.h"#include "betabinlib.h"int add(int x, int y){return x + y;}编译⽣成 .dll 和 .(1)dll的静态加载--将整个dll⽂件加载到 .exe⽂件中特点:程序较⼤,占⽤内存较⼤,但速度较快(免去调⽤函数LOAD1. #include <stdio.h>2. #include "betabinlib.h"3. #include <Windows.h>4. #pragma comment(lib, "newdll.lib")5.6. int main()7. {8. printf("2 + 3 = %d \n", add(2, 3));9. return 0;10. }#include <stdio.h>#include "betabinlib.h"#include <Windows.h>#pragma comment(lib, "newdll.lib")int main(){printf("2 + 3 = %d \n", add(2, 3));return 0;}1. #include <stdio.h>2. #include <Windows.h>3.4. int main()5. {6. HINSTANCE h=LoadLibraryA("newdll.dll");7. typedef int (* FunPtr)(int a,int b);//定义函数指针8.9. if(h == NULL)10. {11. FreeLibrary(h);12. printf("load lib error\n");13. }14. else15. {16. FunPtr funPtr = (FunPtr)GetProcAddress(h,"add");17. if(funPtr != NULL)18. {19. int result = funPtr(3, 3);20. printf("3 + 3 = %d \n", result);21. }22. else23. {24. printf("get process error\n");25. printf("%d",GetLastError());26. }27. FreeLibrary(h);28. }29.30. return 0;31. }。
ELF文件基础
ELF⽂件基础(Executable Linkable Format, )是Linux参考COFF(Common Object File Format)规范⽽定义的可执⾏⽂件格式。
可执⾏⽂件、共享⽬标⽂件(*.so)、⽬标中间⽂件(⼜称可重定位⽂件,*.o)、核⼼转储⽂件(Core Dump File)都是ELF⽂件。
按位数可分为:elf32和elf64;⽀持cpu架构有:aarch64(即:arm64)、arm等。
ELF可能按照不同的字节序(Byte Order)来存储。
例如:elf32-big arm是⼤端(Big-endian)存储;elf64-little aarch64是⼩端(Little-endian)存储。
与COFF⼀样,ELF也是基于段(Segment,有时也被叫节Section)的结构。
其⽂件结构如下:ELF头(ELF Header):ELF魔数、⽂件机器字节长度、数据存储⽅式(⼤⼩端)、版本、运⾏平台、ABI版本、ELF重定位类型、硬件平台及版本、⼊⼝地址、程序头⼊⼝和长度、段表的位置和长度、段的数量。
段表(Section header table):描述了ELF各个段的基本信息,如:每个段的段名、段的长度、在⽂件中偏移、读写权限及段的其他属性。
编译器、链接器和装载器都是依靠段表来定位和访问各个段的属性的。
段的类型:常量值含义SHT_NULL0⽆效段SHT_PROGBITS1程序段、代码段、数据段都是这种类型SHT_SYMTAB2该段的内容为符号表SHT_STRTAB3该段的内容为字符串表SHT_RELA4重定位表,包含重定位信息SHT_HASH5符号表的哈希表SHT_DYNAMIC6动态链接信息SHT_NOTE7提⽰性信息SHT_NOBITS8表⽰该段在⽂件中没有内容。
如:bss段SHT_REL9该段包含了重定位信息SHT_SHLIB10保留SHT_DNYSYM11动态链接的符号表段的标志位表⽰该段在进程虚拟空间中的属性。
ELF文件详解—初步认识
ELF⽂件详解—初步认识⼀、引⾔在讲解ELF⽂件格式之前,我们来回顾⼀下,⼀个⽤C语⾔编写的⾼级语⾔程序是从编写到打包、再到编译执⾏的基本过程,我们知道在CPU上执⾏的是低级别的机器语⾔,从⾼级语⾔到低级别的机器语⾔肯定是要经过翻译过程,这个过程⼤体的过程如下图所⽰:在Unix系统中,从源⽂件到可执⾏⽬标⽂件是由编译驱动程序完成的,如⼤名⿍⿍的gcc,翻译过程包括图中的是个阶段;Ø 预处理阶段预处理器(cpp)根据以字符#开头的命令修给原始的C程序,结果得到另⼀个C程序,通常以.i作为⽂件扩展名。
主要是进⾏⽂本替换、宏展开、删除注释这类简单⼯作。
对应的命令:linux> gcc -E hello.c hello.iØ 编译阶段编译器将⽂本⽂件hello.i翻译成hello.s,包含相应的汇编语⾔程序对应的命令:linux> gcc -S hello.c hello.sØ 汇编阶段将.s⽂件翻译成机器语⾔指令,把这些指令打包成⼀种叫做可重定位⽬标程序的格式,并将结果保存在⽬标⽂件.o中(把汇编语⾔翻译成机器语⾔的过程)。
把⼀个源程序翻译成⽬标程序的⼯作过程分为五个阶段:词法分析;语法分析;语义检查和中间代码⽣成;代码优化;⽬标代码⽣成。
主要是进⾏词法分析和语法分析,⼜称为源程序分析,分析过程中发现有语法错误,给出提⽰信息。
对应的命令:linux> gcc -c hello.c hello.oØ 链接阶段此时hello程序调⽤了printf函数。
printf函数存在于⼀个名为printf.o的单独的预编译⽬标⽂件中。
链接器(ld)就负责处理把这个⽂件并⼊到hello.o程序中,结果得到hello⽂件,⼀个可执⾏⽂件。
最后可执⾏⽂件加载到储存器后由系统负责执⾏, 函数库⼀般分为静态库和动态库两种。
静态库是指编译链接时,把库⽂件的代码全部加⼊到可执⾏⽂件中,因此⽣成的⽂件⽐较⼤,但在运⾏时也就不再需要库⽂件了。
ELF文件动态链接-地址无关代码(GOT)
ELF⽂件动态链接-地址⽆关代码(GOT)Linux 系统中,ELF动态链接⽂件被称为动态共享对象(DSO,Dynamic Shared Object),简称共享对象⽂件拓展名为“.so”动态链接下⼀个程序可以被分成若⼲个⽂件:程序的主要部分 - 可执⾏⽂件和程序所依赖的共享对象(⼀个或多个.so⽂件),它们都可称作为程序的模块。
动态链接⽂件(共享对象)的装载地址为0x00000000;这并⾮⼯作时的实际地址,实际地址由装载器根据当前进程地址空间的空闲情况来动态分配⼀块⾜够⼤的虚拟地址空间给共享对象。
装载时重定位基本思路:在链接时对所有的绝对地址的引⽤不作重定位,⽽把这⼀步放在装载时完成。
⼀旦模块装载完成,既⽬标地址确定,那么系统将对程序中的所有绝对地址的引⽤进⾏重定位。
静态链接时的重定位称为链接时重定位(Link Time Relocation)动态链接时的重定位称为装载时重定位(Load Time Relocation) gcc -shared然⽽光有装载时重定位也不能解决所有问题,因为对于动态链接⽂件来讲,它时可以分为可修改数据部分和不可修改数据部分,如果只有装载重定位,那每个程序必须都有⼀个共享对象的副本,这样会很浪费内存这样就引⼊来下⼀个话题地址⽆关代码(gcc -shared -fPIC)基本思想:把指令中需要被修改的部分分离出来,跟数据部分放在⼀起,这样指令部分就保持不变了,⽽数据部分为每个进程都有⼀个副本,这就是地址⽆关代码(PIC, Position-independent Code)技术共享对象模块内的地址引⽤分为四种情况:1. 模块内部调⽤或跳转: 它们之间位置固定,使⽤相对地址调⽤。
2. 模块内部数据访问 同样使⽤相对寻址(使⽤当前(指令地址)PC + 偏移量)3. 模块与模块之间的数据访问(重点) 模块之间的数据访问,其⽬标地址需要等到装载后才能确定。
这⾥的基本思想时将这部分与地址相关的指令的当前地址放⼊到数据段⾥⾯。
ELF文件
静态链接的主要工作
静态链接
重定位
编译器汇编器从地址0创建目标代码 链接器收集各种代码和数据,组合成单一文件 加载时,将链接好的文件整体重定位到加载地址
符号解析
将符号的定义和引用关联起来
链接的一般步骤
链接的一般步骤
ELF文件的三种类型
可重定位
编译器和汇编器创建 运行前需要被链接器处理 完成了所有重定位工作和符号解析 除了运行时解析的共享库符号 链接器需要的符号信息 运行时可以直接执行的代码
.rodata .data .bss .sym .rel.txt .rel.data .line .debug .strtab 节头部表
ELF头部 .text
ELF文件中的节
.rodata:只读数据,具有ALLOC 属性和PROGBITS类型区段。由 于是只读数据,因此没有WRITE 属性。
.rodata .data .bss .sym .rel.txt .rel.data .line .debug .strtab 节头部表
PIC(位置无关代码)
提纲
ELF重定位
32位PC相关地址重定位 32位绝对地址重定位
进程的图像结构 程序入口代码 动态链接共享库 加载和执行的流程
ELF加载与执行
总结
目标文件格式
一些目标文件格式
MS DOS .COM Unix a.out Unix COFF Unix ELF Windows PE 平坦二进制 0x100入口地址
MS DOS .COM
目标文件格式
Unix a.out
不容易支持动态链接 不支持C++语言 非扩展版本不支持C++和动态链接
elf文件格式与动态链接库
elf文件格式与动态链接库(非常之好)机器执行的是机器指令,而机器指令就是一堆二进制的数字。
高级语言编写的程序之所以可以在不同的机器上移植就因为有为不同机器设计的编译器的存在。
高级语言的编译器就是把高级语言写的程序转换成某个机器能直接执行的二进制代码。
以上的知识在我们学习CS(Computer Science)的初期,老师都会这么对我们讲。
但是我就产生疑问了:既然机器都是执行的二进制代码,那么是不是说只要硬件相互兼容,不同操作系统下的可执行文件可以互相运行呢?答案肯定是不行。
这就要谈到可执行文件的格式问题。
每个操作系统都会有自己的可执行文件的格式,比如以前的Unix?是用a.out格式的,现代的Unix?类系统使用elf格式,WindowsNT?是使用基于COFF格式的可执行文件。
那么最简单的格式应该是DOS的可执行格式,严格来说DOS的可执行文件没有什么格式可言,就是把二进制代码安顺序放在文件里,运行时DOS操作系统就把所有控制计算机的权力都给了这个程序。
这种方式的不足之处是显而易见的,所以现代的操作系统都有一种更好的方式来定义可执行文件的格式。
一种常见的方法就是为可执行文件分段,一般来说把程序指令的内容放在.t e x t段中,把程序中的数据内容放在.d a t a段中,把程序中未初始化的数据放在.b s s段中。
这种做法的好处有很多,可以让操作系统内核来检查程序防止有严重错误的程序破坏整个运行环境。
比如:某个程序想要修改.text段中的内容,那么操作系统就会认为这段程序有误而立即终止它的运行,因为系统会把.t e x t段的内存标记为只读。
在.b s s段中的数据还没有初始化,就没有必要在可执行文件中浪费储存空间。
在.bss中只是表明某个变量要使用多少的内存空间,等到程序加载的时候在由内核把这段未初始化的内存空间初始化为0。
这些就是分段储存可执行文件的内容的好处。
下面谈一下Unix系统里的两种重要的格式:a.out和elf(Executable and Linking Format)。
程序的静态链接,动态链接和装载
程序的静态链接,动态链接和装载一、程序编译链接的整体流程二、目标文件的样子(以linux下的elf文件格式为例)三、静态链接四、装载五、动态链接一、程序编译链接的整体流程通常我们使用gcc来生成可执行程序,命令为:gcchello.c,默认生成可执行文件a.out 其实编译(包括链接)的命令:gcchello.c可分解为如下4个大的步骤:∙预处理(Preprocessing)∙编译(Compilation)∙汇编(Assembly)∙链接(Linking)gcc compilation1. 预处理(Preproceessing)预处理的过程主要处理包括以下过程:∙将所有的#define删除,并且展开所有的宏定义∙处理所有的条件预编译指令,比如#if #ifdef #elif #else #endif等∙处理#include预编译指令,将被包含的文件插入到该预编译指令的位置。
∙删除所有注释“//”和”/* */”.∙添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告行号。
∙保留所有的#pragma编译器指令,因为编译器需要使用它们通常使用以下命令来进行预处理:gcc -E hello.c -o hello.i参数-E表示只进行预处理或者也可以使用以下指令完成预处理过程cpphello.c>hello.i /* cpp – The C Preprocessor */直接cat hello.i你就可以看到预处理后的代码2. 编译(Compilation)编译过程就是把预处理完的文件进行一系列的词法分析,语法分析,语义分析及优化后生成相应的汇编代码。
$gcc –S hello.i –o hello.s或者$ /usr/lib/gcc/i486-linux-gnu/4.4/cc1 hello.c注:现在版本的GCC把预处理和编译两个步骤合成一个步骤,用cc1工具来完成。
gcc其实是后台程序的一些包装,根据不同参数去调用其他的实际处理程序,比如:预编译编译程序cc1、汇编器as、连接器ld3. 汇编(Assembly)汇编器是将汇编代码转变成机器可以执行的命令,每一个汇编语句几乎都对应一条机器指令。
elf文件详解
ARM的可执行文件的格式是ELF格式文件,下文对ELF格式做个详细的介绍。
序言1. OBJECT文件导言ELF头(ELF Header)SectionsString表(String Table)Symbol表(Symbol Table)重定位(Relocation)2. 程序装载与动态连接导言Program头(Program Header)Program装载(Program Loading)Dynamic连接(Dynamic Linking)3. C LIBRARYC Library________________________________________________________________ 导言________________________________________________________________ ELF: 可执行连接格式可执行连接格式是UNIX系统实验室(USL)作为应用程序二进制接口(Application Binary Interface(ABI)而开发和发布的。
工具接口标准委员会(TIS)选择了正在发展中的ELF标准作为工作在32位INTEL体系上不同操作系统之间可移植的二进制文件格式。
假定开发者定义了一个二进制接口集合,ELF标准用它来支持流线型的软件发展。
应该减少不同执行接口的数量。
因此可以减少重新编程重新编译的代码。
关于这片文档这篇文档是为那些想创建目标文件或者在不同的操作系统上执行文件的开发着准备的。
它分以下三个部分:* 第一部分, “目标文件O bject Files”描述了ELF目标文件格式三种主要的类型。
* 第二部分, “程序转载和动态连接”描述了目标文件的信息和系统在创建运行时程序的行为。
* 第三部分, “C 语言库”列出了所有包含在libsys中的符号,标准的ANSI C和libc的运行程序,还有libc运行程序所需的全局的数据符号。
ELF文件的加载过程(load
ELF文件的加载过程(load日期内核版本架构作者GitHub CSDN2016-06-04 Linux-4.6X86 &armgatieme LinuxDeviceDriversLinux进程管理与调度-之-进程的描述加载和动态链接从编译/链接和运行的角度看,应用程序和库程序的连接有两种方式。
一种是固定的、静态的连接,就是把需要用到的库函数的目标代码(二进制)代码从程序库中抽取出来,链接进应用软件的目标映像中;另一种是动态链接,是指库函数的代码并不进入应用软件的目标映像,应用软件在编译/链接阶段并不完成跟库函数的链接,而是把函数库的映像也交给用户,到启动应用软件目标映像运行时才把程序库的映像也装入用户空间(并加以定位),再完成应用软件与库函数的连接。
这样,就有了两种不同的ELF格式映像。
•一种是静态链接的,在装入/启动其运行时无需装入函数库映像、也无需进行动态连接。
•另一种是动态连接,需要在装入/启动其运行时同时装入函数库映像并进行动态链接。
Linux内核既支持静态链接的ELF映像,也支持动态链接的ELF映像,而且装入/启动ELF映像必需由内核完成,而动态连接的实现则既可以在内核中完成,也可在用户空间完成。
因此,GNU把对于动态链接ELF映像的支持作了分工:把ELF映像的装入/启动入在Linux内核中;而把动态链接的实现放在用户空间(glibc),并为此提供一个称为”解释器”(ld-linux.so.2)的工具软件,而解释器的装入/启动也由内核负责,这在后面我们分析ELF文件的加载时就可以看到这部分主要说明ELF文件在内核空间的加载过程,下一部分对用户空间符号的动态解析过程进行说明。
Linux可执行文件类型的注册机制在说明ELF文件的加载过程以前,我们先回答一个问题,就是:为什么Linux可以运行ELF文件?内核对所支持的每种可执行的程序类型都有个struct linux_binfmt的数据结构,这个结构我们在前面的博文中我们已经提到, 但是没有详细讲. 其定义如下/** This structure defines the functions that are used to load the binary formats that* linux accepts.*/struct linux_binfmt {struct list_head lh;struct module *module;int (*load_binary)(struct linux_binprm *);int (*load_shlib)(struct file *);int (*core_dump)(struct coredump_params *cprm);unsigned long min_coredump; /* minimal dump size */};•1•2•3•4• 5• 6• 7• 8• 9• 10• 11• 12linux_binfmt 定义在include/linux/binfmts.h 中linux 支持其他不同格式的可执行程序, 在这种方式下, linux 能运行其他操作系统所编译的程序, 如MS-DOS 程序, 活BSD Unix 的COFF 可执行格式, 因此linux 内核用struct linux_binfmt 来描述各种可执行程序。
ELF文件装载链接过程及hook原理FelixZhangsBlog
ELF⽂件装载链接过程及hook原理FelixZhangsBlog ELF⽂件格式解析可执⾏和可链接格式(Executable and Linkable Format,缩写为ELF),常被称为ELF格式,在计算机科学中,是⼀种⽤于执⾏档、⽬的档、共享库和核⼼转储的标准⽂件格式。
ELF⽂件主要有四种类型:可重定位⽂件(Relocatable File)包含适合于与其他⽬标⽂件链接来创建可执⾏⽂件或者共享⽬标⽂件的代码和数据。
可执⾏⽂件(Executable File)包含适合于执⾏的⼀个程序,此⽂件规定了 exec() 如何创建⼀个程序的进程映像。
共享⽬标⽂件(Shared Object File)包含可在两种上下⽂中链接的代码和数据。
⾸先链接编辑器可以将它和其它可重定位⽂件和共享⽬标⽂件⼀起处理,⽣成另外⼀个⽬标⽂件。
其次,动态链接器(Dynamic Linker)可能将它与某个可执⾏⽂件以及其它共享⽬标⼀起组合,创建进程映像。
以⼀个简单的⽬标⽂件为例:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17#include <stdio.h>int global_init_var = 84;int global_uninit_var;void func1(int i){printf("%d\n",i);}int main(void){char *str = "hello";static int static_var = 85;static int static_var2;int a = 1;int b;func1(static_var + static_var2 + a + b); return 0;}1gcc -c SimpleSection.cELF⽂件结构链接视图和执⾏视图ELF⽂件在磁盘中和被加载到内存中并不是完全⼀样的,ELF⽂件提供了两种视图来反映这两种情况:链接视图和执⾏视图。
(转载)GCC_ELF:程序的启动阶段动态链接器都干了些什么?
(转载)GCC_ELF:程序的启动阶段动态链接器都干了些什么?转自:/blog-1403-3083.html一个动态链接的可执行文件包含了所有的链接器信息。
运行时动态链接器要重定位文件,并解析未定义的符号。
•文件的.dynsym节,即动态符号表,包含了一个文件导入(imported)或导出(exported)的所有的符号。
•文件的.dynstr节,即动态符号字符串表,包含了上述符号的名字字符串。
•文件的.hash(或是.gnu.hash)节,包含的是一个哈希表,连接器可以用它更快的查找符号。
•文件的.dynamic节,也即DYNAMIC段(segment),包含的是动态链接器所需的文件的信息。
这个段是包含在数据段里的,但ELF程序头表(program header table)包含到它的链接。
所以链接器可以很快找到它。
.dynamic节也是一个列表,里面包含一个数值、一个指针、加上这个条目的类型标签。
这些类型标签有的只在可执行文件里有,有的只在库里有,有的则是共有的。
•NEEDED: 这个文件所依赖的库名。
(通常可执行文件都有,有时库也有。
可以多次出现)•SONAME: "shared object name", 链接器要用到的文件的名字(库中才有)。
•SYMTAB, STRTAB, HASH, SYMENT, STRSZ,: point to the symbol table, associated string and hash tables, size of a symbol table entry, size of string table. (共有的)•PLTGOT: points to the GOT, or on some architectures to the PLT (共有的)•REL, RELSZ, and RELENT or RELA, RELASZ, and RELAENT: pointer to, number of, and size of relocation entries. REL entries don't contain addends, RELA entries do. (Both.)•JMPREL, PLTRELSZ, and PLTREL: pointer to, size, and format (REL or RELA) of relocation table for data referred to by the PLT. (Both.)•INIT and FINI: pointer to initializer and finalizer routines to be called at program startup and finish. (Optional but usual in both.)•A few other obscure types not often used.一个程序的结构也可以如图这样描述,先是头,然后是只读区,然后是读写区,最后是BSS区。
编译链接加载
编译链接和加载2011-12-04 10:08:51| 分类:技术专题 | 标签:编译链接加载 |字号订阅作者:phylips@bmy 2011-11-06出处:/blog/static/7097176720111141085197/ 1. 序最近在折腾各种.so,碰到了一些问题,一开始对于很多错误也没有头绪,茫然不知所措。
索性化了一天多时间将<<程序员的自我修养—链接、装载与库>>中部分内容略读了一遍,主要是关于编译,链接和加载这块的。
于是顺便做个笔记,方便以后回顾。
基本上知道了这些,对于编译,链接和加载过程中产生的各种问题,应该就能从根本上理解并解决了。
其实以前上学时也看过那本经典的<<Linker and loader>>,当时还写了篇<<链接器和加载器原理>>,不过此次会更细致深入地了解下整个编译链接和加载过程,并结合经常碰到的问题,提出一些解决方案。
2. 编译和链接2.1. 编译过程广义的代码编译过程,实际上应该细分为:预处理,编译,汇编,链接。
预处理过程,负责头文件展开,宏替换,条件编译的选择,删除注释等工作。
gcc –E表示进行预处理。
编译过程,负载将预处理生成的文件,经过词法分析,语法分析,语义分析及优化后生成汇编文件。
gcc –S表示进行编译。
汇编,是将汇编代码转换为机器可执行指令的过程。
通过使用gcc –C或者as命令完成。
链接,负载根据目标文件及所需的库文件产生最终的可执行文件。
链接主要解决了模块间的相互引用的问题,分为地址和空间分配,符号解析和重定位几个步骤。
实际上在编译阶段生成目标文件时,会暂时搁置那些外部引用,而这些外部引用就是在链接时进行确定的。
链接器在链接时,会根据符号名称去相应模块中寻找对应符号。
待符号确定之后,链接器会重写之前那些未确定的符号的地址,这个过程就是重定位。
2.1.1. 相关选项-WL:这个选项可以将指定的参数传递给链接器。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
ELF 文件的加载和动态链接过程本文的目的:大家对于Hello World 程序应该非常熟悉,随便使用哪一种语言,即使还不熟悉的语言,写出一个Hello World 程序应该毫不费力,但是如果让大家详细的说明这个程序加载和链接的过程,以及后续的符号动态解析过程,可能还会有点困难。
本文就是以一个最基本的C 语言版本Hello World 程序为基础,了解Linux 下ELF 文件的格式,分析并验证ELF 文件和加载和动态链接的具有实现。
本文的实验平台:Ubuntu 7.04Linux kernel 2.6.20gcc 4.1.2glibc 2.5gdb 6.6objdump/readelf 2.17.50本文的组织:第一部分大致描述ELF 文件的格式;第二部分分析ELF 文件在内核空间的加载过程;第三部分分析ELF 文件在运行过程中符号的动态解析过程;(以上各部分都是以Hello World 程序为例说明)第四部分简要总结;第五部分阐明需要深入了解的东西。
一、 ELF 文件格式1. 概述Executable and Linking Format(ELF)文件是x86 Linux 系统下的一种常用目标文件(object file)格式,有三种主要类型:1) 适于连接的可重定位文件(relocatable file),可与其它目标文件一起创建可执行文件和共享目标文件。
2) 适于执行的可执行文件(executable file),用于提供程序的进程映像,加载的内存执行。
3) 共享目标文件(shared object file),连接器可将它与其它可重定位文件和共享目标文件连接成其它的目标文件,动态连接器又可将它与可执行文件和其它共享目标文件结合起来创建一个进程映像。
ELF文件格式比较复杂,本文只是简要介绍它的结构,希望能给想了解ELF文件结构的读者以帮助。
具体详尽的资料请参阅专门的ELF文档。
2.文件格式为了方便和高效,ELF文件内容有两个平行的视角:一个是程序连接角度,另一个是程序运行角度,如图所示。
header在文件开始处描述了整个文件的组织,Section提供了目标文件的各项信息 ELF(如指令、数据、符号表、重定位信息等),Program header table指出怎样创建进程映像,含有每个program header的入口,section header table包含每一个section的入口,给出名字、大小等信息。
3.数据表示ELF数据编码顺序与机器相关,数据类型有六种,见下表:4.ELF文件头象bmp、exe等文件一样,ELF的文件头包含整个文件的控制结构。
它的定义如下:其中E_ident的16个字节标明是个ELF文件(7F+'E'+'L'+'F')。
e_type表示文件类型,2表示可执行文件。
e_machine说明机器类别,3表示386机器,8表示MIPS机器。
e_entry 给出进程开始的虚地址,即系统将控制转移的位置。
e_phoff指出program header table的文件偏移,e_phentsize表示一个program header表中的入口的长度(字节数表示),e_phnum 给出program header表中的入口数目。
类似的,e_shoff,e_shentsize,e_shnum 分别表示section header表的文件偏移,表中每个入口的的字节数和入口数目。
e_flags给出与处理器相关的标志,e_ehsize给出ELF文件头的长度(字节数表示)。
e_shstrndx表示section名表的位置,指出在section header表中的索引。
5.Section Header目标文件的section header table可以定位所有的section,它是一个Elf32_Shdr结构的数组,Section头表的索引是这个数组的下标。
有些索引号是保留的,目标文件不能使用这些特殊的索引。
Section包含目标文件除了ELF文件头、程序头表、section头表的所有信息,而且目标文件section满足几个条件:1)目标文件中的每个section都只有一个section头项描述,可以存在不指示任何section的section头项。
2)每个section在文件中占据一块连续的空间。
3)Section之间不可重叠。
4)目标文件可以有非活动空间,各种headers和sections没有覆盖目标文件的每一个字节,这些非活动空间是没有定义的。
Sectionheader结构定义如下:其中sh_name 指出section 的名字,它的值是后面将会讲到的section header string table 中的索引,指出一个以null 结尾的字符串。
sh_type 是类别,sh_flags 指示该section 在进程执行时的特性。
sh_addr 指出若此section 在进程的内存映像中出现,则给出开始的虚地址。
sh_offset 给出此section 在文件中的偏移。
其它字段的意义不太常用,在此不细述。
文件的section 含有程序和控制信息,系统使用一些特定的section ,并有其固定的类型和属性(由sh_type 和sh_info 指出)。
下面介绍几个常用到的section:“.bss ”段含有占据程序内存映像的未初始化数据,当程序开始运行时系统对这段数据初始为零,但这个section 并不占文件空间。
“.data.”和“.data1”段包含占据内存映像的初始化数据。
“.rodata ”和“.rodata1”段含程序映像中的只读数据。
“.shstrtab ”段含有每个section 的名字,由section 入口结构中的sh_name 索引。
“.strtab ”段含有表示符号表(symbol table)名字的字符串。
“.symtab ”段含有文件的符号表,在后文专门介绍。
“.text ”段包含程序的可执行指令。
当然一个实际的ELF 文件中,会包含很多的section ,如.got ,.plt 等等,我们这里就不一一细述了,需要时再详细的说明。
6. Program Header目标文件或者共享文件的program header table 描述了系统执行一个程序所需要的段或者其它信息。
目标文件的一个段(segment )包含一个或者多个section 。
Program header 只对可执行文件和共享目标文件有意义,对于程序的链接没有任何意义。
结构定义如下:其中p_type 描述段的类型;p_offset 给出该段相对于文件开关的偏移量;p_vaddr给出该段所在的虚拟地址;p_paddr给出该段的物理地址,在Linux x86内核中,这项并没有被使用;p_filesz给出该段的大小,在字节为单元,可能为0;p_memsz给出该段在内存中所占的大小,可能为0;p_filesze与p_memsz的值可能会不相等。
7.Symbol Table目标文件的符号表包含定位或重定位程序符号定义和引用时所需要的信息。
符号表入口结构定义如下:其中st_name包含指向符号表字符串表(strtab)中的索引,从而可以获得符号名。
st_value 指出符号的值,可能是一个绝对值、地址等。
st_size指出符号相关的内存大小,比如一个数据结构包含的字节数等。
st_info规定了符号的类型和绑定属性,指出这个符号是一个数据名、函数名、section名还是源文件名;并且指出该符号的绑定属性是local、global还是weak。
8.Section和Segment的区别和联系可执行文件中,一个program header描述的内容称为一个段(segment)。
Segment包含一个或者多个section,我们以Hello World程序为例,看一下section与segment的映射关系:如上图红色区域所示,就是我们经常提到的文本段和数据段,由图中绿色部分的映射关系可知,文本段并不仅仅包含.text节,数据段也不仅仅包含.data节,而是都包含了多个section。
二、 ELF文件的加载过程1.加载和动态链接的简要介绍从编译/链接和运行的角度看,应用程序和库程序的连接有两种方式。
一种是固定的、静态的连接,就是把需要用到的库函数的目标代码(二进制)代码从程序库中抽取出来,链接进应用软件的目标映像中;另一种是动态链接,是指库函数的代码并不进入应用软件的目标映像,应用软件在编译/链接阶段并不完成跟库函数的链接,而是把函数库的映像也交给用户,到启动应用软件目标映像运行时才把程序库的映像也装入用户空间(并加以定位),再完成应用软件与库函数的连接。
这样,就有了两种不同的ELF格式映像。
一种是静态链接的,在装入/启动其运行时无需装入函数库映像、也无需进行动态连接。
另一种是动态连接,需要在装入/启动其运行时同时装入函数库映像并进行动态链接。
Linux内核既支持静态链接的ELF映像,也支持动态链接的ELF映像,而且装入/启动ELF映像必需由内核完成,而动态连接的实现则既可以在内核中完成,也可在用户空间完成。
因此,GNU把对于动态链接ELF映像的支持作了分工:把ELF映像的装入/启动入在Linux内核中;而把动态链接的实现放在用户空间(glibc),并为此提供一个称为“解释器”(ld-linux.so.2)的工具软件,而解释器的装入/启动也由内核负责,这在后面我们分析ELF文件的加载时就可以看到。
这部分主要说明ELF文件在内核空间的加载过程,下一部分对用户空间符号的动态解析过程进行说明。
2.Linux可执行文件类型的注册机制在说明ELF文件的加载过程以前,我们先回答一个问题,就是:为什么Linux可以运行ELF文件?回答:内核对所支持的每种可执行的程序类型都有个struct linux_binfmt的数据结构,定义如下:其中的load_binary函数指针指向的就是一个可执行程序的处理函数。
而我们研究的ELF 文件格式的定义如下:要支持ELF文件的运行,则必须向内核登记这个数据结构,加入到内核支持的可执行程序的队列中。
内核提供两个函数来完成这个功能,一个注册,一个注销,即:当需要运行一个程序时,则扫描这个队列,让各个数据结构所提供的处理程序,ELF 中即为load_elf_binary,逐一前来认领,如果某个格式的处理程序发现相符后,便执行该格式映像的装入和启动。
3.内核空间的加载过程内核中实际执行execv()或execve()系统调用的程序是do_execve(),这个函数先打开目标映像文件,并从目标文件的头部(第一个字节开始)读入若干(当前Linux内核中是128)字节(实际上就是填充ELF文件头,下面的分析可以看到),然后调用另一个函数search_binary_handler(),在此函数里面,它会搜索我们上面提到的Linux支持的可执行文件类型队列,让各种可执行程序的处理程序前来认领和处理。