深刻理解計算機系統 第七章 連接

連接

定義:函數

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函數調用 延時綁定

相關文章
相關標籤/搜索