linux动态链接库之懒加载

这个玩意理论跟实际不太一致,搞了半天,终于发现我的demo中并没有懒加载,而是一开始就调用ld.so把重定位后的地址填进.got.plt里了。所以导致我各种怀疑自己,各种gdb,objdump,readelf都跟理论不相符。这下拿到铁证了,不用纠结了。

懒加载的逻辑代码在,但是没有执行。比如我写的这个demo

//hello.h
#ifndef __HELLO__
#define __HELLO__

void hello(const char *);
void byebye();

#endif

//hello.c
#include<stdio.h>
int x=0;
void hello(const char *p){
    printf("%s\n",p);
}
void byebye(){
    x=1;
    printf("byebye\n");
}

//test.c
#include"hello.h"
int main(){
    hello("hello world");
    byebye();
    return 0;
}

反汇编test,得到如下代码

objdump -d -j .plt -j .plt.sec test

test:     file format elf64-x86-64


Disassembly of section .plt:

0000000000001020 <.plt>:
    1020:	ff 35 92 2f 00 00    	pushq  0x2f92(%rip)        # 3fb8 <_GLOBAL_OFFSET_TABLE_+0x8>
    1026:	f2 ff 25 93 2f 00 00 	bnd jmpq *0x2f93(%rip)        # 3fc0 <_GLOBAL_OFFSET_TABLE_+0x10>
    102d:	0f 1f 00             	nopl   (%rax)
    1030:	f3 0f 1e fa          	endbr64 
    1034:	68 00 00 00 00       	pushq  $0x0
    1039:	f2 e9 e1 ff ff ff    	bnd jmpq 1020 <.plt>
    103f:	90                   	nop
    1040:	f3 0f 1e fa          	endbr64 
    1044:	68 01 00 00 00       	pushq  $0x1
    1049:	f2 e9 d1 ff ff ff    	bnd jmpq 1020 <.plt>
    104f:	90                   	nop

Disassembly of section .plt.sec:

0000000000001060 <hello@plt>:
    1060:	f3 0f 1e fa          	endbr64 
    1064:	f2 ff 25 5d 2f 00 00 	bnd jmpq *0x2f5d(%rip)        # 3fc8 <hello>
    106b:	0f 1f 44 00 00       	nopl   0x0(%rax,%rax,1)

0000000000001070 <byebye@plt>:
    1070:	f3 0f 1e fa          	endbr64 
    1074:	f2 ff 25 55 2f 00 00 	bnd jmpq *0x2f55(%rip)        # 3fd0 <byebye>
    107b:	0f 1f 44 00 00       	nopl   0x0(%rax,%rax,1)

看test.c的main函数

0000000000001169 <main>:
    1169:	f3 0f 1e fa          	endbr64 
    116d:	55                   	push   %rbp
    116e:	48 89 e5             	mov    %rsp,%rbp
    1171:	48 8d 3d 8c 0e 00 00 	lea    0xe8c(%rip),%rdi        # 2004 <_IO_stdin_used+0x4>
    1178:	e8 e3 fe ff ff       	callq  1060 <hello@plt>
    117d:	b8 00 00 00 00       	mov    $0x0,%eax
    1182:	e8 e9 fe ff ff       	callq  1070 <byebye@plt>
    1187:	b8 00 00 00 00       	mov    $0x0,%eax
    118c:	5d                   	pop    %rbp
    118d:	c3                   	retq   
    118e:	66 90                	xchg   %ax,%ax

main在0x1178调用了hello@plt,hello@plt执行到0x1064 bnd jmpq *0x2f5d(%rip)        # 3fc8 <hello>,这个跳转是去拿got表0x3fc8位置hello的地址,此值为0x1030,即跳转到.plt节那里,执行push 0。0为hello在rela.plt表中的index,此表如下

readelf -r test

Relocation section '.rela.dyn' at offset 0x550 contains 8 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000003da0  000000000008 R_X86_64_RELATIVE                    1160
000000003da8  000000000008 R_X86_64_RELATIVE                    1120
000000004008  000000000008 R_X86_64_RELATIVE                    4008
000000003fd8  000100000006 R_X86_64_GLOB_DAT 0000000000000000 _ITM_deregisterTMClone + 0
000000003fe0  000200000006 R_X86_64_GLOB_DAT 0000000000000000 __libc_start_main@GLIBC_2.2.5 + 0
000000003fe8  000400000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0
000000003ff0  000600000006 R_X86_64_GLOB_DAT 0000000000000000 _ITM_registerTMCloneTa + 0
000000003ff8  000700000006 R_X86_64_GLOB_DAT 0000000000000000 __cxa_finalize@GLIBC_2.2.5 + 0

Relocation section '.rela.plt' at offset 0x610 contains 2 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000003fc8  000300000007 R_X86_64_JUMP_SLO 0000000000000000 hello + 0
000000003fd0  000500000007 R_X86_64_JUMP_SLO 0000000000000000 byebye + 0

第一列offset为hello在test中被引用位置,为0x3fc8,指向的并非代码段,而是got表。因此,重定位过程重写的并非代码,而是数据段的got表相应项。

push 0后,再跳转到.plt开始0x1020,调用相关过程,这段逻辑的作用应该是——对hello进行重定位,把重定位的后的地址填进got表,下次再去got拿地址就是重定位后的hello地址,而不用重复上面这个过程了。懒加载理论上是这样,代码逻辑也是这样,然而我这段demo并没有这样执行,它绕过了懒加载,程序启动之初就通过libld.so重定位了动态库中的函数地址,修改了got表,所以,不会去执行懒加载的逻辑。验证如下

让test停在入口处,在got表中的byebye地址处下内存断点,如下,

gdb test
...
Reading symbols from test...
(gdb) starti
Starting program: /home/sun/code/primer/test 

Program stopped.
0x00007ffff7fd0100 in ?? () from /lib64/ld-linux-x86-64.so.2
(gdb) info files
Symbols from "/home/sun/code/primer/test".
Native process:
	Using the running image of child process 59782.
	While running this, GDB does not access memory from...
Local exec file:
	`/home/sun/code/primer/test', file type elf64-x86-64.
	Entry point: 0x555555555080
...
        0x0000555555555020 - 0x0000555555555050 is .plt
	0x0000555555555050 - 0x0000555555555060 is .plt.got
	0x0000555555555060 - 0x0000555555555080 is .plt.sec
	0x0000555555555080 - 0x0000555555555205 is .text
...
        0x0000555555557fb0 - 0x0000555555558000 is .got
...
--Type <RET> for more, q to quit, c to continue without paging--q
Quit
(gdb) x /10xw 0x0000555555557fb0
0x555555557fb0:	0x00003db0	0x00000000	0x00000000	0x00000000
0x555555557fc0:	0x00000000	0x00000000	0x00001030	0x00000000
0x555555557fd0 <byebye@got.plt>:	0x00001040	0x00000000
(gdb) watch *(void *)0x555555557fd0
Attempt to dereference a generic pointer.
(gdb) watch *(int *)0x555555557fd0
Hardware watchpoint 1: *(int *)0x555555557fd0
(gdb) c
Continuing.

Hardware watchpoint 1: *(int *)0x555555557fd0

Old value = 4160
New value = -134532808

可以看到,刚开始,got中byebye@got.plt内容为0x1040,中断后,知道byebye的got表记录地址原先是4160,即0x1040(进制转换printf "%x\n" 4160->1040),现在被修改为-134532808,通过x /1xg 0x555555557fd0查看现在byebye@got.plt的内容,结果是0x00007ffff7fb3138

(gdb) x /1xg 0x555555557fd0
0x555555557fd0 <byebye@got.plt>:	0x00007ffff7fb3138

这个地址是哪里呢,就是byebye函数现在加载在进程空间的地址

(gdb) x /8i 0x00007ffff7fb3138
   0x7ffff7fb3138:	endbr64 
   0x7ffff7fb313c:	push   %rbp
   0x7ffff7fb313d:	mov    %rsp,%rbp
   0x7ffff7fb3140:	lea    0xeb9(%rip),%rdi        # 0x7ffff7fb4000
   0x7ffff7fb3147:	callq  0x7ffff7fb3050
   0x7ffff7fb314c:	nop
   0x7ffff7fb314d:	pop    %rbp
   0x7ffff7fb314e:	retq   

由此可知,在main函数调用byebye@plt之前,libld.so已经修改了test的got表,把重定位后的地址填进去了。并非如其他帖子里说的那样首次调用时才懒加载。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值