深刻理解计算机系统 第七章 连接

连接

定义:函数

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

7.1 编译器驱动程序

7.2 静态连接

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

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

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

7.3 目标文件

三种形式:.net

一、可重定位目标文件

二、共享目标文件【能够在加载或运行时被动态加载到存储器并连接】

三、可执行目标文件

7.4 可重定位目标文件

图片描述

7.5 符号和符号表

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

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

二、m引用的其余模块的全局符号

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

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

7.6 符号解析

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

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

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

一、不容许有多个强符号

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

三、如有多个弱符号,任意选择一个弱符号【用 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 连接器如何使用静态库来解析引用

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

(课本P460)

7.7 重定位


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

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

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

7.7.1 重定位条目

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

重定位条目格式

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

7.7.2 重定位符号引用

一、重定位PC相对引用

二、重定位绝对引用

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)

一、PIC数据引用

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

二、PIC函数调用 延时绑定

相关文章
相关标签/搜索