计算机科学基础知识(四): 动态库和位置无关代码
一、前言
本文主要描述了動態庫以及和動態庫有緊密聯系的位置無關代碼的相關資訊。首先介紹了動態庫和位置無關代碼的源由,了解這些背景知識有助于理解和學習動態庫。隨后,我們通過加-fPIC和不加這個編譯選項分別編譯出兩個relocatable object file,看看編譯器是如何生成位置無關代碼的。最后,我們自己動手編寫一個簡單的動態庫,并解析了一些symbol Visibility、動態符號表等一些相關基本概念。
本文中的描述是基于ARM MCU,GNU/linux平臺而言的,本文是個人對動態庫的理解,如果有錯誤,請及時指出。
?
二、背景介紹
位置無關代碼實際上是和動態庫概念緊密聯系在一起的,本章首先描述為何會提出動態庫的概念,然后解釋動態庫為何需要編譯成PIC的代碼。
1、為何會提出動態庫的概念?
引入靜態庫后,解決了一些問題,但是仍然存在下面的弊端:
(1)任何對靜態庫的升級都需要rebuild(或者叫做relink)的過程
(2)通用的函數(例如標準IO函數scanf和printf)存在于各個靜態鏈接的程序中,導致編譯后的靜態可執行程序的size比較大,在各個可執行程序中,這些通用的函數代碼是重復的,占用了磁盤和內存資源
正因為如此,動態庫和動態鏈接的概念被提出來來解決這些問題。動態庫也是一種ELF格式的對象文件,在運行的時候,它可以被加載到任何的地址執行。
2、動態庫為何需要編譯成PIC的代碼?
無論是動態庫還是靜態庫,其本質都是代碼共享。對于靜態庫,其代碼以及數據都是在各個靜態鏈接的可執行文件中有一份copy,所有符號的地址已經確定,因此在loading的時候,OS會比較輕松。不過這種代碼共享無法在run time的時候共享代碼,從而導致了資源的浪費。當然,它的好處就是簡單、速度快(無需dynamic linker來重定位符號)。對于靜態編譯,static linker將多個編譯單元(.o文件和庫文件)整合成一個模塊,因此,進入run time,實際上只有一個執行模塊。對于動態鏈接,在run time的時候,除了可執行文件這個模塊,該可執行文件所依賴的各個動態庫也是一個個的運行模塊,這時候,可執行文件調用動態庫的符號實際上是就是需要引用其他運行模塊的符號了。對于可執行文件而言,loader將其加載到哪個地址并不關鍵,反正每個進程都有自己獨一無二的地址空間,可執行文件可以mapping到各自virtual memory space的相同地址也無妨,不過對于動態庫模塊而言,就有些麻煩了。如果我們不將動態庫編譯成PIC的也就是意味著loader一定要把動態庫加載到某個特定的地址(該地址編譯的時候就確定了)上它才可以正確的執行。假設我們有A B C D四個動態庫,假設程序P1依賴A B兩個動態庫,P2依賴C D兩個動態庫,那么A B和C D的動態庫的加載地址有重疊也沒有關系,P1和P2可以同時運行。但是如果有一個新的程序P3依賴A B C D四個動態庫,那么前面為動態庫分配的加載地址就不能正常工作了。當然,重新為這四個動態庫分配load address(讓地址不重疊)也是ok的,但是這樣一來,P1雖然沒有使用C D這兩個動態庫,但是P1的地址空間還是要保留C D動態庫的那段地址,對于地址這樣寶貴資源,這么浪費簡直是暴殄天物。更重要的是:這樣的機制實際上對進程虛擬地址的管理就變得非常復雜了,假設A B C D是分配了一段連續的地址,如果C動態庫更新了,size變大了,原本分配的地址空間不夠了,怎么辦?我們必須再尋找一個新的地址段來加載C動態庫。如果系統只有四個動態庫起始還是OK的,如果動態庫非常非常多……怎么辦?更糟的是:不同的系統使用不同的動態庫,管理起來更令人頭痛
最好的方法就是將動態庫編譯成PIC(Position Independent Code),也就是說動態庫可以被加載到任何地址并正確運行。
?
三、動手實踐:觀察PIC的.o文件的反匯編結果
1、源代碼foo.c
#include <stdio.h>
int xxx = 0x1234;
int yyy;
int foo(void)
{
? yyy = 0x5678;
? printf("xxx=%x yyy=%x\n", xxx, yyy);
? return xxx;
}
2、觀察foo.o文件中的符號定位信息
使用arm-linux-gcc –c foo.c將source code編譯成relocatable file。我們來看看正文段中的relocation信息:
00000030? 00000e1c R_ARM_CALL??????? 00000000?? printf
00000044? 00000f02 R_ARM_ABS32?????? 00000004?? yyy
0000004c? 00000c02 R_ARM_ABS32?????? 00000000?? xxx
R_ARM_ABS32是一種ARM平臺上的absolute 32-bit relocation,在32 bit的ARM平臺上,這種重定位的方式是沒有任何約束的,可以將地址重定位到4G地址空間的任何位置。具體實現方式需要參考反編譯的匯編代碼,我們來看看匯編代碼是如何訪問yyy這個數據的:
……
?? 8:?? e59f2034??????? ldr???? r2, [pc, #52]?? ; 44 <.text+0x44>
?? c:?? e59f3034??????? ldr???? r3, [pc, #52]?? ; 48 <.text+0x48>
? 10:?? e5823000??????? str???? r3, [r2]
……
? 44:?? 00000000??????? .word?? 0x00000000
? 48:?? 00005678??????? .word?? 0x00005678
具體做法非常的簡單,在這段代碼的后面(也是.text section的一部分)給出一個32-bit的跳板memory(上面黑色加粗的那一行),位于<.text+0x44>,這個memory用于保存yyy符號的運行地址。由于同在一個正文段,因此它們之間的offset是確定的,使用“ldr???? r2, [pc, #52] ”這樣的PC-relative的訪問指令可以訪問到yyy變量的地址,通過“str???? r3, [r2]”可以將yyy變量的內容保存到r3中。
下面我們我們再看看函數符號的訪問。R_ARM_CALL這種類型的重定位信息主要用于函數調用的(對應的ARM指令就是BL和BLX),實現也很簡單,如下:
……
? 30:?? ebfffffe??????? bl????? 0
……
BL指令是一個PC-relative指令,會將控制權交給相對于當前PC值的一個地址上去(同時設定lr寄存器),bl這條指令的0~23個bit(用imm24表示))用來表示相對與PC的偏移地址,最終跳轉到的地址是PC+(imm24在低位添加00b,然后做符號擴展),也就是正負32M的區域(注意:BL不能任意跳轉4G范圍的地址空間)。之所以添加兩個0是因為offset地址總是4字節對齊的。
對于靜態鏈接,很簡單,雖然那些重定位信息在正文段,但是沒有關系,在程序loading之前,static linker可以修改正文段的內容。
3、編譯PIC的.o文件并觀察
編譯成位置無關代碼也就意味著這段代碼多半是動態庫的一部分,需要動態加載到一個編譯時候未知的地址上。也就是說上文中使用的方法已經不行了,編譯時候符號的地址還是不確定的,因此static linker無法將地址填入到.text section中。在loading的時候,雖然知道了符號runtime address,但是正文段是read only的,也無法修改。怎么辦呢?我們來一起看看程序如何實現。
使用arm-linux-gcc -fPIC–c foo.c將source code編譯成relocatable file。我們來看看正文段中的relocation信息:
Relocation section '.rel.text' at offset 0x4e0 contains 5 entries:
Offset???? Info??? Type??????????? Sym.Value? Sym. Name
00000048? 00000f1b R_ARM_PLT32?????? 00000000?? printf
00000064? 00001019 R_ARM_BASE_PREL?? 00000000?? _GLOBAL_OFFSET_TABLE_
00000068? 0000111a R_ARM_GOT_BREL??? 00000004?? yyy
00000070? 00000d1a R_ARM_GOT_BREL??? 00000000?? xxx
我們首先看看_GLOBAL_OFFSET_TABLE_這個符號,看起來和傳說中的GOT(Global Offset Table)有關。那么什么是GOT呢?它有什么作用呢?我們先回到c代碼,思考一下對xxx符號的訪問。這時候,我們能確定xxx的runtime address嗎?當然不能,離loading還遠著呢,這時候我們能確定訪問xxx的代碼(.text section中)和xxx符號(.data section)之間offset嗎?也不能,因為還有多個.o文件最后被link成一個動態庫。怎么辦?我們必須借助一個橋梁來讓數據訪問變得Position Independent,這個橋梁就是GOT(Global Offset Table)。當然GOT必須是可讀可寫的,因為后續在run time的時候還要修改其內容。_GLOBAL_OFFSET_TABLE_就是定義了GOT在memory中的位置。因此64那個位置的重定位信息和GOT相關,R_ARM_BASE_PREL這個relocation type則說明這個重定位信息說明該位置保存了GOT offset。由于目前還是.o文件,還沒有確定最后GOT信息,因此需要這個relocation的信息,一旦完成動態庫的編譯,這個relocation entry就不需要了。
R_ARM_GOT_BREL這個type說明這個重定位信息是一個描述GOT entry和GOT起始位置的offset。例如:yyy這個符號還需要relocation,那么它的relocation位于正文段offset是0x68的位置,其內容保存了yyy符號在GOT entry中的地址和GOT起始位置的偏移。OK,有了這些鋪墊,可以看看程序對yyy這個數據是如何訪問的:
……
?? c:?? e59f4050??????? ldr???? r4, [pc, #80]?? ; 64 <.text+0x64>
? 10:?? e08f4004??????? add???? r4, pc, r4 ---------------獲得GOT的起始位置的地址
? 14:?? e59f304c??????? ldr???? r3, [pc, #76]?? ; 68 <.text+0x68> -----獲得yyy符號在GOT中的offset
? 18:?? e7942003??????? ldr???? r2, [r4, r3] --------------獲得yyy符號的runtime address
? 1c:?? e59f3048??????? ldr???? r3, [pc, #72]?? ; 6c <.text+0x6c>
? 20:?? e5823000??????? str???? r3, [r2] ---------------設定yyy符號的內容
……
? 64:?? 0000004c??????? .word?? 0x0000004c-----GOT offset
? 68:?? 00000000??????? .word?? 0x00000000-----yyy的地址在GOT中的偏移
? 6c:?? 00005678??????? .word?? 0x00005678
由此可見,PIC的代碼對全局數據的訪問都是通過GOT來完成的,從而做到了位置無關。
?
四、動手實踐:觀察動態庫的反匯編結果
1、如何生成動態庫?
我們準備動手做一個動態庫了,先看source code,一如既往的簡單(注意:我們不建議導出動態庫中的數據符號,這里主要是為了描述動態庫的概念而這么做的):
int xxx = 0x1234;
int yyy;
int foo(void)
{
? yyy = 0x5678;
? return xxx;
}
通過下面的命令可以編譯出一個libfoo的動態庫:
arm-linux-gcc -shared -fPIC -o libfoo.so foo.c
-shared告知gcc生成share object文件,而-fPIC則告訴gcc請生成位置無關代碼。
2、觀察符號表的變化
我們在relocatable object中已經對符號表進行了描述:對靜態編譯的程序而言,.o文件中的符號表一是要對外宣稱自己定義了哪些符號,另外一個是向外宣布自己引用了哪些符號,需要其他模塊來支持。有了這些信息,static linker才能整合各個relocatable object file中的資源,互通有無,最后融合成一個靜態的可執行程序。因此,實際上,對于靜態的可執行程序,在加載執行的時候,其符號表已經沒有任何意義了(不過可以方便debug),對于CPU而言,其執行就是要知道地址就OK了(靜態編譯程序所有的符號都已經定位了),符號什么的它不關心,因此,實際上符號表可以刪除。如果你愿意,你可以通過strip命令來進行實驗,看看tripped和not stripped的elf文件有什么不同。
然而,計算機科學的發展是不斷前進的,當有了動態庫之后,符號表會怎樣呢?我們自己可以動手生成一個動態鏈接的可執行程序或者動態庫并觀察其中的符號表信息(恰好上一節已經生成一個libfoo.so,就它吧)。通過readelf工具,我們可以看到,動態鏈接的程序中有兩個符號表,一個是大家之前就熟悉的.symtab section(我們稱之符號表),另外一個就是.dynsym section(動態符號表)。這兩個符號表都有自己對應的string table,分別是.strtab和.dynstr section。
.symtab section我們前面的文章都有描述,為何又增加了一個.dynsym section呢?我們先假設我們編譯出來的動態庫只有一個符號表,那么當使用strip命令刪除符號表以及對應的字符串表之后會怎樣?當其他程序調用該動態庫提供的接口API函數的時候,dynamic linker還能找到對應的API函數符號嗎?當然不行,符號表都刪除了還想怎樣。靜態鏈接的程序之所以可以strip掉符號表以及對應的字符串表那是因為程序中所有符號都已經塵埃落定(所有符號已經重定位),因此strip后也毫無壓力,但是動態鏈接的情況下,程序中的沒有定位的符號以及動態庫中宣稱的符號都需要有一個特別的符號表(是正常符號表的子集)來保存動態鏈接符號的信息,這個表就是動態連接符號表(.dynsym section)。
OK,最后總結一下:符號表(.symtab section)是指導static linker工作的,運行的時候可以不需要。動態符號表(.dynsym section)是給dynamic linker用的,程序(或者動態庫)運行的時候,dynamic linker用動態符號表的信息來定位符號。
3、Binding Property和Symbol Visibility
我們在講述relocatable object file的時候已經給出了binding屬性(binding property)的解釋。一個符號可能有global、local和weak三種binding property。這個binding property主要是被static linker用來進行.o之間的符號解析(symbol resolution)的。Bind屬性之外還有一個屬性我們一直沒有描述(通過readelf觀察符號表的時候,該屬性對應列的名字是Vis的那個),我們稱之Symbol Visibility或者符號的可見性。之所以前面的文章中沒有描述主要是因為Symbol visibility是和動態庫以及動態鏈接相關的。
當引入動態連接和動態庫的概念之后,代碼和數據的共享會變得復雜一些。和binding property不一樣,Symbol Visibility是針對運行模塊(動態鏈接的可執行程序或者動態庫)之間的相互引用。例如我們有A.o B.o C.o三個編譯模塊,static linker將這三個.o文件link成一個libABC.so文件。A.o模塊要調用B.o中的一個函數bb,那么bb函數就一定需要是一個GLOBAL類型的,但是bb函數并不是動態庫libABC.so的接口API(或者稱之export symbol),也就是說,為了更好的封裝性,我們希望bb這個函數對外不可見,dynamic linker看不到這個符號,bb不參與動態符號解析。如果動態庫導出所有的符號,那么,在動態鏈接的時候,符號沖突的可能性就非常的大,特別是對于那些大型項目,可能該項目涉及的每個動態庫都是由不同team負責的。除了模塊的封裝性之外,Symbol Visibility也是和程序的性能有關。如果導出太多的符號,除了占用更多的內存,還意味著增加loading time和dynamic linking time。
看,不控制Symbol Visibility的危害還是很大D,這時候閱讀本文的你估計一定會問:那么控制Symbol Visibility哪家強呢?我推薦使用大殺器static關鍵字,簡單,實用,人人會。給function或者全局變量加上static關鍵字,別說是對dynamic linker(運行模塊之間的引用)進行了限制,就是static linker(.o 文件之間的引用)也是拿他毫無辦法。當然,缺點也很明顯:不能在動態庫的多個.o之間共享。在這種場景下,我們需要求助其他方法了,對于gcc,我們可以用下面的方法:
符號類型 符號名字 __attribute__ ((visibility ("xxx")));
其中xxx指明了該符號的Symbol Visibility屬性,Symbol Visibility屬性可以設定為:
(1)DEFAULT(雖然命名是default,但是有些public的味道)。該屬性的符號被導出,該符號可以被其他運行模塊訪問
(2)PROTECTED。同DEFAULT,不過該符號不能被overridden。也就是說,如果一個動態庫中的符號是PROTECTED,那么動態庫中的代碼訪問該符號是享有優先權的,即便其他的運行模塊定義了同名的符號。
(3)HIDDEN。HIDDEN的符號不會被導出,不參與動態鏈接。
(4)INTERNAL。其他運行模塊不能訪問該類型的符號。
回到上一節描述的這個source code,其中有三個符號:xxx、yyy和foo,都是被導出的,可以被其他的模塊調用。如果你有興趣,可以自己試著控制符號的visibility,看看效果如何。
4、動態庫文件的加載
libfoo這個shared object elf文件的加載是根據Program header進行的。在ELF file header中可以看到該動態庫共計4個program header,如下:
Program Headers:
? Type?????????? Offset?? VirtAddr?? PhysAddr?? FileSiz MemSiz? Flg Align
? LOAD?????????? 0x000000 0x00000000 0x00000000 0x005c0 0x005c0 R E 0x8000
? LOAD?????????? 0x0005c0 0x000085c0 0x000085c0 0x00118 0x00120 RW? 0x8000
? DYNAMIC??????? 0x0005cc 0x000085cc 0x000085cc 0x000e0 0x000e0 RW? 0x4
? GNU_STACK????? 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW? 0x4
帶有LOAD標記的那些program header entry會被mapping到進程地址空間上去。第一項是code segment,由于動態庫的代碼是PIC的,因此其VirtAddr和PhysAddr都是0,表示可以運行在任意地址上。第二項是data segment,在實際中,動態庫的code和data segment都是連續加載的,因此,如果code segment的run time地址是0的話,那么data segment的地址應該是0x5c0,不過由于code segment是0x8000對齊的,因此data segment的地址被設定為0x85c0。當然,如果實際該動態庫被加載到了進程的X虛擬地址上的話,data segment的runtime地址應該是X + 0x85c0。對于動態庫而言,其code segment可以被多個進程共享,也就是說,雖然code segment被加載到不同的進程的不同的虛擬地址空間,但是其物理地址是一樣的,只不過各個進程設定自己的page table就OK了。對于code segment,各個進程都有自己的副本,不可能共享的。
沒有LOAD標記,這說明第三項和第四項(DYNAMIC這個entry下一節描述)都是和進程加載無關的(不占用進程虛擬地址空間)。GNU_STACK是用來告訴操作系統,當加載ELF文件的時候,如果控制stack的屬性。這是和系統安全相關(通過stack來攻擊系統),我們在relocatable object file的時候已經描述,這里略過(https://wiki.gentoo.org/wiki/Hardened/GNU_stack_quickstart中有更詳細的信息)。
5、如何找到動態鏈接的信息
和靜態鏈接的可執行序程序相比,DYNAMIC那個program header entry是動態庫文件特有的。既然是動態庫,當然要參與動態鏈接的過程,因此動態庫的ELF文件需要提供一些dynamic linking信息給OS以及dynamic linker,DYNAMIC那個program header entry就是起這個作用的。dynamic segment只包含了一個section,名字是.dynamic。需要注意的是.dynamic section也是data segment的一部分被加載到了進程的地址空間中。下面我們仔細看看libfoo.so的Dynamic section的內容:
Dynamic section at offset 0x5cc contains 24 entries:
? Tag??????? Type???????????????????????? Name/Value
0x00000001 (NEEDED)???????????????????? Shared library: [libc.so.6]
0x0000000c (INIT)?????????????????????? 0x460
0x0000000d (FINI)?????????????????????? 0x5ac
0x00000019 (INIT_ARRAY)???????????????? 0x85c0
0x0000001b (INIT_ARRAYSZ)?????????????? 4 (bytes)
0x0000001a (FINI_ARRAY)???????????????? 0x85c4
0x0000001c (FINI_ARRAYSZ)?????????????? 4 (bytes)
……
我們先不著急看具體的各個項次的含義,我們先看看section table中對.dynamic的描述:
[Nr] Name????????????? Type??????????? Addr???? Off??? Size?? ES Flg Lk Inf Al
……
[16] .dynamic????????? DYNAMIC???????? 000085cc 0005cc 0000e0 08? WA? 3?? 0? 4
由此可知,.dynamic section是有Entry size的,也就是說,這個section中的內容是按照8個byte形成一個個的entry,下面的這個Elf32_Dyn(對于64bit的CPU,對應是Elf64_Dyn)數據結構可以解析這8個bytes:
typedef struct {
? Elf32_Sword??? d_tag;??????????? /* Dynamic entry type */
? union??? {
????? Elf32_Word d_val;??????????? /* Integer value */
????? Elf32_Addr d_ptr;??????????? /* Address value */
??? } d_un;
} Elf32_Dyn;
d_tag定義dynamic entry的類型,而根據tag的不同,附加數據d_un可能是一個整數類型d_val,其含義和具體的tag相關,或者附加數據是一個虛擬地址d_ptr。了解了這些信息后,我們可以來解析.dynamic section的具體內容了。
dynamic tag是NEEDED這個entry標識libfoo這個動態庫依賴的object文件。ldd工具可以打印出給定程序或者動態庫的share library的依賴關系,本質上ldd就是應用了NEEDED這個tag信息。對于libfoo.so這個動態庫,它會依賴libc.so.6這個動態庫,也就是c庫了。不過,你可能會奇怪,我們c代碼沒有引用任何的c庫函數啊,怎么會依賴c庫呢?其實這和靜態鏈接的hello world程序類似,我們在講靜態鏈接的時候已經描述了,你可以在build libfoo.so的時候加上-v的選項,這時候你可以從不斷滾動的屏幕信息中找到答案:你的c代碼不是一個人在戰斗。你可以可以從.text中看到一些端倪,例如.text中有一個call_gmon_start的函數,這個函數本來就不是我們的c代碼定義的符號,我們的c代碼只定義了foo函數以及xxx、yyy這兩個變量符號。本來以為在.text中只有foo的定義,call_gmon_start是從那里冒出來的呢?實際上這個符號定義在crti.o中(在最后生成libfoo.so的動態庫的時候,有若干個crt*.o參與其中)。libfoo.so定義了call_gmon_start這個函數,那么什么時候調用呢?這又回到了linux下動態庫的結構這個問題上:雖然動態庫定義了一些符號(函數或者全局變量),但是,我們希望在調用這些函數或者訪問這些變量之前,先執行一些初始化的代碼(這發生在動態庫加載的時候,dlopen的時候,由dynamic linker負責)。這些初始化代碼被放到一些特殊的section(例如.init),libfoo.so的.init section的反匯編結果如下:
00000460 <_init>:
460:??? e52de004???? str??? lr, [sp, #-4]!
464:??? e24dd004???? sub??? sp, sp, #4??? ; 0x4
468:??? eb000009???? bl??? 494 -----以上來自crti.o
這里可以存放動態庫自己定義的初始化函數,當然我們這么簡單的動態庫當然沒有。
46c:??? e28dd004???? add??? sp, sp, #4??? ; 0x4------以下來自crtn.o
470:??? e8bd8000???? ldmia??? sp!, {pc}
INIT(對應.init section)到FINI_ARRAYSZ這些entry都是和該動態庫的初始化和退出函數相關的。當dynamic linker open這個動態庫的時候(dlopen)會執行初始化函數,當dynamic linker close這個動態庫的時候(dlclose)會執行退出函數。還有很多dynamic tag,這里主要關注結構,暫且略過,一言以蔽之,dynamic linker可以通過.dynamic section找到所有它需要的動態鏈接信息。
6、動態庫中訪問全局變量
我們來看看foo中如何訪問yyy這個符號的。yyy的重定位信息如下(.rel.dyn section中):
000086bc? 00000815 R_ARM_GLOB_DAT??? 000086dc?? yyy
符號表中可以查到GOT的位置:
56: 000086ac???? 0 OBJECT? LOCAL? HIDDEN? ABS _GLOBAL_OFFSET_TABLE_
當然0x86ac是一個offset,并不是run time address,畢竟只有loading后才知道其具體的地址信息。如果該動態庫被loading到address_libfoo,那么GOT實際應該位于address_libfoo+0x86ac。而yyy符號的地址在address_libfoo+0x86bc,dynamic linker會在適當的時間把真實的yyy符號的地址寫入到這個位置的。由此可見,在offset是0x000086bc(GOT中的某個entry)的位置上保存了yyy符號的重定位信息。
……
568:??? e59f202c???? ldr??? r2, [pc, #44]??? ; 59c <.text+0x108> ---獲取GOT到當前指令的偏移
56c:??? e08f2002???? add??? r2, pc, r2 --------------獲取GOT的絕對地址
570:??? e59f3028???? ldr??? r3, [pc, #40]??? ; 5a0 <.text+0x10c> ---獲取yyy在GOT中的偏移
574:??? e7921003???? ldr??? r1, [r2, r3] --------------從GOT entry找到yyy的絕對地址
578:??? e59f3024???? ldr??? r3, [pc, #36]??? ; 5a4 <.text+0x110> ---r3被賦值0x5678
57c:??? e5813000???? str??? r3, [r1] ---------------給yyy賦值
……
59c:??? 00008138???? .word??? 0x00008138 -----------指令到GOT的偏移
5a0:??? 00000010???? .word??? 0x00000010 -----------yyy符號在GOT中的offset
5a4:??? 00005678???? .word??? 0x00005678
5a8:??? 00000018???? .word??? 0x00000018
雖然不知道GOT的絕對地址,但是在靜態鏈接的時候,代碼段的代碼和GOT的偏移是已經確定的(loading的時候是按照program header中的信息進行loading,code segment和data segment是連續的),因此,在指令中可以通過59c這個橋梁獲取GOT的首地址,加上entry偏移就可以獲取指定符號的GOT入口地址,從該GOT入口地址中可以取出runtime的符號的絕對地址。
總結
以上是生活随笔為你收集整理的计算机科学基础知识(四): 动态库和位置无关代码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 几种页面置换算法
- 下一篇: 计算机基础知识:什么是位、字节、字、KB