ELF文件加载

一、ELF相关的背景知识
1. ELF格式文件相关概念
ELF格式文件主要包括以下三种类型的文件:

  • 可重定位的目标文件(.o文件) --> 用于链接生成可执行文件或动态链接库文件(.so)
  • 可执行文件 --> 进程映像文件
  • 共享库(.so文件) --> 用于链接
从链接和执行的角度来讲,ELF文件存在两种视图:链接视图和执行视图。为了区分两种视图,只需记住链接视图由多个section组成,而执行视图由多个segment组成即可。另外,section是程序员可见的,是给链接器使用的概念,汇编文件中通常会显示的定义.text,.data等section,相反,segment是程序员不可见的,是给加载器使用的概念。下图形象的描述了ELF文件两种不同的视图的结构以及二者之间的联系。
elf文件加载
二者之间的联系在于:一个segment包含一个或多个section。
注意:Section Header Table和Program Header Table并不是一定要位于文件的开头和结尾,其位置由ELF Header指出,上图这么画只是为了清晰。-- ELF文件每个部分的详细介绍参见《ELF 文件格式分析》。 elf文件加载TN05.ELF.Format.Summary.pdf

2. ELF文件主要数据结构
上面讲了ELF的相关概念,但是要想用计算机语言(C语言)来实现,必须对应相应的数据结构。linux下通过三个数据结构描述了ELF文件的相关概念。
(1) ELF Header
ELF Header描述了体系结构和操作系统等基本信息,并指出Section Header Table和Program Header Table在文件中的什么位置,每个成员的解释参见注释及附件。

  1. #define EI_NIDENT 16
  2. typedef struct{
  3. /*ELF的一些标识信息,固定值*/
  4. unsigned char e_ident[EI_NIDENT];
  5. /*目标文件类型:1-可重定位文件,2-可执行文件,3-共享目标文件等*/
  6. Elf32_Half e_type;
  7. /*文件的目标体系结构类型:3-intel 80386*/
  8. Elf32_Half e_machine;
  9. /*目标文件版本:1-当前版本*/
  10. Elf32_Word e_version;
  11. /*程序入口的虚拟地址,如果没有入口,可为0*/
  12. Elf32_Addr e_entry;
  13. /*程序头表(segment header table)的偏移量,如果没有,可为0*/
  14. Elf32_Off e_phoff;
  15. /*节区头表(section header table)的偏移量,没有可为0*/
  16. Elf32_Off e_shoff;
  17. /*与文件相关的,特定于处理器的标志*/
  18. Elf32_Word e_flags;
  19. /*ELF头部的大小,单位字节*/
  20. Elf32_Half e_ehsize;
  21. /*程序头表每个表项的大小,单位字节*/
  22. Elf32_Half e_phentsize;
  23. /*程序头表表项的个数*/
  24. Elf32_Half e_phnum;
  25. /*节区头表每个表项的大小,单位字节*/
  26. Elf32_Half e_shentsize;
  27. /*节区头表表项的数目*/
  28. Elf32_Half e_shnum;
  29. /**/
  30. Elf32_Half e_shstrndx;
  31. }Elf32_Ehdr;
下面通过一个具体实例来说明ELF header中每个数据成员对应的值,下面是hello world的ELF文件头,在linux下可以通过"readelf -h ELF文件名"来获得。
elf文件加载
ELF Header用数据结构Elf32_Ehdr来表示,描述了操作系统是UNIX,体系结构是80386。Section Header Table中有30个Section Header,从文件地址4412开始,每个Section Header占40字节,Segment Header Table中有9个segment,每个segment header占32个字节,此ELF文件的类型是可执行文件(EXEC),入口地址是0x8048320。

(2) Section Header Table Entry
从ELF Header中可知,每个ELF文件有个Section Header Table,其中每一个表项对应一个section,由数据结构Elf32_Shdr来描述,每个成员的含义参见注释及附件。在linux下可以通过"readelf -S ELF文件名"来查看。
  1. typedef struct{
  2. /*节区名称*/
  3. Elf32_Word sh_name;
  4. /*节区类型:PROGBITS-程序定义的信息,NOBITS-不占用文件空间(bss),REL-重定位表项*/
  5. Elf32_Word sh_type;
  6. /*每一bit位代表一种信息,表示节区内的内容是否可以修改,是否可执行等信息*/
  7. Elf32_Word sh_flags;
  8. /*如果节区将出现在进程的内存影响中,此成员给出节区的第一个字节应处的位置*/
  9. Elf32_Addr sh_addr;
  10. /*节区的第一个字节与文件头之间的偏移*/
  11. Elf32_Off sh_offset;
  12. /*节区的长度,单位字节,NOBITS虽然这个值非0但不占文件中的空间*/
  13. Elf32_Word sh_size;
  14. /*节区头部表索引链接*/
  15. Elf32_Word sh_link;
  16. /*节区附加信息*/
  17. Elf32_Word sh_info;
  18. /*节区带有地址对齐的约束*/
  19. Elf32_Word sh_addralign;
  20. /*某些节区中包含固定大小的项目,如符号表,那么这个成员给出其固定大小*/
  21. Elf32_Word sh_entsize;
  22. }Elf32_Shdr;
(3) Program Header Table Entry
从ELF Header中可知,每个ELF文件有个Program Header Table,其中每一个表项对应一个segment,由数据结构Elf32_phdr来描述,每个成员的含义参见注释及附件。在linux下可以通过"readelf -l ELF文件名"来查看。
  1. typedef struct
  2. {
  3. /*segment的类型:PT_LOAD= 1 可加载的段*/
  4. Elf32_Word p_type;
  5. /*从文件头到该段第一个字节的偏移*/
  6. Elf32_Off p_offset;
  7. /*该段第一个字节被放到内存中的虚拟地址*/
  8. Elf32_Addr p_vaddr;
  9. /*在linux中这个成员没有任何意义,值与p_vaddr相同*/
  10. Elf32_Addr p_paddr;
  11. /*该段在文件映像中所占的字节数*/
  12. Elf32_Word p_filesz;
  13. /*该段在内存映像中占用的字节数*/
  14. Elf32_Word p_memsz;
  15. /*段标志*/
  16. Elf32_Word p_flags;
  17. /*p_vaddr是否对齐*/
  18. Elf32_Word p_align;
  19. } Elf32_phdr;
二、qemu中ELF文件的加载过程
在了解了ELF文件的基本结构之后,大体可以想到ELF文件的加载过程就是一个查表的过程,即通过ELF Header得到ELF文件的基本信息-Section Header Table和program Header Table,然后再根据Section Header Table和program Header Table的信息加载ELF文件中的相应部分。上面也提到过,section是从链接器的角度来讲的概念,所以,ELF文件的加载过程中,只有segment是有效的,加载器根据program Header Table中的信息来负责ELF文件的加载。
首先,从感性上认识一下segment,还是以上面的hello world为例,其对应的program header table如下。
elf文件加载
第一列type即每个segment的类型,每个类型的具体含义参见附件。通常我们之关心程序的代码段(.text section)和数据段(.date section),这两个section组成LOAD类型的segment。
Offset:当前segment加载到的地址的偏移
VirAddr:当前segment加载到的虚拟地址
PhysAddr:当前segment加载到的物理地址(x86平台上,此值没有意义,并不指物理地址)
FileSiz:当前segment在ELF文件中的偏移
MemSiz:当前segment在内存页中的偏移
Flg:segment的权限,R-可读,W-可写, E-可执行
Align:x86平台内存页面的大小

在了解了segment的相关信息后,分析下qemu代码中ELF文件的加载过程,印证下上面提到的ELF文件的加载的思想。


  1. ret =loader_exec(filename,target_argv, target_environ,regs,info,&bprm);
filename:要加载的ELF文件的名称
target_argv:qemu运行的参数,在这里即hello(hello是生成的可执行文件名, $qemu hello)
target_environ:执行qemu的shell的环境变量
regs,info,bprm是ELF文件加载过程中涉及的三个重要数据结构,下面会详细分析。

loader_exec函数的功能及含义参见代码注释。
  1. int loader_exec(constchar * filename,char **argv, char **envp,
  2. struct target_pt_regs* regs,struct image_info *infop,
  3. struct linux_binprm*bprm)
  4. {
  5. int retval;
  6. int i;

  7. bprm->p= TARGET_PAGE_SIZE*MAX_ARG_PAGES-sizeof(unsignedint);/*MAX_ARG_PAGES= 33*/
  8. memset(bprm->page,0, sizeof(bprm->page));
  9. retval = open(filename,O_RDONLY);/*返回打开文件的fd*/
  10. if (retval< 0)
  11. return retval;
  12. bprm->fd= retval;
  13. bprm->filename= (char*)filename;
  14. bprm->argc= count(argv);
  15. bprm->argv= argv;
  16. bprm->envc= count(envp);
  17. bprm->envp= envp;
  18. /*1.要加载文件的属性判断:是否常规文件,是否可执行文件,是否ELF文件; 2.读取ELF文件的前1024个字节*/
  19. retval = prepare_binprm(bprm);

  20. if(retval>=0){ /*prepare_binrpm函数已经读出了目标文件的前1024个字节,先判断下这个文件是否是ELF文件,即前4个字节*/
  21. if(bprm->buf[0]==0x7f
  22. &&bprm->buf[1]=='E'
  23. &&bprm->buf[2]=='L'
  24. &&bprm->buf[3]=='F'){
  25. retval =load_elf_binary(bprm,regs, infop);
  26. #if defined(TARGET_HAS_BFLT)
  27. }else if(bprm->buf[0]=='b'
  28. &&bprm->buf[1]=='F'
  29. &&bprm->buf[2]=='L'
  30. &&bprm->buf[3]=='T'){
  31. retval= load_flt_binary(bprm,regs,infop);
  32. #endif
  33. }else {
  34. fprintf(stderr,"Unknown binary format\n");
  35. return -1;
  36. }
  37. }

  38. if(retval>=0){
  39. /*success. Initialize important registers*/
  40. do_init_thread(regs,infop);
  41. return retval;
  42. }

  43. /*Something went wrong, return the inodeand free the argument pages*/
  44. for (i=0; i<MAX_ARG_PAGES; i++){
  45. g_free(bprm->page[i]);
  46. }
  47. return(retval);
  48. }

  1. int load_elf_binary(struct linux_binprm* bprm,struct target_pt_regs * regs,
  2. struct image_info* info)
  3. {
  4. struct image_info interp_info;
  5. struct elfhdr elf_ex;
  6. char *elf_interpreter= NULL;

  7. info->start_mmap= (abi_ulong)ELF_START_MMAP;/*ELF_START_MMAP= 0x80000000*/
  8. info->mmap= 0;
  9. info->rss= 0;
  10. /*主要工作就是初始化info,申请进程虚拟地址空间,将ELF文件映射到这段虚拟地址空间上*/
  11. load_elf_image(bprm->filename,bprm->fd,info,
  12. &elf_interpreter,bprm->buf);

... ... ... ...

  1. return 0;
  2. }

  1. static void load_elf_image(constchar *image_name,int image_fd,
  2. struct image_info*info,char **pinterp_name,
  3. char bprm_buf[BPRM_BUF_SIZE])
  4. {
  5. struct elfhdr *ehdr= (struct elfhdr*)bprm_buf;
  6. struct elf_phdr *phdr;
  7. abi_ulong load_addr, load_bias, loaddr,hiaddr, error;
  8. int i,retval;
  9. const char *errmsg;

  10. /*First of all, some simple consistency checks*/
  11. errmsg = "Invalid ELF image for this architecture";
  12. if (!elf_check_ident(ehdr)){/*ELF头检查*/
  13. goto exit_errmsg;
  14. }
  15. bswap_ehdr(ehdr);/*当前为空,是不是主机和目标机大小尾端不一致时才会swap*/
  16. if (!elf_check_ehdr(ehdr)){
  17. goto exit_errmsg;
  18. }
  19. /*下面的代码即读出ELF文件的程序头表,首先判断下是否已经被完全读出*/
  20. i = ehdr->e_phnum* sizeof(struct elf_phdr);/*program header 表的大小*/
  21. if (ehdr->e_phoff+ i <=BPRM_BUF_SIZE) {
  22. phdr =(struct elf_phdr *)(bprm_buf+ ehdr->e_phoff);
  23. } else{
  24. phdr =(struct elf_phdr *)alloca(i);/*申请i个程序头部*/
  25. retval =pread(image_fd,phdr, i,ehdr->e_phoff);/*从文件image_id的偏移为ehdr->e_phoff处读取i个字节到phdr中,即phdr存放program header*/
  26. if(retval !=i) {
  27. goto exit_read;
  28. }
  29. }
  30. bswap_phdr(phdr,ehdr->e_phnum);

  31. #ifdef CONFIG_USE_FDPIC
  32. info->nsegs= 0;
  33. info->pt_dynamic_addr= 0;
  34. #endif

  35. /*Find the maximum size of the image andallocate an appropriate
  36. amount of memory tohandle that. */
  37. loaddr = -1,hiaddr = 0;
  38. for (i= 0;i < ehdr->e_phnum;++i){/*遍历每一个program header*/
  39. if(phdr[i].p_type==PT_LOAD) {
  40. abi_ulong a =phdr[i].p_vaddr;
  41. if(a <loaddr) {/*loaddr= -1而且是unsigned 类型的,所以loaddr是个很大的数*/
  42. loaddr= a;/*loaddr记录segment的起始地址*/
  43. }
  44. a +=phdr[i].p_memsz;/*这个segment在内存中的偏移地址*/
  45. if(a >hiaddr) {/*hiaddr记录segment的结束地址*/
  46. hiaddr= a;
  47. }
  48. #ifdef CONFIG_USE_FDPIC
  49. ++info->nsegs;
  50. #endif
  51. }
  52. }

  53. load_addr = loaddr; /*计算出来的需要加载的起始地址*/
  54. if (ehdr->e_type==ET_DYN) {/*共享目标文件(.so)*/
  55. /*The image indicates that it can be loaded anywhere.Find a
  56. locationthat can hold the memory space required. Ifthe
  57. image ispre-linked,LOADDR will be non-zero.Since we do
  58. notsupply MAP_FIXED here we'll use that addressif and
  59. only ifit remains available. */
  60. load_addr =target_mmap(loaddr,hiaddr - loaddr,PROT_NONE,
  61. MAP_PRIVATE| MAP_ANON |MAP_NORESERVE,
  62. -1,0);
  63. if(load_addr ==-1){
  64. goto exit_perror;
  65. }
  66. } elseif (pinterp_name!=NULL){
  67. /*This is the main executable.Make sure that the low
  68. address does notconflict with MMAP_MIN_ADDR or the
  69. QEMU application itself.*/
  70. probe_guest_base(image_name,loaddr, hiaddr);
  71. }
  72. load_bias = load_addr - loaddr;

  73. #ifdef CONFIG_USE_FDPIC
  74. {
  75. struct elf32_fdpic_loadseg *loadsegs= info->loadsegs=
  76. g_malloc(sizeof(*loadsegs)* info->nsegs);

  77. for(i =0; i <ehdr->e_phnum;++i){
  78. switch (phdr[i].p_type){
  79. casePT_DYNAMIC:
  80. info->pt_dynamic_addr= phdr[i].p_vaddr+ load_bias;
  81. break;
  82. casePT_LOAD:
  83. loadsegs->addr= phdr[i].p_vaddr+ load_bias;
  84. loadsegs->p_vaddr= phdr[i].p_vaddr;
  85. loadsegs->p_memsz= phdr[i].p_memsz;
  86. ++loadsegs;
  87. break;
  88. }
  89. }
  90. }
  91. #endif

  92. info->load_bias= load_bias;/*真实的加载地址和计算出来(读ELF头信息)的加载地址之差*/
  93. info->load_addr= load_addr;/*真实的加载地址*/
  94. info->entry= ehdr->e_entry+ load_bias;/*重新调整下程序的入口*/
  95. info->start_code= -1;
  96. info->end_code= 0;
  97. info->start_data= -1;
  98. info->end_data= 0;
  99. info->brk= 0;

  100. for (i= 0;i < ehdr->e_phnum;i++){
  101. struct elf_phdr *eppnt= phdr +i;
  102. if(eppnt->p_type==PT_LOAD) {
  103. abi_ulong vaddr,vaddr_po, vaddr_ps,vaddr_ef, vaddr_em;
  104. intelf_prot = 0;
  105. /*记录PT_LOAD类型segment的权限:读//可执行*/
  106. if(eppnt->p_flags& PF_R)elf_prot = PROT_READ;
  107. if(eppnt->p_flags& PF_W)elf_prot |=PROT_WRITE;
  108. if(eppnt->p_flags& PF_X)elf_prot |=PROT_EXEC;

  109. vaddr =load_bias + eppnt->p_vaddr;
  110. vaddr_po =TARGET_ELF_PAGEOFFSET(vaddr);/*((vaddr)& ((1<<12)-1)),目的是取页内偏移*/
  111. vaddr_ps =TARGET_ELF_PAGESTART(vaddr);/*((vaddr)& ~(unsigned long)((1<<12)-1)),向下页对齐,目的取页对齐的地址*/
  112. /*将ELF文件映射到进程地址空间中*/
  113. error= target_mmap(vaddr_ps,eppnt->p_filesz+ vaddr_po,/*映射的时候从页内偏移vaddr_po开始映射,即保持原来的偏移量*/
  114. elf_prot,MAP_PRIVATE | MAP_FIXED,
  115. image_fd,eppnt->p_offset- vaddr_po);
  116. if(error==-1){
  117. goto exit_perror;
  118. }

  119. vaddr_ef =vaddr + eppnt->p_filesz;
  120. vaddr_em =vaddr + eppnt->p_memsz;

  121. /*If the load segment requests extra zeros(e.g.bss),map it. */
  122. if(vaddr_ef <vaddr_em) {
  123. zero_bss(vaddr_ef,vaddr_em, elf_prot);
  124. }

  125. /*Find the full program boundaries. */
  126. if(elf_prot &PROT_EXEC) {
  127. if(vaddr <info->start_code){
  128. info->start_code= vaddr;/*代码段的起始虚拟地址(页对齐的地址)*/
  129. }
  130. if(vaddr_ef >info->end_code){
  131. info->end_code= vaddr_ef;/*代码段的结束虚拟地址(页对齐的地址)*/
  132. }
  133. }
  134. if(elf_prot &PROT_WRITE) {
  135. if(vaddr <info->start_data){
  136. info->start_data= vaddr;/*数据段的起始虚拟地址*/
  137. }
  138. if(vaddr_ef >info->end_data){
  139. info->end_data= vaddr_ef;/*数据段的起始虚拟地址(包括bss段的大小)*/
  140. }
  141. if(vaddr_em >info->brk){
  142. info->brk= vaddr_em;/*程序内存映像的顶端(代码段+数据段+bss段)*/
  143. }
  144. }
  145. }else if(eppnt->p_type==PT_INTERP &&pinterp_name) {/*内部解释程序名称:/lib/ld-linux.so.2*/
  146. char *interp_name;

  147. if(*pinterp_name){
  148. errmsg= "Multiple PT_INTERP entries";
  149. goto exit_errmsg;
  150. }
  151. interp_name =malloc(eppnt->p_filesz);
  152. if(!interp_name){
  153. goto exit_perror;
  154. }

  155. if(eppnt->p_offset+ eppnt->p_filesz<=BPRM_BUF_SIZE) {
  156. memcpy(interp_name,bprm_buf + eppnt->p_offset,
  157. eppnt->p_filesz);
  158. }else {
  159. retval= pread(image_fd,interp_name, eppnt->p_filesz,
  160. eppnt->p_offset);
  161. if(retval !=eppnt->p_filesz){
  162. goto exit_perror;
  163. }
  164. }
  165. if(interp_name[eppnt->p_filesz- 1]!=0) {
  166. errmsg= "Invalid PT_INTERP entry";
  167. goto exit_errmsg;
  168. }
  169. *pinterp_name= interp_name;
  170. }
  171. }

  172. if (info->end_data==0) {
  173. info->start_data= info->end_code;
  174. info->end_data= info->end_code;
  175. info->brk= info->end_code;
  176. }

  177. if (qemu_log_enabled()){
  178. load_symbols(ehdr,image_fd, load_bias);
  179. }

  180. close(image_fd);
  181. return;

  182. exit_read:
  183. if (retval>=0) {
  184. errmsg ="Incomplete read of file header";
  185. goto exit_errmsg;
  186. }
  187. exit_perror:
  188. errmsg = strerror(errno);
  189. exit_errmsg:
  190. fprintf(stderr,"%s: %s\n",image_name, errmsg);
  191. exit(-1);
  192. }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值