一、目标文件
在 UNIX® 和 Linux® 中,任何事物都是文件。UNIX 和 Linux 编程实际上是编写处理各种文件的代码。系统由许多类型的文件组成,但目标文件具有一种特殊的设计,提供了灵活和多样的用途。
目标文件是包含带有附加地址和值的助记符号的路线图。这些符号可以用来对各种代码段和数据段进行命名,包括经过初始化的和未初始化的。它们也可以用来定位嵌入的调试信息,就像语义 Web,非常适合由程序进行阅读。
编译器可以将我们在代码编辑器中创建的文本转换为目标文件。最初,目标文件被称为代码的中间表示形式,因为它用作连接编辑器(即连接器)的输入,而连接编辑器最终完成整个任务并生成可执行的程序作为输出。
从代码到可执行代码的转换过程经过了良好的定义并实现了自动化,而目标文件是这个链中有机的连接性环节。在这个转换过程中,目标文件作为连接编辑器所使用的映象,使得它们能够解析各种符号并将不同的代码和数据段连接在一起形成统一的整体。
1.1 目标文件的格式
计算机编程领域中存在许多著名的目标文件格式。DOS 系列包括 COM、OBJ 和 EXE 格式。UNIX 和 Linux 使用 a.out、COFF 和 ELF。Microsoft® Windows® 使用可移植的执行文件 (PE) 格式,而 Macintosh 使用 PEF、Mach-O 和其他文件格式。
最初,各种类型的计算机具有自己独特的目标文件格式,但随着 UNIX 和其他在不同硬件平台上提供可移植性的操作系统的出现,一些常用的文件格式上升为通用的标准。其中包括 a.out、COFF 和 ELF 格式。
要了解目标文件,需要一组可以读取目标文件中不同部分并以更易于读取的格式显示这些内容的工具。
1.2 UNIX 目标文件
写一个简单的程序:hello.c
C 编译器的正常输出是用于所指定的目标处理器的汇编代码。汇编代码是汇编器的输入,在缺省情况下,汇编器将生成所有目标文件的祖先,即 a.out 文件。这个名称本身表示汇编输出 (Assembler Output)。要创建 a.out 文件,可以在 shell 窗口中输入下面的命令:gcc hello.c
最新的 C 编译器将编译和汇编步骤组合成一个步骤。我们可以指定不同开关选项以查看 C 编译器的汇编输出。通过输入下面的命令,我们可以看到 C 编译器的汇编输出:gcc -S hello.c
在当前目录下会生成 hello.s 文件,即为汇编输入文本。
-
接下来研究目标文件,其中使用的有价值的工具有:
- nm:列出目标文件中的符号。
- objdump:显示目标文件中的详细信息。
- readelf:显示关于 ELF 目标文件的信息。
1.2.1 nm:列出目标文件中的符号
执行命令:nm a.out
这些包含可执行代码的段称为正文段。同样地,数据段包含了不可执行的信息或数据。另一种类型的段,称为 BSS 段,它包含以符号数据开头的块。
对于 nm 命令列出的每个符号,它们的值使用十六进制来表示(缺省行为),并且在该符号前面加上了一个表示符号类型的编码字符。
-
常见的各种编码包括:
- A 表示绝对 (absolute),这意味着不能将该值更改为其他的连接;
- B 表示 BSS 段中的符号;
- C 表示引用未初始化的数据的一般符号。
可以将目标文件中所包含的不同的部分划分为段。段可以包含可执行代码、符号名称、初始数据值和许多其他类型的数据。
1.2.2 objdump:显示目标文件中的详细信息
通过输入下面的命令,可以看到目标文件中包含可执行代码的每个段的汇编清单。
objdump -d a.out
命令生成的代码如下所示:
View Code
每个可执行代码段将在需要特定的事件时执行,这些事件包括库的初始化和该程序本身主入口点。
对于着迷于底层编程细节的程序员来说,这是一个功能非常强大的工具,可用于研究编译器和汇编器的输出。细节信息,比如这段代码中所显示的这些信息,可以揭示有关本地处理器本身运行方式的很多内容。对该处理器制造商提供的技术文档进行深入的研究,我们可以收集关于一些有价值的信息,通过这些信息可以深入地了解内部的运行机制,因为功能程序提供了清晰的输出。
1.2.3 readelf:显示关于 ELF 目标文件的信息
readelf 程序也可以清楚地列出目标文件中的内容。输入下面的命令,可以看到这一点:
readelf -all a.out
输出如下:
View Code
ELF Header 为该文件中所有段入口显示了详细的摘要。在列举出这些 Header 中的内容之前,可以看到 Header 的具体数目。在研究一个较大的目标文件时,该信息可能非常有用。
从该输出中看到的,简单的 a.out Hello World 文件中包含了大量有价值的细节信息,包括版本信息、柱状图、各种符号类型的表格,等等。
除了所有这些段之外,编译器可以将调试信息放入到目标文件中,并且还可以显示这些信息。输入下面的命令,仔细分析编译器的输出(假设您扮演了调试程序的角色):readelf --debug-dump a.out | less
1.3 目标文件
在 UNIX 中,可执行文件是目标文件,并且我们可以像对 a.out 文件那样对它们进行分析。
如果倾向于使用编译器和其他的语言工具,那么您可以对组成计算机系统的各种目标文件进行仔细研究。UNIX 操作系统具有许多层次,那些通过工具查看目标文件所公开的层次,非常接近底层硬件。通过这种方式,可以真实地接触到系统。