(转)对linux ELF文件的理解

 

在上一篇文章里,简单的介绍了ELF格式,其实在Linux中,动态链接库(.so),静态链接库(.a),都是按照ELF格式文件存储的。只不过对于库来讲,

大多数都是有许多目标文件捆绑而成,为了快速的查找某个位置上的数据,肯定会有个索引。

 

1.ELF文件分类

在ELF文件标准里,把ELF文件归为4类:

1.可重定位文件(Relocatable File)

    这类文件包含代码和数据,可被链接成可执行文件或者共享目标文件,静态链接库可以归为此类。

    比如Linux下的 .o 文件

2.可执行文件(Excutable File)

    一般就是ELF可执行文件,一般没有扩展名

    比如常用来杀死进程的kill,位于/bin/kill

3.共享目标文件(Shared Object File)

    包含代码和数据。

        一是连接器可以将其与其他可重定位文件或共享目标文件链接,生成新的目标文件。

        二是动态链接器可以将它与可执行文件结合,作为进程映像的一部分来执行。

    比如/lib/ld-linux.so.2

这里涉及了动态链接 和 库 ,关于这两个方面会在后面的文章里介绍。

4.核心转储文件(Core Dump File)

    当进程意外终止时,系统会将进程地址空间内容以及进程终止时的一些信息,转储到核心转储文件。

    比如Linux下的core dump

2.目标文件

目标文件里一般会有指令 ,数据还会有比如调试信息,符号表,字符串等。这些东西按什么规则来存放呢?

在ELF格式的文件里一般是按照Section(Ubuntu里翻译为“节”)来存放的。有时候也称为Segment(“段”),他们的区别会在链接视图和装载视图里区分。

程序源代码编译之后(注意是只编译,不链接),程序的指令会放在代码段,初始化了的全局变量和局部静态变量会放在数据段,未初始化的全局变量和局部静态变量

会放在 .bss 段。

 

下面来看映射关系:(注意程序中画两条深蓝色删除线的那两行,暂时不用理会,后面会说明)

但是,实际上是这样的吗?我们用objcump来看一下。(注意 生成此时的hello.o的里hello.c里,注释掉了画删除线的2行)

从这里我们可以看出:比我们理论上的section要多。先来看每一列是什么意思。

最左边第一列是一个索引值。

第二列是每个section的名字。

第三列是每个section的大小。

第四列是虚拟地址。

第五列是实际加载地址。

  说明: 第四五列都是0,因为该文件是目标文件,并没有被链接成可执行文件,所以不会有值。等被连接器连接后,就会有具体的数值。

第六列是每个section的地址偏移。

第七列是对齐大小。 ** 表示指数。2**2就是2的2次方。

 

每一个section中的CONTENTS ALLOC 等表示的是它的属性。CONTENTS表示在文件里真实存在,而 .bss段没有CONTENTS属性,说明在文件里面不存在。

.bss段跟.rodata段的偏移地址也可以说明。

为什么会是这样呢?

因为未初始化的全局变量 和 局部静态变量 默认都是0,再去占用空间是没必要的,但是程序执行时确实会占用内存,所以 .bss段起的是一个  为 未初始化的全局变量和局部静态变量 预留空间的作用。

 

仔细观察输出的每个section,几个陌生的section .rodata是只读数据段 ,.comment是注释信息段 ,.note.GNU-stack 是堆栈提示段,最后一个.eh_frame段,是与调试有关的。于是我们先将其用图片表示出来。(hello.o文件的大小是1292个字节 = 0x050c)

 

这样,目标文件的样子就呈现在我们面前了。

3.3个重要的段

3.1 代码段

这是截取的一部分,实际上将各个段的16进制内容都打印出来了,下面这张图是fun()函数的反汇编。

我们可以看到55是函数的入口地址,而c3是函数的退出地址,因为fun函数退出了,main函数也退出了,所以main的退出地址也是c3(main的反汇编未在这里列出)。

 

3.2数据段 和 只读数据段

可以看到数据段的大小为0x08,而初始化的全局变量是一个int型,占4个字节,初始化的静态局部变量是一个int型,占4个字节。总共是8个。

但是这里还有一个.rodata,只读数据段,他到底是什么呢?

在fun函数里,有个printf("i=%d\n"); 在这句话中  “i=%d\n”是只读的,刚好是6个字节,以 \0结束,且不算入总大小 。

单独设立了一个只读数据段是很有必要的,可以防止很多违法的数据修改操作,保证程序的稳定安全。

 

3.3.bss段

按照我们前面的理论,它的大小应该也是8个字节,为什么这里是4个字节呢?

其实只有 未初始化的静态局部变量放到了.bss段,未初始化的全局变量只是作为一个符号,在最终链接可执行文件时,才会出现在.bss里。

 

在上面的程序里,局部变量 static int static_init_zero = 0;当我们编译成 hello.o时也会出现在 .bss里,这是为什么?

因为由于未初始化的局部静态变量也是0,所以编译器会优化这个变量,将它放到bss段里,不会占用hello.o的空间。

 

4 自定义段

在上面的程序中有这样一句话

1
__attribute__((section("special"))) int global_special = 33;

这句话会为我们制造一个新的段,名字叫 special。

重新编译后,查看个段信息如下:

关于ELF文件更深入的细节,会在下面的文章中继续讨论。

posted on 2015-03-11 10:16  lysuns  阅读(1250)  评论(0)    收藏  举报

导航