- 论坛徽章:
- 2
|
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 的具体数目。在研究一个较大的目标文件时,该信息可能非常有用。 |
|