c链接,加载,装载(二)
编译驱动程序(compiler driver)
这代表用户在需要时调用语言预处理器、编译器、汇编器和链接器。
预处理器 cpp
c编译器 cc1
汇编器 as
链接器 ld
在需要时体现在,比如
unix> gcc -c xxx.c
经过:
gcc --help
得知:
-c Compile and assemble, but do not link
即生成一个xxx.o的可重定位的目标模块文件。
值得留意得是驱动程序经过相同的程序生成xxx.o,最后,它运行链接器程序ld,将main.o文件和xxx.o以及一些必要的系统目标文件组合起来,创建一个可执行目标文件。
像unix ld这样的静态链接器(static linker)以一组可重定位目标文件和命令行参数作为输入,生成一个完全链接的可以加载和运行的可执行目标文件作为输出。输入的可重定位目标文件由各种不同的代码和数据节(section)组成。指令在一个节中,初始化的全局变量在另一个节中,而未初始化的变量在另外一个节中。
unix> ./p
再次铺垫下,外壳调用操作系统中一个叫做加载器的函数,它拷贝可执行文件p中的代码和数据到存储器,然后将控制转移到这个程序的开头。
目标文件纯属字节块的组合。在这些块中,有些包含程序代码,有些则包含程序数据,而其他的则包含指导链接器和加载器的数据结构。链接器将这些块连接起来,确定被连接块的运行时位置, 并且修改代码和数据块中的各种位置。
目标文件
- 可重定位目标文件
- 可执行目标文件
- 共享目标文件
编译器和汇编器生成可重定位的目标文件(包括共享目标文件)。
链接器生成可执行目标文件。
目标文件的格式:
System V Unix | (COFF)Common Object File Format |
Windows NT | (PE)Portable Executable |
Unix(如Linux, System V Unix后来的版本,各种 BSD Unix等) | (ELF)Executable and Linkable Format |
以下是一个ELF格式的可重定位目标文件:
以下两个主题暂时跳过。
- 符号和符号表
- 符号解析
与静态库链接
在Unix系统中,静态库以一种称为存档(archive)的特殊文件格式存放在磁盘中。
存档文件是由一组连接起来的可重定位目标文件组合,有一个头部用来描述每个成员目标文件的大小和位置。
存档文件名由后缀.a标识。
静态库概念:相关函数可以被编译为独立的目标模块,然后封装成一个单独的静态库文件。然后应用程序可以通过命令行上指定单独的文件名字来使用这些在库中定义的函数。
unix> gcc main.c /usr/lib/libm.a
在链接时,链接器将只拷贝被程序引用的目标模块。
以上概念展示如下,提供数组"加法"与"乘法"的一个静态库:
--------code/addvec.c
void addvec(int *x, int *y, int *z, int n)
{
int i;
for(i = 0;i < n; i++)
z[i] = x[i] + y[i];
}
--------code/addvec.c
void multvec(int *x, int *y, int *z, int n)
{
int i;
for (i = 0; i < n; ++i)
{
z[i] = x[i] + y[i];
}
}
创建这个库:
unix> gcc addvec.c multvec.c
unix> ar rcs libvector.a addvec.o multvec.o
其中经过静态编译分别得到可重定位的addvec.o 和 multvec.o目标文件。
ar工具用以创建静态库文件,并命名为libvector.a。
为了使用这个静态库,以下是main的代码(顺带可以问下自己一个老问题,为什么每个C程序需要main函数?):
#include <stdio.h>
#include "vector.h"
int x[2] = {1, 2};
int y[2] = {3 ,4};
int z[2];
int main()
{
addvec(x, y, z, 2);
printf("z = [%d %d]\n", z[0], z[1]);
return 0;
}
很明显,这个程序调用了libvector.a中一个成员addvec.o的函数addvec。
为了创建可执行文件:
unix> gcc -c main2.c
unic> gcc -static -o p2 main2.o ./libvector.a
这个过程可表示如图:
其中左上角代表了第一条输入终端的命令,然后-static指示链接器完全链接可执行目标文件,这个可执行文件可以加载到存储器运行,在加载时无需进一步的链接。
值得注意的是图中仅仅指示了libvector.a中的addvec.o被拷贝到可执行文件。
额外地,链接器还会拷贝libc.a中的printf.o模块,以及许多C运行时系统中的其他模块。
以下主题省略没有详细看:
- 链接器如何使用静态库来解析引用
- 重定位PC相对引用/绝对引用
段头部表(segment header table)
回到可执行目标文件。然而链接器也可以将多个目标模块(如addvec.o)合并成一个可执行目标文件的。
C程序开始时时一组ASCCII文本文件,最后被转化为一个二进制文件。
需要留意的是这个二进制文件包含加载程序到存储器并运行它的所有信息。比如包括程序的入口点(entry point),也就是程序运行时执行的第一条指令的地址。
ELF可执行文件被设计的很容易加载到存储器(我受够这个翻译的名字了,不就是内存吗?难道还有其他地方?),可执行文件的连续的片(chunk)被映射到连续的存储器段。
图中对段头部表:Maps contiguous(连续的;相邻的) file sections to runtime memory segments
代码段:code segment,只读
数据段:data segment,可读写
相关更详细的数据要研究工具objdump(注意是小写):
指令:
linux> objdump -h a.out
a.out: file format elf64-x86-64 Sections: Idx Name Size VMA LMA File off Algn 0 .interp 0000001c 0000000000400200 0000000000400200 00000200 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA 1 .note.ABI-tag 00000020 000000000040021c 000000000040021c 0000021c 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 2 .note.gnu.build-id 00000024 000000000040023c 000000000040023c 0000023c 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 3 .gnu.hash 0000001c 0000000000400260 0000000000400260 00000260 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 4 .dynsym 00000048 0000000000400280 0000000000400280 00000280 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 5 .dynstr 00000038 00000000004002c8 00000000004002c8 000002c8 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA 6 .gnu.version 00000006 0000000000400300 0000000000400300 00000300 2**1 CONTENTS, ALLOC, LOAD, READONLY, DATA 7 .gnu.version_r 00000020 0000000000400308 0000000000400308 00000308 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 8 .rela.dyn 00000018 0000000000400328 0000000000400328 00000328 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 9 .rela.plt 00000018 0000000000400340 0000000000400340 00000340 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 10 .init 00000018 0000000000400358 0000000000400358 00000358 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 11 .plt 00000020 0000000000400370 0000000000400370 00000370 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 12 .text 000001d8 0000000000400390 0000000000400390 00000390 2**4 CONTENTS, ALLOC, LOAD, READONLY, CODE 13 .fini 0000000e 0000000000400568 0000000000400568 00000568 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 14 .rodata 00000010 0000000000400578 0000000000400578 00000578 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 15 .eh_frame_hdr 00000024 0000000000400588 0000000000400588 00000588 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 16 .eh_frame 0000007c 00000000004005b0 00000000004005b0 000005b0 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 17 .ctors 00000010 0000000000600630 0000000000600630 00000630 2**3 CONTENTS, ALLOC, LOAD, DATA 18 .dtors 00000010 0000000000600640 0000000000600640 00000640 2**3 CONTENTS, ALLOC, LOAD, DATA 19 .jcr 00000008 0000000000600650 0000000000600650 00000650 2**3 CONTENTS, ALLOC, LOAD, DATA 20 .dynamic 00000190 0000000000600658 0000000000600658 00000658 2**3 CONTENTS, ALLOC, LOAD, DATA 21 .got 00000008 00000000006007e8 00000000006007e8 000007e8 2**3 CONTENTS, ALLOC, LOAD, DATA 22 .got.plt 00000020 00000000006007f0 00000000006007f0 000007f0 2**3 CONTENTS, ALLOC, LOAD, DATA 23 .data 00000004 0000000000600810 0000000000600810 00000810 2**2 CONTENTS, ALLOC, LOAD, DATA 24 .bss 00000010 0000000000600818 0000000000600818 00000814 2**3 ALLOC 25 .comment 0000002d 0000000000000000 0000000000000000 00000814 2**0 CONTENTS, READONLY
未完的更精彩的主题:
加载器实际上是如何工作的?(这里牵扯到进程,虚拟存储器等概念,都是帮助了解程序的)
gcc -shared -fPIC -o libvector.so addvec.c multvec.c(动态链接器,加载时)
gcc -rdynamic -o p3 xxx.c -ldl(动态链接器,运行时)
以及PIC!
to be continued ...