4

链接

定义:

 1. 将代码和数据收集并组成为单一文件的过程,且使得该文件可以被加载到存储器并执行。
 
 2. 链接可以发生于[编译]时,也可以发生在[加载]时,也可以执行于[运行]时。

7.1 编译器驱动程序

7.2 静态链接

如 Unix ld 程序这样的静态链接器以一组可重定位目标文件和命令行参数作为输入,生成一个完全链接的可以加载和运行的可执行目标
文件作为输出。
为了构造可执行文件,链接器完成的任务:

1、符号解析【目的:将每个符号引用和一个符号定义联系起来】

2、重定位【目的:链接器把每个符号定义与一个存储器位置联系起来,接着修改所有对这些符号的引用】

7.3 目标文件

三种形式:

1、可重定位目标文件

2、共享目标文件【可以在加载或运行时被动态加载到存储器并链接】

3、可执行目标文件

7.4 可重定位目标文件

图片描述

7.5 符号和符号表

每个可重定位目标模块m都有一个符号表,他包含m所定义和引用的符号的信息。
三种不同的符号:

1、m定义的全局符号【非static的函数以及非static的全局变量】

2、m引用的其他模块的全局符号

3、本地符号【m定义且只能被m引用的符号】

symtab中的符号表不包含对应于本地非静态程序变量的任何符号,这些符号在程序运行时由栈进行管理。
带有static属性的本地过程变量是不在栈中管理的,相反,编译器在.data或.bss中为这类变量分配空间,编译器把初始化为0的变量放在.bss而不是.data中,且在符号表中创建一个本地链接器符号。

7.6 符号解析

编译器遇到一个不是在当前模块中定义的符号时,他会假设该符号是在其他模块定义的,生成一个链接器符号表条目。
链接器在它的任何输入模块中若找不到这个被引用的符号,则报错

7.6.1 链接器如何解析多重定义的全局符号

函数和已初始化的全局变量是强符号,未初始化的全局变量是弱符号
规则:

1、不允许有多个强符号

2、若同时存在强弱符号,选择强符号

3、若有多个弱符号,任意选择一个弱符号【用 GCC-fno-common选项进行链接时,出现多重定义时,输出警告】

7.6.2 与静态库链接

假设存在两个文件:addvec.c,multvec.c(见课本p458), 一个main.c文件【调用了前两个文件中的函数】

gcc -c addvec.c multvec
ar rcs libvector.a addvec.o multvec.o // rcs参数,详见 man 1 ar

gcc -O2 -c main.c
#当静态库和动态库同时存在时,程序会优先使用动态库,
#如果需要强制只使用静态库,可使用 -static 选项,此时不会链接任何动态库,生成的目标文件会比较大
gcc -static -o a.out main.o ./libvector.a

7.6.3 链接器如何使用静态库来解析引用

1、在符号解析的阶段,链接器从左到右按照他们在编译器驱动程序命令行上出现的相同顺序来扫描可重定位目标文件和存档文件。
2、在一次扫描中,链接器维持一个可重定位目标文件的集合E,一个未解析的符号集合U, 以及一个在前面输入文件中
已经定义的符号集合D
步骤:

(课本P460)

7.7 重定位


符号解析完成之后,链接器知道它的输入目标模块中的代码节和数据节的确切大小。现在就可以重定位了。
在该阶段,将合并输入模块,并为每个符号分配运行时地址。
重定位的步骤:

1、重定位节和[符号定义]: 合并类型相同的节,将运行时存储器地址赋给新的节,赋给输入模块定义的每个符号

2、重定位节中的[符号引用]

7.7.1 重定位条目

无论何时汇编器遇到对最终位置未知的目标引用,它就会生成一个重定位条目,告诉链接器在将目标文件合并成可执行文件时如何修改
这个引用。代码的重定位条目放在.rel.text。已经初始化数据的重定位条目在.rel.data

重定位条目格式

typedef struct{
    int offset;/*需要被修改的引用的节偏移*/
    int symbol:24,/*标示被修改的引用应该指向的符号*/
        type:8;/*重定位类型*/
}Elf32_Rel;

7.7.2 重定位符号引用

1、重定位PC相对引用

2、重定位绝对引用

7.8 可执行目标文件 & 7.9 加载可执行目标文件

图片描述

7.10 动态链接共享库

共享库:一个目标模块,在运行时,可以加载到任意的存储器地址,并和一个在存储器中程序链接起来。
这个过程成为动态链接,是由一个[动态链接器]的程序执行的。
-shared -fPIC 疑问

gcc -shared -fPIC -o libvector.so addvec.c multvec.c

/*-fPIC,指示编译器生成与位置无关的代码*/
/*-shared, 指示链接器创建一个共享的目标文件*/

gcc -o main2 main2.c ./libvector.so

main2在未执行前没有任何有关libvector.so的代码和数据节内容。 链接器只是拷贝了一个重定位和符号表信息,他们在运行时可以解析对libvector.so中代码和数据的引用。main2在运行时可以和libvector.so链接

图片描述

7.11 从应用程序中加载和链接共享库

//example.c
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
int (*input)();
void output(int i);

int main(int argc, char* argv[]){
    void* handle;
    handle = dlopen("./lib.so", RTLD_LAZY);
    if (!handle){
        fprintf(stderr,"%s\n", dlerror());
        exit(1);
    }
    input = dlsym(handle, "input");
    if (!input){
        fprintf(stderr, "%s\n",dlerror());
        exit(1);
    }    
    int i = input();
    fprintf (stdout,"%i\n",i);
    if (dlclose(handle) < 0){
        fprintf(stderr, "%s\n", dlerror());
    }
    return 0;
}
gcc -rdynamic -O2 -o example example.c -ldl

/*-rdynamic 指示链接器将所有符号添加到动态符号表,便于dlopen等函数的使用*/
/*-ldl 生成的对象模块需要使用共享库*/

7.12 与位置无关的代码(PIC)

1、PIC数据引用

无论我们在存储器中的何处加载一个目标模块(包括共享目标模块),数据段总是被分配成紧随在代码段后面。因此,代码段中任何指令
和数据段中任何变量之间的距离都是一个运行时常量。

2、PIC函数调用
延时绑定


shiyang6017
158 声望59 粉丝