gcc/g++編譯選項: -fPIC

    在理解PIC概念以前,先了解一下動態連接庫的載入時重定位概念。ios

    載入時重定位:安全

        咱們知道,Linux的可執行文件通常是elf格式的,在這個可執行文件的頭部包含了不少重要的信息:如文件格式,加載地址,符號表等。當鏈接器連接生成可執行文件時,會將程序的加載地址寫入可執行文件頭。在程序運行時,動態加載器將可執行文件載入文件頭指定的加載地址處,並加載該地址,開始從該地址處運行。因而可知,可執行文件的起始地址是在編譯時就決定的:函數

ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x4005b0           // 程序入口地址
  Start of program headers:          64 (bytes into file)
  Start of section headers:          4472 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         30
  Section header string table index: 27
 this

        對於動態庫來講,不像靜態庫(靜態庫是在連接可執行文件時,代碼段和數據段直接拷貝到可執行文件中),是在運行時加載動態庫代碼,所以沒法在編譯和連接階段獲取代碼段的符號地址(代碼段的符號包括引用的全局數據,調用的函數等)。在調用動態庫中的函數時,動態加載器動態分配一段進程地址空間,將動態庫加載到該地址空間後,再修改代碼段的符號地址。至於須要修改的哪些地址,連接器在動態庫的文件頭中預先寫好,供加載器讀取修改,動態庫的重定位節舉例以下:spa

Relocation section '.rela.text' at offset 0x8f8 contains 11 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
00000000000a  000e0000000a R_X86_64_32       0000000000000000 _ZSt4cout + 0
00000000000f  000f00000002 R_X86_64_PC32     0000000000000000 _ZStlsISt11char_traits - 4
000000000014  00100000000a R_X86_64_32       0000000000000000 _ZSt4endlIcSt11char_tr + 0
00000000001c  001100000002 R_X86_64_PC32     0000000000000000 _ZNSolsEPFRSoS_E - 4
000000000040  00040000000a R_X86_64_32       0000000000000000 .bss + 0
000000000045  001200000002 R_X86_64_PC32     0000000000000000 _ZNSt8ios_base4InitC1E - 4
00000000004a  00130000000a R_X86_64_32       0000000000000000 __dso_handle + 0
00000000004f  00040000000a R_X86_64_32       0000000000000000 .bss + 0
000000000054  00140000000a R_X86_64_32       0000000000000000 _ZNSt8ios_base4InitD1E + 0
000000000059  001500000002 R_X86_64_PC32     0000000000000000 __cxa_atexit - 4設計

    以上每一項對應着代碼段中的一處重定位:在代碼段的Offset處,進行Type類型的轉換。這就是載入時重定位的基本概念和過程。進程

    載入時重定位的缺點:string

    (1)動態庫的代碼段不能在進程間共享:多個進程加載同一個動態庫到各自不一樣的地址空間,致使代碼段須要不一樣的重定位,因此最終每一個引用該動態庫的進程擁有一份該動態庫代碼段的不一樣拷貝。it

    (2)代碼段必須是可寫的,增長了被攻擊風險。io

  

    爲了解決載入時重定位的問題,引入了PIC的概念,即位置無關代碼。

    PIC實現原理:

    (1)GOT:在動態庫的數據段增長GOT(Global Offset Table),該表的每一項是符號到地址的絕對映射。因爲代碼段到數據段的偏移是固定的,所以能夠在編譯時肯定代碼段中的某個符號到GOT特定項之間的偏移。這樣,代碼段中的符號偏移就能夠在編譯時肯定了,在加載時也無需修改代碼段的內容,只須要填寫位於數據段的GOT的全部項的符號的絕對地址就完成了。由於數據段原本就是進程間不共享,每一個進程獨立的一份,所以GOT的設計徹底解決了以上兩個問題,從而達到兩個目的:1,代碼段能夠在多進程間共享;2,代碼段是隻讀的。

    (2)PLT:PLT是 Program Linkage Table 的縮寫,即程序連接表,PLT的出現是爲了延時定位的目的。一個動態庫中的函數每每要遠多於全局變量,而且被調用的函數每每少於定義的函數。GOT中包含了該動態庫中的全部的全局變量的映射,而且在鏈接器加載時解析全部的全局變量的地址。若是用一樣的方式去處理函數調用符號,則開銷會很是大。所以在代碼段設計了一個PLT表,每一項實際上是個代碼段,用於執行以下邏輯:首次訪問時,解析參數和向GOT填寫函數地址,後續訪問直接訪問GOT中的函數地址。如此達到了延時定位的目的。

    所以,一個PIC的動態庫中,對全局變量使用GOT來映射,對函數調用使用PLT+GOT來映射,從而達到共享庫代碼段複用,代碼段安全訪問的目的。而這些就是 PIC 的意義。

相關文章
相關標籤/搜索