歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> Linux技術

linux中動態鏈接延遲綁定的實現

本文為閱讀程序員的自我修養第7.4節延遲綁定後,自己寫程序並用gdb調試後的發現。

實驗代碼如下:

a.c:

#include <stdio.h>
extern void b();
int main(){
	printf("in main\n");
	b();
}
b.c:

#include <stdio.h>
void b(){
	printf("b\n");
}
編譯命令:

gcc -fPIC -g -DDBUG -o lib.so b.c

gcc -g -o a_test a.c ./lib.so

使用objdump反匯編lib.so:

objdump -D lib.so >lib.dump

objdump -D a_test >a.dump

其中a.dump中調用函數b的過程如下:

400696: b8 00 00 00 00 mov $0x0,%eax

40069b: e8 d0 fe ff ff callq 400570 <b@plt>

0000000000400570 <b@plt>:

400570: ff 25 92 0a 20 00 jmpq *0x200a92(%rip) # 601008 <_GLOBAL_OFFSET_TABLE_+0x20>

400576: 68 01 00 00 00 pushq $0x1

40057b: e9 d0 ff ff ff jmpq 400550 <_init+0x18>

程序先調用b@plt。後者先jmpq到一個地址處,這個地址為601008處的值.此地址附近的數據如下(這些數據被objdump反匯編了,其實這些根本不是機器指令,而僅僅是數據):

600fff: 00 66 05 add %ah,0x5(%rsi)

601002: 40 00 00 add %al,(%rax)

601005: 00 00 add %al,(%rax)

601007: 00 76 05 add %dh,0x5(%rsi)

60100a: 40 00 00 add %al,(%rax)

60100d: 00 00 add %al,(%rax)

60100f: 00 86 05 40 00 00 add %al,0x4005(%rsi)

601015: 00 00 add %al,(%rax)

601017: 00 96 05 40 00 00 add %dl,0x4005(%rsi)

60101d: 00 00 add %al,(%rax)

可以發現601008處的地址是0x400576(小端),所以jmp指令將跳到這個地址處(jmp指令後面有個*,和指針含義類似)。這個地址就是jmpq指令的下一個指令地址。

這樣就實現了延遲綁定的目的:第一次調用函數時,由連接器負責重定位,第二次調用函數時直接調用,不必經過連接器。

這個過程可以通過gdb調試進行驗證。在函數被調用前,通過x命令查看got處的內存值,在函數被調用後再次查看got中的內存值便可發現函數地址已被重置。

連接器負責重定位是如何進行的呢?代碼如下:

0000000000400550 <puts@plt-0x10>:

400550: ff 35 9a 0a 20 00 pushq 0x200a9a(%rip) # 600ff0 <_GLOBAL_OFFSET_TABLE_+0x8>

400556: ff 25 9c 0a 20 00 jmpq *0x200a9c(%rip) # 600ff8 <_GLOBAL_OFFSET_TABLE_+0x10>

40055c: 0f 1f 40 00 nopl 0x0(%rax)

在調用函數b@plt的過程中,由於之前沒有進行過重定位,所以需要調用連接器進行重定位工作,通過下面代碼實現。

40057b: e9 d0 ff ff ff jmpq 400550 <_init+0x18>

地址400550處的代碼就是puts@plt-0x10函數,它跳轉地址為600ff8處的值(可以發現,這個跳轉地址和b@plt的跳轉地址都在.got.plt段中)而這個值是在程序加載時由動態鏈接器填寫的。(也就是在加載過程中,動態鏈接器會將負責符號解析和重定位工作的函數_dl_runtime_resolve()的地址寫到這個地方)。這樣在需要重定位時,puts@plt(got.plt的第三個函數)將會被調用,需要重定位的函數將會被重定位。之後,dl_runtime_resolve應該會恢復堆棧平衡,並跳轉到b@plt處執行,隨後通過ret指令返回調用處。

Copyright © Linux教程網 All Rights Reserved