日B视频 亚洲,啪啪啪网站一区二区,91色情精品久久,日日噜狠狠色综合久,超碰人妻少妇97在线,999青青视频,亚洲一区二卡,让本一区二区视频,日韩网站推荐

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

動(dòng)態(tài)鏈接過(guò)程中是如何進(jìn)行符號(hào)重定位的

Linux愛好者 ? 來(lái)源:Linux愛好者 ? 作者:Linux愛好者 ? 2022-06-02 14:52 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

在上一篇文章中,我們一起學(xué)習(xí)了Linux系統(tǒng)中 GCC編譯器在編譯可執(zhí)行程序時(shí),靜態(tài)鏈接過(guò)程中是如何進(jìn)行符號(hào)重定位的。GCC 鏈接過(guò)程中的【重定位】過(guò)程分析

為了完整性,我們這篇文章來(lái)一起探索一下:動(dòng)態(tài)鏈接過(guò)程中是如何進(jìn)行符號(hào)重定位的。

老樣子,文中使用大量的【代碼+圖片】的方式,來(lái)真實(shí)的感受一下實(shí)際的內(nèi)存模型。

文中使用了大量的圖片,建議您在電腦上閱讀此文。

關(guān)于為什么使用動(dòng)態(tài)鏈接,這里就不展開討論了,無(wú)非就幾點(diǎn):

  1. 節(jié)省物理內(nèi)存;

  2. 可以動(dòng)態(tài)更新;

動(dòng)態(tài)鏈接要解決什么問(wèn)題?

靜態(tài)鏈接得到的可執(zhí)行程序,被操作系統(tǒng)加載之后就可以執(zhí)行執(zhí)行。

因?yàn)樵阪溄拥臅r(shí)候,鏈接器已經(jīng)把所有目標(biāo)文件中的代碼、數(shù)據(jù)等Section,都組裝到可執(zhí)行文件中了。

并且把代碼中所有使用的外部符號(hào)(變量、函數(shù)),都進(jìn)行了重定位(即:把變量、函數(shù)的地址,都填寫到代碼段中需要重定位的地方),因此可執(zhí)行程序在執(zhí)行的時(shí)候,不依賴于其它的外部模塊即可運(yùn)行。

詳細(xì)的靜態(tài)鏈接過(guò)程,請(qǐng)參考上一篇文章:GCC 鏈接過(guò)程中的【重定位】過(guò)程分析。

也就是說(shuō):符號(hào)重定位的過(guò)程,是直接對(duì)可執(zhí)行文件進(jìn)行修改。

但是對(duì)于動(dòng)態(tài)鏈接來(lái)說(shuō),在編譯階段,僅僅是在可執(zhí)行文件或者動(dòng)態(tài)庫(kù)中記錄了一些必要的信息。

真正的重定位過(guò)程,是在這個(gè)時(shí)間點(diǎn)來(lái)完成的:可執(zhí)行程序、動(dòng)態(tài)庫(kù)被加載之后,調(diào)用可執(zhí)行程序的入口函數(shù)之前。

只有當(dāng)所有需要被重定位的符號(hào)被解決了之后,才能開始執(zhí)行程序。

既然也是重定位,與靜態(tài)鏈接過(guò)程一樣:也需要把符號(hào)的目標(biāo)地址填寫到代碼段中需要重定位的地方。

矛盾:代碼段不可寫

問(wèn)題來(lái)了!

我們知道,在現(xiàn)代操作系統(tǒng)中,對(duì)于內(nèi)存的訪問(wèn)是有權(quán)限控制的,一般來(lái)說(shuō):

代碼段:可讀、可執(zhí)行;

數(shù)據(jù)段:可讀、可寫;

如果進(jìn)行符號(hào)重定位,就需要對(duì)代碼進(jìn)行修改(填寫符號(hào)的地址),但是代碼段又沒(méi)有可寫的權(quán)限,這是一個(gè)矛盾!

251fd06a-e228-11ec-ba43-dac502259ad0.png

解決這個(gè)矛盾的方案,就是Linux系統(tǒng)中動(dòng)態(tài)鏈接器的核心工作!

解決矛盾:增加一層間接性

David Wheeler有一句名言:“計(jì)算機(jī)科學(xué)中的大多數(shù)問(wèn)題,都可以通過(guò)增加一層間接性來(lái)解決?!?/p>

解決動(dòng)態(tài)鏈接中的代碼重定位問(wèn)題,同樣也可以通過(guò)增加一層間接性來(lái)解決。

既然代碼段在被加載到內(nèi)存中之后不可寫,但是數(shù)據(jù)段是可寫的。

在代碼段中引用的外部符號(hào),可以在數(shù)據(jù)段中增加一個(gè)跳板:讓代碼段先引用數(shù)據(jù)段中的內(nèi)容,然后在重定位時(shí),把外部符號(hào)的地址填寫到數(shù)據(jù)段中對(duì)應(yīng)的位置,不就解決這個(gè)矛盾了嗎?!

如下圖所示:

25528d70-e228-11ec-ba43-dac502259ad0.png

理解了上圖的解決思路,基本上就理解了動(dòng)態(tài)鏈接過(guò)程中重定位的核心思想。

示例代碼

我們需要3個(gè)源文件來(lái)討論動(dòng)態(tài)鏈接中重定位的過(guò)程:main.ca.c、b.c,其中的a.cb.c被編譯成動(dòng)態(tài)庫(kù),然后main.c與這兩個(gè)動(dòng)態(tài)庫(kù)一起動(dòng)態(tài)鏈接成可執(zhí)行程序。

它們之間的依賴關(guān)系是:

2583e64a-e228-11ec-ba43-dac502259ad0.png

b.c

代碼如下:

#include 

int b = 30;

void func_b(void)
{
    printf("in func_b. b = %d 
", b);
}

代碼說(shuō)明:

定義一個(gè)全局變量和一個(gè)全局函數(shù),被 a.c 調(diào)用。

a.c

代碼如下(稍微復(fù)雜一些,主要是為了探索:不同類型的符號(hào)如何處理重定位):

#include 

// 內(nèi)部定義【靜態(tài)】全局變量
static int a1 = 10;

// 內(nèi)部定義【非靜態(tài)】全局變量
int a2 = 20;

// 聲明外部變量
extern int b;

// 聲明外部函數(shù)
extern void func_b(void);

// 內(nèi)部定義的【靜態(tài)】函數(shù)
static void func_a2(void)
{
    printf("in func_a2 
");
}

// 內(nèi)部定義的【非靜態(tài)】函數(shù)
void func_a3(void)
{
    printf("in func_a3 
");
}

// 被 main 調(diào)用
void func_a1(void)
{
    printf("in func_a1 
");

    // 操作內(nèi)部變量
    a1 = 11;
    a2 = 21;

    // 操作外部變量
    b  = 31;

    // 調(diào)用內(nèi)部函數(shù)
    func_a2();
    func_a3();

    // 調(diào)用外部函數(shù)
    func_b();
}

代碼說(shuō)明:

  1. 定義了 2 個(gè)全局變量:一個(gè)靜態(tài),一個(gè)非靜態(tài);

  2. 定義了 3 個(gè)函數(shù):

func_a2是靜態(tài)函數(shù),只能在本文件中調(diào)用;

func_a1func_a3是全局函數(shù),可以被外部調(diào)用;

  1. 在 main.c 中會(huì)調(diào)用func_a1

main.c

代碼如下:

#include 
#include 
#include 

// 聲明外部變量
extern int a2;
extern void func_a1();

typedef void (*pfunc)(void);

int main(void)
{
    printf("in main 
");

    // 打印此進(jìn)程的全局符號(hào)表
    void *handle = dlopen(0, RTLD_NOW);
    if (NULL == handle)
    {
        printf("dlopen failed! 
");
        return -1;
    }

    printf("
------------ main ---------------
");
    // 打印 main 中變量符號(hào)的地址
    pfunc addr_main = dlsym(handle, "main");
    if (NULL != addr_main)
        printf("addr_main = 0x%x 
", (unsigned int)addr_main);
    else
        printf("get address of main failed! 
");

    printf("
------------ liba.so ---------------
");
    // 打印 liba.so 中變量符號(hào)的地址
    unsigned int *addr_a1 = dlsym(handle, "a1");
    if (NULL != addr_a1)
        printf("addr_a1 = 0x%x 
", *addr_a1);
    else
        printf("get address of a1 failed! 
");

    unsigned int *addr_a2 = dlsym(handle, "a2");
    if (NULL != addr_a2)
        printf("addr_a2 = 0x%x 
", *addr_a2);
    else
        printf("get address of a2 failed! 
");

    // 打印 liba.so 中函數(shù)符號(hào)的地址
    pfunc addr_func_a1 = dlsym(handle, "func_a1");
    if (NULL != addr_func_a1)
        printf("addr_func_a1 = 0x%x 
", (unsigned int)addr_func_a1);
    else
        printf("get address of func_a1 failed! 
");

    pfunc addr_func_a2 = dlsym(handle, "func_a2");
    if (NULL != addr_func_a2)
        printf("addr_func_a2 = 0x%x 
", (unsigned int)addr_func_a2);
    else
        printf("get address of func_a2 failed! 
");

    pfunc addr_func_a3 = dlsym(handle, "func_a3");
    if (NULL != addr_func_a3)
        printf("addr_func_a3 = 0x%x 
", (unsigned int)addr_func_a3);
    else
        printf("get address of func_a3 failed! 
");


    printf("
------------ libb.so ---------------
");
    // 打印 libb.so 中變量符號(hào)的地址
    unsigned int *addr_b = dlsym(handle, "b");
    if (NULL != addr_b)
        printf("addr_b = 0x%x 
", *addr_b);
    else
        printf("get address of b failed! 
");

    // 打印 libb.so 中函數(shù)符號(hào)的地址
    pfunc addr_func_b = dlsym(handle, "func_b");
    if (NULL != addr_func_b)
        printf("addr_func_b = 0x%x 
", (unsigned int)addr_func_b);
    else
        printf("get address of func_b failed! 
");

    dlclose(handle);

    // 操作外部變量
    a2 = 100;

    // 調(diào)用外部函數(shù)
    func_a1();

    // 為了讓進(jìn)程不退出,方便查看虛擬空間中的地址信息
    while(1) sleep(5);
    return 0;
}

糾正:代碼中本來(lái)是想打印變量的地址的,但是不小心加上了 *,變成了打印變量值。最后檢查的時(shí)候才發(fā)現(xiàn),所以就懶得再去修改了。

代碼說(shuō)明:

  1. 利用 dlopen 函數(shù)(第一個(gè)參數(shù)傳入 NULL),來(lái)打印此進(jìn)程中的一些符號(hào)信息(變量和函數(shù));

  2. 賦值給 liba.so 中的變量 a2,然后調(diào)用 liba.so 中的 func_a1 函數(shù);

編譯成動(dòng)態(tài)鏈接庫(kù)

把以上幾個(gè)源文件編譯成動(dòng)態(tài)庫(kù)以及可執(zhí)行程序:

$ gcc -m32 -fPIC --shared b.c -o libb.so
$ gcc -m32 -fPIC --shared a.c -o liba.so -lb -L./
$ gcc -m32 -fPIC main.c -o main -ldl -la -lb -L./

有幾點(diǎn)內(nèi)容說(shuō)明一下:

  1. -fPIC 參數(shù)意思是:生成位置無(wú)關(guān)代碼(Position Independent Code),這也是動(dòng)態(tài)鏈接中的關(guān)鍵;

  2. 既然動(dòng)態(tài)庫(kù)是在運(yùn)行時(shí)加載,那為什么在編譯的時(shí)候還需要指明?

因?yàn)樵诰幾g的時(shí)候,需要知道每一個(gè)動(dòng)態(tài)庫(kù)中提供了哪些符號(hào)。Windows 中的動(dòng)態(tài)庫(kù)的顯性的導(dǎo)出和導(dǎo)入標(biāo)識(shí),更能體現(xiàn)這個(gè)概念(__declspec(dllexport), __declspec(dllimport))。

此時(shí),就得到了如下幾個(gè)文件:

25c0662e-e228-11ec-ba43-dac502259ad0.png

動(dòng)態(tài)庫(kù)的依賴關(guān)系

對(duì)于靜態(tài)鏈接的可執(zhí)行程序來(lái)說(shuō),被操作系統(tǒng)加載之后,可以認(rèn)為直接從可執(zhí)行程序的入口函數(shù)開始(也就是ELF文件頭中指定的e_entry這個(gè)地址),執(zhí)行其中的指令碼。

但是對(duì)于動(dòng)態(tài)鏈接的程序來(lái)說(shuō),在執(zhí)行入口函數(shù)的指令之前,必須把該程序所依賴的動(dòng)態(tài)庫(kù)加載到內(nèi)存中,然后才能開始執(zhí)行。

對(duì)于我們的實(shí)例代碼來(lái)說(shuō):main程序依賴于liba.so庫(kù),而liba.so庫(kù)又依賴于libb.so庫(kù)。

可以用ldd工具來(lái)分別看一下動(dòng)態(tài)庫(kù)之間的依賴關(guān)系:

25fd6524-e228-11ec-ba43-dac502259ad0.png26351a50-e228-11ec-ba43-dac502259ad0.png2666fbf6-e228-11ec-ba43-dac502259ad0.png

可以看出:

  1. 在 liba.so 動(dòng)態(tài)庫(kù)中,記錄了信息:依賴于 libb.so;

  2. 在 main 可執(zhí)行文件中,記錄了信息:依賴于 liba.so, libb.so;

也可以使用另一個(gè)工具patchelf來(lái)查看一個(gè)可執(zhí)行程序或者動(dòng)態(tài)庫(kù),依賴于其他哪些模塊。例如:

26c8b508-e228-11ec-ba43-dac502259ad0.png

那么,動(dòng)態(tài)庫(kù)的加載是由誰(shuí)來(lái)完成的呢?動(dòng)態(tài)鏈接器!

動(dòng)態(tài)庫(kù)的加載過(guò)程

動(dòng)態(tài)鏈接器加載動(dòng)態(tài)庫(kù)

當(dāng)執(zhí)行main程序的時(shí)候,操作系統(tǒng)首先把main加載到內(nèi)存,然后通過(guò).interp段信息來(lái)查看該文件依賴哪些動(dòng)態(tài)庫(kù):

26f86a5a-e228-11ec-ba43-dac502259ad0.png

上圖中的字符串/lib/ld-linux.so.2,就表示main依賴動(dòng)態(tài)鏈接庫(kù)。

ld-linux.so.2也是一個(gè)動(dòng)態(tài)鏈接庫(kù),在大部分情況下動(dòng)態(tài)鏈接庫(kù)已經(jīng)被加載到內(nèi)存中了(動(dòng)態(tài)鏈接庫(kù)就是為了共享),操作系統(tǒng)此時(shí)只需要把動(dòng)態(tài)鏈接庫(kù)所在的物理內(nèi)存,映射main進(jìn)程的虛擬地址空間中就可以了,然后再把控制權(quán)交給動(dòng)態(tài)鏈接器。

動(dòng)態(tài)鏈接器發(fā)現(xiàn):main依賴liba.so,于是它就在虛擬地址空間中找一塊能放得下liba.so的空閑空間,然后把liba.so中需要加載到內(nèi)存中的代碼段、數(shù)據(jù)段都加載進(jìn)來(lái)。

當(dāng)然,在加載liba.so時(shí),又會(huì)發(fā)現(xiàn)它依賴libb.so,于是又把在虛擬地址空間中找一塊能放得下libb.so的空閑空間,把libb.so中的代碼段、數(shù)據(jù)段等加載到內(nèi)存中,示意圖如下所示:

271df75c-e228-11ec-ba43-dac502259ad0.png

動(dòng)態(tài)鏈接器自身也是一個(gè)動(dòng)態(tài)庫(kù),而且是一個(gè)特殊的動(dòng)態(tài)庫(kù):它不依賴于其他的任何動(dòng)態(tài)庫(kù),因?yàn)楫?dāng)它被加載的時(shí)候,沒(méi)有人幫它去加載依賴的動(dòng)態(tài)庫(kù),否則就形成雞生蛋、蛋生雞的問(wèn)題了。

動(dòng)態(tài)庫(kù)的加載地址

一個(gè)進(jìn)程在運(yùn)行時(shí)的實(shí)際加載地址(或者說(shuō)虛擬內(nèi)存區(qū)域),可以通過(guò)指令:$ cat /proc/[進(jìn)程的 pid]/maps 讀取出來(lái)。

例如:我的虛擬機(jī)中執(zhí)行main程序時(shí),看到的地址信息是:

273b953c-e228-11ec-ba43-dac502259ad0.png

黃色部分分別是:main, liba.so, libb.so3個(gè)模塊的加載信息。

另外,還可以看到c庫(kù)(libc-2.23.so)、動(dòng)態(tài)鏈接器(ld-2.23.so)以及動(dòng)態(tài)加載庫(kù)libdl-2.23.so的虛擬地址區(qū)域,布局如下:

27842cac-e228-11ec-ba43-dac502259ad0.png

可以看出出來(lái):main可執(zhí)行程序是位于低地址,所有的動(dòng)態(tài)庫(kù)都位于4G內(nèi)存空間的最后1G空間中。

還有另外一個(gè)指令也很好用 $ pmap [進(jìn)程的 pid],也可以打印出每個(gè)模塊的內(nèi)存地址:

27bb516e-e228-11ec-ba43-dac502259ad0.png

符號(hào)重定位

全局符號(hào)表

在之前的靜態(tài)鏈接中學(xué)習(xí)過(guò),鏈接器在掃描每一個(gè)目標(biāo)文件(.o文件)的時(shí)候,會(huì)把每個(gè)目標(biāo)文件中的符號(hào)提取出來(lái),構(gòu)成一個(gè)全局符號(hào)表。

然后在第二遍掃描的時(shí)候,查看每個(gè)目標(biāo)文件中需要重定位的符號(hào),然后在全局符號(hào)表中查找該符號(hào)被安排在什么地址,然后把這個(gè)地址填寫到引用的地方,這就是靜態(tài)鏈接時(shí)的重定位。

但是動(dòng)態(tài)鏈接過(guò)程中的重定位,與靜態(tài)鏈接的處理方式差別就大很多了,因?yàn)?span>每個(gè)符號(hào)的地址只有在運(yùn)行的時(shí)候才能知道它們的地址。

例如:liba.so引用了libb.so中的變量和函數(shù),而libb.so中的這兩個(gè)符號(hào)被加載到什么位置,直到main程序準(zhǔn)備執(zhí)行的時(shí)候,才能被鏈接器加載到內(nèi)存中的某個(gè)隨機(jī)的位置。

也就是說(shuō):動(dòng)態(tài)鏈接器知道每個(gè)動(dòng)態(tài)庫(kù)中的代碼段、數(shù)據(jù)段被加載的內(nèi)存地址,因此動(dòng)態(tài)鏈接器也會(huì)維護(hù)一個(gè)全局符號(hào)表,其中存放著每一個(gè)動(dòng)態(tài)庫(kù)中導(dǎo)出的符號(hào)以及它們的內(nèi)存地址信息。

在示例代碼main.c函數(shù)中,我們通過(guò)dlopen返回的句柄來(lái)打印進(jìn)程中的一些全局符號(hào)的地址信息,輸出內(nèi)容如下:

2af96064-e228-11ec-ba43-dac502259ad0.png

上文已經(jīng)糾錯(cuò)過(guò):本來(lái)是想打印變量的地址信息,但是 printf 語(yǔ)句中不小心加上了型號(hào),變成了打印變量值。

可以看到:在全局符號(hào)表中,沒(méi)有找到liba.so中的變量a1和函數(shù)func_a2這兩個(gè)符號(hào),因?yàn)樗鼈z都是static類型的,在編譯成動(dòng)態(tài)庫(kù)的時(shí)候,沒(méi)有導(dǎo)出到符號(hào)表中。

既然提到了符號(hào)表,就來(lái)看看這 3 個(gè)ELF文件中的動(dòng)態(tài)符號(hào)表信息:

  1. 動(dòng)態(tài)鏈接庫(kù)中保護(hù)兩個(gè)符號(hào)表:.dynsym(動(dòng)態(tài)符號(hào)表: 表示模塊中符號(hào)的導(dǎo)出、導(dǎo)入關(guān)系) 和 .symtab(符號(hào)表: 表示模塊中的所有符號(hào));

.symtab 中包含了 .dynsym;

  1. 由于圖片太大,這里只貼出 .dynsym 動(dòng)態(tài)符號(hào)表。

綠色矩形框前面的Ndx列是數(shù)字,表示該符號(hào)位于當(dāng)前文件的哪一個(gè)段中(即:索引);

紅色矩形框前面的Ndx列是UND,表示這個(gè)符號(hào)沒(méi)有找到,是一個(gè)外部符號(hào)(需要重定位);

2b4c801e-e228-11ec-ba43-dac502259ad0.png2b8768dc-e228-11ec-ba43-dac502259ad0.png2bb9c53e-e228-11ec-ba43-dac502259ad0.png

全局偏移表GOT

在我們的示例代碼中,liba.so是比較特殊的,它既被main可執(zhí)行程序所依賴,又依賴于libb.so

而且,在liba.so中,定義了靜態(tài)、動(dòng)態(tài)的全局變量和函數(shù),可以很好的概況很多種情況,因此這部分內(nèi)容就主要來(lái)分析liba.so這個(gè)動(dòng)態(tài)庫(kù)。

前文說(shuō)過(guò):代碼重定位需要修改代碼段中的符號(hào)引用,而代碼段被加載到內(nèi)存中又沒(méi)有可寫的權(quán)限,動(dòng)態(tài)鏈接解決這個(gè)矛盾的方案是:增加一層間接性。

例如:liba.so的代碼中引用了libb.so中的變量b,在liba.so的代碼段,并不是在引用的地方直接指向libb.so數(shù)據(jù)段中變量b的地址,而是指向了liba.so自己的數(shù)據(jù)段中的某個(gè)位置,在重定位階段,鏈接器再把libb.so中變量b的地址填寫到這個(gè)位置。

因?yàn)?code style="font-size:14px;padding:2px 4px;margin-right:2px;margin-left:2px;background-color:rgba(27,31,35,.05);font-family:'Operator Mono', Consolas, Monaco, Menlo, monospace;color:rgb(239,112,96);">liba.so自己的代碼段和數(shù)據(jù)段位置是相對(duì)固定的,這樣的話,liba.so的代碼段被加載到內(nèi)存之后,就再也不用修改了。

而數(shù)據(jù)段中這個(gè)間接跳轉(zhuǎn)的位置,就稱作:全局偏移表(GOT: Global Offset Table)。

劃重點(diǎn):

liba.so的代碼段中引用了libb.so中的符號(hào)b,既然b的地址需要在重定位時(shí)才能確定,那么就在數(shù)據(jù)段中開辟一塊空間(稱作:GOT表),重定位時(shí)把b的地址填寫到GOT表中。

liba.so的代碼段中,把GOT表的地址填寫到引用b的地方,因?yàn)?code style="font-size:14px;padding:2px 4px;margin-right:2px;margin-left:2px;background-color:rgba(27,31,35,.05);font-family:'Operator Mono', Consolas, Monaco, Menlo, monospace;color:rgb(239,112,96);">GOT表在編譯階段是可以確定的,使用的是相對(duì)地址。

這樣,就可以在不修改liba.so代碼段的前提下,動(dòng)態(tài)的對(duì)符號(hào)b進(jìn)行了重定位!

其實(shí),在一個(gè)動(dòng)態(tài)庫(kù)中存在 2 個(gè)GOT表,分別用于重定位變量符號(hào)(section名稱:.got)和函數(shù)符號(hào)( section 名稱:.got.plt)。

也就是說(shuō):所有變量類型的符號(hào)重定位信息都位于.got中,所有函數(shù)類型的符號(hào)重定位信息都位于.got.plt中。

并且,在一個(gè)動(dòng)態(tài)庫(kù)文件中,有兩個(gè)特殊的段(.rel.dyn.rel.plt)來(lái)告訴鏈接器:.got.got.plt這兩個(gè)表中,有哪些符號(hào)需要進(jìn)行重定位,這個(gè)問(wèn)題下面會(huì)深入討論。

liba.so動(dòng)態(tài)庫(kù)文件的布局

為了更深刻的理解.got.got.plt這兩個(gè)表,有必要來(lái)拆解一下liba.so動(dòng)態(tài)庫(kù)文件的內(nèi)部結(jié)構(gòu)。

通過(guò)readelf -S liba.so指令來(lái)看一下這個(gè)ELF文件中都有哪些section:

2bf7a778-e228-11ec-ba43-dac502259ad0.png

可以看到:一共有28個(gè)section,其中的21、22就是兩個(gè)GOT表。

另外,從裝載的角度來(lái)看,裝載器并不是把這些sections分開來(lái)處理,而是根據(jù)不同的讀寫屬性,把多個(gè)section看做一個(gè)segment。

再次通過(guò)指令 readelf -l liba.so ,來(lái)查看一下segment信息:

2c426c2c-e228-11ec-ba43-dac502259ad0.png

也就是說(shuō):

28個(gè)section中(關(guān)注綠色線條):

  1. section 0 ~ 16 都是可讀、可執(zhí)行權(quán)限,被當(dāng)做一個(gè) segment;

  2. section 17 ~ 24 都是可讀、可寫的權(quán)限,被動(dòng)作另一個(gè) segment;

再來(lái)重點(diǎn)看一下.got.got.plt這兩個(gè)section(關(guān)注黃色矩形框):

可見:.got.got.plt與數(shù)據(jù)段一樣,都是可讀、可寫的,所以被當(dāng)做同一個(gè) segment被加載到內(nèi)存中。

通過(guò)以上這2張圖(紅色矩形框),可以得到liba.so動(dòng)態(tài)庫(kù)文件的內(nèi)部結(jié)構(gòu)如下:

2c860b26-e228-11ec-ba43-dac502259ad0.png

liba.so動(dòng)態(tài)庫(kù)的虛擬地址

來(lái)繼續(xù)觀察liba.so文件segment信息中的AirtAddr列,它表示的是被加載到虛擬內(nèi)存中的地址,重新貼圖如下:

2cb92b64-e228-11ec-ba43-dac502259ad0.png

因?yàn)榫幾g動(dòng)態(tài)庫(kù)時(shí),使用了代碼位置無(wú)關(guān)參數(shù)(-fPIC),這里的虛擬地址從0x0000_0000開始。

當(dāng)liba.so的代碼段、數(shù)據(jù)段被加載到內(nèi)存中時(shí),動(dòng)態(tài)鏈接器找到一塊空閑空間,這個(gè)空間的開始地址,就相當(dāng)于一個(gè)基地址

liba.so中的代碼段和數(shù)據(jù)段中所有的虛擬地址信息,只要加上這個(gè)基地址,就得到了實(shí)際虛擬地址。

我們還是把上圖中的輸出信息,畫出詳細(xì)的內(nèi)存模型圖,如下所示:

2ce49164-e228-11ec-ba43-dac502259ad0.png

GOT表的內(nèi)部結(jié)構(gòu)

現(xiàn)在,我們已經(jīng)知道了liba.so庫(kù)的文件布局,也知道了它的虛擬地址,此時(shí)就可以來(lái)進(jìn)一步的看一下.got.got.plt這兩個(gè)表的內(nèi)部結(jié)構(gòu)了。

從剛才的圖片中看出:

  1. .got 表的長(zhǎng)度是 0x1c,說(shuō)明有 7 個(gè)表項(xiàng)(每個(gè)表項(xiàng)占 4 個(gè)字節(jié));

  2. .got.plt 表的長(zhǎng)度是 0x18,說(shuō)明有 6 個(gè)表項(xiàng);

上文已經(jīng)說(shuō)過(guò),這兩個(gè)表是用來(lái)重定位所有的變量和函數(shù)等符號(hào)的。

那么:liba.so通過(guò)什么方式來(lái)告訴動(dòng)態(tài)鏈接器:需要對(duì).got.got.plt這兩個(gè)表中的表項(xiàng)進(jìn)行地址重定位呢?

在靜態(tài)鏈接的時(shí)候,目標(biāo)文件是通過(guò)兩個(gè)重定位表.rel.text.rel.data這兩個(gè)段信息來(lái)告訴鏈接器的。

對(duì)于動(dòng)態(tài)鏈接來(lái)說(shuō),也是通過(guò)兩個(gè)重定位表來(lái)傳遞需要重定位的符號(hào)信息的,只不過(guò)名字有些不同:.rel.dyn.rel.plt

通過(guò)指令 readelf -r liba.so來(lái)查看重定位信息:

2d0aeaf8-e228-11ec-ba43-dac502259ad0.png

從黃色和綠色的矩形框中可以看出:

  1. liba.so 引用了外部符號(hào) b,類型是 R_386_GLOB_DAT,這個(gè)符號(hào)的重定位描述信息在 .rel.dyn 段中;

  2. liba.so 引用了外部符號(hào) func_b, 類型是 R_386_JUMP_SLOT,這個(gè)符號(hào)的重定位描述信息在 .rel.plt 段中;

從左側(cè)紅色的矩形框可以看出:每一個(gè)需要重定位的表項(xiàng)所對(duì)應(yīng)的虛擬地址,畫成內(nèi)存模型圖就是下面這樣:

2d67ec8a-e228-11ec-ba43-dac502259ad0.png

暫時(shí)只專注表項(xiàng)中的紅色部分:.got表中的b, .got.plt表中的func_b,這兩個(gè)符號(hào)都是libb.so中導(dǎo)出的。

也就是說(shuō):

liba.so的代碼中在操作變量b的時(shí)候,就到.got表中的0x0000_1fe8這個(gè)地址處來(lái)獲取變量b的真正地址;

liba.so的代碼中在調(diào)用func_b函數(shù)的時(shí)候,就到.got.plt表中的0x0000_200c這個(gè)地址處來(lái)獲取函數(shù)的真正地址;

匯編liba.so代碼

下面就來(lái)反匯編一下liba.so,看一下指令碼中是如何對(duì)這兩個(gè)表項(xiàng)進(jìn)行尋址的。

執(zhí)行反匯編指令:$ objdump -d liba.so,這里只貼出func_a1函數(shù)的反匯編代碼:

3092d5be-e228-11ec-ba43-dac502259ad0.png

第一個(gè)綠色矩形框(call 490 <__x86.get_pc_thunk.bx>)的功能是:把下一條指令(add)的地址存儲(chǔ)到%ebx中,也就是:

%ebx = 0x622

然后執(zhí)行: add $0x19de,%ebx,讓%ebx加上0x19de,結(jié)果就是:%ebx = 0x2000。

0x2000正是.got.plt表的開始地址!

看一下第2個(gè)綠色矩形框:

mov -0x18(%ebx),%eax: 先用%ebx減去0x18的結(jié)果,存儲(chǔ)到%eax中,結(jié)果是:%eax = 0x1fe8,這個(gè)地址正是變量b.got表中的虛擬地址。

movl $0x1f,(%eax):在把0x1f(十進(jìn)制就是31),存儲(chǔ)到0x1fe8表項(xiàng)中存儲(chǔ)的地址所對(duì)應(yīng)的內(nèi)存單元中(libb.so的數(shù)據(jù)段中的某個(gè)位置)。

因此,當(dāng)鏈接器進(jìn)行重定位之后,0x1fe8表項(xiàng)中存儲(chǔ)的就是變量b的真正地址,而上面這兩步操作,就把數(shù)值31賦值給變量b了。

3個(gè)綠色矩形框,是調(diào)用函數(shù)func_b,稍微復(fù)雜一些,跳轉(zhuǎn)到符號(hào) func_b@plt的地方,看一下反匯編代碼:

30cd8a7e-e228-11ec-ba43-dac502259ad0.png

jmp指令調(diào)用了%ebx + 0xc處的那個(gè)函數(shù)指針,從上面的.got.plt布局圖中可以看出,重定位之后這個(gè)表項(xiàng)中存儲(chǔ)的正是func_b函數(shù)的地址(libb.so中代碼段的某個(gè)位置),所以就正確的跳轉(zhuǎn)到該函數(shù)中了。

審核編輯 :李倩


聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 數(shù)據(jù)
    +關(guān)注

    關(guān)注

    8

    文章

    7349

    瀏覽量

    95062
  • 代碼
    +關(guān)注

    關(guān)注

    30

    文章

    4977

    瀏覽量

    74428
  • 動(dòng)態(tài)鏈接
    +關(guān)注

    關(guān)注

    0

    文章

    5

    瀏覽量

    5930

原文標(biāo)題:Linux 動(dòng)態(tài)鏈接過(guò)程中的【重定位】底層原理

文章出處:【微信號(hào):LinuxHub,微信公眾號(hào):Linux愛好者】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    上海光機(jī)所在激光焊接過(guò)程監(jiān)測(cè)方面取得新進(jìn)展

    coated PHS 22MnB5”為題,發(fā)表于Optics & Laser Technology。 隨著高端制造業(yè)對(duì)焊接質(zhì)量與可靠性的要求不斷提升,激光焊接過(guò)程中的熔深狀態(tài)監(jiān)測(cè)已成為保障結(jié)構(gòu)安全與服役性能的關(guān)鍵環(huán)節(jié)。然
    的頭像 發(fā)表于 04-01 06:40 ?115次閱讀
    上海光機(jī)所在激光焊<b class='flag-5'>接過(guò)程</b>監(jiān)測(cè)方面取得新進(jìn)展

    硅片清洗過(guò)程中的慢提拉是如何進(jìn)行

    硅片清洗過(guò)程中的慢提拉是確保硅片表面潔凈度和干燥效果的關(guān)鍵步驟,以下是其具體操作方式:準(zhǔn)備工作硅片裝載:將經(jīng)過(guò)前面工序清洗后的硅片小心地放入特制的花籃或吊籃,注意硅片之間的間距要合適,一般間隔
    的頭像 發(fā)表于 01-12 11:55 ?537次閱讀
    硅片清洗<b class='flag-5'>過(guò)程中</b>的慢提拉是如<b class='flag-5'>何進(jìn)行</b>的

    深入理解?RK3506 U-Boot?定位:從代碼到原理

    在嵌入式系統(tǒng),U-Boot?作為引導(dǎo)加載程序,其啟動(dòng)流程的核心環(huán)節(jié)之一就是 定位(Relocation) 。對(duì)于?RK3506?這類基于?ARM Cortex-A?架構(gòu)的芯片,
    的頭像 發(fā)表于 11-28 07:05 ?1031次閱讀
    深入理解?RK3506 U-Boot?<b class='flag-5'>重</b><b class='flag-5'>定位</b>:從代碼到原理

    在極海APM32系列MCU如何把代碼定位到SDRAM運(yùn)行

    在有些情況下,我們想要把代碼放到SDRAM運(yùn)行。下面介紹在APM32的MCU,如何把代碼定位到SDRAM運(yùn)行。對(duì)于不同APM32系列的MCU,方法都是一樣的。
    的頭像 發(fā)表于 11-04 09:14 ?5437次閱讀
    在極海APM32系列MCU<b class='flag-5'>中</b>如何把代碼<b class='flag-5'>重</b><b class='flag-5'>定位</b>到SDRAM運(yùn)行

    晶圓制造過(guò)程中的摻雜技術(shù)

    在超高純度晶圓制造過(guò)程中,盡管晶圓本身需達(dá)到11個(gè)9(99.999999999%)以上的純度標(biāo)準(zhǔn)以維持基礎(chǔ)半導(dǎo)體特性,但為實(shí)現(xiàn)集成電路的功能化構(gòu)建,必須通過(guò)摻雜工藝在硅襯底表面局部引入特定雜質(zhì)。
    的頭像 發(fā)表于 10-29 14:21 ?1328次閱讀
    晶圓制造<b class='flag-5'>過(guò)程中</b>的摻雜技術(shù)

    飛凌嵌入式ElfBoard-Vim編輯器之靜態(tài)鏈接動(dòng)態(tài)鏈接

    1.靜態(tài)鏈接靜態(tài)鏈接通過(guò)靜態(tài)庫(kù)進(jìn)行鏈接,生成的目標(biāo)程序包含運(yùn)行需要的所有庫(kù),可以直接運(yùn)行,不過(guò)就是文件比較大。靜態(tài)庫(kù)是匯編產(chǎn)生的.o文件的
    發(fā)表于 10-17 09:07

    【開發(fā)指南】全志系列核心板開發(fā)過(guò)程中的常見問(wèn)題及排查策略

    在長(zhǎng)期提供技術(shù)支持服務(wù)的過(guò)程中,飛凌嵌入式總結(jié)了用戶開發(fā)全志系列產(chǎn)品時(shí)常見的問(wèn)題及排查方法。本文中,小編將為大家梳理這些經(jīng)驗(yàn),助力開發(fā)者快速定位問(wèn)題,提升開發(fā)效率。
    的頭像 發(fā)表于 10-15 08:04 ?7073次閱讀
    【開發(fā)指南】全志系列核心板開發(fā)<b class='flag-5'>過(guò)程中</b>的常見問(wèn)題及排查策略

    UPS不間斷電源在放電過(guò)程中的注意事項(xiàng)

    UPS在日常的使用過(guò)程中,只有定期對(duì)UPS放電才能延長(zhǎng)UPS的使用壽命,UPS 電源電池需要每三個(gè)月進(jìn)行一次充放電,怎樣對(duì)UPS進(jìn)行放電才能讓其保持在最佳工作狀態(tài)? 現(xiàn)在,由匯智天源工程師和大家聊一
    的頭像 發(fā)表于 10-11 11:33 ?829次閱讀
    UPS不間斷電源在放電<b class='flag-5'>過(guò)程中</b>的注意事項(xiàng)

    大電流起弧過(guò)程中電弧聲壓/超聲波信號(hào)的特征提取與故障診斷

    接觸不良、絕緣破損、元件老化等故障時(shí),電弧的燃燒狀態(tài)會(huì)發(fā)生改變,相應(yīng)的聲壓超聲波信號(hào)也會(huì)出現(xiàn)異常變化。因此,通過(guò)提取這些信號(hào)的關(guān)鍵特征,并結(jié)合特征變化規(guī)律進(jìn)行分析,就能實(shí)現(xiàn)對(duì)大電流起弧過(guò)程中故障的精準(zhǔn)診
    的頭像 發(fā)表于 09-29 09:27 ?747次閱讀
    大電流起弧<b class='flag-5'>過(guò)程中</b>電弧聲壓/超聲波信號(hào)的特征提取與故障診斷

    何進(jìn)行聲音定位?

    文章主要介紹了如何利用一種簡(jiǎn)單的TDOA算法進(jìn)行聲音點(diǎn)位,并使用數(shù)據(jù)采集卡進(jìn)行聲音定位的實(shí)驗(yàn)。
    的頭像 發(fā)表于 09-23 15:47 ?2193次閱讀
    如<b class='flag-5'>何進(jìn)行</b>聲音<b class='flag-5'>定位</b>?

    創(chuàng)想智控焊接熔池相機(jī),打造焊接過(guò)程的“智慧之眼”

    ,及時(shí)、準(zhǔn)確地掌握熔池動(dòng)態(tài)?今天一起了解創(chuàng)想智控焊接熔池相機(jī),打造焊接過(guò)程的“智慧之眼”。 實(shí)時(shí)監(jiān)控,焊接過(guò)程可視化 創(chuàng)想智控焊接熔池相機(jī)通過(guò)高動(dòng)態(tài)成像技術(shù),能夠在高亮弧光與暗背景的強(qiáng)
    的頭像 發(fā)表于 08-26 14:03 ?697次閱讀
    創(chuàng)想智控焊接熔池相機(jī),打造焊<b class='flag-5'>接過(guò)程</b>的“智慧之眼”

    使用AURIX進(jìn)行調(diào)試的過(guò)程中,如果進(jìn)入某個(gè)函數(shù)的時(shí)候出現(xiàn)問(wèn)題,是配置項(xiàng)的問(wèn)題還是函數(shù)的變量的問(wèn)題?

    在使用AURIX進(jìn)行調(diào)試的過(guò)程中,如果進(jìn)入某個(gè)函數(shù)的時(shí)候出現(xiàn)問(wèn)題,是配置項(xiàng)的問(wèn)題還是函數(shù)的變量的問(wèn)題?
    發(fā)表于 08-11 07:17

    固件升級(jí)過(guò)程中,如何禁用EC INT中斷?

    固件升級(jí)過(guò)程中,EC INT中斷經(jīng)常會(huì)被觸發(fā),如何禁用? 這個(gè)中斷,協(xié)議棧是怎么觸發(fā)的或者說(shuō)需要滿足什么條件?
    發(fā)表于 07-25 06:43

    使用CY7C65213開發(fā)過(guò)程中,應(yīng)該用哪個(gè)interface進(jìn)行uart通信?

    在使用CY7C65213開發(fā)過(guò)程中,我想用CyUartRead讀數(shù)據(jù),但是好像沒(méi)有接口的deviceType是CY_TYPE_UART,想請(qǐng)問(wèn)我應(yīng)該用哪個(gè)interface進(jìn)行uart通信? 是否有相關(guān)指導(dǎo)文件,或描述符指導(dǎo)?
    發(fā)表于 06-03 07:04

    飛凌嵌入式ElfBoard ELF 1板卡-uboot編譯system.map/uboot.map

    情況。u-boot.map包含了鏈接過(guò)程中涉及到的目標(biāo)文件以及其所依賴的庫(kù)文件的各個(gè)符號(hào)的地址信息,以及我們所涉及到的函數(shù)所在目錄信息,這里不再展開講,有興趣的同學(xué)可以自己查看研究
    發(fā)表于 05-22 11:22
    江门市| 白朗县| 天柱县| 汪清县| 旬邑县| 保靖县| 通化县| 遂宁市| 和田市| 襄城县| 永顺县| 横山县| 济宁市| 大宁县| 揭阳市| 文安县| 郯城县| 炉霍县| 虎林市| 龙岩市| 马龙县| 蒲江县| 双柏县| 宿迁市| 五家渠市| 景东| 卓资县| 威信县| 灵台县| 饶平县| 县级市| 通渭县| 清流县| 文昌市| 漳浦县| 纳雍县| 雅安市| 康马县| 赤城县| 清远市| 汉川市|