本来想自己写一篇关于可执行程序的连接及程序执行时进程空间等问题的博客,结果被我发现了一篇好文章,遂翻译出来顺便学习。原文COMPILER, ASSEMBLER, LINKER AND LOADER:A BRIEF STORY,翻译主要还是更具自己的理解来,如有疑问请参考原文。
注意: 本文提供了一个进程(运行程序)相当多的细节故事。它试图探讨C/C++源代码如何预处理,编译,链接和加载为一个正在运行的程序。它是基于GCC(GNU编译器集合)来说明的。当你使用IDE(集成开发环境)编译器,如微软的Visual C + +,Borland的C++ Builder等,这里讨论过程对IDE用户来说是透明的。关于Linux的GNU GCC,G++,GDB等工具的命令和示例请参考这里[待添加]。
C编译器的能力: 能理解和预处理过程,编译,连接,加载和运行的C / C + +程序。
##1. 编译器,汇编器和连接器 COMPILERS, ASSEMBLERS and LINKERS
通常情况下,C程序的构建过程包括四个阶段,分别采用不同的“工具”,如预处理器,编译器,汇编器和链接器。在最后应该有一个单一的可执行文件。下面就是这几个阶段(忽略了操作系统/编译器差异)发生的顺序。表1和图1展示了这几个阶段。
如果你使用IDE进行开发,这些过程对你来说就是透明的。现在,我们将研究发生在链接阶段之前和之后过程中的更多细节。对于任何给定的输入文件,文件名后缀(文件扩展名)确定由什么样的编译工具来完成,下表给出了GCC的例子,关于这方面可以参考GCC的文档。在UNIX / Linux上,该可执行文件或二进制文件没有扩展名,而在Windows中的可执行文件,可能有.exe,.com和.dll的后缀。
下面的一张图给出了参与构建C程序,从编译开始,直到加载可执行文件成为内存中运行的程序(一个进程)的步骤,关于这些步骤的具体情况会在下面进行说明。
##2.目标文件和可执行 OBJECT FILES and EXECUTABLE
下面是一个用 readelf
工具读取目标对象内容的例子. 另外还可以使用的工具有 objdump
. 对windows来说可以使用dumpbin
工具,或者更强大的PEBrowseFor
.下面是代码:
/* testprog1.c */
#include <stdio.h>
static void display(int i, int *ptr);
int main(void)
{
int x = 5;
int *xptr = &x;
printf("In main() program:\n");
printf("x value is %d and is stored at address %p.\n", x, &x);
printf("xptr pointer points to address %p which holds a value of %d.\n", xptr, *xptr);
display(x, xptr);
return 0;
}
void display(int y, int *yptr)
{
char var[7] = "ABCDEF";
printf("In display() function:\n");
printf("y value is %d and is stored at address %p.\n", y, &y);
printf("yptr pointer points to address %p which holds a value of %d.\n", yptr, *yptr);
}
编译并查看目标文件:
$ gcc -c testprog1.c
$ readelf -a testprog1.o
输出:
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: REL (Relocatable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x0
Start of program headers: 0 (bytes into file)
Start of section headers: 672 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 40 (bytes)
Number of section headers: 11
Section header string table index: 8
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00000000 000034 0000de 00 AX 0 0 4
[ 2] .rel.text REL 00000000 00052c 000068 08 9 1 4
[ 3] .data PROGBIT 00000000 000114 000000 00 WA 0 0 4
[ 4] .bss NOBIT 00000000 000114 000000 00 WA 0 0 4
[ 5] .rodata PROGBITS 00000000 000114 00010a 00 A 0 0 4
[ 6] .note.GNU-stack PROGBITS 00000000 00021e 000000 00 0 0 1
[ 7] .comment PROGBITS 00000000 00021e 000031 00 0 0 1
[ 8] .shstrtab STRTAB 00000000 00024f 000051 00 0 0 1
[ 9] .symtab SYMTAB 00000000 000458 0000b0 10 10 9 4
[10] .strtab STRTAB 00000000 000508 000021 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
There are no program headers in this file.
Relocation section '.rel.text' at offset 0x52c contains 13 entries:
Offset Info Type Sym.Value Sym. Name
0000002d 00000501 R_386_32 00000000 .rodata
00000032 00000a02 R_386_PC32 00000000 printf
00000044 00000501 R_386_32 00000000 .rodata
00000049 00000a02 R_386_PC32 00000000 printf
0000005c 00000501 R_386_32 00000000 .rodata
00000061 00000a02 R_386_PC32 00000000 printf
0000008c 00000501 R_386_32 00000000 .rodata
0000009c 00000501 R_386_32 00000000 .rodata
000000a1 00000a02 R_386_PC32 00000000 printf
000000b3 00000501 R_386_32 00000000 .rodata
000000b8 00000a02 R_386_PC32 00000000 printf
000000cb 00000501 R_386_32 00000000 .rodata
000000d0 00000a02 R_386_PC32 00000000 printf
There are no unwind sections in this file.
Symbol table '.symtab' contains 11 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FILE LOCAL DEFAULT ABS testprog1.c
2: 00000000 0 SECTION LOCAL DEFAULT 1
3: 00000000 0 SECTION LOCAL DEFAULT 3
4: 00000000 0 SECTION LOCAL DEFAULT 4
5: 00000000 0 SECTION LOCAL DEFAULT 5
6: 00000080 94 FUNC LOCAL DEFAULT 1 display
7: 00000000 0 SECTION LOCAL DEFAULT 6
8: 00000000 0 SECTION LOCAL DEFAULT 7
9: 00000000 128 FUNC GLOBAL DEFAULT 1 main
10: 00000000 0 NOTYPE GLOBAL DEFAULT UND printf
No version information found in this file.
当使用汇编语言编写程序时,要和assembler directives的段选项相对应,下面是我们感兴趣的部分选项列表:
Assembler directives在用汇编语言编写程序时可以用来识别code, data段 ; allocate/initialize memory,让符号外部可见或者不可见。
##3.重定位记录RELOCATION RECORDS
因为目标文件可能包涵很多对其他目标文件代码或者(和)数据的引用,所以在链接的时候有相当多的地址需要被组合起来。 例如上面的代码,在main()中就包含了对printf()和display()的引用。 当把所有的目标文件连接在一起的时候,链接器使用重定位记录来确定那些地址。
###4.符号表 SYMBOL TABLE