Linux中《動/靜態庫原理》

目錄

  • 目標文件
  • ELF文件
  • ELF從形成到加載輪廓
    • ELF形成可執行
    • readelf命令
    • ELF可執行文件加載
  • 理解連接與加載
    • 靜態鏈接
    • ELF加載與進程地址空間
      • 虛擬地址/邏輯地址
    • 重新理解進程虛擬地址空間
  • 動態鏈接與動態庫加載
    • 進程如何看到動態庫
    • 進程間如何共享庫的
    • 動態鏈接
      • 動態鏈接到底是如何工作的??
      • 我們的可執行程序被編譯器動了手腳
      • 動態庫中的相對地址
      • 我們的程序,怎么和庫具體映射起來的
      • 我們的程序,怎么進行庫函數調用
      • 全局偏移量表GOT(global offset table)
      • 庫間依賴
    • 總結

目標文件

編譯和鏈接這兩個步驟,在Windows下被我們的IDE封裝的很完美,我們?般都是?鍵構建?常?便,但?旦遇到錯誤的時候呢,尤其是鏈接相關的錯誤,很多?就束??策了。在Linux下,我們之前也學習過如何通過gcc編譯器來完成這?系列操作。
在這里插入圖片描述
先來回顧下什么是編譯呢?編譯的過程其實就是將我們程序的源代碼翻譯成CPU能夠直接運?的機器
代碼。
?如:在?個源?件 hello.c ?便簡單輸出"hello world!",并且調??個run函數,?這個函數被定義在另?個原?件 code.c 中。這?我們就可以調? gcc -c 來分別編譯這兩個原?件。


在這里插入圖片描述

在這里插入圖片描述

可以看到,在編譯之后會?成兩個擴展名為 .o 的?件,它們被稱作?標?件(可重定位目標文件)。要注意的是如果我們修改了?個原?件,那么只需要單獨編譯它這?個,?不需要浪費時間重新編譯整個?程。?標?件是?個?進制的?件,?件的格式是 ELF ,是對?進制代碼的?種封裝。
在這里插入圖片描述

file name //用于辨別文件類型

ELF文件

要理解編譯鏈接的細節,我們不得不了解?下ELF?件。其實有以下四種?件其實都是ELF?件:

  • 可重定位?件(Relocatable File) :即 xxx.o ?件。包含適合于與其他?標?件鏈接來創建可執??件或者共享?標?件(動態庫)的代碼和數據。
  • 可執??件(Executable File) :即可執?程序。
  • 共享?標?件(Shared Object File) :即 xxx.so?件。
  • 內核轉儲(core dumps) ,存放當前進程的執?上下?,?于dump信號觸發。
    在這里插入圖片描述

?個ELF文件由以下四部分組成:

  • ELF頭(ELF header) :描述?件的主要特性。其位于?件的開始位置,它的主要?的是定位?
    件的其他部分。
  • 程序頭表(Program header table) :列舉了所有有效的段(segments)和他們的屬性。表?
    記著每個段的開始的位置和位移(offset)、?度,畢竟這些段,都是緊密的放在?進制?件中,
    需要段表的描述信息,才能把他們每個段分割開。
  • 節頭表(Section header table) :包含對節(sections)的描述。
  • 節(Section ):ELF?件中的基本組成單位,包含了特定類型的數據。ELF?件的各種信息和
    數據都存儲在不同的節中,如代碼節存儲了可執?代碼,數據節存儲了全局變量和靜態數據等。

最常見的節:

代碼節(.text):?于保存機器指令,是程序的主要執?部分。
數據節(.data):保存已初始化的全局變量和局部靜態變量。

在這里插入圖片描述

ELF從形成到加載輪廓

ELF形成可執行

step-1:將多份 C/C++ 源代碼,翻譯成為?標 .o ?件
step-2:將多份 .o ?件section進?合并

在這里插入圖片描述
注意:

實際合并是在鏈接時進?的,但是并不是這么簡單的合并,也會涉及對庫合并。鏈接就是把.o目標文件,可執行程序和共享文件中的section進行合并。

readelf命令

readelf 是 Linux 系統中用于分析 ELF(Executable and Linkable Format)格式文件的命令行工具,常用于查看可執行文件、動態庫、目標文件等的內部結構信息。
常用選項:

-h --file-header :顯示 ELF 文件的頭部信息,包括文件類型、機器架構、程序入口地址等。
-S --section-headers :顯示節頭表信息,包括節名、節類型、大小、偏移、屬性等。
-l --program-headers :顯示程序頭信息,包括節區段的大小、偏移、虛擬地址等。
-s--symbols:顯示符號表信息,包括符號名稱、類型、綁定屬性、大小、值等。
-r --relocs:顯示重定位表信息,包括重定位節、符號、類型等。
-a --all:顯示所有信息,相當于同時使用 -h、-l、-S、-s、-r、-d 等選項。

ELF可執行文件加載

  • ?個ELF會有多種不同的Section,在加載到內存的時候,也會進?Section合并,形成segment。
  • 合并原則:相同屬性,?如:可讀,可寫,可執?,需要加載時申請空間等。
  • 這樣,即便是不同的Section,在加載到內存中,可能會以segment的形式,加載到?起。
  • 很顯然,這個合并?作也已經在形成ELF的時候,合并?式已經確定了,具體合并原則被記錄在了ELF的 程序頭表(Program header table) 中。
//查看可執?程序的section
readelf -S a.out
There are 29 section headers, starting at offset 0x1968:Section Headers:[Nr] Name              Type             Address           OffsetSize              EntSize          Flags  Link  Info  Align[ 0]                   NULL             0000000000000000  000000000000000000000000  0000000000000000           0     0     0[ 1] .interp           PROGBITS         0000000000000238  00000238000000000000001c  0000000000000000   A       0     0     1[ 2] .note.ABI-tag     NOTE             0000000000000254  000002540000000000000020  0000000000000000   A       0     0     4[ 3] .note.gnu.build-i NOTE             0000000000000274  000002740000000000000024  0000000000000000   A       0     0     4[ 4] .gnu.hash         GNU_HASH         0000000000000298  00000298000000000000001c  0000000000000000   A       5     0     8[ 5] .dynsym           DYNSYM           00000000000002b8  000002b800000000000000a8  0000000000000018   A       6     1     8[ 6] .dynstr           STRTAB           0000000000000360  000003600000000000000082  0000000000000000   A       0     0     1[ 7] .gnu.version      VERSYM           00000000000003e2  000003e2000000000000000e  0000000000000002   A       5     0     2[ 8] .gnu.version_r    VERNEED          00000000000003f0  000003f00000000000000020  0000000000000000   A       6     1     8[ 9] .rela.dyn         RELA             0000000000000410  0000041000000000000000c0  0000000000000018   A       5     0     8[10] .rela.plt         RELA             00000000000004d0  000004d00000000000000018  0000000000000018  AI       5    22     8[11] .init             PROGBITS         00000000000004e8  000004e80000000000000017  0000000000000000  AX       0     0     4[12] .plt              PROGBITS         0000000000000500  000005000000000000000020  0000000000000010  AX       0     0     16[13] .plt.got          PROGBITS         0000000000000520  000005200000000000000008  0000000000000008  AX       0     0     8[14] .text             PROGBITS         0000000000000530  0000053000000000000001b2  0000000000000000  AX       0     0     16[15] .fini             PROGBITS         00000000000006e4  000006e40000000000000009  0000000000000000  AX       0     0     4[16] .rodata           PROGBITS         00000000000006f0  000006f0000000000000001a  0000000000000000   A       0     0     4[17] .eh_frame_hdr     PROGBITS         000000000000070c  0000070c0000000000000044  0000000000000000   A       0     0     4[18] .eh_frame         PROGBITS         0000000000000750  000007500000000000000128  0000000000000000   A       0     0     8[19] .init_array       INIT_ARRAY       0000000000200db8  00000db80000000000000008  0000000000000008  WA       0     0     8[20] .fini_array       FINI_ARRAY       0000000000200dc0  00000dc00000000000000008  0000000000000008  WA       0     0     8[21] .dynamic          DYNAMIC          0000000000200dc8  00000dc800000000000001f0  0000000000000010  WA       6     0     8[22] .got              PROGBITS         0000000000200fb8  00000fb80000000000000048  0000000000000008  WA       0     0     8[23] .data #            PROGBITS         0000000000201000  000010000000000000000010  0000000000000000  WA       0     0     8[24] .bss              NOBITS           0000000000201010  000010100000000000000008  0000000000000000  WA       0     0     1[25] .comment          PROGBITS         0000000000000000  000010100000000000000029  0000000000000001  MS       0     0     1[26] .symtab           SYMTAB           0000000000000000  000010400000000000000618  0000000000000018          27    44     8[27] .strtab           STRTAB           0000000000000000  00001658000000000000020e  0000000000000000           0     0     1[28] .shstrtab         STRTAB           0000000000000000  0000186600000000000000fe  0000000000000000           0     0     1......
//查看可執行程序的ELF Header
readelf -h a.out
ELF Header:Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 //用來表示該文件是ELF格式的,告訴系統可以用ELF格式讀取文件內容Class:                             ELF64Data:                              2's complement, little endianVersion:                           1 (current)OS/ABI:                            UNIX - System VABI Version:                       0Type:                              DYN (Shared object file)Machine:                           Advanced Micro Devices X86-64Version:                           0x1Entry point address:               0x530Start of program headers:          64 (bytes into file)Start of section headers:          6504 (bytes into file)Flags:                             0x0Size of this header:               64 (bytes)Size of program headers:           56 (bytes)Number of program headers:         9Size of section headers:           64 (bytes)Number of section headers:         29Section header string table index: 28
//知道了size和number,我們就可以用起始地址+偏移量的方式訪文件內容了
//查看section合并的segment
readelf -l a.outElf file type is DYN (Shared object file)
Entry point 0x530
There are 9 program headers, starting at offset 64Program Headers:Type           Offset             VirtAddr           PhysAddrFileSiz            MemSiz              Flags  AlignPHDR           0x0000000000000040 0x0000000000000040 0x00000000000000400x00000000000001f8 0x00000000000001f8  R      0x8INTERP         0x0000000000000238 0x0000000000000238 0x00000000000002380x000000000000001c 0x000000000000001c  R      0x1[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]LOAD           0x0000000000000000 0x0000000000000000 0x00000000000000000x0000000000000878 0x0000000000000878  R E    0x200000LOAD           0x0000000000000db8 0x0000000000200db8 0x0000000000200db80x0000000000000258 0x0000000000000260  RW     0x200000
......Section to Segment mapping:Segment Sections...00     01     .interp 02     .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame 03     .init_array .fini_array .dynamic .got .data .bss 04     .dynamic 05     .note.ABI-tag .note.gnu.build-id 06     .eh_frame_hdr 07     08     .init_array .fini_array .dynamic .got 

.text是代碼段(權限也是只讀的),.data是數據段,.rodata是只讀數據段,符合合并規則。
bss段:是 ELF(或其他可執行文件格式)中用于存儲未初始化的全局變量和靜態變量的內存區域,可以節省可執行程序占據的磁盤空間。

size a.outtext	   data	    bss	    dec	    hex	filename1580	    600	      8	   2188	    88c	a.out

?個ELF會有多種不同的Section,在加載到內存的時候,也會進?Section合并,形成segment
在這里插入圖片描述

為什么要將section合并成為segment

  • Section合并的主要原因是為了減少??碎?,提?內存使?效率。如果不進?合并,假設????為4096字節(內存塊基本??,加載,管理的基本單位),如果.text部分為4097字節,.init部分為512字節,那么它們將占?3個??,?合并后,它們只需2個??。
  • 此外,操作系統在加載程序時,會將具有相同屬性的section合并成?個?的segment這樣就可以實現不同的訪問權限(eg:頁表),從?優化內存管理和權限訪問控制。
    注意:內存是以4kb為基本單位的,從文件讀取數據也是4kb為單位讀取。

對于 程序頭表 和 節頭表 ?有什么?呢,其實 ELF ?件提供 2 個不同的視圖/視?來讓我們理解這
兩個部分:

鏈接視圖(Linking view) - 對應節頭表 Section header table

  • ?件結構的粒度更細,將?件按功能模塊的差異進?劃分,靜態鏈接分析的時候?般關注的是鏈接視圖,能夠理解ELF?件中包含的各個部分的信息。
  • 為了空間布局上的效率,將來在鏈接?標?件時,鏈接器會把很多節(section)合并,規整成可執?的段(segment)、可讀寫的段、只讀段等。合并了后,空間利?率就?了,否則,很?的很?的?段,未來物理內存?浪費太?(物理內存?分配?般都是整數倍?塊給你,?如4k),所以,鏈接器趁著鏈接就把?塊們都合并了。

執?視圖(execution view) - 對應程序頭表 Program header table

  • 告訴操作系統,如何加載可執??件,完成進程內存的初始化。?個可執?程序的格式中,?定有 program header table
  • 說?了就是:?個在鏈接時作?,?個在運?加載時作?

在這里插入圖片描述
從 鏈接視圖 來看:
? 命令 readelf -S hello.o 可以幫助查看ELF?件的 節頭表。
? .text節 :是保存了程序代碼指令的代碼節。
? .data節 :保存了初始化的全局變量和局部靜態變量等數據。
? .rodata節 :保存了只讀的數據,如??C語?代碼中的字符串。由于.rodata節是只讀的,所
以只能存在于?個可執??件的只讀段中。因此,只能是在text段(不是data段)中找到.rodata
節。
? .BSS節 :為未初始化的全局變量和局部靜態變量預留位置
? .symtab節 : Symbol Table 符號表,就是源碼??那些函數名、變量名和代碼的對應關系。
? .got.plt節 (全局偏移表-過程鏈接表):.got節保存了全局偏移表。.got節和.plt節?起提供了對導?的共享庫函數的訪問??,由動態鏈接器在運?時進?修改。對于GOT的理解,我們后?會說。
? 使? readelf 命令查看 .so ?件可以看到該節。
從 執行視圖 來看:
? 告訴操作系統哪些模塊可以被加載進內存。
? 加載進內存之后哪些分段是可讀可寫,哪些分段是只讀,哪些分段是可執行的。

理解連接與加載

靜態鏈接

  • ?論是??的.o, 還是靜態庫中的.o,本質都是把.o?件進?連接的過程
  • 所以:研究靜態鏈接,本質就是研究.o是如何鏈接的

在這里插入圖片描述

查看編譯后的.o?標?件

在這里插入圖片描述

objdump -d 命令:將代碼段(.text)進行反匯編查看
code.o 中的 main 函數不認識 printfrun 函數

 //cat code.c
#include <stdio.h>
void run();
int main()
{printf("Hello world\n");run();return 0;
}

我們可以看到這?的call指令,它們分別對應之前調?的printf和run函數,但是你會發現他們的跳轉地址都被設成了0。那這是為什么呢?其實就是在編譯 hello.c 的時候,編譯器是完全不知道printfrun函數的存在的,?如他們位于內存的哪個區塊,代碼?什么樣都是不知道的。因此,編輯器只能將這兩個函數的跳轉地址先暫時設為0。

這個地址會在哪個時候被修正?鏈接的時候!為了讓鏈接器將來在鏈接時能夠正確定位到這些被修正的地址,在代碼塊(.data)中還存在?個重定位表,這張表將來在鏈接的時候,就會根據表?記錄的地址將其修正。

//讀取code.o的符號表
readelf -s code.o
Symbol table '.symtab' contains 13 entries:Num:    Value          Size Type    Bind   Vis      Ndx Name0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS code.c2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 5: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 6: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 7: 0000000000000000     0 SECTION LOCAL  DEFAULT    8 8: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 9: 0000000000000000    33 FUNC    GLOBAL DEFAULT    1 main10: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _GLOBAL_OFFSET_TABLE_11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND puts12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND run
 readelf -s hello.oSymbol table '.symtab' contains 12 entries:Num:    Value          Size Type    Bind   Vis      Ndx Name0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS hello.c2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 5: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 6: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 7: 0000000000000000     0 SECTION LOCAL  DEFAULT    8 8: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 9: 0000000000000000    19 FUNC    GLOBAL DEFAULT    1 run10: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _GLOBAL_OFFSET_TABLE_11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND puts

puts:就是printf的實現
run:就是run的實現
UND就是:undefine,表?未定義說?了就是本.o?件找不到

//讀取a.out的符號表
readelf -s a.outSymbol table '.dynsym' contains 7 entries:Num:    Value          Size Type    Bind   Vis      Ndx Name0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 1: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterTMCloneTab2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.2.5 (2)3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)4: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__5: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMCloneTable6: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __cxa_finalize@GLIBC_2.2.5 (2)Symbol table '.symtab' contains 65 entries:Num:    Value          Size Type    Bind   Vis      Ndx Name0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 1: 0000000000000238     0 SECTION LOCAL  DEFAULT    1 2: 0000000000000254     0 SECTION LOCAL  DEFAULT    2 3: 0000000000000274     0 SECTION LOCAL  DEFAULT    3 4: 0000000000000298     0 SECTION LOCAL  DEFAULT    4 5: 00000000000002b8     0 SECTION LOCAL  DEFAULT    5 6: 0000000000000360     0 SECTION LOCAL  DEFAULT    6 7: 00000000000003e2     0 SECTION LOCAL  DEFAULT    7 8: 00000000000003f0     0 SECTION LOCAL  DEFAULT    8 9: 0000000000000410     0 SECTION LOCAL  DEFAULT    9 10: 00000000000004d0     0 SECTION LOCAL  DEFAULT   10 11: 00000000000004e8     0 SECTION LOCAL  DEFAULT   11 12: 0000000000000500     0 SECTION LOCAL  DEFAULT   12 13: 0000000000000520     0 SECTION LOCAL  DEFAULT   13 14: 0000000000000530     0 SECTION LOCAL  DEFAULT   14 15: 00000000000006e4     0 SECTION LOCAL  DEFAULT   15 16: 00000000000006f0     0 SECTION LOCAL  DEFAULT   16 17: 000000000000070c     0 SECTION LOCAL  DEFAULT   17 18: 0000000000000750     0 SECTION LOCAL  DEFAULT   18 19: 0000000000200db8     0 SECTION LOCAL  DEFAULT   19 20: 0000000000200dc0     0 SECTION LOCAL  DEFAULT   20 21: 0000000000200dc8     0 SECTION LOCAL  DEFAULT   21 22: 0000000000200fb8     0 SECTION LOCAL  DEFAULT   22 23: 0000000000201000     0 SECTION LOCAL  DEFAULT   23 24: 0000000000201010     0 SECTION LOCAL  DEFAULT   24 25: 0000000000000000     0 SECTION LOCAL  DEFAULT   25 26: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c27: 0000000000000560     0 FUNC    LOCAL  DEFAULT   14 deregister_tm_clones28: 00000000000005a0     0 FUNC    LOCAL  DEFAULT   14 register_tm_clones29: 00000000000005f0     0 FUNC    LOCAL  DEFAULT   14 __do_global_dtors_aux30: 0000000000201010     1 OBJECT  LOCAL  DEFAULT   24 completed.769831: 0000000000200dc0     0 OBJECT  LOCAL  DEFAULT   20 __do_global_dtors_aux_fin32: 0000000000000630     0 FUNC    LOCAL  DEFAULT   14 frame_dummy33: 0000000000200db8     0 OBJECT  LOCAL  DEFAULT   19 __frame_dummy_init_array_34: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS code.c35: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS hello.c36: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c37: 0000000000000874     0 OBJECT  LOCAL  DEFAULT   18 __FRAME_END__38: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS 39: 0000000000200dc0     0 NOTYPE  LOCAL  DEFAULT   19 __init_array_end40: 0000000000200dc8     0 OBJECT  LOCAL  DEFAULT   21 _DYNAMIC41: 0000000000200db8     0 NOTYPE  LOCAL  DEFAULT   19 __init_array_start42: 000000000000070c     0 NOTYPE  LOCAL  DEFAULT   17 __GNU_EH_FRAME_HDR43: 0000000000200fb8     0 OBJECT  LOCAL  DEFAULT   22 _GLOBAL_OFFSET_TABLE_44: 00000000000006e0     2 FUNC    GLOBAL DEFAULT   14 __libc_csu_fini45: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterTMCloneTab46: 0000000000201000     0 NOTYPE  WEAK   DEFAULT   23 data_start47: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@@GLIBC_2.2.548: 0000000000201010     0 NOTYPE  GLOBAL DEFAULT   23 _edata49: 000000000000065b    19 FUNC    GLOBAL DEFAULT   14 run50: 00000000000006e4     0 FUNC    GLOBAL DEFAULT   15 _fini51: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@@GLIBC_52: 0000000000201000     0 NOTYPE  GLOBAL DEFAULT   23 __data_start53: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__54: 0000000000201008     0 OBJECT  GLOBAL HIDDEN    23 __dso_handle55: 00000000000006f0     4 OBJECT  GLOBAL DEFAULT   16 _IO_stdin_used56: 0000000000000670   101 FUNC    GLOBAL DEFAULT   14 __libc_csu_init57: 0000000000201018     0 NOTYPE  GLOBAL DEFAULT   24 _end58: 0000000000000530    43 FUNC    GLOBAL DEFAULT   14 _start59: 0000000000201010     0 NOTYPE  GLOBAL DEFAULT   24 __bss_start60: 000000000000063a    33 FUNC    GLOBAL DEFAULT   14 main61: 0000000000201010     0 OBJECT  GLOBAL HIDDEN    23 __TMC_END__62: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMCloneTable63: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __cxa_finalize@@GLIBC_2.264: 00000000000004e8     0 FUNC    GLOBAL DEFAULT   11 _init

在這里插入圖片描述

兩個.o進?合并之后,在最終的可執?程序中,就找到了run
000000000000065b:其實是地址,后?說
FUNC:表?run符號類型是個函數
14:就是run函數所在的section被合并最終的那?個section中了,14就是下標

 readelf -S a.out
There are 29 section headers, starting at offset 0x1968:Section Headers:[Nr] Name              Type             Address           OffsetSize              EntSize          Flags  Link  Info  Align
······[14] .text             PROGBITS         0000000000000530  0000053000000000000001b2  0000000000000000  AX       0     0     16
······
//[14]->.text代碼段

靜態鏈接就是把庫中的.o進?合并,和上述過程?樣。
所以鏈接其實就是將編譯之后的所有?標?件連同?到的?些靜態庫運?時庫組合,拼裝成?個獨?
的可執??件。其中就包括我們之前提到的地址修正,當所有模塊組合在?起之后,鏈接器會根據我
們的.o?件或者靜態庫中的重定位表找到那些需要被重定位的函數全局變量,從?修正它們的地址。這
其實就是靜態鏈接的過程。
在這里插入圖片描述

所以,鏈接過程中會涉及到對.o中外部符號進?地址重定位。

ELF加載與進程地址空間

虛擬地址/邏輯地址

問題:
? ?個ELF程序,在沒有被加載到內存的時候,有沒有地址呢?
? 進程mm_struct、vm_area_struct在進程剛剛創建的時候,初始化數據從哪?來的?
答案:
?個ELF程序,在沒有被加載到內存的時候,有地址,這個地址也叫做邏輯地址。
進程mm_struct、vm_area_struct在進程剛剛創建的時候,初始化數據從ELF程序來的。

?個ELF程序,在沒有被加載到內存的時候,本來就有地址,當代計算機?作的時候,都采?"平坦
模式"進??作。所以也要求ELF對??的代碼和數據進?統?編址,下?是 objdump -S 反匯編
之后的代碼
在這里插入圖片描述

最左側的就是ELF的虛擬地址,其實,嚴格意義上應該叫做邏輯地址(起始地址+偏移量), 但是我們
認為起始地址是0.也就是說,其實虛擬地址在我們的程序還沒有加載到內存的時候,就已經把可執
?程序進?統?編址了.

進程mm_struct、vm_area_struct在進程剛剛創建的時候,初始化數據從哪?來的?從ELF各個
segment來,每個segment有??的起始地址和??的?度,?來初始化內核結構中的[start, end]
等范圍數據,另外在?詳細地址,填充?表

所以:虛擬地址機制,不光光OS要?持,編譯器也要?持.
磁盤上的可執行程序代碼和數據編址,其實就是虛擬地址的統一編址,在內存里叫虛擬地址,在磁盤上叫邏輯地址(線性地址),內存上面的地址叫做物理地址。

重新理解進程虛擬地址空間

ELF 在被編譯好之后,會把??未來程序的??地址記錄在ELF header的Entry字段中:

在這里插入圖片描述
在這里插入圖片描述

動態鏈接與動態庫加載

進程如何看到動態庫

在這里插入圖片描述

進程間如何共享庫的

在這里插入圖片描述

動態鏈接

動態鏈接其實遠?靜態鏈接要常?得多。?如我們查看下 a.out這個可執?程序依賴的動態庫,會發
現它就?到了?個c動態鏈接庫:

ldd a.outlinux-vdso.so.1 (0x00007ffe9dfde000)libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fca4ba0f000)/lib64/ld-linux-x86-64.so.2 (0x00007fca4c002000)# ldd命令?于打印程序或者庫?件所依賴的共享庫列表。

這?的 libc.soC語?的運?時庫,??提供了常?的標準輸?輸出?件字符串處理等等這些功能。那為什么編譯器默認不使?靜態鏈接呢?靜態鏈接會將編譯產?的所有?標?件,連同?到的各種庫,合并形成?個獨?的可執??件,它不需要額外的依賴就可以運?。照理來說應該更加?便才對是吧?

靜態鏈接最大的問題在于生成的文件體積大,并且相當耗費內存資源。隨著軟件復雜度的提升,我們的操作系統也越來越臃腫,不同的軟件就有可能都包含了相同的功能和代碼,顯然會浪費?量的硬盤空間。

這個時候,動態鏈接的優勢就體現出來了,我們可以將需要共享的代碼單獨提取出來,保存成?個獨?的動態鏈接庫,等到程序運?的時候再將它們加載到內存,這樣不但可以節省空間,因為同?個模塊在內存中只需要保留?份副本,可以被不同的進程所共享。

動態鏈接到底是如何工作的??

?先要交代?個結論,動態鏈接實際上將鏈接的整個過程推遲到了程序加載的時候(這個時候程序和動態庫已經被加載到內存了)。?如我們去運??個程序,操作系統會?先將程序的數據代碼連同它?到的?系列動態庫先加載到內存,其中每個動態庫的加載地址都是不固定的,操作系統會根據當前地址空間的使?情況為它們動態分配?段內存。當動態庫被加載到內存以后,?旦它的內存地址被確定,我們就可以去修正動態庫中的那些函數跳轉地址了。

我們的可執行程序被編譯器動了手腳

在這里插入圖片描述

C/C++程序中,當程序開始執?時,它?先并不會直接跳轉到 main 函數。實際上,程序的??點是 _start ,這是?個由C運?時庫(通常是glibc)或鏈接器(如ld)提供的特殊函數。在 _start 函數中,會執??系列初始化操作,這些操作包括:

  1. 設置堆棧:為程序創建?個初始的堆棧環境。

  2. 初始化數據段:將程序的數據段(如全局變量和靜態變量)從初始化數據段復制到相應的內存位
    置,并清零未初始化的數據段。

  3. 動態鏈接:這是關鍵的?步, _start 函數會調?動態鏈接器的代碼來解析和加載程序所依賴的
    動態庫(shared libraries)。動態鏈接器會處理所有的符號解析和重定位,確保程序中的函數調
    ?和變量訪問能夠正確地映射到動態庫中的實際地址。

    在這里插入圖片描述
    動態鏈接器:
    ? 動態鏈接器(如ld-linux.so)負責在程序運?時加載動態庫。
    ? 當程序啟動時,動態鏈接器會解析程序中的動態庫依賴,并加載這些庫到內存中。
    環境變量和配置文件:
    ? Linux系統通過環境變量(如LD_LIBRARY_PATH)和配置?件(如/etc/ld.so.conf及其?配置
    ?件)來指定動態庫的搜索路徑。
    ? 這些路徑會被動態鏈接器在加載動態庫時搜索。
    緩存文件:
    ? 為了提?動態庫的加載效率,Linux系統會維護?個名為/etc/ld.so.cache的緩存?件。
    ? 該?件包含了系統中所有已知動態庫的路徑和相關信息,動態鏈接器在加載動態庫時會?先
    搜索這個緩存?件。

  4. 調?__libc_start_main:?旦動態鏈接完成,_start函數會調?__libc_start_main (這是glibc提供的?個函數)。 __libc_start_main 函數負責執??些額外的初始化?作,?如設置信號處理函數、初始化線程庫(如果使?了線程)等。

  5. 調? main 函數:最后, __libc_start_main 函數會調?程序的main函數,此時程序的執?控制權才正式交給?戶編寫的代碼。

  6. 處理main函數的返回值:當 main 函數返回時,__libc_start_main 會負責處理這個返回值,并最終調? _exit 函數來終?程序。

上述過程描述了C/C++程序在 main 函數之前執?的?系列操作,但這些操作對于?多數程序員來說是透明的。程序員通常只需要關注main 函數中的代碼,?不需要關?底層的初始化過程。然?,了解這些底層細節有助于更好地理解程序的執?流程和調試問題。

動態庫中的相對地址

動態庫為了隨時進?加載,為了?持并映射到任意進程的任意位置,對動態庫中的?法,統?編址,采?相對編址的?案進?編制的(其實可執?程序也?樣,都要遵守平坦模式,只不過exe是直接加載的)。

我們的程序,怎么和庫具體映射起來的

注意:

  1. 動態庫也是?個?件,要訪問也是要被先加載,要加載也是要被打開的
  2. 讓我們的進程找到動態庫的本質:也是?件操作,不過我們訪問庫函數,通過虛擬地址進 ?跳轉訪問的,所以需要把動態庫映射到進程的地址空間中

在這里插入圖片描述

我們的程序,怎么進行庫函數調用

📌 注意:
? 庫已經被我們映射到了當前進程的地址空間中
? 庫的虛擬起始地址我們也已經知道了
? 庫中每?個?法的偏移量地址我們也知道
? 所有:訪問庫中任意?法,只需要知道庫的起始虛擬地址+?法偏移量即可定位庫中的?法
? ?且:整個調?過程,是從代碼區跳轉到共享區,調?完畢在返回到代碼區,整個過程完全在進程地址空間中進?的

全局偏移量表GOT(global offset table)

注意:
? 也就是說,我們的程序運?之前,先把所有庫加載并映射,所有庫的起始虛擬地址都應該提前知道
? 然后對我們加載到內存中的程序的庫函數調?進?地址修改,在內存中?次完成地址設置(這個叫做加載地址重定位)
? 等等,修改的是代碼區?不是說代碼區在進程中是只讀的嗎?怎么修改?能修改嗎?
所以:動態鏈接采?的做法是在 .data (可執?程序或者庫??)中專?預留??區域?來存放函數
的跳轉地址,它也被叫做全局偏移表GOT,表中每?項都是本運?模塊要引?的?個全局變量或函數
的地址。

? 因為.data區域是可讀寫的,所以可以?持動態進?修改

 readelf -S a.out
There are 29 section headers, starting at offset 0x1968:Section Headers:[Nr] Name              Type             Address           OffsetSize              EntSize          Flags  Link  Info  Align
......[14] .text             PROGBITS         0000000000000530  0000053000000000000001b2  0000000000000000  AX       0     0     16
......00000000000001f0  0000000000000010  WA       6     0     8[22] .got              PROGBITS         0000000000200fb8  00000fb8
......
readelf -l a.out 
......03     .init_array .fini_array .dynamic .got .data .bss 
......
# .got在加載的時候,會和.data合并成為?個segment,然后加載在?起
  1. 由于代碼段只讀,我們不能直接修改代碼段。但有了GOT表,代碼便可以被所有進程共享。但在不同進程的地址空間中,各動態庫的絕對地址、相對位置都不同。反映到GOT表上,就是每個進程的每個動態庫都有獨?的GOT表,所以進程間不能共享GOT表。
  2. 在單個.so下,由于GOT表與 .text 的相對位置是固定的,我們完全可以利?CPU的相對尋址來找到GOT表。
  3. 在調?函數的時候會?先查表,然后根據表中的地址來進?跳轉,這些地址在動態庫加載的時候會被修改為真正的地址。
  4. 這種?式實現的動態鏈接就被叫做PIC地址?關代碼 。換句話說,我們的動態庫不需要做任何修改,被加載到任意內存地址都能夠正常運?,并且能夠被所有進程共享,這也是為什么之前我們給編譯器指定-fPIC參數的原因,PIC=相對編址+GOT

在這里插入圖片描述
PLT是什么?

庫間依賴

注意:
? 不僅僅有可執?程序調?庫
? 庫也會調?其他庫!!庫之間是有依賴的,如何做到庫和庫之間互相調?也是與地址?關的呢??
? 庫中也有.GOT,和可執??樣!這也就是為什么?家為什么都是ELF的格式!
在這里插入圖片描述
由于動態鏈接在程序加載的時候需要對?量函數進?重定位,這?步顯然是?常耗時的。為了進?步降低開銷,我們的操作系統還做了?些其他的優化,?如延遲綁定,或者也叫PLT(過程連接表
Procedure Linkage Table))。與其在程序?開始就對所有函數進?重定位,不如將這個過程推遲到函數第?次被調?的時候,因為絕?多數動態庫中的函數可能在程序運?期間?次都不會被使?到。

思路是:GOT中的跳轉地址默認會指向?段輔助代碼,它也被叫做樁代碼/stup。在我們第?次調?函數的時候,這段代碼會負責查詢真正函數的跳轉地址,并且去更新GOT表。于是我們再次調?函數的時候,就會直接跳轉到動態庫中真正的函數實現。

在這里插入圖片描述

總??之,動態鏈接實際上將鏈接的整個過程,?如符號查詢、地址的重定位從編譯時推遲到了程序的運?時,它雖然犧牲了?定的性能和程序加載時間,但絕對是物有所值的。因為動態鏈接能夠更有 效的利?磁盤空間和內存資源,以極??便了代碼的更新和維護,更關鍵的是,它實現了?進制級別的代碼復?。

總結

? 靜態鏈接的出現,提?了程序的模塊化?平。對于?個?的項?,不同的?可以獨?地測試和開發??的模塊。通過靜態鏈接,?成最終的可執??件。
? 我們知道靜態鏈接會將編譯產?的所有?標?件,和?到的各種庫合并成?個獨?的可執??件,其中我們會去修正模塊間函數的跳轉地址,也被叫做編譯重定位(也叫做靜態重定位)。
? ?動態鏈接實際上將鏈接的整個過程推遲到了程序加載的時候。?如我們去運??個程序,操作系統會?先將程序的數據代碼連同它?到的?系列動態庫先加載到內存,其中每個動態庫的加載地址都是不固定的,但是?論加載到什么地?,都要映射到進程對應的地址空間,然后通過.GOT?式進?調?(運?重定位,也叫做動態地址重定位)。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/86753.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/86753.shtml
英文地址,請注明出處:http://en.pswp.cn/web/86753.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Android大圖加載優化:BitmapRegionDecoder深度解析與實戰

在移動端開發中&#xff0c;超大圖片加載一直是性能優化的難點。本文將深入剖析BitmapRegionDecoder原理&#xff0c;提供完整Kotlin實現方案&#xff0c;并分享性能調優技巧。 一、為什么需要大圖加載優化&#xff1f; 典型場景&#xff1a; 醫療影像&#xff1a;2000015000…

基于ApachePOI實現高德POI分類快速導入PostgreSQL數據庫實戰

目錄 前言 一、高德POI分類簡介 1、數據表格 2、分類結構 二、從Excel導入到Postgresql 1、Excel解析流程 2、Mybatis批量導入 3、數據入庫 三、總結 前言 在大數據與地理信息深度交融的當下&#xff0c;地理信息系統&#xff08;GIS&#xff09;的觸角已延伸至各個領域…

如何打造Apache Top-Level開源時序數據庫IoTDB

引言 數據與時間結合后&#xff0c;便擁有了生命。在金融、系統日志、工業產線和智能設備等領域&#xff0c;時序數據每毫秒都在不斷產生。管理這些海量時序數據需要專業的數據庫系統。時序數據庫產品正逐漸受到市場的關注&#xff0c;本文將分享如何通過開源的方式&#xff0…

高并發內存池實戰指南

項目源碼&#xff1a;https://gitee.com/kkkred/thread-caching-malloc 目錄 一、脫離new&#xff1a;高并發內存池如何替代傳統動態分配 1.1 new的痛點&#xff1a;碎片、延遲與鎖競爭 1.2 高并發內存池的替代方案&#xff1a;分層預分配無鎖管理 二、大內存&#xff08;…

基于springboot+vue的數字科技風險報告管理系統

開發語言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服務器&#xff1a;tomcat7數據庫&#xff1a;mysql 5.7數據庫工具&#xff1a;Navicat12開發軟件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;Maven3.3.9 系統展示 管理員登錄 管理…

實戰篇----利用 LangChain 和 BERT 用于命名實體識別-----完整代碼

上一篇文章講解了Langchain,實現一個簡單的demo,結合利用 LangChain 和 BERT 用于命名實體識別。 一、命名實體識別模型訓練(bert+CRF) bert作為我們的預訓練模型(用于將輸入文本轉換為特征向量),CRF作為我們的條件隨機場(將嵌入特征轉為標簽),既然要訓練,那么我們的損失函…

現代 C++ 容器深度解析及實踐

一、線性容器&#xff1a;std::array 與 std::forward_list 1. std::array&#xff1a;固定大小的高效容器 在傳統 C 中&#xff0c;數組與 vector 的抉擇常讓人糾結&#xff1a;數組缺乏安全檢查&#xff0c;vector 存在動態擴容開銷。C11 引入的std::array完美平衡了兩者優…

數據集|豬姿態檢測PigBehaviorRecognitionDataset

數據集|豬姿態檢測PigBehaviorRecognitionDataset 一、數據集介紹1.1 介紹1.2 用途1.3 數據集統計 二、樣本類別介紹1. Lying&#xff08;躺臥&#xff09;2. Sleeping&#xff08;睡眠&#xff09;3. Investigating&#xff08;探索&#xff09;4. Eating&#xff08;進食&…

Vue-13-前端框架Vue之應用基礎路由器的使用步驟

文章目錄 1 路由和路由器2 基本切換效果2.1 App.vue(根組件)2.2 components(子組件)2.2.1 Home.vue(首頁)2.2.2 News.vue(新聞)2.2.3 About.vue(關于)2.3 路由器2.3.1 router/index.ts2.3.2 main.ts2.4 效果展示2.5 程序流程3 筆記3.1 路由組件和一般組件3.1.1 Header.vue(一般…

GaussDB實例級自動備份策略:構建數據安全的“自動防護網”

GaussDB實例級自動備份策略&#xff1a;構建數據安全的“自動防護網” 在數字化轉型的浪潮中&#xff0c;數據庫作為企業核心數據的載體&#xff0c;其安全性與可恢復性直接關系到業務的連續性。對于分布式數據庫GaussDB而言&#xff0c;實例級自動備份策略是保障數據安全的關…

推薦幾本關于網絡安全的書

對于網絡安全從業者、相關專業學生以及對網絡安全感興趣的人士而言&#xff0c;掌握扎實的網絡安全知識和技能至關重要。以下推薦的幾本網絡安全書籍&#xff0c;涵蓋了網絡安全領域的多個重要方面&#xff0c;是學習和研究網絡安全的優質參考資料。 1、攻擊網絡協議&#xff…

工業4.0浪潮下PROFIBUS DP轉ETHERNET/IP在軋鋼廠的創新實踐

在工業自動化4.0推動制造業向智能化升級的背景下&#xff0c;軋鋼廠生產對設備互聯與數據協同提出更高要求。PROFIBUS DP與ETHERNET/IP協議的特性差異&#xff0c;制約著西門子PLC與工業測距儀等設備的高效協作。通過協議轉換技術實現兩者互通&#xff0c;為軋鋼生產線注入智能…

從0開始學習R語言--Day31--概率圖模型

在探究變量之間的相關性時&#xff0c;由于并不是每次分析數據時所用的樣本集都能囊括所有的情況&#xff0c;所以單純從樣本集去下判斷會有武斷的嫌疑&#xff1b;同樣的&#xff0c;我們有時候也想要在數據樣本不夠全面時就能對結果有個大概的了解。 例如醫生在給患者做診斷…

微信小程序進度條progress支持漸變色

微信小程序自帶進度條progress支持漸變色代碼 .wx-progress-inner-bar {border-radius: 8rpx !important;background: linear-gradient(90deg, #FFD26E 8%, #ED0700 100%) !important; }<view class"progress-box"><progress percent"80" back…

Linux內核網絡協議棧深度解析:面向連接的INET套接字實現

深入剖析Linux內核中TCP連接管理的核心機制,揭示高效網絡通信的實現奧秘。 一、源地址匹配:連接建立的第一道關卡 在TCP連接建立過程中,內核需要驗證源地址是否匹配。inet_rcv_saddr_equal()函數是實現這一功能的核心,它巧妙地處理了IPv4/IPv6雙棧環境: bool inet_rcv_s…

Vue 項目中 Excel 導入導出功能筆記

功能概述 該代碼實現了 Vue 項目中 Excel 文件的三大核心功能&#xff1a; Excel 導入&#xff1a;上傳文件并解析數據&#xff0c;刷新表格展示。模板下載&#xff1a;獲取并下載標準 Excel 模板文件。數據導出&#xff1a;將表格數據按多級表頭結構導出為 Excel 文件。 一…

71. 簡化路徑 —day94

前言&#xff1a; 作者&#xff1a;神的孩子在歌唱 一個算法小菜雞 大家好&#xff0c;我叫智 71. 簡化路徑 給你一個字符串 path &#xff0c;表示指向某一文件或目錄的 Unix 風格 絕對路徑 &#xff08;以 / 開頭&#xff09;&#xff0c;請你將其轉化為 更加簡潔的規范路徑…

Linux系統編程 | 互斥鎖

1、什么是互斥鎖 如果信號量的值最多為 1&#xff0c;那實際上相當于一個共享資源在任意時刻最多只能有一個線程在訪問&#xff0c;這樣的邏輯被稱為“互斥”。這時&#xff0c;有一種更加方便和語義更加準確的工具來滿足這種邏輯&#xff0c;他就是互斥鎖。 “鎖”是一種非常形…

數據文件寫入技術詳解:從CSV到Excel的ETL流程優化

文章大綱&#xff1a; 引言&#xff1a;數據文件寫入在ETL流程中的重要性 在現代數據處理中&#xff0c;ETL&#xff08;提取、轉換、加載&#xff09;流程是數據分析和業務決策的核心環節&#xff0c;而數據文件寫入作為ETL的最后一步&#xff0c;扮演著至關重要的角色。它不…

在Cline中使用Gemini CLI,圖形化界面操作:從命令行到可視化操作的全新體驗,爽炸天!

在軟件開發的進程中&#xff0c;命令行工具雖功能強大&#xff0c;但對部分開發者而言&#xff0c;圖形化界面的直觀與便捷性有著獨特魅力。此前&#xff0c;Cline 新版本集成 Gemini CLI 的消息在開發者社群引發熱議&#xff0c;尤其對于偏好圖形界面的開發者來說&#xff0c;…