从程序员角度看ELF (1)(转载)
时间:2007-04-09 来源:Echo CHEN
原文:《 ELF:From The Programmer‘s Perspective》
作者:Hongjiu Lu <mailto: [email protected]>
NYNEX Science & Technology, Inc.
500 Westchester Avenue
White Plains, NY 10604, USA
翻译:alert7 <mailto: [email protected] [email protected] >
主页: http://www.xfocus.org
时间: 2001-9-10
★概要:
这片文档从程序员的角度讨论了linux的ELF二进制格式。介绍了一些ELF执行
文件在运行控制的技术。展示了如何使用动态连接器和如何动态装载ELF。
我们也演示了如何在LINUX使用GNU C/C++编译器和一些其他工具来创建共享的
C/C++库。
★1前言
最初,UNIX系统实验室(USL)开发和发布了Executable and linking Format
(ELF)这样的二进制格式。在SVR4和Solaris 2.x上,都做为可执行文件默认的
二进制格式。ELF比a.out和COFF更强大更灵活。结合一些适当的工具,程序员
使用ELF就可以在运行时控制程序的流程。
★2 ELF类型
三种主要的ELF文件类型:
.可执行文件:包含了代码和数据。具有可执行的程序。
例如这样一个程序
# file dltest
dltest: ELF 32-bit LSB executable, Intel 80386, version 1,
dynamically linked (uses shared libs), not stripped
.可重定位文件:包含了代码和数据(这些数据是和其他重定位文件和共享的
object文件一起连接时使用的)
例如这样文件
# file libfoo.o
libfoo.o: ELF 32-bit LSB relocatable, Intel 80386, version 1,
not stripped
.共享object文件(又可叫做共享库):包含了代码和数据(这些数据是在连接
时候被连接器ld和运行时动态连接器使用的)。动态连接器可能称为
ld.so.1,libc.so.1 或者 ld-linux.so.1。
例如这样文件
# file libfoo.so
libfoo.so: ELF 32-bit LSB shared object, Intel 80386, version
1, not stripped
ELF section部分是非常有用的。使用一些正确的工具和技术,程序员就能
熟练的操作可执行文件的执行。
★3 .init和.fini sections
在ELF系统上,一个程序是由可执行文件或者还加上一些共享object文件组成。
为了执行这样的程序,系统使用那些文件创建进程的内存映象。进程映象
有一些段(segment),包含了可执行指令,数据,等等。为了使一个ELF文件
装载到内存,必须有一个program header(该program header是一个描述段
信息的结构数组和一些为程序运行准备的信息)。
一个段可能有多个section组成.这些section在程序员角度来看更显的重要。
每个可执行文件或者是共享object文件一般包含一个section table,该表
是描述ELF文件里sections的结构数组。这里有几个在ELF文档中定义的比较
特别的sections.以下这些是对程序特别有用的:
.fini
该section保存着进程终止代码指令。因此,当一个程序正常退出时,
系统安排执行这个section的中的代码。
.init
该section保存着可执行指令,它构成了进程的初始化代码。
因此,当一个程序开始运行时,在main函数被调用之前(c语言称为
main),系统安排执行这个section的中的代码。
.init和.fini sections的存在有着特别的目的。假如一个函数放到
.init section,在main函数执行前系统就会执行它。同理,假如一
个函数放到.fini section,在main函数返回后该函数就会执行。
该特性被C++编译器使用,完成全局的构造和析构函数功能。
当ELF可执行文件被执行,系统将在把控制权交给可执行文件前装载所以相关
的共享object文件。构造正确的.init和.fini sections,构造函数和析构函数
将以正确的次序被调用。
★3.1 在c++中全局的构造函数和析构函数
在c++中全局的构造函数和析构函数必须非常小心的处理碰到的语言规范问题。
构造函数必须在main函数之前被调用。析构函数必须在main函数返回之后
被调用。例如,除了一般的两个辅助启动文件crti.o和crtn.o外,GNU C/C++
编译器--gcc还提供两个辅助启动文件一个称为crtbegin.o,还有一个被称为
crtend.o。结合.ctors和.dtors两个section,c++全局的构造函数和析构函数
能以运行时最小的负载,正确的顺序执行。
.ctors
该section保存着程序的全局的构造函数的指针数组。
.dtors
该section保存着程序的全局的析构函数的指针数组。
ctrbegin.o
有四个section:
1 .ctors section
local标号__CTOR_LIST__指向全局构造函数的指针数组头。在
ctrbegin.o中的该数组只有一个dummy元素。