日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux 链接详解----动态链接库

發布時間:2025/7/14 linux 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux 链接详解----动态链接库 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

靜態庫的缺點:

  • 庫函數被包含在每一個運行的進程中,會造成主存的浪費。
  • 目標文件的size過大
  • 每次更新一個模塊都需要重新編譯,更新困難,使用不方便。
  • 動態庫: 是一個目標文件,包含代碼和數據,它可以在程序運行時動態的加載并鏈接。修改動態庫不需要重新編譯目標文件,只需要更新動態庫即可。動態庫還可以同時被多個進程使用。在linux下生成動態庫 gcc -c a.c? -fPIC -o a.o ? ? gcc -shared -fPIC a.o -o a.so. ? ? 這里的PIC含義就是生成位置無關代碼,動態庫允許動態裝入修改,這就必須要保證動態庫的代碼被裝入時,可執行程序不依賴與動態庫被裝入的位置,即使動態庫的長度發生變化也不會影響調用它的程序。

    動態鏈接器:

    在加載可執行文件時,加載器發現在可執行文件的程序頭表中有.interp段,其中包含了動態連接器路徑ld-linux.so . 加載器加載動態鏈接器,動態鏈接器完成相應的重定位工作后,再將控制權交給可執行文件。

    程序頭:Type Offset VirtAddr PhysAddrFileSiz MemSiz Flags AlignPHDR 0x0000000000000040 0x0000000000400040 0x00000000004000400x00000000000001f8 0x00000000000001f8 R E 8INTERP 0x0000000000000238 0x0000000000400238 0x00000000004002380x000000000000001c 0x000000000000001c R 1[正在請求程序解釋器:/lib64/ld-linux-x86-64.so.2]

    位置無關代碼PIC:

    其中動態庫用到的一個核心概念就是與代碼無關PIC。共享庫代碼位置可以是不確定的,即使代碼長度發生變化也不影響調用它的程序,動態鏈接器是不會把可執行文件的代碼段數據段與動態鏈接庫合并的。那么這里牽涉到模塊內與模塊間的引用和跳轉問題。

    模塊內的跳轉和引用:

    目標文件與靜態庫中模塊內的跳轉大致相同。如下代碼:

    //b.c
    static
    int temp = 12344; extern int a; void add(int c) {a += c;temp += c; }

    //a.c
    int a = 1000;
    void add(int c);
    int main()
    {
    ?? ?int c = 123;
    ?? ?add(c);
    ?? ?return 0;

    }

    我們將b.c編譯為動態庫,這里的temp就是模塊內的引用,我們將動態庫反編譯可以看到

    471 00000000000006a8 <add>: 472 6a8: 55 push %rbp 473 6a9: 48 89 e5 mov %rsp,%rbp 474 6ac: 89 7d fc mov %edi,-0x4(%rbp) 475 6af: 48 8b 05 2a 09 20 00 mov 0x20092a(%rip),%rax # 200fe0 < _DYNAMIC+0x1d0> 476 6b6: 8b 10 mov (%rax),%edx 477 6b8: 8b 45 fc mov -0x4(%rbp),%eax 478 6bb: 01 c2 add %eax,%edx 479 6bd: 48 8b 05 1c 09 20 00 mov 0x20091c(%rip),%rax # 200fe0 < _DYNAMIC+0x1d0> 480 6c4: 89 10 mov %edx,(%rax) 481 6c6: 8b 15 5c 09 20 00 mov 0x20095c(%rip),%edx # 201028 < temp> 482 6cc: 8b 45 fc mov -0x4(%rbp),%eax 483 6cf: 01 d0 add %edx,%eax 484 6d1: 89 05 51 09 20 00 mov %eax,0x200951(%rip) # 201028 < temp>


    785 0000000000201028 <temp>: ? ? ? ? ? ? #數據段temp
    786?? 201028:?? 38 30?????????????????? cmp??? %dh,(%rax)

    這里的mov 0x20095c(%rip),%edx ?取rip中下一條指令地址+0x20095c 的數據放到edx寄存器中,地址為0x201028 正是temp的地址。模塊內的函數跳轉與此類似。

    模塊間的跳轉和引用:

    首先看一下全局變量的引用,看上例中b.so的反編譯 這里引用了一個全局變量a 它的定義在另一個模塊a.o 中

    ?

    471 00000000000006a8 <add>: 472 6a8: 55 push %rbp 473 6a9: 48 89 e5 mov %rsp,%rbp 474 6ac: 89 7d fc mov %edi,-0x4(%rbp) 475 6af: 48 8b 05 2a 09 20 00 mov 0x20092a(%rip),%rax # 200fe0 < _DYNAMIC+0x1d0> 476 6b6: 8b 10 mov (%rax),%edx 477 6b8: 8b 45 fc mov -0x4(%rbp),%eax 478 6bb: 01 c2 add %eax,%edx 479 6bd: 48 8b 05 1c 09 20 00 mov 0x20091c(%rip),%rax # 200fe0 < _DYNAMIC+0x1d0> 764 Disassembly of section .got: 765 766 0000000000200fd0 <.got>: 767 ... 768 769 Disassembly of section .got.plt: 770 771 0000000000201000 <_GLOBAL_OFFSET_TABLE_>: 772 201000: 10 0e adc %cl,(%rsi) 773 201002: 20 00 and %al,(%rax)

    ?

    這里的mov 取地址是一個got數組的某個地址。GOT 全局偏移表,在data段的開始處的一個指針數組,每一個指針可以指向一個全局變量, GOT與引用數據的指令之間的相對距離固定,編譯器為GOT每一項生成一個重定位項,加載時 動態鏈接器對GOT中的各項進行重定位,填入引用的地址。(32位 占4個字節? 64位 8個字節)

    ?

    每一個引用全局數據的目標模塊都有一張自己的GOT,那么就需要一個額外的寄存器來保持GOT表目的地址。至于模塊間的函數調用和跳轉也可以使用此模塊,但是這種情況下過程調用都要求三條額外的指令,Linux這里就使用了叫做延遲綁定的技術,將過程調用的綁定推遲到第一次調用該過程。這種技術是通過倆個數據結構之間的交互來實現,即GOT 和PLT 全局偏移表和過程鏈接表, 如果一個目標模塊調用定義在共享庫中的任何函數,那么它就有自己的GOT和PLT,GOT是data節的一部分,PLT是text節的一部分<深入理解計算機系統>

    上圖為 32 位linux。 從GOT[3]開始才是函數調用地址。我們將a.c b.so編譯鏈接為可執行文件a, 反匯編a 觀察函數add的調用(無關代碼已省略)。(64位機器)

    Disassembly of section .plt:0000000000400580 <add@plt-0x10>:400580: ff 35 82 0a 20 00 pushq 0x200a82(%rip) # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>400586: ff 25 84 0a 20 00 jmpq *0x200a84(%rip) # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>40058c: 0f 1f 40 00 nopl 0x0(%rax)0000000000400590 <add@plt>:400590: ff 25 82 0a 20 00 jmpq *0x200a82(%rip) # 601018 <_GLOBAL_OFFSET_TABLE_+0x18> // ++++++++++++++++++++++400596: 68 00 00 00 00 pushq $0x040059b: e9 e0 ff ff ff jmpq 400580 <_init+0x20>00000000004005a0 <__libc_start_main@plt>:4005a0: ff 25 7a 0a 20 00 jmpq *0x200a7a(%rip) # 601020 <_GLOBAL_OFFSET_TABLE_+0x20>4005a6: 68 01 00 00 00 pushq $0x14005ab: e9 d0 ff ff ff jmpq 400580 <_init+0x20>00000000004006b0 <main>:4006b0: 55 push %rbp4006b1: 48 89 e5 mov %rsp,%rbp4006b4: 48 83 ec 10 sub $0x10,%rsp4006b8: c7 45 fc 7b 00 00 00 movl $0x7b,-0x4(%rbp)4006bf: 8b 45 fc mov -0x4(%rbp),%eax                4006c2: 89 c7 mov %eax,%edi                                       4006c4: e8 c7 fe ff ff callq 400590 <add@plt> ? ? ? ? ? //call add--------------------------------4006c9: b8 00 00 00 00 mov $0x0,%eax4006ce: c9 leaveq 4006cf: c3 retq Disassembly of section .got:0000000000600ff8 <.got>:...Disassembly of section .got.plt: #data段 ?無效反編譯代碼 0000000000601000 <_GLOBAL_OFFSET_TABLE_>:601000: GOT[0] ? GOT ? ?64位下每個條目8個字節 ? 32位是4個字節601008: ? GOT[1] 鏈接器標示信息
    ? 601010 ?GOT[2] ?動態連接器入口地址
    601017: 00 96 05 40 00 00 add %dl,0x4005(%rsi)60101d: 00 00 add %al,(%rax)60101f: 00 a6 05 40 00 00 add %ah,0x4005(%rsi)601025: 00 00 add %al,(%rax)601027: 00 b6 05 40 00 00 add %dh,0x4005(%rsi)60102d: 00 00 add %al,(%rax)...

    我們可以看到main函數中跳轉call add 跳轉到了地址0x400590 處,執行 jmpq *0x200a82(%rip) 指令 跳轉到 0x601018的地址 ,還是跳到GOT數組中的add位置。這個位置其實就是 jmpq *0x200a82(%rip)的下一條指令地址400596: 68 00 00 00 00 pushq $0x0。我們可以使用gdb看一下。

    ?

    (gdb) disassemble main Dump of assembler code for function main:0x00000000004006b0 <+0>: push %rbp0x00000000004006b1 <+1>: mov %rsp,%rbp0x00000000004006b4 <+4>: sub $0x10,%rsp => 0x00000000004006b8 <+8>: movl $0x7b,-0x4(%rbp)0x00000000004006bf <+15>: mov -0x4(%rbp),%eax0x00000000004006c2 <+18>: mov %eax,%edi0x00000000004006c4 <+20>: callq 0x400590 <add@plt>0x00000000004006c9 <+25>: mov $0x0,%eax0x00000000004006ce <+30>: leaveq 0x00000000004006cf <+31>: retq End of assembler dump. (gdb) disassemble 0x400590 Dump of assembler code for function add@plt:0x0000000000400590 <+0>: jmpq *0x200a82(%rip) # 0x601018 <add@got.plt> 這個地址就是GOT數組中add對應的那條0x0000000000400596 <+6>: pushq $0x00x000000000040059b <+11>: jmpq 0x400580 End of assembler dump. (gdb) x 0x601018 0x601018 <add@got.plt>: 0x00400596

    ?

    ?pushq $0x0 這條指令將add符號的ID壓入棧,然后 jmpq? 0x400580 它將會跳轉到PLT[0]接下來的指令就是:

    ?

    0000000000400580 <add@plt-0x10>:400580: ff 35 82 0a 20 00 pushq 0x200a82(%rip) # 601008 <_GLOBAL_OFFSET_TABLE_+0x8> ? 鏈接器標示信息入棧 ?前面還有個ID400586: ff 25 84 0a 20 00 jmpq *0x200a84(%rip) # 601010 <_GLOBAL_OFFSET_TABLE_+0x10> ?跳轉到鏈接器入口地址40058c: 0f 1f 40 00 nopl 0x0(%rax)

    ?

    跳轉到動態連接器之后,鏈接器根據這兩個棧中的信息(其實就是重定位描述符地址和索引值)得到 add的實際地址, 在將地址放入GOT[3]中 通過gdb詳細看一下。

    (gdb) n 6 add(c); ?運行 ?add之前 (gdb) x /32x 0x601010 0x601010: 0xf7df0210 0x00007fff 0x00400596 0x00000000 ?
    這時GOT[3]add地址還不是add的實際地址 而是0000000000400590<add@plt>: jmp的目的地址0x00400596
    0x601020 <__libc_start_main@got.plt>: 0xf7839a20 0x00007fff 0x004005b6 0x00000000 0
    x601030: 0x00000000 0x000003e8 0x00000000 0x00000000 0x601040: 0x00000000 0x00000000 0x00000000 0x00000000 0x601050: 0x00000000 0x00000000 0x00000000 0x00000000 0x601060: 0x00000000 0x00000000 0x00000000 0x00000000 0x601070: 0x00000000 0x00000000 0x00000000 0x00000000 0x601080: 0x00000000 0x00000000 0x00000000 0x00000000 (gdb) print add $2 = {<text variable, no debug info>} 0x7ffff7bd96a8 <add> ? add函數的地址 (gdb) n 7 return 0; (gdb) x /32x 0x601010 0x601010: 0xf7df0210 0x00007fff 0xf7bd96a8 0x00007fff ?
    進入add GOT[3]中地址變為了 add的實際地址 再之后的
    0000000000400590 <add@plt>: jmpq *0x200a82(%rip) 就會直接跳到add的地址 開始執行指令。

    0x601020 <__libc_start_main@got.plt>: 0xf7839a20 0x00007fff 0x004005b6 0x00000000 0
    x601030: 0x00000000 0x00000463 0x00000000 0x00000000 0x601040: 0x00000000 0x00000000 0x00000000 0x00000000 0x601050: 0x00000000 0x00000000 0x00000000 0x00000000 0x601060: 0x00000000 0x00000000 0x00000000 0x00000000 0x601070: 0x00000000 0x00000000 0x00000000 0x00000000 0x601080: 0x00000000 0x00000000 0x00000000 0x00000000

    這里可以看到被引用的函數調用之前 GOT中的地址并沒有被值為函數的實際地址。之后實際地址就被裝入,跳轉到相應地址開始執行,之后就可以直接跳轉運行了,只需要一個跳轉指令即可。

    ?

    轉載于:https://www.cnblogs.com/MaAce/p/7999795.html

    總結

    以上是生活随笔為你收集整理的Linux 链接详解----动态链接库的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 人妻无码一区二区三区 | 国产性生活毛片 | 六月丁香啪啪 | 精品婷婷 | 国产一区二区黄 | 欧美xxxx18国产 | 亚洲综合在线一区二区 | 国产精品久久久久一区二区三区 | 综合久久2o19 | 美女视频在线免费观看 | 18视频在线观看网站 | 日韩极品视频在线观看 | www一级片 | 自拍1页 | 欧美久久久久 | 亚洲成人少妇 | 久久永久免费 | 国产精品一区二区av日韩在线 | 欧美日韩综合一区二区三区 | jzjzjzjzj亚洲成熟少妇 | 欧美高清性xxxxhd | 成人写真福利网 | 乖疼润滑双性初h | 美日韩中文字幕 | 亚洲欧美国产另类 | 国产破处av | caoporen在线| www.男人的天堂.com | a天堂视频在线观看 | 好男人天堂网 | www国产亚洲精品久久麻豆 | 色噜噜狠狠成人中文 | 夜夜春很很躁夜夜躁 | 色综合色综合网色综合 | 六月婷婷网| 欧美三级自拍 | 日日爱夜夜操 | 欧美在线xxxx | 色婷婷色婷婷 | 欧美顶级毛片在线播放 | 91日韩一区二区 | 精品不卡一区二区三区 | 国产精品美女久久久久图片 | 91精品网站| 久久亚洲av无码西西人体 | 自拍中文字幕 | 无码精品国产一区二区三区 | www色综合| 久久久三级视频 | 第一色综合| 亚洲高清视频网站 | 亚洲第一黄色片 | 日韩中文字幕在线视频 | 天天看夜夜操 | 丁香色婷婷| 爱啪啪av| 亚洲色图视频在线 | 国产精品久久精品三级 | 99国产视频 | 亚洲男人天堂2024 | 免费看三级黄色片 | www.香蕉.com| 怡红院精品视频 | 亚洲一卡二卡三卡四卡 | 香蕉视频亚洲一级 | 老汉av在线 | 亚洲中文字幕无码一区二区三区 | 丰满饥渴老女人hd | 亚洲天堂中文字幕在线 | 青青草精品在线 | www.av天天 | 波多野结衣久久精品 | 黄色一极片 | 精品人妻一区二区三区蜜桃 | 超碰人人超碰 | 91蜜桃传媒精品久久久一区二区 | 国产网红女主播精品视频 | 麻豆婷婷 | 国产免费又爽又色又粗视频 | 国产成人精品a视频 | 无人在线观看的免费高清视频 | 999精品在线 | 最新日韩一区 | 色原网| 九九99视频| 欧美性生活网站 | 香蕉视频18 | 成人私密视频 | 欧美激情爱爱 | 人妻夜夜爽天天爽三区麻豆av网站 | 午夜精品一区二区三区三上悠亚 | 黑人极品ⅴideos精品欧美棵 | 在线不卡| 木木影院| 伊人网大香 | 亚洲人吸女人奶水 | 五月天综合久久 | 国产免费一区二区三区三州老师 | 一区二区视频在线观看 |