这个玩意理论跟实际不太一致,搞了半天,终于发现我的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表,把重定位后的地址填进去了。并非如其他帖子里说的那样首次调用时才懒加载。