Elf可执行文件,程序加载

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版本中则分如下三个部分,主要的不同点是对与操作系统相关的部分进行了重新组织:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值