当前位置:   article > 正文

ELF 可执行链接文件分析(a.out)、虚拟存储、虚拟内存管理、程序加载_c++中解析elf文件需要考虑虚实地址转换吗

c++中解析elf文件需要考虑虚实地址转换吗
ELF 可执行链接文件分析(a.out)、虚拟存储、虚拟内存管理、程序加载
 
0 自我小结
 
==================虚拟存储器管理================================
linux就像是一个大的加工生产企业, 它接了很多客户的很多任务, 各任务的原材料等都放在仓库中, linux的内存就像是他的加工生产区,这个区被分为很多的块,一块就是一个加工生产车间,因任务众多,加工生产车间有限,需要合理利用生产车间; linux根据仓库中生产计划指令,在这个时刻去加工某个客户任务的某部分资料,如果这部分资料已在某个生产车间内,则继续加工,如果不在,则找一个生产车间,将待加工材料装进来,加工,该车间加工完这个任务当前的计划后,很可能被放入仓库中,生产车间被用来加工生产另一任务的计划,类似这样...
 
可执行文件的各个段 被映射到虚拟地址空间中, 同时虚拟地址空间中还包含有其他的各个代码部分(如库 堆 栈等)【因为虚拟空间32位,有4G,而实际物理存储器,内存很小,所以很有可能可执行文件的某个虚拟空间(页)中的内容是在CPU真正执行到了那里后,才被调到内存对应的某个页中,再被CPU执行的,所以很可能并不是可执行文件的所有段都一开始就全部加载到物理内存中】, cpu要访问某个虚拟地址单元,先检查该单元是否已经映射到了物理内存中,如果已在,就直接处理,否则就要先把这个地址对应的可执行文件的虚拟页且存放在磁盘上内容先加载到某个物理页中,再执行...
 
虚拟存储器(VM)被组织为一个由存放 在磁盘上的N个连续的字节大小的单元组成的数组。(概念)
VM系统将虚拟存储器分割为  虚拟页(VP)
物理存储器被分割为  物理页(PP); 虚拟页与物理页的大小一致;
虚拟页中的数据或代码要被执行时,需要将该虚拟页的内容缓存(映射)到某物理页中;
 
ELF <wbr>可执行链接文件分析(a.out)、虚拟存储、虚拟内存管理、程序加载

ELF <wbr>可执行链接文件分析(a.out)、虚拟存储、虚拟内存管理、程序加载

ELF <wbr>可执行链接文件分析(a.out)、虚拟存储、虚拟内存管理、程序加载

ELF <wbr>可执行链接文件分析(a.out)、虚拟存储、虚拟内存管理、程序加载
 

存储器映射

虚拟存储器区域 与 一个磁盘上的对象关联起来,用这个关联对象来初始化这个虚拟存储器区域的内容,这个过程叫做存储器映射【存储器强调的是对虚拟存储器区域的初始化】;

虚拟存储器区域与普通的文件映射,比如与可执行文件的映射,这时可执行文件中的程序头已经包含了可执行文件与虚拟空间地址的映射关系,这个程序头中指明了可执行文件的各个段映射到虚拟存储器空间的虚拟地址 以及 读写权限等信息。这就是ELF中的映射信息。虚拟存储器只不过根据块的大小将这些规划好的段(区域)再映射一下而已。

虚拟存储器区域还可以映射到一个全0的匿名文件(使用该虚拟区域时,用这个全0的匿名文件初始化);

其实不论如何映射(映射只是在使用到该分配且未缓存的虚拟区域时,用对应的映射区域进行初始化对应的物理页而已,一旦一个虚拟页被初始化了,他就在一个由内核维护的专门的交换文件(swap file)之间进行换来换去【这里强调的是虚拟页对应的物理页初始化后,换进换出的过程】。这个交换文件就是交换空间(交换区域)。因此交换空间限制了当前运行着的(所有)进程能够分配的虚拟页面的总是。

ELF <wbr>可执行链接文件分析(a.out)、虚拟存储、虚拟内存管理、程序加载

共享对象

由于很多的进程包含有公共的只读文件区域等(如C库中printf等),如果每个进程都在屋里存储器重包含这样的常用代码,那将是一种浪费。

存储器映射提供了一种机制,用来控制多个进程如何共享对象;

我们可以将一个对象映射到虚拟存取器区域中去,且这个对象要么是共享对象要么是私有对象;

共享对象:多个进程将这个共享对象映射到各自的虚拟空间,且各进程共享同一个共享对象的物理空间,某个进程进程对该共享对象的写操作,其他的进程是可见的,且写操作会反映在磁盘原始文件中;每个对象有唯一的文件名,内核可迅速判定进程1已映射该对象,且进程2的页表条目指向相应的物理页面,关键是即使对象被映射到多个进程的共享区域,物理存储器中也只存放共享对象的一个拷贝
ELF <wbr>可执行链接文件分析(a.out)、虚拟存储、虚拟内存管理、程序加载

ELF <wbr>可执行链接文件分析(a.out)、虚拟存储、虚拟内存管理、程序加载

私有对象:私有对象使用写时拷贝的方法,物理存储器只保持私有对象的一份拷贝,这个各个进程共享这个对象的一个物理拷贝

Fork函数:

一个现有进程可以调用fork函数创建一个新进程。由fork创建的新进程被称为子进程(child process)。fork函数被调用一次但返回两次。两次返回的唯一区别是子进程中返回0值而父进程中返回子进程ID。子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。注意,子进程持有的是上述存储空间的“副本”,这意味着父子进程间不共享这些存储空间。

当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID。为了给这个新进程创建虚拟存储器,它创建了当前进程的mm_struct、区域结构和页表的原样拷贝。它将两个进程中的每个页面都为标记只读,并将两个进程中的每个区域结构都标记为私有的写时拷贝。

当fork在新进程中返回时,新进程现在的虚拟存储器刚好和调用fork时存在的虚拟存储器相同。当这两个进程中的任一个后来进行写操作时,写时拷贝机制就会创建新页面,因此,也就为每个进程保持了私有地址空间的抽象概念。

execve函数:

execve("a.out",NULL,NULL) ;

execve函数在当前进程中加载并运行包含在可执行目标文件a.out中的程序,用a.out程序有效地替代了当前程序。加载并运行a.out需要以下几个步骤:

删除已存在的用户区域。删除当前进程虚拟地址用户部分中的已存在的区域结构。

映射私有区域。为新程序的文本、数据、bss和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时拷贝的。文本和数据区域被映射为a.out文件中的文本和数据区。bss区域是请求二进制零的,映射到匿名文件,其大小包含在a.out中。栈和堆区域也是请求二进制零的。

映射共享区域。如果a.out程序与共享对象(或目标)链接,比如标准C库libc.so,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。

设置程序计数器(PC)。execve做的最后一件事情就是设置当前进程上下文中的程序计数器,使之指向文本区域的入口点。

下一次调度这个进程时,它将从这个入口点开始执行。Linux将根据需要换入代码和数据页面。
ELF <wbr>可执行链接文件分析(a.out)、虚拟存储、虚拟内存管理、程序加载
 

使用mmap函数的用户级存储器映射:

 

[cpp] view plaincopy

#include 

#include 

void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offset) ; 

//返回:若成功时则为指向映射区域的指针,若出错则为MAP_FAILED(-1) 

 

mmap函数要求内核创建一个新的虚拟存储器区域是,最好是从地址start开始的一个区域,并将文件描述符fd指定的对象的一个连续的片(chunk)映射到这个新区域。连续的对象片大小为length字节,从距文件开始处偏移量为offset字节的地方开始。start地址仅仅是一个暗示,通常被定义为NULL。

ELF <wbr>可执行链接文件分析(a.out)、虚拟存储、虚拟内存管理、程序加载

munmap函数删除虚拟存储器的区域:

#include 

#include 

int  munmap( void *start,   size_t length);

Munmap函数删除从虚拟地址start开始的,由接下来的length字节组成的区域。接下来对已删除区域的引用会导致段错误。

 

动态存储器分配

虽可通过低级的mmap、munmap函数来创建与删除虚拟存取器的区域,但是用动态存储器分配器更方便,更易于移植;

 

动态存储器分配器维护着一个进程的虚拟存储器区域—堆heap; 堆紧接着未初始化的bss区域后面,并向上生长,对每个进程,内核维护着一个变量brk,他指向堆的顶部;


ELF <wbr>可执行链接文件分析(a.out)、虚拟存储、虚拟内存管理、程序加载

分配器的分类:

显式分配器:要求应用显式地释放任何已分配的块。C程序通过调用malloc函数来分配一个块,并通过调用free函数来释放一个块。

隐式分配器:要求适配器检测一个已分配块何时不再被程序所使用,那么就释放这个块。隐式分配器也叫垃圾收集器(java),而自动释放未使用的已分配的块的过程叫做垃收集。

C语音用malloc free等来从堆中分配虚拟空间;

ELF <wbr>可执行链接文件分析(a.out)、虚拟存储、虚拟内存管理、程序加载
 

 

 

 
linux的虚拟存储器系统:linux是如何组成虚拟存储器的?
  linux将虚拟存储器组织成一些区域(段)的集合,一个区域就是已经存在的(已分配的)虚拟存储器的连续片(chunk),这些页是以某种方式相关联的。例如代码中的 代码段  数据段 堆 共享库段 用户栈 都是不同的区域。每个存在的虚拟页面都保存在某个区域中,而不属于某个区域的虚拟页是不存在的,并且不能被进程引用。因为虚拟地址空间有间隙,即内核不用记录那些不存在的虚拟页(未分配的),而这样的页也不占用存储器、磁盘或内核本身的任何额外资源。
内核为系统中的每个进程维护一个单独的任务结构(task_struct),任务结构中的元素包含或者指向内核运行该进程所需要的所有信息(如 PID、指向用户栈的指针、可执行目标文件的名字以及程序计数器)
task_struct 中的一个条目指向mm_struct,他描述了虚拟存储器的当前状态,mm_struct中包含有pgd 和 mmap,pgd指向第一级页表(页全局目录)的基址,mmap指向一个vm_area_structs的链接,该链表中的每一个vm_area_structs都描述了当前虚拟地址空间的一个区域(也就是一个段),当内核运行这个进程时,他就将pgd存放在CR3控制寄存器中;
vm_start:指向这个区域(段)的起始处 
vm_end:指向这个区域(段)的结束处 
vm_prot:指向这个区域(段)内包含的所有页的读写许可权限
vm_flags:描述该区域的页面是与其他进程共享的还是这个进程私有的
vm_next:指向下一个
ELF <wbr>可执行链接文件分析(a.out)、虚拟存储、虚拟内存管理、程序加载
 
存储在磁盘上的虚拟页(VP)   DRAM中的物理页
                     /media/note/2012/03/29/virtual-memory/fig6.png

Linux进程的虚拟存储器

 

虚拟页(VP)可以分为 未分配的虚拟页  缓存的虚拟页 未缓存的虚拟页
未分配的虚拟页:VM系统还没有分配(创建)(或使用)的虚拟页,未分配的虚拟页的块没有任何的数据和他们               相关联。
缓存的虚拟页:当前缓存在物理存储器中的已分配页
未缓存的虚拟页:还没有缓存到物理存储器中的已分配页
ELF <wbr>可执行链接文件分析(a.out)、虚拟存储、虚拟内存管理、程序加载
虚拟存储器系统必须有某种方法来判定一个虚拟页是否存放在DRAM中的某个地方,如果是,还必须知道这个虚拟页存放在哪个物理页中,如果否(没有命中),系统必须知道这个虚拟页存放在磁盘的哪个位置,在物理存储器中选择一个牺牲页,并将虚拟页从磁盘拷贝到DRAM中,替换这个牺牲页;
 
页表:存放在物理存储器中页表(page table)的数据结构, 页表将虚拟页映射到物理页;每次地址翻译硬件将一个虚拟地址转换为物理地址时都会读取页表,页表由操作系统来维护;如下图中的左图就是一个页表,页表就是一个页表条目(PTE page table entry)的数组,虚拟地址空间中的 每个页在页表中一个固定偏移量处都对应有一个PTE。下图中左部分通过有效位 1简化表示 表右部的物理页号或磁盘地址有效,即该虚拟页已缓存映射到了这个地址对应的物理页中, 0&null 表示 未分配的虚拟页
页命中:如果代码中 去读 VP1 VP2 VP7 VP4的字时, 由于这些虚拟页都已经缓存到DRAM中,故直接可以命中,虚拟地址直接转换为物理地址,进行操作;
缺页: 如果代码中 去读 VP3的字时  , 由于VP3 没有缓存到DRAM中,所以会产生一个缺页异常,这时内核选择一个牺牲页如PP3,再从磁盘中拷贝VP3到存储PP3,更新PTE3。
ELF <wbr>可执行链接文件分析(a.out)、虚拟存储、虚拟内存管理、程序加载
 
尽管在整个运行过程中,程序引用的不同页面的总数可能超出物理存储器的总的大小,但是局部性原则保证了再任意时刻,程序将往往在一个较小的活动页面(active page)集合上工作,这个集合叫做工作集(working set) 或者驻集(resident set),在初始开销,也就是将工作集页面调度到存储器中之后,接下来对这个工作集的引用将导致命中,而不会产生额外的磁盘流量。如果程序没有良好的局部性,页面不停的换进换出,程序将出现一种不幸的现象,叫做颠簸(thrashing),程序运行很卡的时候就一般这样了,可以用getrusage函数来监测缺页的数量
 
Linux缺页异常处理
MMU在翻译某个虚拟地址A时,触发一个缺页,这个异常导致控制转移到内核的缺页处理程序,处理程序随后就执行下面的步骤:
1:虚拟地址A是合法的吗?A在某个区域(段)结构定义的区域内吗?缺页处理程序搜索区域结构的链表,把A和每个区域结构中的vm_start vm_end比较,如果非法,那么缺页处理程序就触发一个段错误,从而终止进程;其实进程可以通过mmap函数创建任意数量的新虚拟存储器区域,所以搜索区域结构链表花销很大,一般linux在链表中构建了一棵树,在这棵树上搜索。
2:试图进行存储器的访问是否合法? 进程是否有读写执行这个区域内页面的权限,访问不合法,缺页处理触发一个保护异常,进程终止
3:内核知道了这个缺页是由合法地址引起的,内核会先选择一个牺牲页,如果牺牲页被修改过,那么先将他交换出去,换入新的页面并更新页表,当缺页处理程序返回时,CPU重新启动引起缺页的指令,这条指令将再次发生A到MMU,这次MMU就能正常翻译A地址了
Linux存储器映射(就是将虚拟存储空间 与 磁盘上的对象【这个对象可是普通文件(虚拟存储空间直接与可执行文件进行映射) 或 匿名文件】 进行映射)
通过将虚拟存存储器区域(段) 与 一个磁盘上的对象关联起来,以初始化这个虚拟存储器区域(段)的内容,叫存储器的映射。虚拟存储器区域可以映射到 两种类型的对象中的一种:
(1)Unix文件上的普通文件:
一个区域可以映射到一个普通磁盘文件的连续部分,例如一个可执行目标文件。文件区(section)被分成页大小的片,每一片包含一个虚拟页面的初始化内容。因为按需进行页面调度,所以这些虚拟页面没有实际交换进入物理存储器,直到CPU第一次引用到页面(即发射一个虚拟地址,落在地址空间这个页面的范围之内)。如果区域比文件区要大,那么就用零来填充这个区域的余下部分。
(2)匿名文件:
一个区域也可以映射到一个 匿名文件,匿名文件是由内核创建的,包含的全是二进制零。CPU第一次引用这样一个区域内的虚拟页面时,内核就在物理存储器中找到一个合适的牺牲页面, 如果该页面被修改过,就将这个页面换出来,用二进制零覆盖牺牲页面并更新页表,将这个页面标记为是驻留在存储器中的。注意在磁盘和存储器之间没有实际的数据传送。因为这个原因,映射到匿名文件的区域中的页面有时也叫做请求二进制零的页(demand-zero page)。
无论在哪种情况下,一旦一个虚拟页面被初始化了, 它就在一个由内核维护的专门的交换文件(swap file)之间换来换去。交换文件也叫做交换空间(swap space)或者交换区域(swap area)。需要意识到的很重要的一点, 在任何时刻,交换空间都限制着当前运行着的进程能够分配的虚拟页面的总数。
===================================================
 
【参加 深入理解计算机系统 第7章】
gcc -v查看编译的过程
文件编译过程: 语言预处理器--> xxx.i-->编译器--> xxx.s-->汇编器--> xxx.o-->链接器--> 可执行文件
 
目标文件分为三类:可重定位目标文件、共享目标文件、可执行目标文件
编译器、汇编器 可生产可重定位的目标文件(包括共享目标文件); 链接器生成可执行目标文件
 
什么是符号解析?在代码中,变量 函数等符号 有 定义 和引用之分,因此符号解析的目的就是将每个符号引用与一个符号定义联系起来;  链接器来做符号解析,链接器将每个符号的引用 与 此时输入的可重定位目标文件中的符号表中的一个符号定义 联系起来。
 
什么是重定位?编译器和汇编器  生成从0地址开始的代码和数据节,链接器通过吧每个符号定义与一个存储器位置联系起来(简单理解为符号定义的位置),然后修改所有对这些符号的引用,使得他们指向这个存储器位置,从而 重定位这些节。链接器在完成符号解析后(即把一个符号的引用与一个符号的定义联系起来)。接下来, 链接器准备对各目标文件中的确定长度的相关节进行重定位了,合并输入模块,并为每个符号分配运行时的地址
 
重定位分为两步:
1:重定位节和符号定义:  链接器将多个目标文件中所有相同类型的节合并为同一类型的新的聚合节。如,各输入模块的.data节在最终可执行目标文件中被合并为一个.data节 ,然后,链接器将运行时存储器地址赋给新的聚合节(里面输入各模块的各个节 以及 各个符号),这步完成后,程序中的每个指令和全局变量都有唯一的运行时存储地址了。
2:重定位节中的符号引用
在这一步,链接器修改代码节和数据节中对每个符号的引用,使得他们指向正确的运行时地址。
 
==============================================================
可以将各个.o目标文件打包成一个库来使用
 
ar:创建静态库(各个xx.o),插入 删除 列出 和 提前成员
strings:列出一个目标文件中所有可打印的字符串
strip:从目标文件中删除符号表信息
nm:列出一个目标文件的符号表中定义的符号
size:列出目标文件中节的名字和大小
readelf:显示一个目标文件的完整结构,包括ELF头中编码的所有信息(包含size和nm的功能)
objdump:所有二进制工具之母,能够显示一个目标文件中的所有的信息,他最大的作用是反汇编.text节中的二进制指令
ldd:列出一个可执行文件在运行时所需要的共享库
=================== 可重定位目标文件============================
可重定位目标文件 与 可执行目标文件 的组织结构大致是相同的;有些小差别;
ELF头:
.text:
.rodata:
.bss:
.symbol: 符号表 【符号表由汇编器构造的】,存放的是程序中定义和引用的函数 和全局变量 或 静态变量(静态变量在有些书中统称为全局变量)的信息;因为局部变量是存放在堆栈中,所以 符号表中是不包含局部变量条目的符号
.rel.text:.text中需要重定位的位置,比如引用另一个目标文件的函数等;可执行文件中 并不需要重定位信息,因此通常省略,除非用户显示地指示链接器包含这些信息;
.rel.data:
.debug:一个调试符号表,其条目是程序中定义局部变量 和类型定义(-g选项)
.line:原始C源程序中的行号与.text节中机器指令之间的对应关系
.strtab:一个字符串表,其内容包括.symbol和.debug节中的符号表 以及节头部中的节名字,字符串表 就是以null结尾的字符串序列         
 
=================== 可执行目标文件加载============================
先看下面的可执行目标文件的分析,再看这里的加载吧,排版问题
 
     Program Headers\\这个就是ELF Header中描述的程序头描述表的起始位置,他告诉系统如何创建 进程映                \\像用来构造进程映像的目标文件必须具有程序头部表
                  \\他描述了进程映像的目标文件的“段”包含对应的一个或者多个“节区”,也就                      \\是“段内容(Segment Contents)”
                  \\该程序头描述表(数组)来描述了内存中的可执行的进程映射是如何构成的信息
                \\Type  Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
                \\如 LOAD 0x000000 0x08048000 0x08048000 0x0076c 0x0076c R E 0x1000
               \\上面描述的是一个加载段,加载目标文件中的对应的内容到内存中对应的位置
 
proc/进程PID/maps    查看某进程的虚拟地址空间是如何分配利用的。
 
cat /proc/1/statm
487 185 133 31 0 67 0
很简单地返回7组数字,每一个的单位都是一页 (常见的是4KB)
分别是
size:任务虚拟地址空间大小
Resident:正在使用的物理内存大小
Shared:共享页数
Trs:程序所拥有的可执行虚拟内存大小
Lrs:被映像倒任务的虚拟内存空间的库的大小
Drs:程序数据段和用户态的栈的大小
dt:脏页数量
 
=================== 可执行目标文件============================
ELF可执行文件(a.out)或类似编译器编译出的文件(.bin), 都是由各个节(section:如.text .bss .data等)组成;运行时,各个节被加载到内存中,此时的内存中以段(Segment:如代码段 数据段)的形式组织相关的节,以便程序运行;
 
ELF可执行文件(a.out): 在linux下,可使用readelf  nm objdump 以及gdb 来查看与调试可运行的文件;
ELF可执行文件的组成形式如下:
 
 readelf -a a.out: 读取显示执行文件中的所有的头表信息
 objdump -s a.out: 显示可节的具体内容
 nm a.out:列出目标文件中的所有符号信息
 gdb :调试
 
一般C语言的编译后执行语句都编译成机器代码,保存在.text段;已初始化的全局变量和局部静态变量都保存在. data段;未初始化的全局变量和局部静态变量一般放在一个叫."bss"的段里。我们知道未初始化的全局变量和局部静态变量默认值都为0,本来它们也可以被放在.data段的,但是因为它们都是0,所以为它们在.data段分配空间并且存放数据0是没有必要的。程序运行的时候它们的确是要占内存空间的,并且可执行文件必须记录所有未初始化的全局变量和局部静态变量的大小总和,记为.bss段。所以.bss段只是为未初始化的全局变量和局部静态变量预留位置而已,它并没有内容,所以它在文件中也不占据空间。
【函数中的局部变量将放在栈中,既不在.data 也不在.bss中】
ELF <wbr>可执行链接文件分析(a.out)、虚拟存储、虚拟内存管理、程序加载
ELF <wbr>可执行链接文件分析(a.out)、虚拟存储、虚拟内存管理、程序加载
一个进程的内存映像,从低地址开始分为五部分
正文段
初始化数据段
未初始化数据段
堆区
栈区
【栈由该区域的最高地址向低地址增长,而堆由该区域的低地址向高地址增长】
【当一程序启动运行的初期,并没有把该程 序所需要的所有的物理空间分配给它,而是只分配了满足当时可以使之运行的几个页面。程序继续运行(虚拟地址)需读取新的页面时,发现该页面不在内存 中,就要用一定的算法 为该进程对应的虚拟地址空间 分配一内存页面】
 

简单来讲,程序的装入到运行的主要包含以下几个步骤:

1:读入可执行文件的头部信息以确定其文件格式及地址空间的大小;

2:以段的形式划分地址空间;

3:将可执行程序读入地址空间中的各个段,建立虚实地址间的映射关系;

4:将bbs段清零;

5:创建堆栈段;

6:建立程序参数、环境变量等程序运行过程中所需的信息;

7:启动运行。

 
//程序头表中 描述可执行文件 到 虚拟内存空间的映射关系
Program Headers:
  Type          Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR          0x000034 0x08048034 0x08048034 0x00120 0x00120 R E 0x4
  INTERP        0x000154 0x08048154 0x08048154 0x00013 0x00013 R  0x1
     [Requesting program interpreter: /lib/ld-linux.so.2]
  LOAD          0x000000 0x08048000 0x08048000 0x0076c 0x0076c R E 0x1000
  LOAD          0x000f08 0x08049f08 0x08049f08 0x00240 0x00324 RW  0x1000
  DYNAMIC       0x000f14 0x08049f14 0x08049f14 0x000e8 0x000e8 RW  0x4
  NOTE          0x000168 0x08048168 0x08048168 0x00044 0x00044 R  0x4
  GNU_EH_FRAME   0x000690 0x08048690 0x08048690 0x0002c 0x0002c R  0x4
  GNU_STACK     0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x10
  GNU_RELRO     0x000f08 0x08049f08 0x08049f08 0x000f8 0x000f8 R   0x1
//程序头表中 描述段 到 节 的映射关系
 Section to Segment mapping:
  Segment Sections...
   00    
   01    .interp 
   02    .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame 
   03    .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss 
   04    .dynamic 
   05    .note.ABI-tag .note.gnu.build-id 
   06    .eh_frame_hdr 
   07    
   08    .init_array .fini_array .jcr .dynamic .got 
 
    ELF Header\\用来描述整个文件的组织,文件类型,机器类型,程序入口地址,
              \\Start of program headers程序头(描述表)起始位置,这是一个数组起始地址
              \\Number of program headers告诉我们这个数组包含了多少个程序段描述子成员
              \\Start of section headers节区头(描述表)起始位置,这是一个数组起始地址
              \\Number of section headers告诉我们这个数组包含了多少个节区描述子成员           
     Program Headers\\这个就是ELF Header中描述的程序头描述表的起始位置,他告诉系统如何创建 进程映                \\像用来构造进程映像的目标文件必须具有程序头部表
                  \\他描述了进程映像的目标文件的“段”包含对应的一个或者多个“节区”,也就                       \\是“段内容(Segment Contents)”
                   \\该程序头描述表(数组)来描述了内存中的可执行的进程映射是如何构成的信息
               \\Type  Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
               \\如 LOAD 0x000000 0x08048000 0x08048000 0x0076c 0x0076c R E 0x1000
              \\上面描述的是一个加载段,加载目标文件中的对应的内容到内存中对应的位置
     【接下来就是各个节的数据,各个节的偏移地址与长度 在Section Headers中做描述】 
  【各个节区有各自对应的读写属性标志,MMU通过这个标志来控制各节区的可读写特性,.rodata等】
    【gcc 编译时 加入-g 选项,目标文件中将包含调试信息】 
     .interp \\ 此节区包含程序解释器的路径名      
     .note.ABI-tag \\   
     .note.gnu.build-i \\ 
     .gnu.hash \\   
     .dynsym  \\  
     .dynstr \\    
     .gnu.version \\   
     .gnu.version_r \\   
     .rel.dyn \\     
     .rel.plt \\    
     .init  \\ 此节区包含了可执行指令,是进程初始化代码的一部分。当程序开始执行时,系统要在开始调            \\用主程序入口之前(通常指 C 语言的 main 函数)执行这些代码。
     .plt  \\    
     .text \\  包含程序的可执行指令   
     .fini \\ 此节区包含了可执行的指令,是进程终止代码的一部分。程序正常退出时,系统将安排执行这           \\里的代码。  
     .rodata \\  包含只读数据
     .eh_frame_hdr \\ 
     .eh_frame  \\   
     .init_array\\   
     .fini_array  \\ 
     .jcr  \\      
     .dynamic  \\ 
     .got    \\  
     .got.plt  \\  
     .data    \\ 初始化了的数据  
     .bss     \\ 未初始化了的数据
     .comment  \\ 
     .shstrtab  \\ 
     .symtab  \\此节区包含一个符号表
     .strtab \\ 
     Section Headers\\这个就是ELF Header中描述的节区头描述表的起始位置,文件包含了多少个节,这个                  \\数组就有多少个成员,每个成员描述了对应一个节区的名字,偏移地址,虚拟地址,                   \\可读写信息等
 
readelf  -a  a.out :可查看ELF文件的各个头信息(这些信息描述了整个可执行文件的组织信息)
     如:
 
1 简介
    可执行链接格式(Executable and Linking Format)最初是由UNIX系统实验室(UNIX System Laboratories,USL)开发并发布的,作为应用程序二进制接口(Application Binary Interface,ABI)的一部分。工具接口标准(Tool Interface Standards,TIS)委员会将还在发展的ELF标准选作为一种可移植的目标文件格式,可以在32位Intel体系结构上的很多操作系统中使用[1, 2]。
      ELF标准的目的是为软件开发人员提供一组二进制接口定义,这些接口可以延伸到多种操作环境,从而减少重新编码、重新编译程序的需要。接口的内容包括目标模块格式、可执行文件格式以及调试记录信息与格式等。
    TIS给出的Portable Formats Specification 1.1版本中主要针对三种不同类型的目标文件作了规定,并规定了程序加载与动态链接相关过程细节,给出了标准ANSI C和libc例程必须提供的符号[1]。在该组织随后发布的Executable and Linking Format(ELF) Specification 1.2版本中则分如下三个部分,主要的不同点是对与操作系统相关的部分进行了重新组织:
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/160630
推荐阅读
相关标签