中动态路径加载_GOT段在linux系统中实现代码动态加载的作用和其他段的说明
上一節我們看到,當程序想調用系統函數時,在編譯階段無法確認被調用函數所在的虛擬地址。因此必須有機制讓程序在運行過程中,在調用系統API的時候有辦法去確定所調用的系統函數對應的入口地址,這就是代碼運行時對應動態加載的過程。
動態加載,也就是在調用系統函數時再去確認所調用的函數地址的技術需要使用兩個段,一個是.plt段,一個是.got.plt段。后者其實是.got段的一種特定形式,.got段在程序的加載和執行過程中還有其他形式和作用,在后續章節我們再研究。
上一節我們以調研系統函數puts為例描述了動態加載的基本過程。當我們在代碼中使用puts函數時,編譯器并不是將代碼編譯成直接調用該函數的形式。因為編譯器根本不知道操作系統將puts函數的代碼加載到虛擬內存的哪個位置。所以編譯器會在調用puts函數的地方,先自動跳轉到.plt段里面的給定位置,這個位置用puts@glt來表示。
從puts@glt對應的地址開始是一段二進制指令代碼,其基本內容如下所示:
。
上圖顯示的代碼也叫“函數樁”,每個系統函數都對應一段這樣的代碼。為何要用“樁”來描述這些代碼呢,因為這些指令都做了相同的工作,首先他們都將一個數值壓入堆棧,這個數值對應該段代碼的序號。我們可以把這些“函數樁”集合看成是一個數組,它們都是數組中的元素,push指令壓入的數值就是元素對應的下標。
注意到無論是puts@plt,還是__libe_start_main@plt它們對應的代碼除了push一個數值后,然后都跳轉到地址4003f0,而該地址就落在.plt段里面的某個位置。4003f0這個位置其實對應一段代碼的起始地址,這段代碼的作用是從.got.plt段里面取出一個數值作為下一步跳轉的地址,然后通過Jmp跳轉到取出地址所在位置,將程序控制權交給那里的代碼。
一開始從.got.plt取出的地址其實是系統動態鏈接庫的入口地址,于是跳轉過去之后動態鏈接庫會接管程序的控制權,這時候原來push壓入堆棧的數值就產生作用,根據該數值連接器就能知道代碼想要調用哪個系統接口。比如連接器看到堆棧上的值是0x0時,它就知道程序想要調用puts函數。于是動態鏈接庫在系統內存里面查找到puts函數的地址,然后將該地址填寫到.got.plt里面,所填寫的位置正好就是4003f0對應代碼從.got.plt里面取出來的數值所在位置。
然后動態連接器再次調用puts@plt這里的指令,于是前面的流程再運行一次。這里需要注意的是,第二次執行4003f0這個位置對應的指令時,從.got.plt取出的數值就不再是動態鏈接庫的入口地址,而是puts函數對應的入口地址,于是動態鏈接工作完成,代碼能夠在運行時正確的調用到它想要執行的系統函數。
為何不直接將被調用函數的地址直接寫入到ELF文件中,而是要繞一個大彎,先要把函數地址寫入.got.plt然后再寫入到.plt段里面的“函數樁”呢,主要原因在于安全考慮。由于.text段設置為不可寫,如果可寫,那么就可能讓人直接修改其中代碼指令了。.got.plt段屬于數據段,因此里面的數據可以修改,繞這個彎的目的就是防止代碼被他人直接修改。除了.got.plt段外,還需要理解的是.got段,后者的作用主要在于訪問共享代碼庫到處的變量。兩者區別在于.got.plt段包含了代碼,而.got段會直接包含共享庫到處的變量地址而不是包含代碼。
我們再看其他一些重要的段。在后面二進制分析中,我們還需了解.rel.或.rela.這類重定向段。他們的類型屬于SHT_RELA,這些段的作用在于幫助鏈接器實現代碼重定向。這些段告訴鏈接器代碼的哪些地方需要進行重定向,以及告訴鏈接器如何修改需要重定向的代碼,我們可以使用命令readelf —relocs a.out來查看ELF文件的重定向段:
上圖展示的是重定向段中的兩條記錄,其中展示了需要重定向的地址在內存中的偏移,其中顯示的是兩個地址分別為0x601018和601020,這兩個地址其實都落在.got段里面。重定向段又分為不同種類,最常見的種類是R_X86_64_GLOB_DAT和R_X86_64_JUMP_SLO,前者主要用于查找鏈接庫里變量的地址,后者主要用于查找鏈接庫中的函數入口。
另外還需要關注的是.dynamic段,使用命令 readelf —dynamic a.out可以查看:
在TYPE一欄為NEED的表明,對應共享庫需要在代碼運行時加載到系統內存。可以看到第一行對應的libc.so.6就表明該ELF文件如果要加載運行就必須確保共享庫libc.so.6要被加載到內存里
需要關注的還有.init_array和.fini_array段,前者包含了一系列代碼在運行前需要執行的一系列初始化函數,在.init_aray中包含了一系列初始化函數入口地址所構成的數組,在main函數執行時,數組中的函數會被提前調用進行初始化,我們可以使用命令objdump -d —section .init_array.out來查看其內容:
上圖表明.init_array中只有一個函數地址,對應的是0x400400,也就是函數__libc_start_main的入口地址。同理.fini_array段也包含了一系列函數地址,他們在代碼運行結束后會被系統調用,下一節我們再回頭看看程序表頭。
總結
以上是生活随笔為你收集整理的中动态路径加载_GOT段在linux系统中实现代码动态加载的作用和其他段的说明的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 斗地主源代码!
- 下一篇: linux获取笔记本摄像头视频,如何在w