[转载]UNIX 目标文件初探

字体:        | 上一篇 下一篇 | 打印


Bill Zimmerly (bill@zimmerly.com), 自由撰稿人兼知识工程师, Author
FROM:IBM.COM
2007 年 3 月 06 日
UNIX(R) 系统中运行的程序遵守一种称为目标文件格式的精心设计。了解更多关于目标文件格式的内容,以及可以用来研究系统中目标文件的工具。

计算机编程的最新技术将一种特殊的人性与一组特殊的工具结合在一起,用以生产出对其他人非常有帮助的一种神奇的产品,即软件。计算机程序员是一群注重细节的人,他们可以处理计算机中各种各样的困难。计算机的要求非常苛刻,并且不能容忍其中存在任何的偏差。毫无疑问,无论您的个性如何以及在工作中使用了何种辅助工具,计算机程序的编写都是非常困难的。

在 UNIX® 和 Linux® 中,任何事物都是文件。您可以认为,UNIX 和 Linux 编程实际上是编写处理各种文件的代码。系统由许多类型的文件组成,但目标文件具有一种特殊的设计,提供了灵活和多样的用途。

目标文件是包含带有附加地址和值的助记符号的路线图。这些符号可以用来对各种代码段和数据段进行命名,包括经过初始化的和未初始化的。它们也可以用来定位嵌入的调试信息,就像语义 Web,非常适合由程序进行阅读。

行业工具

计算机编程中使用的工具包括代码编辑器,如 vi 或 Emacs,您可以使用这些工具输入和编辑希望计算机在完成所需任务时执行的指令,以及编译器和连接器,它们可以生成真正实现这些目标的机器代码。

高级的工具,称为集成调试环境 (IDE),它以统一的外观集成了不同工具的功能。IDE 使得编辑器、编译器、连接器和调试器之间的界限变得很模糊。因此,为了更深入地研究和了解系统,在使用集成的套件之前,最好先单独地使用这些工具。(注意:IDE 也通常被称为集成开发环境。)

编译器可以将您在代码编辑器中创建的文本转换为目标文件。最初,目标文件被称为代码的中间表示形式,因为它用作连接编辑器(即连接器)的输入,而连接编辑器最终完成整个任务并生成可执行的程序作为输出。

从代码到可执行代码的转换过程经过了良好的定义并实现了自动化,而目标文件是这个链中有机的连接性环节。在这个转换过程中,目标文件作为连接编辑器所使用的映象,使得它们能够解析各种符号并将不同的代码和数据段连接在一起形成统一的整体。

历史

计算机编程领域中存在许多著名的目标文件格式。DOS 系列包括 COM、OBJ 和 EXE 格式。UNIX 和 Linux 使用 a.out、COFF 和 ELF。Microsoft® Windows® 使用可移植的执行文件 (PE) 格式,而 Macintosh 使用 PEF、Mach-O 和其他文件格式。

最初,各种类型的计算机具有自己独特的目标文件格式,但随着 UNIX 和其他在不同硬件平台上提供可移植性的操作系统的出现,一些常用的文件格式上升为通用的标准。其中包括 a.out、COFF 和 ELF 格式。

要了解目标文件,需要一组可以读取目标文件中不同部分并以更易于读取的格式显示这些内容的工具。本文将讨论这些工具中比较重要的方面。但首先,您必须创建一个工作台,并在其中建立一个研究对象。

工作台

启动一个 xterm 会话,让我们先创建一个空白的工作台,并开始对目标文件进行研究。下面的命令创建了一个目录,可以将目标文件放到该目录中进行研究:cd

mkdir src
cd src
mkdir hw
cd hw



然后,使用您最喜欢的代码编辑器,在 $HOME/src/hw 目录中输入清单 1 中的程序,并命名为 hw.c。

清单 1. hw.c 程序

#include <stdio.h>

int main(void)
{
  printf("Hello World!\n");
  return 0;
}



要使用 UNIX 工具库中提供的各种工具,可以将这个简单的“Hello World”程序作为研究的对象。您将学习构建和查看目标文件的输出,而不是使用任何快捷方法直接创建可执行文件(的确有许多这样的快捷方法)。

文件格式

C 编译器的正常输出是用于您所指定的目标处理器的汇编代码。汇编代码是汇编器的输入,在缺省情况下,汇编器将生成所有目标文件的祖先,即 a.out 文件。这个名称本身表示汇编输出 (Assembler Output)。要创建 a.out 文件,可以在 xterm 窗口中输入下面的命令:cc hw.c



注意:如果出现了任何错误或者没有创建 a.out 文件,那么您可能需要检查自己的系统或源文件 (hw.c),以找出其中的错误。还需要检查是否已将 cc 定义为运行您的 C/C++ 编译器。

最新的 C 编译器将编译和汇编步骤组合成一个步骤。您可以指定不同开关选项以查看 C 编译器的汇编输出。通过输入下面的命令,您可以看到 C 编译器的汇编输出:cc -S hw.c



这个命令生成了一个新的文件 hw.s,其中包含您通常无法看到的汇编输入文本,因为编译器在缺省情况下将生成 a.out 文件。正如所预期的,UNIX 汇编程序可以对这种输入文件进行汇编,以生成 a.out 文件。

UNIX 特定的工具

假定编译过程一切顺利,那么在该目录中就有了一个 a.out 文件,下面让我们来对其进行研究。有许多可用于研究目标文件的有价值的工具,下面便是其中一组:
nm:列出目标文件中的符号。
objdump:显示目标文件中的详细信息。
readelf:显示关于 ELF 目标文件的信息。

列表中的第一个工具是 nm,它可以列出目标文件中的符号。如果您输入 nm 命令,您将注意到在缺省情况下,它会寻找一个名为 a.out 的文件。如果没有找到该文件,这个工具会给出相应的提示。然而,如果该工具找到了编译器创建的 a.out 文件,它将显示类似清单 2 的清单。

清单 2. nm 命令的输出

08049594 A __bss_start
080482e4 t call_gmon_start
08049594 b completed.4463
08049498 d __CTOR_END__
08049494 d __CTOR_LIST__
08049588 D __data_start
08049588 W data_start
0804842c t __do_global_ctors_aux
0804830c t __do_global_dtors_aux
0804958c D __dso_handle
080494a0 d __DTOR_END__
0804949c d __DTOR_LIST__
080494a8 d _DYNAMIC
08049594 A _edata
08049598 A _end
08048458 T _fini
08049494 a __fini_array_end
08049494 a __fini_array_start
08048478 R _fp_hw
0804833b t frame_dummy
08048490 r __FRAME_END__
08049574 d _GLOBAL_OFFSET_TABLE_
         w __gmon_start__
08048308 T __i686.get_pc_thunk.bx
08048278 T _init
08049494 a __init_array_end
08049494 a __init_array_start
0804847c R _IO_stdin_used
080494a4 d __JCR_END__
080494a4 d __JCR_LIST__
         w _Jv_RegisterClasses
080483e1 T __libc_csu_fini
08048390 T __libc_csu_init
         U __libc_start_main@@GLIBC_2.0
08048360 T main
08049590 d p.4462
         U puts@@GLIBC_2.0
080482c0 T _start



这些包含可执行代码的段称为正文段。同样地,数据段包含了不可执行的信息或数据。另一种类型的段,称为 BSS 段,它包含以符号数据开头的块。

对于 nm 命令列出的每个符号,它们的值使用十六进制来表示(缺省行为),并且在该符号前面加上了一个表示符号类型的编码字符。常见的各种编码包括:A 表示绝对 (absolute),这意味着不能将该值更改为其他的连接;B 表示 BSS 段中的符号;而 C 表示引用未初始化的数据的一般符号。

可以将目标文件中所包含的不同的部分划分为段。段可以包含可执行代码、符号名称、初始数据值和许多其他类型的数据。有关这些类型的数据的详细信息,可以阅读 UNIX 中 nm 的 man 页面,其中按照该命令输出中的字符编码分别对每种类型进行了描述。

细节,细节…

在目标文件阶段,即使是一个简单的 Hello World 程序,其中也包含了大量的细节信息。nm 程序可用于列举符号及其类型和值,但是,要更仔细地研究目标文件中这些命名段的内容,需要使用功能更强大的工具。

其中两种功能强大的工具是 objdump 和 readelf 程序。通过输入下面的命令,您可以看到目标文件中包含可执行代码的每个段的汇编清单。对于这么一个小的程序,编译器生成了这么多的代码,真的很令人惊异!objdump -d a.out



这个命令生成的输出如清单 3 所示。每个可执行代码段将在需要特定的事件时执行,这些事件包括库的初始化和该程序本身主入口点。

清单 3. objdump 命令的输出

a.out:     file format elf32-i386

Disassembly of section .init:

08048278 <_init>:
 8048278:       55                      push   %ebp
 8048279:       89 e5                   mov    %esp,%ebp
 804827b:       83 ec 08                sub    $0x8,%esp
 804827e:       e8 61 00 00 00          call   80482e4 <call_gmon_start>
 8048283:       e8 b3 00 00 00          call   804833b <frame_dummy>
 8048288:       e8 9f 01 00 00          call   804842c <__do_global_ctors_aux>
 804828d:       c9                      leave
 804828e:       c3                      ret
Disassembly of section .plt:

08048290 <puts@plt-0x10>:
 8048290:       ff 35 78 95 04 08       pushl  0x8049578
 8048296:       ff 25 7c 95 04 08       jmp    *0x804957c
 804829c:       00 00                   add    %al,(%eax)
        ...

080482a0 <puts@plt>:
 80482a0:       ff 25 80 95 04 08       jmp    *0x8049580
 80482a6:       68 00 00 00 00          push   $0x0
 80482ab:       e9 e0 ff ff ff          jmp    8048290 <_init+0x18>

080482b0 <__libc_start_main@plt>:
 80482b0:       ff 25 84 95 04 08       jmp    *0x8049584
 80482b6:       68 08 00 00 00          push   $0x8
 80482bb:       e9 d0 ff ff ff          jmp    8048290 <_init+0x18>
Disassembly of section .text:

080482c0 <_start>:
 80482c0:       31 ed                   xor    %ebp,%ebp
 80482c2:       5e                      pop    %esi
 80482c3:       89 e1                   mov    %esp,%ecx
 80482c5:       83 e4 f0                and    $0xfffffff0,%esp
 80482c8:       50                      push   %eax
 80482c9:       54                      push   %esp
 80482ca:       52                      push   %edx
 80482cb:       68 e1 83 04 08          push   $0x80483e1
 80482d0:       68 90 83 04 08          push   $0x8048390
 80482d5:       51                      push   %ecx
 80482d6:       56                      push   %esi
 80482d7:       68 60 83 04 08          push   $0x8048360
 80482dc:       e8 cf ff ff ff          call   80482b0 <__libc_start_main@plt>
 80482e1:       f4                      hlt
 80482e2:       90                      nop
 80482e3:       90                      nop

080482e4 <call_gmon_start>:
 80482e4:       55                      push   %ebp
 80482e5:       89 e5                   mov    %esp,%ebp
 80482e7:       53                      push   %ebx
 80482e8:       e8 1b 00 00 00          call   8048308 <__i686.get_pc_thunk.bx>
 80482ed:       81 c3 87 12 00 00       add    $0x1287,%ebx
 80482f3:       83 ec 04                sub    $0x4,%esp
 80482f6:       8b 83 fc ff ff ff       mov    0xfffffffc(%ebx),%eax
 80482fc:       85 c0                   test   %eax,%eax
 80482fe:       74 02                   je     8048302 <call_gmon_start+0x1e>
 8048300:       ff d0                   call   *%eax
 8048302:       83 c4 04                add    $0x4,%esp
 8048305:       5b                      pop    %ebx
 8048306:       5d                      pop    %ebp
 8048307:       c3                      ret

08048308 <__i686.get_pc_thunk.bx>:
 8048308:       8b 1c 24                mov    (%esp),%ebx
 804830b:       c3                      ret

0804830c <__do_global_dtors_aux>:
 804830c:       55                      push   %ebp
 804830d:       89 e5                   mov    %esp,%ebp
 804830f:       83 ec 08                sub    $0x8,%esp
 8048312:       80 3d 94 95 04 08 00    cmpb   $0x0,0x8049594
 8048319:       74 0c                   je     8048327 <__do_global_dtors_aux+0x1b>
 804831b:       eb 1c                   jmp    8048339 <__do_global_dtors_aux+0x2d>
 804831d:       83 c0 04                add    $0x4,%eax
 8048320:       a3 90 95 04 08          mov    %eax,0x8049590
 8048325:       ff d2                   call   *%edx
 8048327:       a1 90 95 04 08          mov    0x8049590,%eax
 804832c:       8b 10                   mov    (%eax),%edx
 804832e:       85 d2                   test   %edx,%edx
 8048330:       75 eb                   jne    804831d <__do_global_dtors_aux+0x11>
 8048332:       c6 05 94 95 04 08 01    movb   $0x1,0x8049594
 8048339:       c9                      leave
 804833a:       c3                      ret

0804833b <frame_dummy>:
 804833b:       55                      push   %ebp
 804833c:       89 e5                   mov    %esp,%ebp
 804833e:       83 ec 08                sub    $0x8,%esp
 8048341:       a1 a4 94 04 08          mov    0x80494a4,%eax
 8048346:       85 c0                   test   %eax,%eax
 8048348:       74 12                   je     804835c <frame_dummy+0x21>
 804834a:       b8 00 00 00 00          mov    $0x0,%eax
 804834f:       85 c0                   test   %eax,%eax
 8048351:       74 09                   je     804835c <frame_dummy+0x21>
 8048353:       c7 04 24 a4 94 04 08    movl   $0x80494a4,(%esp)
 804835a:       ff d0                   call   *%eax
 804835c:       c9                      leave
 804835d:       c3                      ret
 804835e:       90                      nop
 804835f:       90                      nop

08048360 <main>:
 8048360:       55                      push   %ebp
 8048361:       89 e5                   mov    %esp,%ebp
 8048363:       83 ec 08                sub    $0x8,%esp
 8048366:       83 e4 f0                and    $0xfffffff0,%esp
 8048369:       b8 00 00 00 00          mov    $0x0,%eax
 804836e:       83 c0 0f                add    $0xf,%eax
 8048371:       83 c0 0f                add    $0xf,%eax
 8048374:       c1 e8 04                shr    $0x4,%eax
 8048377:       c1 e0 04                shl    $0x4,%eax
 804837a:       29 c4                   sub    %eax,%esp
 804837c:       c7 04 24 80 84 04 08    movl   $0x8048480,(%esp)
 8048383:       e8 18 ff ff ff          call   80482a0 <puts@plt>
 8048388:       b8 00 00 00 00          mov    $0x0,%eax
 804838d:       c9                      leave
 804838e:       c3                      ret
 804838f:       90                      nop

08048390 <__libc_csu_init>:
 8048390:       55                      push   %ebp
 8048391:       89 e5                   mov    %esp,%ebp
 8048393:       57                      push   %edi
 8048394:       56                      push   %esi
 8048395:       31 f6                   xor    %esi,%esi
 8048397:       53                      push   %ebx
 8048398:       e8 6b ff ff ff          call   8048308 <__i686.get_pc_thunk.bx>
 804839d:       81 c3 d7 11 00 00       add    $0x11d7,%ebx
 80483a3:       83 ec 0c                sub    $0xc,%esp
 80483a6:       e8 cd fe ff ff          call   8048278 <_init>
 80483ab:       8d 83 20 ff ff ff       lea    0xffffff20(%ebx),%eax
 80483b1:       8d 93 20 ff ff ff       lea    0xffffff20(%ebx),%edx
 80483b7:       89 45 f0                mov    %eax,0xfffffff0(%ebp)
 80483ba:       29 d0                   sub    %edx,%eax
 80483bc:       c1 f8 02                sar    $0x2,%eax
 80483bf:       39 c6                   cmp    %eax,%esi
 80483c1:       73 16                   jae    80483d9 <__libc_csu_init+0x49>
 80483c3:       89 d7                   mov    %edx,%edi
 80483c5:       ff 14 b2                call   *(%edx,%esi,4)
 80483c8:       8b 45 f0                mov    0xfffffff0(%ebp),%eax
 80483cb:       83 c6 01                add    $0x1,%esi
 80483ce:       29 f8                   sub    %edi,%eax
 80483d0:       89 fa                   mov    %edi,%edx
 80483d2:       c1 f8 02                sar    $0x2,%eax
 80483d5:       39 c6                   cmp    %eax,%esi
 80483d7:       72 ec                   jb     80483c5 <__libc_csu_init+0x35>
 80483d9:       83 c4 0c                add    $0xc,%esp
 80483dc:       5b                      pop    %ebx
 80483dd:       5e                      pop    %esi
 80483de:       5f                      pop    %edi
 80483df:       5d                      pop    %ebp
 80483e0:       c3                      ret

080483e1 <__libc_csu_fini>:
 80483e1:       55                      push   %ebp
 80483e2:       89 e5                   mov    %esp,%ebp
 80483e4:       83 ec 18                sub    $0x18,%esp
 80483e7:       89 5d f4                mov    %ebx,0xfffffff4(%ebp)
 80483ea:       e8 19 ff ff ff          call   8048308 <__i686.get_pc_thunk.bx>
 80483ef:       81 c3 85 11 00 00       add    $0x1185,%ebx
 80483f5:       89 75 f8                mov    %esi,0xfffffff8(%ebp)
 80483f8:       89 7d fc                mov    %edi,0xfffffffc(%ebp)
 80483fb:       8d b3 20 ff ff ff       lea    0xffffff20(%ebx),%esi
 8048401:       8d bb 20 ff ff ff       lea    0xffffff20(%ebx),%edi
 8048407:       29 fe                   sub    %edi,%esi
 8048409:       c1 fe 02                sar    $0x2,%esi
 804840c:       eb 03                   jmp    8048411 <__libc_csu_fini+0x30>
 804840e:       ff 14 b7                call   *(%edi,%esi,4)
 8048411:       83 ee 01                sub    $0x1,%esi
 8048414:       83 fe ff                cmp    $0xffffffff,%esi
 8048417:       75 f5                   jne    804840e <__libc_csu_fini+0x2d>
 8048419:       e8 3a 00 00 00          call   8048458 <_fini>
 804841e:       8b 5d f4                mov    0xfffffff4(%ebp),%ebx
 8048421:       8b 75 f8                mov    0xfffffff8(%ebp),%esi
 8048424:       8b 7d fc                mov    0xfffffffc(%ebp),%edi
 8048427:       89 ec                   mov    %ebp,%esp
 8048429:       5d                      pop    %ebp
 804842a:       c3                      ret
 804842b:       90                      nop

0804842c <__do_global_ctors_aux>:
 804842c:       55                      push   %ebp
 804842d:       89 e5                   mov    %esp,%ebp
 804842f:       53                      push   %ebx
 8048430:       83 ec 04                sub    $0x4,%esp
 8048433:       a1 94 94 04 08          mov    0x8049494,%eax
 8048438:       83 f8 ff                cmp    $0xffffffff,%eax
 804843b:       74 12                   je     804844f <__do_global_ctors_aux+0x23>
 804843d:       bb 94 94 04 08          mov    $0x8049494,%ebx
 8048442:       ff d0                   call   *%eax
 8048444:       8b 43 fc                mov    0xfffffffc(%ebx),%eax
 8048447:       83 eb 04                sub    $0x4,%ebx
 804844a:       83 f8 ff                cmp    $0xffffffff,%eax
 804844d:       75 f3                   jne    8048442 <__do_global_ctors_aux+0x16>
 804844f:       83 c4 04                add    $0x4,%esp
 8048452:       5b                      pop    %ebx
 8048453:       5d                      pop    %ebp
 8048454:       c3                      ret
 8048455:       90                      nop
 8048456:       90                      nop
 8048457:       90                      nop
Disassembly of section .fini:

08048458 <_fini>:
 8048458:       55                      push   %ebp
 8048459:       89 e5                   mov    %esp,%ebp
 804845b:       53                      push   %ebx
 804845c:       e8 a7 fe ff ff          call   8048308 <__i686.get_pc_thunk.bx>
 8048461:       81 c3 13 11 00 00       add    $0x1113,%ebx
 8048467:       83 ec 04                sub    $0x4,%esp
 804846a:       e8 9d fe ff ff          call   804830c <__do_global_dtors_aux>
 804846f:       83 c4 04                add    $0x4,%esp
 8048472:       5b                      pop    %ebx
 8048473:       5d                      pop    %ebp
 8048474:       c3                      ret



对于那些着迷于底层编程细节的程序员来说,这是一个功能非常强大的工具,可用于研究编译器和汇编器的输出。细节信息,比如这段代码中所显示的这些信息,可以揭示有关本地处理器本身运行方式的很多内容。对该处理器制造商提供的技术文档进行深入的研究,您可以收集关于一些有价值的信息,通过这些信息可以深入地了解内部的运行机制,因为功能程序提供了清晰的输出。

类似地,readelf 程序也可以清楚地列出目标文件中的内容。输入下面的命令,您将可以看到这一点:readelf -all a.out



这个命令生成的输出如清单 4 所示。ELF Header 为该文件中所有段入口显示了详细的摘要。在列举出这些 Header 中的内容之前,您可以看到 Header 的具体数目。在研究一个较大的目标文件时,该信息可能非常有用。



查看全部评论(0)我来说两句

-5 -3 -1 - +1 +3 +5