程序的链接和装入及Linux下动态链接的实现. 链接器和装入器的基本工作原理 一个程序要想在内存中运行,除了编译之外还要经过链接和装入这两个步骤。 从程序员的角度来看,引入这两个步骤带来的好处就是可以直接在程序中使用printf和errno这种有意义的函数名和变量名,而不用明确指明printf和errno在标准C库中的地址。 当然,为了将程序员从早期直接使用地址编程的梦魇中解救出来,编译器和汇编器在这当中做出了革命性的贡献。 编译器和汇编器的出现使得程序员可以在程序中使用更具意义的符号来为函数和变量命名,这样使得程序在正确性和可读性等方面都得到了极大的提高。 但是随着C语言这种支持分别编译的程序设计语言的流行,一个完整的程序往往被分割为若干个独立的部分并行开发,而各个模块间通过函数接口或全局变量进行通讯。
这就带来了一个问题,编译器只能在一个模块内部完成符号名到地址的转换工作,不同模块间的符号解析由谁来做呢? 为了解决不同模块间的链接问题,链接器主要有两个工作要做――符号解析和重定位: 符号解析:当一个模块使用了在该模块中没有定义过的函数或全局变量时,编译器生成的符号表会标记出所有这样的函数或全局变量,而链接器的责任就是要到别的模块中去查找它们的定义,如果没有找到合适的定义或者找到的合适的定义不唯一,符号解析都无法正常完成。 重定位:编译器在编译生成目标文件时,通常都使用从零开始的相对地址。 举个简单的例子,上面的概念对读者来说就一目了然了。 /* m.c */ int i = 1; int j = 2; extern int sum(); void main() { int s; s = sum(i, j); /* f.c */ int sum(int i, int j) { return i + j; } 在Linux用gcc分别将两段源程序编译成目标文件: $ gcc -c m.c $ gcc -c f.c 我们通过objdump来看看在编译过程中生成的符号表和重定位表: 首先,我们注意到符号表里面的sum被标记为UND(undefined),也就是在m.o中没有定义,所以将来要通过ld(Linux下的链接器)的符号解析功能到别的模块中去查找是否存在函数sum的定义。 以sum为例,对函数sum的调用是通过call指令实现的,使用IP相对寻址方式。 可执行程序生成后,下一步就是将其装入内存运行。 回页首 链接和装入技术的发展史 1. 2. 3. Linux下GCC使用方法简介 - 自由的天堂. GCC 中文手册. Linux内存管理浅析. The Linux Kernel Archives.