1. 程式人生 > >GOT表和PLT表知識詳解

GOT表和PLT表知識詳解

GOT表和PLT表在程式中的作用非常巨大,接下來的講解希望大家可以仔細看看

我們用一個非常簡單的例子來講解,程式碼如下:
圖1

這裡寫圖片描述

然後我們編譯

我們直接gdb./a.out來進行反編譯處理,然後通過disasmain檢視main函式中的反編譯程式碼如下:

圖3

這裡寫圖片描述

我們可以觀察到gets@pltputs@plt這兩個函式,為什麼後面加了個@plt,因為這個為PLT表中的資料的地址。那為什麼反編譯中的程式碼地址為PLT表中的地址呢。

原因

為了更好的使用者體驗和記憶體CPU的利用率,程式編譯時會採用兩種表進行輔助,一個為PLT表,一個為GOT表,PLT表可以稱為內部函式表,G

OT表為全域性函式表(也可以說是動態函式表這是個人自稱),這兩個表是相對應的,什麼叫做相對應呢,PLT表中的資料就是GOT表中的一個地址,可以理解為一定是一一對應的,如下圖:

4

這裡寫圖片描述

PLT表中的每一項的資料內容都是對應的GOT表中一項的地址這個是固定不變的,到這裡大家也知道了PLT表中的資料根本不是函式的真實地址,而是GOT表項的地址,好坑啊。

其實在大家進入帶有@plt標誌的函式時,這個函式其實就是個過渡作用,因為GOT表項中的資料才是函式最終的地址,而PLT表中的資料又是GOT表項的地址,我們就可以通過PLT表跳轉到GOT表來得到函式真正的地址。

那問題來了,這個@plt函式時怎麼來的,這個函式是編譯系統自己加的,大家可以通過disas gets

看看裡面的程式碼,如下圖:

5

這裡寫圖片描述

大家可以發現,這個函式只有三行程式碼,第一行跳轉,第二行壓棧,第三行又是跳轉,解釋:
第一行跳轉,它的作用是通過PLT表跳轉到GOT表,而在第一次執行某一個函式之前,這個函式PLT表對應的GOT表中的資料為@plt函式中第二行指令的地址,針對圖中來說步驟如下:

  1. jmp指令跳轉到GOT
  2. GOT表中的資料為0x400486
  3. 跳轉到指令地址為0x400486
  4. 執行push 0x3#這個為在GOT中的下標序號
  5. 在執行jmp 0x400440
  6. 0x400440PLT[0]的地址
  7. PLT[0]的指令會進入動態連結器的入口
  8. 執行一個函式將真正的函式地址覆蓋到GOT表中

這裡我們要提幾個問題:
1. P

LT[0]處到底做了什麼,按照我們之前的思路它不是應該跳轉到GOT[0]嗎?
2. 為什麼中間要進行push壓棧操作?
3. 壓入的序號為什麼為0x3,不是最開始應該為0x0嗎?

解決問題

問題1

看下圖:
6

這裡寫圖片描述

我們嘗試著檢視0x400440地址的資料內容發現一個問題,從0x4004400x400450之間的資料完全不知道是什麼,而真正的PLT[x]中的資料是從0x400450開始的,從這裡才有了@plt為字尾的地址,但是我們disas gets看程式碼的時候是從0x400440開始的,我們可以通過x /5i 0x400440檢視0x400440處的程式碼,如下:
7

這裡寫圖片描述

我們看到了後面的#之後又一個16進位制數,一看便可以知道是GOT表的地址,為什麼這麼肯定呢,因為我們可以通過objdump -R ./a.out檢視一個程式的GOT函式的地址,如下圖:
8

這裡寫圖片描述

這裡都是些GOT地址,我們發現都是0x601...這些,所以可以斷定圖7中的也是GOT地址,那麼我們可以猜想出,在正式儲存一個函式的GOT地址前,我們的PLT表前面有一項進行一些處理,我們暫且不具體深入剖析這些程式碼有什麼用,但是我們可以肯定puts@plt前面那16個位元組也算是PLT表中的內容,這其實就是我們的PLT[0],正如我們之前問