Linux下可执行文件格式详解

Linux下面,目标文件、共享对象文件、可执行文件都是使用ELF文件格式来存储的。程序经过编译之后会输出目标文件,然后经过链接可以产生可执行文件或者共享对象文件。Linux下面使用的ELF文件和Windows操作系统使用的PE文件都是从Unix系统的COFF文件格式演化来的。 

我们先来了解一些基本的想法。

首先,最重要的思路是一个程序从人能读懂的格式转换为供操作系统执行的二进制格式之后,代码和数据是分开存放的,之所以这样设计有这么几个原因:

1、程序执行之后,代码和数据可以被映射到不同属性的虚拟内存中。因为代码一般是只读的,而数据是可读可写的;

2、现代CPU有强大的缓存体系。程序和代码分离可以提高程序的局部性,增加缓存命中的概率;

3、还有最重要的一个原因是当有多个程序副本在运行的时候,只读部分可以只在内存中保留一份,这样大大节省了内存。

在ELF的定义中,把他们分开存放的地方称为一个 Section ,就是一个段。

一个ELF文件中重要的段包括:

.text 段:存储 只读程序

.data 段:存储 已经初始化的全局变量和静态变量

.bss 段:存储 未初始化的全局变量和静态变量,因为这些变量的值为0,所以这个段在文件当中不占据空间

.rodata 段:存储 只读数据,比如字符串常量


我们用一个例子来看一下ELF文件的格式到底是什么。首先,在Linux下编写一个C程序:SimpleSection.c

  1. int printf(const char *format, ... );  
  2.   
  3. int global_init_var = 16;  
  4. int global_unint_var;  
  5.   
  6. void func1 (int );  
  7.   
  8. int main()  
  9. {  
  10.     static int static_var = -32;  
  11.     static int static_var_uninit;  
  12.   
  13.     int a = 1;  
  14.     int b;  
  15.   
  16.     func1(static_var + global_init_var + a + b);  
  17.   
  18.     return a;  
  19. }  
  20.   
  21. void func1 (int i)  
  22. {  
  23.     printf("%d\n", i);  
  24. }  

然后,产生目标文件:
  1. [root@xuxingwang-centos Program]# gcc -c SimpleSection.c  
  2. [root@xuxingwang-centos Program]# file SimpleSection.o  
  3. SimpleSection.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped  

file命令的结果也告诉我们,这是一个32位ELF的文件,类型是 relocatable ,就是可重定位。所以目标文件又叫做可重定位文件。

elf文件的最开始是elf文件头信息,32位有52个字节组成。我们可以使用 readelf 工具来查看一下:

  1. [root@xuxingwang-centos Program]# readelf -h SimpleSection.o  
  2. ELF Header:  
  3.   Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00  
  4.   Class:                             ELF32  
  5.   Data:                              2's complement, little endian  
  6.   Version:                           1 (current)  
  7.   OS/ABI:                            UNIX - System V  
  8.   ABI Version:                       0  
  9.   Type:                              REL (Relocatable file)  
  10.   Machine:                           Intel 80386  
  11.   Version:                           0x1  
  12.   Entry point address:               0x0  
  13.   Start of program headers:          0 (bytes into file)  
  14.   Start of section headers:          224 (bytes into file)  
  15.   Flags:                             0x0  
  16.   Size of this header:               52 (bytes)  
  17.   Size of program headers:           0 (bytes)  
  18.   Number of program headers:         0  
  19.   Size of section headers:           40 (bytes)  
  20.   Number of section headers:         11  
  21.   Section header string table index: 8  

Entry point address 指的是程序入口地址,如果是可执行文件,这个字段会有值;

他之前的字段是一些说明字段;

Start of program headers 指的是 程序头表 的起始位置。程序头表 是从装载视图的角度对elf的各个段进行的分类信息;结构和段表相似;

Start of section headers 指出了elf除文件头以外的最重要的信息:段表 的起始位置。段表包含了各个段的名称、属性、大小、位置等重要信息。操作系统首先找到段表,然后根据段表的信息去找到各个段。段表是一个类似数组的结构,一个段的信息是这个数组的一个元素。

Size of this header 指的是头文件大小,32位都是 52 个字节,0x34个字节。

Size of program headers 指的是每个 程序头表 的大小。

Number of program headers 指的是 程序头表 的数目。

Size of sections headers 指的是每个 段表 的大小;

Number of section headers 指的是 段表的数量;

Section header string table index 指出了段表当中用到的字符串表在段表中的下标。


文件头之后,紧跟着的是 程序头,因为目标文件没有链接,所以没有装载信息。我们这里可以先不理会这个东西,以后专门再说他。

程序头之后就是各个段的数据,我们用工具查看一下:

  1. [root@xuxingwang-centos Program]# readelf -S SimpleSection.o  
  2. There are 11 section headers, starting at offset 0xe0:  
  3.   
  4. Section Headers:  
  5.   [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al  
  6.   [ 0]                   NULL            00000000 000000 000000 00      0   0  0  
  7.   [ 1] .text             PROGBITS        00000000 000034 000020 00  AX  0   0  4  
  8.   [ 2] .rel.text         REL             00000000 0003f4 000010 08      9   1  4  
  9.   [ 3] .data             PROGBITS        00000000 000054 000008 00  WA  0   0  4  
  10.   [ 4] .bss              NOBITS          00000000 00005c 000004 00  WA  0   0  4  
  11.   [ 5] .rodata           PROGBITS        00000000 00005c 000004 00   A  0   0  1  
  12.   [ 6] .comment          PROGBITS        00000000 000060 00002d 01  MS  0   0  1  
  13.   [ 7] .note.GNU-stack   PROGBITS        00000000 00008d 000000 00      0   0  1  
  14.   [ 8] .shstrtab         STRTAB          00000000 00008d 000051 00      0   0  1  
  15.   [ 9] .symtab           SYMTAB          00000000 000298 0000f0 10     10  10  4  
  16.   [10] .strtab           STRTAB          00000000 000388 00006b 00      0   0  1  
  17. Key to Flags:  
  18.   W (write), A (alloc), X (execute), M (merge), S (strings)  
  19.   I (info), L (link order), G (group), x (unknown)  
  20.   O (extra OS processing required) o (OS specific), p (processor specific)  

各个字段意思依次是:段序号、段名称、段类型、段虚拟地址、偏移量、大小、ES、标志、Lk、Inf、对齐。

没有解释的列可以先不考虑,我们先关注其他几个列。

第0个段是为了读取的时候下标不用减1。

紧跟着的就是代码段,偏移量为0x34,就是说在文件头结尾之后马上就是代码段;

代码段之后,偏移量 0x54 的地方就是 数据段,占8个字节,就是程序中已经被赋值的一个全局变量和一个静态变量;

紧接着是.bss段,这里只存储了一个static变量,因为 未初始化的那个全局变量被一种优化机制存储到了 .common 段,这里可以不做理会;

然后是只读数据段.rodata,这里存储的是 printf 里面的 %d\n 这三个字符,外加结束符\0,总共4个字节的空间


我们根据Size这一列来算一下这些段总共占据的空间,(.bss由于不占空间,不用算进来):

.text 0x20

.data 0x8

.rodata 0x4

.comment 0x2d

.shstrtab 0x51

.rel.text 0x10

.symtab 0xf0

.strtab 0x6b

这里的每一个段都有一个段表元素来描述,总共11个。从头文件得知,每个元素的大小为40字节。也就是说段表总共占了 0x1b8 个字节的空间。而且段表的开始地址由于内存对齐需要,中间空了2个字节。因为段表的开始地址是第224个字节;

.rel.text 的开始地址也由于内存对齐的要求,补了一个空字节。

在加上头文件的 0x34 个字节,总共加起来是   1028 字节。

  1. [root@xuxingwang-centos Program]# ls -al SimpleSection.o  
  2. -rw-r--r-- 1 root root 1028 Aug 21 16:09 SimpleSection.o  

这个目标文件的大小恰好是1028个字节。


转自:http://blog.csdn.net/topasstem8/article/details/38730971

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java8新特性及实战视频教程完整版Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。这种风格将要处理的元素集合看作一种流, 流在管道传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。元素流在管道经过间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。 Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法)。使用Lambda 表达式可以使代码变的更加简洁紧凑。Java8实战视频-01让方法参数具备行为能力Java8实战视频-02Lambda表达式初探Java8实战视频-03Lambda语法精讲Java8实战视频-04Lambda使用深入解析Java8实战视频-05Lambda方法推导详细解析-上.wmvJava8实战视频-06Lambda方法推导详细解析-下Java8实战视频-07Stream入门及Stream在JVM的线程表现Java8实战视频-08Stream知识点总结Stream源码阅读Java8实战视频-09如何创建Stream上集Java8实战视频-10如何创建Stream下集.wmvJava8实战视频-11Stream之filter,distinct,skip,limit,map,flatmap详细介绍Java8实战视频-12Stream之Find,Match,Reduce详细介绍Java8实战视频-13NumericStream的详细介绍以及和Stream之间的相互转换Java8实战视频-14Stream综合练习,熟练掌握API的用法Java8实战视频-15在Optional出现之前经常遇到的空指针异常.wmvJava8实战视频-16Optional的介绍以及API的详解Java8实战视频-17Optional之flatMap,综合练习,Optional源码剖析Java8实战视频-18初识Collector体会Collector的强大Java8实战视频-19Collector使用方法深入详细介绍-01Java8实战视频-20Collector使用方法深入详细介绍-02Java8实战视频-21Collector使用方法深入详细介绍-03.wmvJava8实战视频-22Collector使用方法深入详细介绍-04Java8实战视频-23Collector原理讲解,JDK自带Collector源码深度剖析Java8实战视频-24自定义Collector,结合Stream的使用详细介绍Java8实战视频-25Parallel Stream编程体验,充分利用多核机器加快计算速度Java8实战视频-26Fork Join框架实例深入讲解Java8实战视频-27Spliterator接口源码剖析以及自定义Spliterator实现一个Stream.wmvJava8实战视频-28Default方法的介绍和简单的例子Java8实战视频-29Default方法解决多重继承冲突的三大原则详细介绍Java8实战视频-30多线程Future设计模式原理详细介绍,并且实现一个Future程序Java8实战视频-31JDK自带Future,Callable,ExecutorService介绍Java8实战视频-32实现一个异步基于事件回调的Future程序.wmvJava8实战视频-33CompletableFuture用法入门介绍Java8实战视频-34CompletableFuture之supplyAsync详细介绍Java8实战视频-35CompletableFuture流水线工作,join多个异步任务详细讲解Java8实战视频-36CompletableFuture常用API的重点详解-上Java8实战视频-37CompletableFuture常用API的重点详解-下Java8实战视频-38JDK老DateAPI存在的问题,新的DateAPI之LocalDate用法及其介绍.wmvJava8实战视频-39New Date API之LocalTime,LocalDateTime,Instant,Duration,Period详细介绍Java8实战视频-40New Date API之format和parse介绍

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值