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
    评论
### 回答1: Linux动态链接库是一种在程序运行时动态加载的库文件,它包含了一些可被程序调用的函数和变量。与静态链接库不同,动态链接库在程序运行时才会被加载,可以减小程序的体积,提高程序的灵活性和可移植性。动态链接库可以被多个程序共享,从而节省系统资源。在Linux系统中,动态链接库的文件名通常以“.so”结尾。 ### 回答2: Linux动态链接库是一种在运行时加载的共享库,它可以被多个程序共享使用,提供可重用的代码和功能。与静态链接库相比,动态链接库允许程序在运行时加载,并在内存中共享已加载的库。这种方式可以减少内存占用,提高系统性能,并降低可执行文件的大小。 Linux动态链接库分为两种类型:系统级动态链接库和用户级动态链接库。系统级动态链接库例如libc,libpthread等,它们提供了操作系统的基本功能和系统调用的封装。用户级动态链接库则是由开发者自行编写的共享库,它包含了特定功能的代码,可以被多个程序调用和重用。 动态链接库的使用有以下优势: 1. 代码重用:多个程序可以共享同一个动态链接库,避免了重复代码的编写和维护。 2. 系统升级:当动态链接库的代码需要更新时,只需要替换库文件,而不需要重新编译所有使用该库的程序。 3. 空间节省:动态链接库在内存中只需加载一次,并被多个程序共享使用,减少了内存占用和可执行文件的大小。 4. 运行时动态性:程序可以在运行时加载或卸载动态链接库,增加了程序的灵活性和扩展性。 然而,使用动态链接库也存在一些注意事项: 1. 依赖关系:程序需要正确的动态链接库版本,否则会导致运行错误或崩溃。 2. 性能开销:动态链接库的加载和链接需要额外的时间和开销,可能会稍微降低程序的执行速度。 3. 安全性:动态链接库易受到恶意代码的攻击,因此需要确保库的来源和完整性。 总之,Linux动态链接库是一种强大的机制,可以提高代码的重用性和灵活性,在系统开发和维护中起到了重要的作用。 ### 回答3: Linux动态链接库(Dynamic Link Library,简称DLL)是指一种在Linux操作系统中,将不同的代码库以动态链接的方式组合在一起,供程序调用的机制。 在Linux中,动态链接库是以共享库(shared library)的形式存在的。它与静态链接库(static library)相比,可以在程序运行时动态加载和卸载,并且可以被多个程序同时共享使用。 Linux动态链接库的特点有以下几点: 1. 节省内存空间:相比静态链接库,动态链接库只需在内存中加载一次,就可以被多个程序共享使用,节省了内存空间。 2. 灵活更新:由于动态链接库是以单独的文件存在的,所以可以通过替换或更新动态链接库文件,实现对其功能的升级或修复,而不用重新编译和链接整个程序。 3. 高度可移植性:动态链接库可以跨平台使用,只需在目标系统中存在相同的动态链接库文件即可。 4. 动态加载和卸载:在程序运行时,可以通过动态链接库的加载和卸载机制,动态添加或删除某个功能模块,实现程序的动态扩展和精简。 5. 提高开发效率:动态链接库将一些常用的函数和代码库进行打包,供多个程序调用,可以提高开发效率,并且减少了代码重复性。 总之,Linux动态链接库是一种方便、灵活、可共享的代码组织方式,提高了程序的开发效率和可维护性,也为程序的升级和优化提供了便利。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值