ld.so分析4 PIC,GOT和PLT
1.PIC
PIC就是Position Independent Code(位置无关代码).那么何谓位置无关代码?
如果代码不需要被重定位,那么这种代码就是位置无关的。
我们要区分位置无关代码和可重入代码(Reentry Code)的不同,两者是无关的概念,不能混淆。
例如
int f()
{
return 1;
}
[zws@mail ~]$gcc -S x.c
[zws@mail ~]$cat x.s
.file "x.c"
.text
.globl f
.type f,@function
f:
pushl %ebp
movl %esp, %ebp
movl $1, %eax
leave
ret
.Lfe1:
.size f,.Lfe1-f
.ident "GCC: (GNU) 3.2.2 20030222 (Red Hat Linux 3.2.2-5)"
这是PIC也是RC
char * f()
{
return "a";
}
[zws@mail ~]$gcc -S x.c
[zws@mail ~]$cat x.s
.file "x.c"
.section .rodata
.LC0:
.string "a"
.text
.globl f
.type f,@function
f:
pushl %ebp
movl %esp, %ebp
movl $.LC0, %eax
leave
ret
.Lfe1:
.size f,.Lfe1-f
.ident "GCC: (GNU) 3.2.2 20030222 (Red Hat Linux 3.2.2-5)"
这不是PIC但是是RC
int f()
{
static int a=0;
a++;
return a;
}
[zws@mail ~]$gcc -S x.c
[zws@mail ~]$cat x.s
.file "x.c"
.data
.align 4
.type a.0,@object
.size a.0,4
a.0:
.long 0
.text
.globl f
.type f,@function
f:
pushl %ebp
movl %esp, %ebp
incl a.0
movl a.0, %eax
leave
ret
.Lfe1:
.size f,.Lfe1-f
.ident "GCC: (GNU) 3.2.2 20030222 (Red Hat Linux 3.2.2-5)"
两者都不是
将上面的汇编语言改成PIC
[zws@mail ~]$cat x.s
.file "x.c"
.data
.align 4
.type a.0,@object
.size a.0,4
a.0:
.long 0
.text
.globl f
.type f,@function
f:
pushl %ebp
movl %esp, %ebp
pushl %ebx
call .L2
.L2:
popl %ebx// %ebx中为当前指令的地址
subl $.L2-a.0, %ebx//该指令地址-相对a.0的偏移,即为a.0的地址,在%ebx中
incl (%ebx)
movl (%ebx), %eax
popl %ebx
leave
ret
.Lfe1:
.size f,.Lfe1-f
.ident "GCC: (GNU) 3.2.2 20030222 (Red Hat Linux 3.2.2-5)"
现在这个代码是PIC而不是RC
综上,PIC代码中不能引用绝对地址,否则需要重定位.(上面的 subl .L2-a.0, %ebx不算引用,因为gas会计算偏移,最终形成的指令中存放的是数)
RC代码不能使用共享变量,否则需要锁。
位置无关代码有什么优点?多个可执行程序运行这样的代码(例如动态链接库)时,虽然加载地址可能不一样,但是该代码不需要重定位,也就是不
需要修改代码,那么多个可执行程序就能共享一个代码副本,从而节省内存。缺点是占用一个寄存器计算地址,代码长度增加一点,执行时间增加
一点。
2.GOT
GOT是GLOBAL OFFSET TABLE(全局偏移表).
我们称一个可执行文件或动态链接库为一个模块.
一个模块中的数据或函数只允许被自己访问,称为本地局部数据或本地局部函数.例如static 类型的变量或函数就是这种类型.
一个模块中的数据或函数不但允许被自己访问,也允许外部访问,称为本地全局数据或本地全局函数.例如没有static修饰的变量或函数就是这种类
型.
相应地一个模块引用另一个模块中的数据或函数,则称为外部全局数据或外部全局函数.例如使用extern修饰的类型的变量或函数就是这种类型
.
局部肯定是本地的,外部一定是全局的。
GOT有四种功能:
>>为本地访问本地局部数据(静态变量或常量)访问提供PIC支持。
>>为本地访问本地全局数据访问提供PIC支持(配合.got节)
>>为本地访问外地全局数据访问提供PIC支持(配合.got节)
>>为本地访问本地全局函数访问提供PIC支持(配合.plt节和.got.plt节)
>>为本地访问外地全局函数调用提供PIC支持(配合.plt节和.got.plt节)
>>为动态链接提供支持(配合.rel.dyn节,rel.plt节,.got节,.got.plt节)
由于函数调用使用的都是相对寻址,且本地局部函数地址已知,因此本地访问本地局部函数调用不需要GOT支持.
(1)为本地访问本地局部数据(静态变量或常量)访问提供PIC支持。
[zws@mail ~]$cat x.c
static int a=0;
int f()
{
a++;
return a;
}
[zws@mail ~]$gcc -fPIC -S x.c
[zws@mail ~]$cat x.s
.file "x.c"
.data
.align 4
.type a,@object
.size a,4
a:
.long 0
.text
.globl f
.type f,@function
f:
pushl %ebp
movl %esp, %ebp
pushl %ebx
call .L2
.L2:
popl %ebx
addl $_GLOBAL_OFFSET_TABLE_+[.-.L2], %ebx
incl a@GOTOFF(%ebx)
movl a@GOTOFF(%ebx), %eax
popl %ebx
leave
ret
.Lfe1:
.size f,.Lfe1-f
.ident "GCC: (GNU) 3.2.2 20030222 (Red Hat Linux 3.2.2-5)"
[zws@mail ~]$readelf -a x.o
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: REL (Relocatable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x0
Start of program headers: 0 (bytes into file)
Start of section headers: 196 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 40 (bytes)
Number of section headers: 9
Section header string table index: 6
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00000000 000034 00001f 00 AX 0 0 4
[ 2] .rel.text REL 00000000 0002dc 000018 08 7 1 4
[ 3] .data PROGBITS 00000000 000054 000004 00 WA 0 0 4
[ 4] .bss NOBITS 00000000 000058 000000 00 WA 0 0 4
[ 5] .comment PROGBITS 00000000 000058 000033 00 0 0 1
[ 6] .shstrtab STRTAB 00000000 00008b 000039 00 0 0 1
[ 7] .symtab SYMTAB 00000000 00022c 000090 10 8 7 4
[ 8] .strtab STRTAB 00000000 0002bc 00001f 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
There are no section groups in this file.
There are no program headers in this file.
Relocation section '.rel.text' at offset 0x2dc contains 3 entries:
Offset Info Type Sym.Value Sym. Name
0000000c 0000080a R_386_GOTPC 00000000 _GLOBAL_OFFSET_TABLE_
00000012 00000309 R_386_GOTOFF 00000000 .data
00000018 00000309 R_386_GOTOFF 00000000 .data
There are no unwind sections in this file.
Symbol table '.symtab' contains 9 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FILE LOCAL DEFAULT ABS x.c
2: 00000000 0 SECTION LOCAL DEFAULT 1
3: 00000000 0 SECTION LOCAL DEFAULT 3
4: 00000000 0 SECTION LOCAL DEFAULT 4
5: 00000000 4 OBJECT LOCAL DEFAULT 3 a
6: 00000000 0 SECTION LOCAL DEFAULT 5
7: 00000000 31 FUNC GLOBAL DEFAULT 1 f
8: 00000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
No version information found in this file.
[zws@mail ~]$ objdump -d x.o
x.o: file format elf32-i386
Disassembly of section .text:
00000000 :
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 53 push %ebx
4: e8 00 00 00 00 call 9
9: 5b pop %ebx
a: 81 c3 03 00 00 00 add $0x3,%ebx
10: ff 83 00 00 00 00 incl 0x0(%ebx)
16: 8b 83 00 00 00 00 mov 0x0(%ebx),%eax
1c: 5b pop %ebx
1d: c9 leave
1e: c3 ret
分析
call .L2,将下一条指令地址压栈
popl %ebx,将本指令地址弹出到%ebx寄存器
addl $_GLOBAL_OFFSET_TABLE_+[.-.L2], %ebx
$说明这个操作数是立即数,_GLOBAL_OFFSET_TABLE_,特殊符号,gas能够识别,并为改该操作数生成R_386_GOTPC重定位类型.例如上面的
0000000c 0000080a R_386_GOTPC 00000000 _GLOBAL_OFFSET_TABLE_
地址0000000c指向指令 a: 81 c3 03 00 00 00 add $0x3,%ebx的源操作数部分[03 00 00 00]
ld链接时,检查重定位表,发现包含R_386_GOTPC重定位项,创建.got和.got.plt节,.got节存放全局数据地址,.got.plt存放全局函数地址,GOT地
址是.got.plt的地址(ld也可以合并这两个节成一个.got节),并计算地址GOT和地址0000000c的差值,加入0000000c处的值并写入,这就是R_386_GO
TPC重定位的内容。
上面的[.-.L2]意思是计算当前指令地址和.L2地址之差,即popl %ebx指令长度,应该是1.但是为何最终的指令却是add $0x3,%ebx呢?操作数3是如
何计算出来的呢?
这是因为重定位R_386_GOTPC项时计算的是该操作数与GOT的差值,而不是该条指令与GOT的差值.因此需要计算该操作数的偏移,即指令
a: 81 c3 03 00 00 00 add $0x3,%ebx
地址00000009加上81 c3这两字节操作码长度,形成最终地址0000000c.
相应的%ebx存放的应该是该操作数的加载地址,即(popl %ebx指令地址)+(popl %ebx指令长度)+(add
$0x3,%ebx指令操作码长度)=%ebx+1+2=add $0x3,%ebx
然而指令
addl $_GLOBAL_OFFSET_TABLE_+[.-.L2], %ebx
并没有明确指出再加上addl的操作码长度,其实这是gas替我们隐含计算了.gas分析该指令的操作数,碰到是立即数,且含有符号_GLOBAL_OFFS
ET_TABLE_,会在形成最终的操作时,自动加上操作码长度,得到我们想要的结果。
a++生成的指令是 incl a@GOTOFF(%ebx)生成的机器指令是
10: ff 83 00 00 00 00 incl 0x0(%ebx)
重定位项是
00000012 00000309 R_386_GOTOFF 00000000 .data
在连接时重定位类型R_386_GOTOFF执行的操作时计算计算该符号与GOT的偏移,并加入重定位处(GOTOFF即GOT OFFSET).
可见a@GOTOFF会指示gas生成R_386_GOTOFF重定位项,比较适合只被自己使用的变量。
在符号表中
5: 00000000 4 OBJECT LOCAL DEFAULT 3 a
a的bind类型是local.
(2)为本地访问本地全局数据访问提供PIC支持(配合.got节)
[zws@mail ~]$cat x.c
int a=0;
int f()
{
a++;
return a;
}
[zws@mail ~]$cat x.s
.file "x.c"
.globl a
.data
.align 4
.type a,@object
.size a,4
a:
.long 0
.text
.globl f
.type f,@function
f:
pushl %ebp
movl %esp, %ebp
pushl %ebx
call .L2
.L2:
popl %ebx
addl $_GLOBAL_OFFSET_TABLE_+[.-.L2], %ebx
movl a@GOT(%ebx), %eax
incl (%eax)
movl a@GOT(%ebx), %eax
movl (%eax), %eax
popl %ebx
leave
ret
.Lfe1:
.size f,.Lfe1-f
.ident "GCC: (GNU) 3.2.2 20030222 (Red Hat Linux 3.2.2-5)"
[zws@mail ~]$readelf -a x.o
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: REL (Relocatable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x0
Start of program headers: 0 (bytes into file)
Start of section headers: 200 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 40 (bytes)
Number of section headers: 9
Section header string table index: 6
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00000000 000034 000023 00 AX 0 0 4
[ 2] .rel.text REL 00000000 0002e0 000018 08 7 1 4
[ 3] .data PROGBITS 00000000 000058 000004 00 WA 0 0 4
[ 4] .bss NOBITS 00000000 00005c 000000 00 WA 0 0 4
[ 5] .comment PROGBITS 00000000 00005c 000033 00 0 0 1
[ 6] .shstrtab STRTAB 00000000 00008f 000039 00 0 0 1
[ 7] .symtab SYMTAB 00000000 000230 000090 10 8 6 4
[ 8] .strtab STRTAB 00000000 0002c0 00001f 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
There are no section groups in this file.
There are no program headers in this file.
Relocation section '.rel.text' at offset 0x2e0 contains 3 entries:
Offset Info Type Sym.Value Sym. Name
0000000c 0000080a R_386_GOTPC 00000000 _GLOBAL_OFFSET_TABLE_
00000012 00000603 R_386_GOT32 00000000 a
0000001a 00000603 R_386_GOT32 00000000 a
There are no unwind sections in this file.
Symbol table '.symtab' contains 9 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FILE LOCAL DEFAULT ABS x.c
2: 00000000 0 SECTION LOCAL DEFAULT 1
3: 00000000 0 SECTION LOCAL DEFAULT 3
4: 00000000 0 SECTION LOCAL DEFAULT 4
5: 00000000 0 SECTION LOCAL DEFAULT 5
6: 00000000 4 OBJECT GLOBAL DEFAULT 3 a
7: 00000000 35 FUNC GLOBAL DEFAULT 1 f
8: 00000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
No version information found in this file.
[zws@mail ~]$
和前面的唯一差别就是对变量的访问方式.由a@GOTOFF变成a@GOT,重定位方式也从R_386_GOTOFF变成R_386_GOT32.
a@GOT的访问方式是,将变量a的地址值存入.got节,访问a时,先根据GOT计算存放变量a的地址值在.got中的地址,然后取该地址值,即为变量a的
地址,用一条指令就能实现
movl a@GOT(%ebx), %eax
然后就可以对该变量执行操作了
例如a++生成的指令时 incl (%eax),对该地址处的值增一。
在符号表中
6: 00000000 4 OBJECT GLOBAL DEFAULT 3 a
a的bind类型是GLOBAL.
再写一个y.c
[zws@mail ~]$cat y.c
void f();
int main()
{
f();
return 0;
}
[zws@mail ~]$gcc y.c x.o
分析生成的可执行文件a.out可发现
ld在处理R_386_GOT32时,将该符号的地址x存入.got节,并记录其在.got
节中的地址y,然后计算y相对于GOT偏移,存入该符号所有的R_386_GOT32类型重定位地址处。最后在目标文件中为该符号生成R_386_GLOB_D
AT类型重定位项例如
readelf -a a.out
[20] .got PROGBITS 080494e0 0004e0 000008 04 WA 0 0 4
[21] .got.plt PROGBITS 080494e8 0004e8 000010 04 WA 0 0 4
080494e4 00000406 R_386_GLOB_DAT 08049504 a(显然地址080494e4在.got中)
R_386_GLOB_DAT类型执行的操作是,将模块加载地址加入该重定位处.这样变量的地址就确定了,可以功过y来访问,而且不需要对代码重定位。
如果该变量被其他模块访问(例如动态链接库中的变量被可执行文件访问或动态链接库中的变量被其他动态链接库库访问),则执行动态链接时,只
需要将该变量所在的地址x存入引用模块的.got节y处,就能实现共享且PIC.
其实本地访问本地全局数据访问也可以使用GOTOFF方式(例如本例的x.c).想一想为什么不这样做?从指导ld的方面去想。
(3)为本地访问外部全局数据访问提供PIC支持(配合.got节)
[zws@mail ~]$cat x.c
extern int a;
int f()
{
a++;
return a;
}
[zws@mail ~]$gcc -fPIC -S x.c
[zws@mail ~]$cat x.s
.file "x.c"
.text
.globl f
.type f,@function
f:
pushl %ebp
movl %esp, %ebp
pushl %ebx
call .L2
.L2:
popl %ebx
addl $_GLOBAL_OFFSET_TABLE_+[.-.L2], %ebx
movl a@GOT(%ebx), %eax
incl (%eax)
movl a@GOT(%ebx), %eax
movl (%eax), %eax
popl %ebx
leave
ret
.Lfe1:
.size f,.Lfe1-f
.ident "GCC: (GNU) 3.2.2 20030222 (Red Hat Linux 3.2.2-5)"
[zws@mail ~]$gcc -shared x.s -o libx.so
readelf 查看一下是否和上面分析的一致
(4)为本地访问本地全局函数调用提供PIC支持(配合.plt节和.got.plt节)
[zws@mail ~]$cat x.c
void f()
{
}
void g()
{
f();
}
[zws@mail ~]$gcc -fPIC -S x.c
[zws@mail ~]$cat x.s
.file "x.c"
.text
.globl f
.type f,@function
f:
pushl %ebp
movl %esp, %ebp
leave
ret
.Lfe1:
.size f,.Lfe1-f
.globl g
.type g,@function
g:
pushl %ebp
movl %esp, %ebp
pushl %ebx
subl $4, %esp
call .L3
.L3:
popl %ebx
addl $_GLOBAL_OFFSET_TABLE_+[.-.L3], %ebx
call f@PLT
addl $4, %esp
popl %ebx
leave
ret
.Lfe2:
.size g,.Lfe2-g
.ident "GCC: (GNU) 3.2.2 20030222 (Red Hat Linux 3.2.2-5)"
[zws@mail ~]$as x.s -o x.o
[zws@mail ~]$readelf -r x.o
Relocation section '.rel.text' at offset 0x2dc contains 2 entries:
Offset Info Type Sym.Value Sym. Name
00000014 0000080a R_386_GOTPC 00000000 _GLOBAL_OFFSET_TABLE_
00000019 00000604 R_386_PLT32 00000000 f
本地调用本地全局函数生成的代码是 call f@PLT
gas为call f@PLT生成的重定位项是R_386_PLT32 ,指导ld生成.plt节。
[zws@mail ~]$gcc -shared x.o -o libx.so
[zws@mail ~]$readelf -r libx.so
Relocation section '.rel.dyn' at offset 0x22c contains 5 entries:
Offset Info Type Sym.Value Sym. Name
00001500 00000008 R_386_RELATIVE
00001504 00000008 R_386_RELATIVE
000014dc 00000106 R_386_GLOB_DAT 00000000 __gmon_start__
000014e0 00000206 R_386_GLOB_DAT 00000000 _Jv_RegisterClasses
000014e4 00000806 R_386_GLOB_DAT 00000000 __cxa_finalize
Relocation section '.rel.plt' at offset 0x254 contains 3 entries:
Offset Info Type Sym.Value Sym. Name
000014f4 00000207 R_386_JUMP_SLOT 00000000 _Jv_RegisterClasses
000014f8 00000607 R_386_JUMP_SLOT 00000390 f
000014fc 00000807 R_386_JUMP_SLOT 00000000 __cxa_finalize
[zws@mail ~]$objdump -d libx.so
Disassembly of section .plt:
00000284 <_Jv_RegisterClasses@plt-0x10>:
284: ff b3 04 00 00 00 pushl 0x4(%ebx)
28a: ff a3 08 00 00 00 jmp *0x8(%ebx)
290: 00 00 add %al,(%eax)
...
00000294 <_Jv_RegisterClasses@plt>:
294: ff a3 0c 00 00 00 jmp *0xc(%ebx)
29a: 68 00 00 00 00 push $0x0
29f: e9 e0 ff ff ff jmp 284 <_init+0x18>
000002a4 :
2a4: ff a3 10 00 00 00 jmp *0x10(%ebx)
2aa: 68 08 00 00 00 push $0x8
2af: e9 d0 ff ff ff jmp 284 <_init+0x18>
000002b4 <__cxa_finalize@plt>:
2b4: ff a3 14 00 00 00 jmp *0x14(%ebx)
2ba: 68 10 00 00 00 push $0x10
2bf: e9 c0 ff ff ff jmp 284 <_init+0x18>
。。。。。。。。。。。。。
00000390 :
390: 55 push %ebp
391: 89 e5 mov %esp,%ebp
393: c9 leave
394: c3 ret
00000395 :
395: 55 push %ebp
396: 89 e5 mov %esp,%ebp
398: 53 push %ebx
399: 83 ec 04 sub $0x4,%esp
39c: e8 00 00 00 00 call 3a1
3a1: 5b pop %ebx
3a2: 81 c3 47 11 00 00 add $0x1147,%ebx
3a8: e8 f7 fe ff ff call 2a4
3ad: 83 c4 04 add $0x4,%esp
3b0: 5b pop %ebx
3b1: c9 leave
3b2: c3 ret
3b3: 90 nop
至于这里的涉及到的原理看下面,这里的%ebx存放的是本模块的GOT地址
(5)为本地访问外部全局函数调用提供PIC支持(配合.plt节和.got.plt节)
[zws@mail ~]$cat x.c
int a=0;
int f()
{
a++;
return a;
}
[zws@mail ~]$gcc -fPIC -shared x.c -o x.o
[zws@mail ~]$cat y.c
void f();
int main()
{
f();
return 0;
}
[zws@mail ~]$gcc y.c libx.so
[zws@mail ~]objdump -d a.out
看看外部全局函数调用使用什么方式
080483e8 :
80483e8: 55 push %ebp
80483e9: 89 e5 mov %esp,%ebp
80483eb: 83 ec 08 sub $0x8,%esp
80483ee: 83 e4 f0 and $0xfffffff0,%esp
80483f1: b8 00 00 00 00 mov $0x0,%eax
80483f6: 29 c4 sub %eax,%esp
80483f8: e8 2b ff ff ff call 8048328
80483fd: b8 00 00 00 00 mov $0x0,%eax
8048402: c9 leave
8048403: c3 ret
call 8048328,这个地址在.plt节中
Disassembly of section .plt:
08048308 <__libc_start_main@plt-0x10>:
8048308: ff 35 a0 95 04 08 pushl 0x80495a0
804830e: ff 25 a4 95 04 08 jmp *0x80495a4
8048314: 00 00 add %al,(%eax)
...
08048318 <__libc_start_main@plt>:
8048318: ff 25 a8 95 04 08 jmp *0x80495a8
804831e: 68 00 00 00 00 push $0x0
8048323: e9 e0 ff ff ff jmp 8048308 <_init+0x18>
08048328 :
8048328: ff 25 ac 95 04 08 jmp *0x80495ac
804832e: 68 08 00 00 00 push $0x8
8048333: e9 d0 ff ff ff jmp 8048308 <_init+0x18>
jmp *0x80495ac,这个地址在.got.plt节中
[zws@mail ~]$objdump -sj .got.plt a.out
a.out: file format elf32-i386
Contents of section .got.plt:
804959c c8940408 00000000 00000000 1e830408 ................
80495ac 2e830408 ....
该地址处的值是0804832e,就是前面jmp *0x80495ac的下一条指令地址
push $0x8,压入立即数8,其实是f的重定位项的在.rel.plt节中偏移(一个重定位项占8字节)
Relocation section '.rel.plt' at offset 0x2e0 contains 2 entries:
Offset Info Type Sym.Value Sym. Name
080495a8 00000407 R_386_JUMP_SLOT 00000000 __libc_start_main
080495ac 00000807 R_386_JUMP_SLOT 00000000 f
该f符号的重定位偏移是080495ac(就是在前面的.got.plt节中),类型是R_386_JUMP_SLOT.这样动态连接时,查找到f的地址后,写入080495ac处.
这样下次调用f时,就会直接跳到f的真实地址。
push $0x8的下一条指令时jmp 8048308,8048308处的指令时
8048308: ff 35 a0 95 04 08 pushl 0x80495a0
804830e: ff 25 a4 95 04 08 jmp *0x80495a4
第一条pushl 0x80495a0,将0x80495a0地址处的值压栈。0x80495a0在.got.plt中
[zws@mail ~]$objdump -sj .got.plt a.out
a.out: file format elf32-i386
Contents of section .got.plt:
804959c c8940408 00000000 00000000 1e830408 ................
80495ac 2e830408 ....
.got.plt的前三项是有特殊意义的,他们都是地址,在执行动态连接时要用到.第0项080494c8是.dynamic节地址.第1项是本模块的link_map地址,这
里是0,动态连接时会存入真实地址,第2项是_dl_runtime_resolve的地址,动态链接时存入.
将本模块的link_map地址压栈后,jmp *0x80495a4, 显然是跳到_dl_runtime_resolve中,执行链接f任务,_dl_runtime_resolve解析到f地址后,
会存入80495ac处,并将该地址替换栈上的返回地址,这样,_dl_runtime_resolve返回时,直接返回到f中,并执行f.而下次再执行f时就不需要这么
麻烦了。
这种在需要执行时才进行符号链接是所谓的lazy方式动态链接,还有一种就是模块加载时一次性为所有的符号进行链接,无论用不用得到,所谓的
now方式动态链接。
综上.got节存放的都是被本地引用的本地全局数据(没有被本地引用的不会出现)和外部全局数据,.got.plt前三项特殊,后面都是被本地引用的本地全
局函数(没有被本地引用的不会出现)和外部全局函数地址..plt存放过程链接信息(procedure link
table)..rel.dyn重定位.got(类型为R_386_GLOB_DAT的项),.rel.plt重定位.got.plt.
阅读(2119) | 评论(0) | 转发(0) |