免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12345下一页
最近访问板块 发新帖
查看: 9015 | 回复: 43
打印 上一主题 下一主题

从程序员角度看ELF[z [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2003-06-24 22:41 |只看该作者 |倒序浏览
转自http://www.xfocus.net/articles/200109/260.html

这篇文章从ELF的文件格式讨论到了ELF的构成及动态库的构成
是任何一个想深入了解UNIX编程的程序员应该深读的



创建时间:2001-09-15
文章属性:翻译
文章来源:http://www.xfocus.org
文章提交:alert7 (sztcww_at_sina.com)

从程序员角度看ELF

原文:《 ELF:From The Programmer's Perspective》

作者:Hongjiu Lu <mailto: hjl@nynexst.com>;
    NYNEX Science & Technology, Inc.
    500 Westchester Avenue
    White Plains, NY 10604, USA

翻译:alert7 <mailto: alert7@21cn.com
                      alert7@xfocus.org
             >;

主页: http://www.xfocus.org
时间: 2001-9-10

论坛徽章:
0
2 [报告]
发表于 2003-06-24 22:42 |只看该作者

从程序员角度看ELF[z

★概要:

这片文档从程序员的角度讨论了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就可以在运行时控制程序的流程。

论坛徽章:
0
3 [报告]
发表于 2003-06-24 22:42 |只看该作者

从程序员角度看ELF[z

★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部分是非常有用的。使用一些正确的工具和技术,程序员就能
熟练的操作可执行文件的执行。

论坛徽章:
0
4 [报告]
发表于 2003-06-24 22:43 |只看该作者

从程序员角度看ELF[z

★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,构造函数和析构函数
将以正确的次序被调用。

论坛徽章:
0
5 [报告]
发表于 2003-06-24 22:43 |只看该作者

从程序员角度看ELF[z

★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元素。

    [译注:
    # objdump -s -j .ctors                 
    /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/crtbegin.o

    /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/crtbegin.o:
    file format elf32-i386
    Contents of section .ctors:
    0000 ffffffff                             ....
    这里说的dummy元素应该就是指的是ffffffff
    ]

    2 .dtors section
    local标号__DTOR_LIST__指向全局析构函数的指针数组头。在
    ctrbegin.o中的该数组仅有也只有一个dummy元素。

    3 .text section
    只包含了__do_global_dtors_aux函数,该函数遍历__DTOR_LIST__
    列表,调用列表中的每个析构函数。
函数如下:

  1. Disassembly of section .text:

  2. 00000000 <__do_global_dtors_aux>;:
  3.    0:   55                      push   %ebp
  4.    1:   89 e5                   mov    %esp,%ebp
  5.    3:   83 3d 04 00 00 00 00    cmpl   $0x0,0x4
  6.    a:   75 38                   jne    44 <__do_global_dtors_aux+0x44>;
  7.    c:   eb 0f                   jmp    1d <__do_global_dtors_aux+0x1d>;
  8.    e:   89 f6                   mov    %esi,%esi
  9.   10:   8d 50 04                lea    0x4(%eax),%edx
  10.   13:   89 15 00 00 00 00       mov    %edx,0x0
  11.   19:   8b 00                   mov    (%eax),%eax
  12.   1b:   ff d0                   call   *%eax
  13.   1d:   a1 00 00 00 00          mov    0x0,%eax
  14.   22:   83 38 00                cmpl   $0x0,(%eax)
  15.   25:   75 e9                   jne    10 <__do_global_dtors_aux+0x10>;
  16.   27:   b8 00 00 00 00          mov    $0x0,%eax
  17.   2c:   85 c0                   test   %eax,%eax
  18.   2e:   74 0a                   je     3a <__do_global_dtors_aux+0x3a>;
  19.   30:   68 00 00 00 00          push   $0x0
  20.   35:   e8 fc ff ff ff          call   36 <__do_global_dtors_aux+0x36>;
  21.   3a:   c7 05 04 00 00 00 01    movl   $0x1,0x4
  22.   41:   00 00 00
  23.   44:   c9                      leave
  24.   45:   c3                      ret
  25.   46:   89 f6                   mov    %esi,%esi
复制代码

论坛徽章:
0
6 [报告]
发表于 2003-06-24 22:44 |只看该作者

从程序员角度看ELF[z

esi


    4 .fini section
    它只包含一个__do_global_dtors_aux的函数调用。请记住,它仅是
    一个函数调用而不返回的,因为crtbegin.o的.fini section是这个
    函数体的一部分。
函数如下:
Disassembly of section .fini:

00000000 <.fini>;:
   0:   e8 fc ff ff ff          call   1 <.fini+0x1>;


crtend.o
    也有四个section:

    1 .ctors section
    local标号__CTOR_END__指向全局构造函数的指针数组尾部。

    2 .dtors section
    local标号__DTOR_END__指向全局析构函数的指针数组尾部。

    3 .text section
    只包含了__do_global_ctors_aux函数,该函数遍历__CTOR_LIST__
    列表,调用列表中的每个构造函数。
函数如下:

  1. 00000000 <__do_global_ctors_aux>;:
  2.    0:   55                      push   %ebp
  3.    1:   89 e5                   mov    %esp,%ebp
  4.    3:   53                      push   %ebx
  5.    4:   bb fc ff ff ff          mov    $0xfffffffc,%ebx
  6.    9:   83 3d fc ff ff ff ff    cmpl   $0xffffffff,0xfffffffc
  7.   10:   74 0c                   je     1e <__do_global_ctors_aux+0x1e>;
  8.   12:   8b 03                   mov    (%ebx),%eax
  9.   14:   ff d0                   call   *%eax
  10.   16:   83 c3 fc                add    $0xfffffffc,%ebx
  11.   19:   83 3b ff                cmpl   $0xffffffff,(%ebx)
  12.   1c:   75 f4                   jne    12 <__do_global_ctors_aux+0x12>;
  13.   1e:   8b 5d fc                mov    0xfffffffc(%ebp),%ebx
  14.   21:   c9                      leave
  15.   22:   c3                      ret
  16.   23:   90                      nop
复制代码

论坛徽章:
0
7 [报告]
发表于 2003-06-24 22:44 |只看该作者

从程序员角度看ELF[z

nop

    4 .init section
    它只包含一个__do_global_ctors_aux的函数调用。请记住,它仅是
    一个函数调用而不返回的,因为crtend.o的.init section是这个函
    数体的一部分。
函数如下:
Disassembly of section .init:

00000000 <.init>;:
   0:   e8 fc ff ff ff          call   1 <.init+0x1>;

论坛徽章:
0
8 [报告]
发表于 2003-06-24 22:45 |只看该作者

从程序员角度看ELF[z

crti.o
    在.init section中仅是个_init的函数标号。
    在.fini section中的_fini函数标号。

crtn.o
    在.init和.fini section中仅是返回指令。

Disassembly of section .init:

00000000 <.init>;:
   0:   8b 5d fc                mov    0xfffffffc(%ebp),%ebx
   3:   c9                      leave
   4:   c3                      ret
Disassembly of section .fini:

00000000 <.fini>;:
   0:   8b 5d fc                mov    0xfffffffc(%ebp),%ebx
   3:   c9                      leave
   4:   c3                      ret

论坛徽章:
0
9 [报告]
发表于 2003-06-24 22:45 |只看该作者

从程序员角度看ELF[z

编译产生可重定位文件时,gcc把每个全局构造函数挂在__CTOR_LIST上
(通过把指向构造函数的指针放到.ctors section中)。
它也把每个全局析构函挂在__DTOR_LIST上(通过把指向析构函的指针
放到.dtors section中)。

连接时,gcc在所有重定位文件前处理crtbegin.o,在所有重定位文件后处理
crtend.o。另外,crti.o在crtbegin.o之前被处理,crtn.o在crtend.o之后
被处理。

当产生可执行文件时,连接器ld分别的连接所有可重定位文件的ctors 和
.dtors section到__CTOR_LIST__和__DTOR_LIST__列表中。.init section
由所有的可重定位文件中_init函数组成。.fini由_fini函数组成。

运行时,系统将在main函数之前执行_init函数,在main函数返回后执行
_fini函数。

论坛徽章:
0
10 [报告]
发表于 2003-06-24 22:45 |只看该作者

从程序员角度看ELF[z

★4 ELF的动态连接与装载

★4.1 动态连接

当在UNIX系统下,用C编译器把C源代码编译成可执行文件时,c编译驱动器一般
将调用C的预处理,编译器,汇编器和连接器。

.     c编译驱动器首先把C源代码传到C的预处理器,它以处理过的宏和
    指示器形式输出纯C语言代码。

.    c编译器把处理过的C语言代码翻译为机器相关的汇编代码。

.    汇编器把结果的汇编语言代码翻译成目标的机器指令。结果这些
    机器指令就被存储成指定的二进制文件格式,在这里,我们使用的
    ELF格式。

.    最后的阶段,连接器连接所有的object文件,加入所有的启动代码和
    在程序中引用的库函数。

    下面有两种方法使用lib库
   
    --static library
    一个集合,包含了那些object文件中包含的library例程和数据。用
    该方法,连接时连接器将产生一个独立的object文件(这些
    object文件保存着程序所要引用的函数和数据)的copy。
   
    --shared library
    是共享文件,它包含了函数和数据。用这样连接出来的程序仅在可执行
    程序中存储着共享库的名字和一些程序引用到的标号。在运行时,动态
    连接器(在ELF中也叫做程序解释器)将把共享库映象到进程的虚拟
    地址空间里去,通过名字解析在共享库中的标号。该处理过程也称为
    动态连接(dynamic linking)

程序员不需要知道动态连接时用到的共享库做什么,每件事情对程序员都是
透明的。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP