(转)对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文件更深入的细节,会在下面的文章中继续讨论。









浙公网安备 33010602011771号