bin和elf文件(ARM) 五

2023-10-31

原文地址:http://www.iteye.com/topic/1121480

近段时间在研究Erlang核心特性的实现,也许过段时间会有个系列的总结,期待...

 

今天看到有人写一个深入Hello World的文章,想起来读研的时候做的一个关于程序加载和链接的课程设计,也是以Hello World为例说明的,随发出来共享。文后有下载链接。

 

======================================================

 

本文的目的:大家对于Hello World程序应该非常熟悉,随便使用哪一种语言,即使还不熟悉的语言,写出一个Hello World程序应该毫不费力,但是如果让大家详细的说明这个程序加载和链接的过程,以及后续的符号动态解析过程,可能还会有点困难。本文就是以一个最基本的C语言版本Hello World程序为基础,了解Linux下ELF文件的格式,分析并验证ELF文件和加载和动态链接的具有实现。

 

C代码   收藏代码
  1. /* hello.c */  
  2. #include <stdio.h>  
  3.   
  4. int main()  
  5. {  
  6.     printf(“hello world!\n”);  
  7.     return 0;  
  8. }  
  9. $ gcc –o hello hello.c  

 本文的实验平台:

 

<!--[if !supportLists]-->Ø  <!--[endif]-->Ubuntu 7.04

<!--[if !supportLists]-->Ø  <!--[endif]-->Linux kernel 2.6.20

<!--[if !supportLists]-->Ø  <!--[endif]-->gcc 4.1.2

<!--[if !supportLists]-->Ø  <!--[endif]-->glibc 2.5

<!--[if !supportLists]-->Ø  <!--[endif]-->gdb 6.6

<!--[if !supportLists]-->Ø  <!--[endif]-->objdump/readelf 2.17.50

本文的组织:

       第一部分大致描述ELF文件的格式;

       第二部分分析ELF文件在内核空间的加载过程;

       第三部分分析ELF文件在运行过程中符号的动态解析过程;

       (以上各部分都是以Hello World程序为例说明)

       第四部分简要总结;

       第五部分阐明需要深入了解的东西。

 

 ELF文件格式

概述

       Executable and Linking Format(ELF)文件是x86 Linux系统下的一种常用目标文件(object file)格式,有三种主要类型:

<!--[if !supportLists]-->1)        <!--[endif]-->适于连接的可重定位文件(relocatable file),可与其它目标文件一起创建可执行文件和共享目标文件。

<!--[if !supportLists]-->2)        <!--[endif]-->适于执行的可执行文件(executable file),用于提供程序的进程映像,加载的内存执行。

<!--[if !supportLists]-->3)        <!--[endif]-->共享目标文件(shared object file),连接器可将它与其它可重定位文件和共享目标文件连接成其它的目标文件,动态连接器又可将它与可执行文件和其它共享目标文件结合起来创建一个进程映像。

       ELF文件格式比较复杂,本文只是简要介绍它的结构,希望能给想了解ELF文件结构的读者以帮助。具体详尽的资料请参阅专门的ELF文档。

   文件格式

       为了方便和高效,ELF文件内容有两个平行的视角:一个是程序连接角度,另一个是程序运行角度,如图所示。

<!--[endif]-->

       ELF header在文件开始处描述了整个文件的组织,Section提供了目标文件的各项信息(如指令、数据、符号表、重定位信息等),Program header table指出怎样创建进程映像,含有每个program header的入口,section header table包含每一个section的入口,给出名字、大小等信息。

    数据表示

       ELF数据编码顺序与机器相关,数据类型有六种,见下表:

      ELF文件头

       象bmp、exe等文件一样,ELF的文件头包含整个文件的控制结构。它的定义如下:

 

C代码   收藏代码
  1. 190 #define EI_NIDENT       16  
  2. 191   
  3. 192 typedef struct elf32_hdr{  
  4. 193   unsigned char e_ident[EI_NIDENT];   
  5. 194   Elf32_Half    e_type;     /* file type */  
  6. 195   Elf32_Half    e_machine;  /* architecture */  
  7. 196   Elf32_Word e_version;  
  8. 197   Elf32_Addr    e_entry;    /* entry point */  
  9. 198   Elf32_Off e_phoff;        /* PH table offset */  
  10. 199   Elf32_Off e_shoff;        /* SH table offset */  
  11. 200   Elf32_Word    e_flags;  
  12. 201   Elf32_Half    e_ehsize;       /* ELF header size in bytes */  
  13. 202   Elf32_Half    e_phentsize;    /* PH size */  
  14. 203   Elf32_Half    e_phnum;        /* PH number */  
  15. 204   Elf32_Half    e_shentsize;    /* SH size */  
  16. 205   Elf32_Half    e_shnum;        /* SH number */  
  17. 206   Elf32_Half    e_shstrndx; /* SH name string table index */  
  18. 207 } Elf32_Ehdr;  

 其中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表中的索引。

 

Section Header

       目标文件的section header table可以定位所有的section,它是一个Elf32_Shdr结构的数组,Section头表的索引是这个数组的下标。有些索引号是保留的,目标文件不能使用这些特殊的索引。

       Section包含目标文件除了ELF文件头、程序头表、section头表的所有信息,而且目标文件section满足几个条件:

<!--[if !supportLists]-->1)        <!--[endif]-->目标文件中的每个section都只有一个section头项描述,可以存在不指示任何section的section头项。

<!--[if !supportLists]-->2)        <!--[endif]-->每个section在文件中占据一块连续的空间。

<!--[if !supportLists]-->3)        <!--[endif]-->Section之间不可重叠。

<!--[if !supportLists]-->4)        <!--[endif]-->目标文件可以有非活动空间,各种headers和sections没有覆盖目标文件的每一个字节,这些非活动空间是没有定义的。

       Section header结构定义如下:

 

C代码   收藏代码
  1. 288 typedef struct {  
  2. 289   Elf32_Word    sh_name;    /* name of section, index */  
  3. 290   Elf32_Word    sh_type;      
  4. 291   Elf32_Word    sh_flags;  
  5. 292   Elf32_Addr     sh_addr;       /* memory address, if any */  
  6. 293   Elf32_Off      sh_offset;  
  7. 294   Elf32_Word    sh_size;        /* section size in file */  
  8. 295   Elf32_Word    sh_link;  
  9. 296   Elf32_Word    sh_info;  
  10. 297   Elf32_Word    sh_addralign;  
  11. 298   Elf32_Word    sh_entsize;     /* fixed entry size, if have */  
  12. 299 } Elf32_Shdr;  

 其中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等等,我们这里就不一一细述了,需要时再详细的说明。

Program Header

       目标文件或者共享文件的program header table描述了系统执行一个程序所需要的段或者其它信息。目标文件的一个段(segment)包含一个或者多个section。Program header只对可执行文件和共享目标文件有意义,对于程序的链接没有任何意义。结构定义如下:

 

C代码   收藏代码
  1. 232 typedef struct elf32_phdr{  
  2. 233   Elf32_Word    p_type;   
  3. 234   Elf32_Off      p_offset;  
  4. 235   Elf32_Addr    p_vaddr;        /* virtual address */  
  5. 236   Elf32_Addr    p_paddr;        /* ignore */  
  6. 237   Elf32_Word    p_filesz;       /* segment size in file */  
  7. 238   Elf32_Word    p_memsz;        /* size in memory */  
  8. 239   Elf32_Word    p_flags;  
  9. 240   Elf32_Word    p_align;       
  10. 241 } Elf32_Phdr;  

 其中p_type描述段的类型;p_offset给出该段相对于文件开关的偏移量;p_vaddr给出该段所在的虚拟地址;p_paddr给出该段的物理地址,在Linux x86内核中,这项并没有被使用;p_filesz给出该段的大小,在字节为单元,可能为0;p_memsz给出该段在内存中所占的大小,可能为0;p_filesze与p_memsz的值可能会不相等。

 

Symbol Table

       目标文件的符号表包含定位或重定位程序符号定义和引用时所需要的信息。符号表入口结构定义如下:

 

C代码   收藏代码
  1. 171 typedef struct elf32_sym{  
  2. 172   Elf32_Word    st_name;  
  3. 173   Elf32_Addr    st_value;  
  4. 174   Elf32_Word    st_size;  
  5. 175   unsigned char     st_info;  
  6. 176   unsigned char st_other;  
  7. 177   Elf32_Half     st_shndx;  
  8. 178 } Elf32_Sym;  

 其中st_name包含指向符号表字符串表(strtab)中的索引,从而可以获得符号名。st_value指出符号的值,可能是一个绝对值、地址等。st_size指出符号相关的内存大小,比如一个数据结构包含的字节数等。st_info规定了符号的类型和绑定属性,指出这个符号是一个数据名、函数名、section名还是源文件名;并且指出该符号的绑定属性是local、global还是weak。

 

Section和Segment的区别和联系

       可执行文件中,一个program header描述的内容称为一个段(segment)。Segment包含一个或者多个section,我们以Hello World程序为例,看一下section与segment的映射关系:

 

 如上图红色区域所示,就是我们经常提到的文本段和数据段,由图中绿色部分的映射关系可知,文本段并不仅仅包含.text,数据段也不仅仅包含.data节,而是都包含了多个section。

ELF文件的加载过程

 

加载和动态链接的简要介绍

       从编译/链接和运行的角度看,应用程序和库程序的连接有两种方式。一种是固定的、静态的连接,就是把需要用到的库函数的目标代码(二进制)代码从程序库中抽取出来,链接进应用软件的目标映像中;另一种是动态链接,是指库函数的代码并不进入应用软件的目标映像,应用软件在编译/链接阶段并不完成跟库函数的链接,而是把函数库的映像也交给用户,到启动应用软件目标映像运行时才把程序库的映像也装入用户空间(并加以定位),再完成应用软件与库函数的连接。

       这样,就有了两种不同的ELF格式映像。一种是静态链接的,在装入/启动其运行时无需装入函数库映像、也无需进行动态连接。另一种是动态连接,需要在装入/启动其运行时同时装入函数库映像并进行动态链接。Linux内核既支持静态链接的ELF映像,也支持动态链接的ELF映像,而且装入/启动ELF映像必需由内核完成,而动态连接的实现则既可以在内核中完成,也可在用户空间完成。因此,GNU把对于动态链接ELF映像的支持作了分工:把ELF映像的装入/启动入在Linux内核中;而把动态链接的实现放在用户空间(glibc),并为此提供一个称为“解释器”(ld-linux.so.2)的工具软件,而解释器的装入/启动也由内核负责,这在后面我们分析ELF文件的加载时就可以看到。

       这部分主要说明ELF文件在内核空间的加载过程,下一部分对用户空间符号的动态解析过程进行说明。

 

Linux可执行文件类型的注册机制

       在说明ELF文件的加载过程以前,我们先回答一个问题,就是:为什么Linux可以运行ELF文件?

       回答:内核对所支持的每种可执行的程序类型都有个struct linux_binfmt的数据结构,定义如下:

 

C代码   收藏代码
  1. 53 /* 
  2. 54  * This structure defines the functions that are used to load the binary formats that 
  3. 55  * linux accepts. 
  4. 56  */  
  5. 57 struct linux_binfmt {  
  6. 58         struct linux_binfmt * next;  
  7. 59         struct module *module;  
  8. 60         int (*load_binary)(struct linux_binprm *, struct  pt_regs * regs);  
  9. 61         int (*load_shlib)(struct file *)  
  10. 62         int (*core_dump)(long signr, struct pt_regs * regs, struct file * file);  
  11. 63         unsigned long min_coredump;     /* minimal dump size */  
  12. 64         int hasvdso;  
  13. 65 };  

 

 其中的load_binary函数指针指向的就是一个可执行程序的处理函数。而我们研究的ELF文件格式的定义如下:

 

C代码   收藏代码
  1. 74 static struct linux_binfmt elf_format = {  
  2. 75                 .module      = THIS_MODULE,  
  3. 76                 .load_binary = load_elf_binary,  
  4. 77                 .load_shlib      = load_elf_library,  
  5. 78                 .core_dump       = elf_core_dump,  
  6. 79                 .min_coredump    = ELF_EXEC_PAGESIZE,  
  7. 80                 .hasvdso     = 1  
  8. 81 };  

 

 要支持ELF文件的运行,则必须向内核登记这个数据结构,加入到内核支持的可执行程序的队列中。内核提供两个函数来完成这个功能,一个注册,一个注销,即:

 

C代码   收藏代码
  1. 72 int register_binfmt(struct linux_binfmt * fmt)  
  2. 96 int unregister_binfmt(struct linux_binfmt * fmt)  

 当需要运行一个程序时,则扫描这个队列,让各个数据结构所提供的处理程序,ELF中即为load_elf_binary,逐一前来认领,如果某个格式的处理程序发现相符后,便执行该格式映像的装入和启动。

 

 

内核空间的加载过程

       内核中实际执行execv()或execve()系统调用的程序是do_execve(),这个函数先打开目标映像文件,并从目标文件的头部(第一个字节开始)读入若干(当前Linux内核中是128)字节(实际上就是填充ELF文件头,下面的分析可以看到),然后调用另一个函数search_binary_handler(),在此函数里面,它会搜索我们上面提到的Linux支持的可执行文件类型队列,让各种可执行程序的处理程序前来认领和处理。如果类型匹配,则调用load_binary函数指针所指向的处理函数来处理目标映像文件。在ELF文件格式中,处理函数是load_elf_binary函数,下面主要就是分析load_elf_binary函数的执行过程(说明:因为内核中实际的加载需要涉及到很多东西,这里只关注跟ELF文件的处理相关的代码):

 

C代码   收藏代码
  1. 550         struct {  
  2. 551                 struct elfhdr elf_ex;  
  3. 552                 struct elfhdr interp_elf_ex;  
  4. 553                 struct exec interp_ex;  
  5. 554         } *loc;  
  6. 556         loc = kmalloc(sizeof(*loc), GFP_KERNEL);  
  7. 562         /* Get the exec-header */  
  8. 563         loc->elf_ex = *((struct elfhdr *)bprm->buf);  
  9.                ……  
  10. 566         /* First of all, some simple consistency checks */  
  11. 567         if (memcmp(loc->elf_ex.e_ident, ELFMAG, SELFMAG) != 0)  
  12. 568                 goto out;  
  13. 570         if (loc->elf_ex.e_type != ET_EXEC && loc->elf_ex.e_type != ET_DYN)  
  14. 571                 goto out;  

 

 在load_elf_binary之前,内核已经使用映像文件的前128个字节对bprm->buf进行了填充,563行就是使用这此信息填充映像的文件头(具体数据结构定义见第一部分,ELF文件头节),然后567行就是比较文件头的前四个字节,查看是否是ELF文件类型定义的“\177ELF”。除这4个字符以外,还要看映像的类型是否ET_EXEC和ET_DYN之一;前者表示可执行映像,后者表示共享库。

 

C代码   收藏代码
  1. 577         /* Now read in all of the header information */  
  2. 580         if (loc->elf_ex.e_phnum < 1 ||  
  3. 581                 loc->elf_ex.e_phnum > 65536U / sizeof(struct elf_phdr))  
  4. 582                 goto out;  
  5. 583         size = loc->elf_ex.e_phnum * sizeof(struct elf_phdr);  
  6.                ……  
  7. 585         elf_phdata = kmalloc(size, GFP_KERNEL);  
  8.                ……  
  9. 589         retval = kernel_read(bprm->file, loc->elf_ex.e_phoff,  
  10. 590                              (char *)elf_phdata, size);  

 

 这块就是通过kernel_read读入整个program header table。从代码中可以看到,一个可执行程序必须至少有一个段(segment),而所有段的大小之和不能超过64K。

 

C代码   收藏代码
  1. 614 elf_ppnt = elf_phdata;  
  2.                 ……  
  3. 623 for (i = 0; i < loc->elf_ex.e_phnum; i++) {  
  4. 624     if (elf_ppnt->p_type == PT_INTERP) {  
  5.             ……  
  6. 635         elf_interpreter = kmalloc(elf_ppnt->p_filesz, GFP_KERNEL);  
  7.             ……  
  8. 640         retval = kernel_read(bprm->file, elf_ppnt->p_offset,  
  9. 641                          elf_interpreter,  
  10. 642                          elf_ppnt->p_filesz);  
  11.             ……  
  12. 682         interpreter = open_exec(elf_interpreter);  
  13.             ……  
  14. 695         retval = kernel_read(interpreter, 0, bprm->buf,  
  15. 696                          BINPRM_BUF_SIZE);  
  16.             ……  
  17. 703         /* Get the exec headers */  
  18.             ……  
  19. 705         loc->interp_elf_ex = *((struct elfhdr *)bprm->buf);  
  20. 706             break;  
  21. 707     }  
  22. 708     elf_ppnt++;  
  23. 709 }  

 

 这个for循环的目的在于寻找和处理目标映像的“解释器”段。“解释器”段的类型为PT_INTERP,找到后就根据其位置的p_offset和大小p_filesz把整个“解释器”段的内容读入缓冲区(640~640)。事个“解释器”段实际上只是一个字符串,即解释器的文件名,如“/lib/ld-linux.so.2”。有了解释器的文件名以后,就通过open_exec()打开这个文件,再通过kernel_read()读入其开关128个字节(695~696),即解释器映像的头部。我们以Hello World程序为例,看一下这段中具体的内容:

其实从readelf程序的输出中,我们就可以看到需要解释器/lib/ld-linux.so.2,为了进一步的验证,我们用hd命令以16进制格式查看下类型为INTERP的段所在位置的内容,在上面的各个域可以看到,它位于偏移量为0x000114的位置,文件内占19个字节:

从上面红色部分可以看到,这个段中实际保存的就是“/lib/ld-linux.so.2”这个字符串。

 

C代码   收藏代码
  1. 814         for(i = 0, elf_ppnt = elf_phdata;  
  2. 815             i < loc->elf_ex.e_phnum; i++, elf_ppnt++) {  
  3.                        ……   
  4. 819                 if (elf_ppnt->p_type != PT_LOAD)  
  5. 820                         continue;  
  6.                        ……   
  7. 870                 error = elf_map(bprm->file, load_bias + vaddr, elf_ppnt,  
  8. 871                                 elf_prot, elf_flags);  
  9.                        ……  
  10. 920         }  

 

 这段代码从目标映像的程序头中搜索类型为PT_LOAD的段(Segment)。在二进制映像中,只有类型为PT_LOAD的段才是需要装入的。当然在装入之前,需要确定装入的地址,只要考虑的就是页面对齐,还有该段的p_vaddr域的值(上面省略这部分内容)。确定了装入地址后,就通过elf_map()建立用户空间虚拟地址空间与目标映像文件中某个连续区间之间的映射,其返回值就是实际映射的起始地址。

 

C代码   收藏代码
  1. 946     if (elf_interpreter) {  
  2.                 ……  
  3. 951         elf_entry = load_elf_interp(&loc->interp_elf_ex,  
  4. 952                                 interpreter,  
  5. 953                                     &interp_load_addr);  
  6.                                 ……  
  7. 965     } else {  
  8. 966         elf_entry = loc->elf_ex.e_entry;  
  9.                 ……  
  10. 972     }  

 这段程序的逻辑非常简单:如果需要装入解释器,就通过load_elf_interp装入其映像(951~953),并把将来进入用户空间的入口地址设置成load_elf_interp()的返回值,即解释器映像的入口地址。而若不装入解释器,那么这个入口地址就是目标映像本身的入口地址。

 

C代码   收藏代码
  1. 991        create_elf_tables(bprm, &loc->elf_ex,  
  2. 992                           (interpreter_type == INTERPRETER_AOUT),  
  3. 993                           load_addr, interp_load_addr);  
  4.                ……  
  5. 1028       start_thread(regs, elf_entry, bprm->p);  

 在完成装入,启动用户空间的映像运行之前,还需要为目标映像和解释器准备好一些有关的信息,这些信息包括常规的argc、envc等等,还有一些“辅助向量(Auxiliary Vector)”。这些信息需要复制到用户空间,使它们在CPU进入解释器或目标映像的程序入口时出现在用户空间堆栈上。这里的create_elf_tables()就起着这个作用。

 

       最后,start_thread()这个宏操作会将eip和esp改成新的地址,就使得CPU在返回用户空间时就进入新的程序入口。如果存在解释器映像,那么这就是解释器映像的程序入口,否则就是目标映像的程序入口。那么什么情况下有解释器映像存在,什么情况下没有呢?如果目标映像与各种库的链接是静态链接,因而无需依靠共享库、即动态链接库,那就不需要解释器映像;否则就一定要有解释器映像存在。

       以我们的Hello World为例,gcc在编译时,除非显示的使用static标签,否则所有程序的链接都是动态链接的,也就是说需要解释器。由此可见,我们的Hello World程序在被内核加载到内存,内核跳到用户空间后并不是执行Hello World的,而是先把控制权交到用户空间的解释器,由解释器加载运行用户程序所需要的动态库(Hello World需要libc),然后控制权才会转移到用户程序。

 

ELF文件中符号的动态解析过程

 

       上面一节提到,控制权是先交到解释器,由解释器加载动态库,然后控制权才会到用户程序。因为时间原因,动态库的具体加载过程,并没有进行深入分析。大致的过程就是将每一个依赖的动态库都加载到内存,并形成一个链表,后面的符号解析过程主要就是在这个链表中搜索符号的定义。

       我们后面主要就是以Hello World为例,分析程序是如何调用printf的:

查看一下gcc编译生成的Hello World程序的汇编代码(main函数部分):

 

C代码   收藏代码
  1. 08048374 <main>:  
  2.  8048374:       8d 4c 24 04         lea     0x4(%esp),%ecx  
  3.                 ……  
  4.  8048385:       c7 04 24 6c 84 04 08    movl    $0x804846c,(%esp)  
  5.  804838c:       e8 2b ff ff ff          call        80482bc <puts@plt>  
  6.  8048391:       b8 00 00 00 00          mov     $0x0,%eax  

 从上面的代码可以看出,经过编译后,printf函数的调用已经换成了puts函数(原因读者可以想一下)。其中的call指令就是调用puts函数。但从上面的代码可以看出,它调用的是puts@plt这个标号,它代表什么意思呢?在进一步说明符号的动态解析过程以前,需要先了解两个概念,一个是global offset table,一个是procedure linkage table。

 

 

       Global Offset Table(GOT)

       在位置无关代码中,一般不能包含绝对虚拟地址(如共享库)。当在程序中引用某个共享库中的符号时,编译链接阶段并不知道这个符号的具体位置,只有等到动态链接器将所需要的共享库加载时进内存后,也就是在运行阶段,符号的地址才会最终确定。因此,需要有一个数据结构来保存符号的绝对地址,这就是GOT表的作用,GOT表中每项保存程序中引用其它符号的绝对地址。这样,程序就可以通过引用GOT表来获得某个符号的地址。

       在x86结构中,GOT表的前三项保留,用于保存特殊的数据结构地址,其它的各项保存符号的绝对地址。对于符号的动态解析过程,我们只需要了解的就是第二项和第三项,即GOT[1]和GOT[2]:GOT[1]保存的是一个地址,指向已经加载的共享库的链表地址(前面提到加载的共享库会形成一个链表);GOT[2]保存的是一个函数的地址,定义如下:GOT[2] = &_dl_runtime_resolve,这个函数的主要作用就是找到某个符号的地址,并把它写到与此符号相关的GOT项中,然后将控制转移到目标函数,后面我们会详细分析。

       Procedure Linkage Table(PLT)

       过程链接表(PLT)的作用就是将位置无关的函数调用转移到绝对地址。在编译链接时,链接器并不能控制执行从一个可执行文件或者共享文件中转移到另一个中(如前所说,这时候函数的地址还不能确定),因此,链接器将控制转移到PLT中的某一项。而PLT通过引用GOT表中的函数的绝对地址,来把控制转移到实际的函数。

       在实际的可执行程序或者共享目标文件中,GOT表在名称为.got.plt的section中,PLT表在名称为.plt的section中。

大致的了解了GOT和PLT的内容后,我们查看一下puts@plt中到底是什么内容:

 

C代码   收藏代码
  1. Disassembly of section .plt:  
  2.   
  3. 0804828c <__gmon_start__@plt-0x10>:  
  4.  804828c:       ff 35 68 95 04 08       pushl   0x8049568  
  5.  8048292:       ff 25 6c 95 04 08       jmp     *0x804956c  
  6.  8048298:       00 00  
  7.         ......  
  8. 0804829c <__gmon_start__@plt>:  
  9.  804829c:       ff 25 70 95 04 08       jmp     *0x8049570  
  10.  80482a2:       68 00 00 00 00          push        $0x0  
  11.  80482a7:       e9 e0 ff ff ff          jmp     804828c <_init+0x18>  
  12.   
  13. 080482ac <__libc_start_main@plt>:  
  14.  80482ac:       ff 25 74 95 04 08       jmp     *0x8049574  
  15.  80482b2:       68 08 00 00 00          push        $0x8  
  16.  80482b7:       e9 d0 ff ff ff          jmp     804828c <_init+0x18>  
  17. 080482bc <puts@plt>:  
  18.  80482bc:       ff 25 78 95 04 08       jmp     *0x8049578  
  19.  80482c2:       68 10 00 00 00          push    $0x10  
  20.  80482c7:       e9 c0 ff ff ff          jmp     804828c <_init+0x18>  

 可以看到puts@plt包含三条指令,程序中所有对有puts函数的调用都要先来到这里(Hello World里只有一次)。可以看出,除PLT0以外(就是__gmon_start__@plt-0x10所标记的内容),其它的所有PLT项的形式都是一样的,而且最后的jmp指令都是0x804828c,即PLT0为目标的。所不同的只是第一条jmp指令的目标和push指令中的数据。PLT0则与之不同,但是包括PLT0在内的每个表项都占16个字节,所以整个PLT就像个数组(实际是代码段)。另外,每个PLT表项中的第一条jmp指令是间接寻址的。比如我们的<span l


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

bin和elf文件(ARM) 五 的相关文章

  • UITableView titleForHeaderInSection 显示全部大写

    我正在使用 titleForHeaderInSection 显示 UITableView 部分的标题 它在 iOS6 SDK 中工作得很好 但 iOS7 SDK 显示的标题全部大写 我猜这是苹果更新的人机界面指南的一部分 那里的所有示例都显
  • 下载 .zip 文件会运行损坏的文件 php

    我试图强制下载受保护的 zip 文件 我不希望人们在没有先登录的情况下访问它 我为以下创建了函数login等等 但我遇到了下载的文件损坏的问题 这是我的代码 file downloads filename header Content ty
  • Safari 11 X-XSRF-TOKEN 刷新后未更新

    最近 Mac OSX 上的 Safari 11 发布 此更新导致我们的 Web 应用程序与请求标题上的 XSRF 结合出现问题 我将尝试以逻辑方式描述问题 好的情况应该是这样的 当用户想要登录时 他会收到服务器的响应 其中包含包含 XSRF
  • 显示名称而不是电子邮件的电子邮件标题的格式是什么?

    我正在尝试创建一个 php 脚本 该脚本将使用 mySQL 数据库为我处理邮件列表 并且我已经准备好了大部分内容 不幸的是 我似乎无法让标题正常工作 而且我不确定问题是什么 headers From email protected cdn
  • Economist.com 如何实施其粘性标题? jQuery?

    如果您访问 经济学人 网站上的一篇文章 例如 http www economist com node 17629757 http www economist com node 17629757 当您向下滚动页面超过某个点 使用 PAGEDO
  • Scapy:如何获取完整的 IP 数据包标头?

    在 Scapy 中 我想要manually将数据包与其相应的 ICMP 超时消息进行匹配 我需要匹配 ICMP 数据包的 IP in ICMP 字段 IP 标头和数据包的前 8 个字节 ICMP 数据包不是问题 icmpPayload st
  • PHP + PDF,如何使用curl保存下载的PDF?

    Welcome 我在页面上保存下载的 pdf 时遇到了一些问题 要下载 pdf 我使用 Curl CurlConnect curl init curl setopt CurlConnect CURLOPT URL http website
  • .h 是从 .h.in 生成的吗?

    我的库在构建后创建的 h 文件中有结构定义 但我在相应的 h in 中找不到这些定义 有人可以告诉我这一切是如何工作的以及它从哪里获取额外信息吗 具体来说 我正在构建 pth 用户空间线程库 它有 pth p h in 它不包含我正在寻找的
  • C++:在小型项目的头文件中编写整个类的缺点?

    只是一个风格问题 我是一名独立工作的独立游戏开发者 我养成了在标题中编写整个类的 坏 习惯 我知道 h cpp 文件组合的一些好处是它们允许将代码分割成编译块 只要它们保持不变就不需要重新编译 并允许将接口与实现分开 然而 这些事情对我来说
  • 单独文件中的函数实现

    在单独的文件中实现函数的正确语法是什么 例如 foo h int Multiply const int Number foo cpp include foo h int Multiply const int Number return Nu
  • Java 中的 HTTP 标头编码/解码

    自定义 HTTP 标头被传递到 Servlet 应用程序以进行身份 验证 标头值必须能够包含重音符号和其他非 ASCII 字符 因此必须采用某种编码 最好是 UTF 8 控制身份验证环境的开发人员向我提供了这段 Java 代码 String
  • 为什么 firefox/chrome 显示的页面与 IE8 不同?

    当我看着 我看到最新版本 with Firefox and Chrome 但是一个旧版本 with IE8 另外 通过屏幕抓取PHP Curl给我一个旧版本 我试过了CTRL 刷新在 IE8 中 但我无法让它向我显示最新版本 无论heade
  • 非分组表的 UITableView 节标题高度

    问候 我知道UI表格视图 节标题高度仅用于分组表 但无论如何我都会问 如果有某种方法可以做到这一点并不明显 有没有办法更改节标题高度 以及字体 大小 NON 分组表 希望 是 或至少是 也许 但担心这可能是 否 伙计们 努力吧 Yes 使用
  • Retrofit 2 - 在 api 级别添加标头的优雅方式

    我的改装2 2 0 2当前 客户端需要向请求添加自定义标头 我正在使用一个Interceptor将这些标头添加到所有请求中 OkHttpClient httpClient new OkHttpClient httpClient networ
  • Android 列表视图与部分

    您好 我在尝试了解分段列表视图的工作原理时遇到问题 我让它工作到正常的列表视图中 但现在我想将部分添加到我的列表中 如何在其中添加节标题 这是我有效的代码 public class ChooseTeamActivity extends Li
  • 生成 Java JNI 标头

    我想做的就是使用 JDK Javah exe 程序生成 JNI 头文件 但无论我尝试什么 我都会收到错误消息 错误 找不到 ddg ndkTest NativeLib 的类文件 其中 ddg ndkTest 是命名空间 NativeLib
  • 将搜索栏从 magento 主页的标题中移动

    我是 magento 的新手 我想将搜索栏从标题移动到主页的中间位置 以便它仅显示在主页上 我在 magento 论坛上阅读了许多相关答案 但所有人都在尝试编辑 box css 中的 mini search 元素 但不幸的是我在此文件中没有
  • 标题中的全日历自定义按钮

    我需要在同一页面上的两个 或更多 完整日历之间切换 并且希望将此功能添加到日历标题内的自定义按钮中 我在自定义按钮上发现了一些有趣的代码 但它有点过时 因为它引用的是 Fullcalendar v 1 6 1 而我正在使用 2 3 1 这是
  • 如何避免函数的多重定义(Linux、GCC/G++、Code::Blocks)

    我有一个代码块项目 它使用许多不同的文件 通常是由其他程序员编写的 目前我遇到的情况是 我有两个不同的子项目 其中包含以相同方式命名的函数 比方说 F int x 因此 F int x 是在两个不同位置的两个源文件中定义的 并且它们有两个不
  • Mac 操作系统的 windows.h 替代品

    早上好 我正在使用 Codeblock c 程序进行编程 但是 我需要使用窗口 h and conio h MacBook 上没有这个头文件 拜托 你能帮我吗 不起作用的来源 void gotoxy int x int y COORD co

随机推荐

  • Windows应用-C#使用命令行执行PowerShell脚本

    前言 类似于bat脚本 能够自动执行一些任务 但是对bat不熟悉 因此选择使用C 来实现 具体是能够通过执行特定的语句实现对文件的读写与执行 代码 using System using System Collections Generic
  • 排序算法-堆排序

    思路 堆排序 Heapsort 是指利用堆积树 堆 这种数据结构所设计的一种排序算法 它是选择排序的一种 它是通过堆 来进行选择数据 需要注意的是排升序要建大堆 排降序建小堆 我们先将要排序的数据建成堆 然后通过下图所示的步骤进行排序 特性
  • 调试经验 - Fat文件打开失败的问题

    问题描述 在M7上调试打开SD卡中的某个文件 并读取内容的过程中 发现在SD卡中存在的一个文件firmware bin 通过open接口访问时 出现访问失败的问题 排查过程 发现f open 中传入的open flag为FA READ FA
  • 算法之拓扑关系

    目录 前言 算法解析 Kahn算法 DFS算法 总结 参考资料 前言 如何确定代码源文件的编译依赖关系 我们知道 一个完整的项目往往会包含很多代码源文件 编译器在编译整个项目的时候 需要按照依赖关系 依次编译每个源文件 比如 A cpp 依
  • Nginx动态负载均衡

    Nginx一般作为反向代理服务器来实现反向代理来转发处理请求 同时也可以作为静态资源服务器来加快静态资源的获取和处理 1 正向代理与反向代理 正向代理 正向代理 是一个位于客户端和原始服务器之间的服务器 为了从原始服务器取得内容 客户端向代
  • Elasticsearch整合springboot

    Elasticsearch 项目整合 Elasticsearch Rest Client 1 通过 9300 TCP spring data elasticsearch transport api jar springboot版本不同 ra
  • 爬虫学习小结

    前几天学习爬虫 总结如下 目录 一 Request 1 get函数初介绍 2 response 对象 3 Request库异常 4 通用代码框架 5 Request库七个主要方法 6 控制访问参数 二 Robots协议 三 beautifu
  • ML算法推导细节08—极端梯度提升XGBoost

    探究算法细节 深入了解算法原理 XGBoost的参考资料 XGBoost A Scalable Tree Boosting System 论文原文 KDD 2016 华盛顿大学 陈天奇博士 XGBoost PPT资料 陈天奇 XGBoost
  • Java 序列化

    Java 序列化 Java 提供了一种对象序列化的机制 该机制中 一个对象可以被表示为一个字节序列 该字节序列包括该对象的数据 有关对象的类型的信息和存储在对象中数据的类型 将序列化对象写入文件之后 可以从文件中读取出来 并且对它进行反序列
  • gauss 函数

    高斯滤波 高斯平滑 是图像处理 计算机视觉里面最常见的操作 平时 我们都是用matlab或者opencv的函数调用 imfilter或者cvSmooth 并不关心底层的实现 然而当开发者要实做高斯滤波的时候 往往就会很迷惘 往往会被以下几个
  • 数据结构——八叉树

    八叉树 Octree 是一种用于表示和管理三维空间的树状数据结构 它将三维空间递归地分割成八个八分体 octant 每个八分体可以继续分割 以实现对三维空间的更精细的划分 八叉树通常用于解决空间搜索和查询问题 例如三维物体碰撞检测 体素化
  • Golang的pprof性能分析

    文章目录 一 pprof 概述 二 服务开启pprof 1 代码中引用pprof 2 服务开启一个端口 用来监听pprof 三 使用pprof采集CPU耗时 1 调用流程图 2 查看火焰图 四 使用pprof分析内存泄漏问题 查看当前程序的
  • MySQL可视化,python操作mysql,多表查询

    目录 navicat 可视化软件 python 操作mysql 获取结果 SQL注入问题 多表查询练习题 navicat 可视化软件 Navicat Premium 是一套数据库管理工具 结合了其它 Navicat 成员的功能 支持单一程序
  • 随机变量,随机函数,随机过程,随机场是什么

    之前看过的 后来就忘了 记一下加深印象 随机变量 随机变量的比较容易理解 概念是设随机试验E的样本空间为 若对任一 都有一个实数Z与之对应 且对任一实数z Z 事件 z Z 都有一个确定的概率 则称Z是一随机变量 其实看定义还是有点不容易理
  • MTU值及查询方法

    通信术语 最大传输单元 Maximum Transmission Unit MTU 是指一种通信协议的某一层上面所能通过的最大数据包大小 以字节为单位 最大传输单元这个参数通常与通信接口有关 网络接口卡 串口等 因为协议数据单元的包头和包尾
  • 设置postgres最大连接数-解决odoo并发报错

    此教程案例postgres版本为12 问题描述 psycopg2 OperationalError FATAL sorry too many clients already on 早上发现我维护的python后端服务报错出现了这个 百度 一
  • PyTorch学习笔记(19) ——NIPS2019 PyTorch: An Imperative Style, High-Performance Deep Learning Library

    0 前言 波兰小哥Adam Paszke从15年的Torch开始 到现在发表了关于PyTorch的Neurips2019论文 令我惊讶的是只中了Poster 而不是Spotlight 中间经历了漫长的过程 这里 把原文进行翻译放出来 以供读
  • Clickhouse、Hawq、Hive、Spark SQL、Presto、Kylin、Impala、Druid、Greeplum对比

    在上一章节中 我们讲到实时数仓的建设 互联网大数据技术发展到今天 各个领域基本已经成熟 有各式各样的解决方案可以供我们选择 在实时数仓建设中 解决方案成熟 消息队列Kafka Redis Hbase鲜有敌手 几乎已成垄断之势 而OLAP的选
  • 【RocketMQ】顺序消息实现原理

    全局有序 在RocketMQ中 如果使消息全局有序 可以为Topic设置一个消息队列 使用一个生产者单线程发送数据 消费者端也使用单线程进行消费 从而保证消息的全局有序 但是这种方式效率低 一般不使用 局部有序 假设一个Topic分配了两个
  • bin和elf文件(ARM) 五

    原文地址 http www iteye com topic 1121480 近段时间在研究Erlang核心特性的实现 也许过段时间会有个系列的总结 期待 今天看到有人写一个深入Hello World的文章 想起来读研的时候做的一个关于程序加