浅谈ELF文件

首先我们知道程序经过编译器例如gcc,在打下gcc 1.c的时候经历了什么,分为如下四步

预处理

主要处理那些以’#‘开始的指令
1、将所有#define展开
2、处理所有预编译条件指令。#ifdef等
3、处理’#include’预编译指令,将被包含的文件插入到该预编译指令的位置。这个过程是递归的,可能会包含重复的头文件。(所有需要#ifndef #define)
4、删除所有注释。保留所有#pragma编译器指令。

编译

编译就是对文件进行一系列的词法分析,语法分析,语义分析及优化后生成的相应汇编代码。

汇编

汇编器是将汇编代码转换为机器可执行的指令,每一个汇编语句几乎都对应了一条机器指令。
汇编后生成的文件叫做目标文件。

链接

链接的主要内容就是把各个模块之前相互引用的部分都处理好,使得各个模块之间能够正确地衔接。
链接过程主要包括了地址和空间分配(address and storage allocation)、符号决议(symbol resolution)和重定位(relocation)等步骤。

当目标文件当前不知道其内部变量(设为var)var的地址时,编译器先将其地址置为0,等到链接器链接后,确定了var的地址后,我们再将var的地址进行修正,这一部就叫做重定位。

在本篇博客我们主要讲解汇编之后生成的目标文件(ELF)。

目标文件的格式

在linux下,可执行文件的格式是ELF(Executable Linkable Format),目标文件就是源代码经过编译后但未进行链接的那些中间文件,它一般跟可执行文件采用一种格式存储。动态链接库文件按照可执行文件格式存储。静态链接库稍有不同,它是把很多目标文件捆绑在一起形成一个文件,再加上一些索引,可以将静态链接库理解为包含了许多个目标文件的文件包。
在这里插入图片描述
既然段表决定了所有的段的属性,那么ELF文件中的段究竟是个什么东西呢?其实段只是对ELF文件内的不同类型的数据的一种分类。例如,我们把所有的代码(指令)放到一个段中,并且给这个段起名.text;把所有的已经初始化的数据放在.data段;把所有的未初始化的数据放在.bss段;把所有的只读数据放在.rodata段,等等。
至于为什么要把数据(指令在ELF文件中也算是一种数据,它是ELF文件的数据之一)分为不同的类型,除了方便进行区分之外,还有以下几个原因

  • 便于给段设置读写权限,有些段只需要设置只读权限即可
  • 方便CPU缓存的生效
  • 有利于节省内存,例如程序有多个副本情况下,此时只需要一份代码段即可
    总体来说,程序源代码被编译后主要分成两种段:程序段和程序数据。代码段属于程序指令,而数据段和.bss段属于程序数据。
  • 当程序被装载后,数据和指令分别被映射到两个虚存区域。由于数据区域对于进程来说是可读写的,而指令区域对于进程来说是只读的,所以这两个虚存区域的权限可以被分别设置成可读写和只读。这样可以防止程序的指令被有意或无意的改写。
  • 另外一方面是对于现代的cpu来说,它有着极为强大的缓存(cache)体系。由于缓存在现代的计算机中地位非常重要,所以程序必须尽量提高缓存的命中率。指令区和数据区的分离有利于提高程序的局部性。现代cpu的缓存一般都被设计成数据缓存和指令缓存分离,所以程序的指令和数据被分开存放对cpu的缓存命中率提高有好处。
  • 第三个原因也是最重要的原因,当系统中运行这多个该程序的副本时,它们的指令都是一样的,所以内存中只需要保存一份该程序的指令部分。对于指令这种只读的区域来说是这样,对于其它的只读数据也一样,比如很多程序里面带有的图标,图片、文本等资源也是属于共享的。当然每个副本进程的数据区域是不一样的,
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值