linux的动态库可以被共享,这个符号会怎么被链接进去呢,让我们来看一下过程。下面以常见的printf来举例。
函数入口
首先,我们编码过程中遇到的printf函数,并不是直接调用的,首先会进入如下的代码位置。
Dump of assembler code for function printf@plt:
0x00015454 <+0>: bx pc
0x00015456 <+2>: nop ; (mov r8, r8)
->0x00015458 <+4>: add r12, pc, #6291456 ; 0x600000
0x0001545c <+8>: add r12, r12, #872448 ; 0xd5000
0x00015460 <+12>: ldr pc, [r12, #3084]! ; 0xc0c
End of assembler dump.
外部调用printf入口是0x15458,我们根据汇编来分析下,
15460位置计算, pc = *(0x15460 + 0x6d5c0c) = *(0x6EB06C) (*标识地址引用,类似指针的意义),同时,这条指令还会让r12寄存器保存0x6EB06C(<printf@got.plt>)
其中,地址0x6EB06C的数据为真实printf加载前的代码桩位置,指向PLT的一段函数:
0x6eb06c <printf@got.plt>: 0x00015310
最后一行,pc会被设置成0x00015310,进入下面一个环节
函数重定位流程
我们看一下0x00015310为PLT代码段的头,也就是函数重定位流程入口
00015310 <.plt>:
15310: e52de004 push {lr} ; (str lr, [sp, #-4]!)
15314: e59fe004 ldr lr, [pc, #4] ; 15320 <.plt+0x10>
15318: e08fe00e add lr, pc, lr
1531c: e5bef008 ldr pc, [lr, #8]!
15320: 006d5ce0 .word 0x006d5ce0
我们计算一下下面几行的数据值:
15314计算,lr=*(0x1531c+4)= 0x006d5ce0
15318计算,lr= 0x15320 + *(0x15320) = 0x6EB000
1531c计算,pc=*(lr+8)= *0x6EB008 = 0xb6f22320,同时lr也被更新为0x6EB008
0x6eb008: 0xb6f22320
在1531c行,pc被载入0xb6f22320这个函数入口,看一下原来是动态链接符号解析函数
Dump of assembler code for function _dl_linux_resolve:
0xb6f22320 <+0>: push {r0, r1, r2, r3, r4}
0xb6f22324 <+4>: ldr r0, [lr, #-4]
0xb6f22328 <+8>: sub r1, lr, r12
0xb6f2232c <+12>: mvn r1, r1, asr #2
0xb6f22330 <+16>: blx 0xb6f1f9a8 <_dl_linux_resolver>
0xb6f22334 <+20>: mov r12, r0
0xb6f22338 <+24>: pop {r0, r1, r2, r3, r4, lr}
0xb6f2233c <+28>: bx r12
End of assembler dump.
这个流程完毕之后,我们再来看下加载后的printf@got.plt位置函数指针变成了:
0x6eb06c <printf@got.plt>: 0xb6ead0e9
反汇编看一下,原来是真的printf了
(gdb) disassemble 0xb6ead0e9
Dump of assembler code for function printf:
0xb6ead0e8 <+0>: push {r0, r1, r2, r3}
0xb6ead0ea <+2>: push {r0, r1, r2, lr}
这个过程中为什么printf@got.plt的值能被知道呢?
还记得开始入口时,r12寄存器也会被记录,正对应printf@got.plt,同时根据这个地址相对GOT的位置,可以隐式确认其为printf的GOT。总地来说,只需知道其相对地址即可。
动态库的共享
动态库在物理内存上是只有一份的,但由于不同的进程具有不同的虚拟地址空间,因此其会产生一份进程内虚拟空间到物理内存的页表映射。
动态库的内存分两部分,一部分为进程所有的,另一部分是动态库所有的。定位动态库在进程地址的部分编入了进程内,其他的在动态库加载后动态定位。
通过cat /proc/800/smaps观察代码段,找到libc的代码段,得到如下信息,其中Rss为动态库内存,Pss为分摊内存,这个值会被所有使用libc的进程分摊。
b6f28000-b6f29000 r--p 00004000 1f:04 241 /lib/ld-uClibc-1.0.31.so
Size: 4 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Rss: 4 kB
Pss: 4 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 4 kB
Referenced: 4 kB
Anonymous: 4 kB
LazyFree: 0 kB
AnonHugePages: 0 kB
ShmemPmdMapped: 0 kB
FilePmdMapped: 0 kB
Shared_Hugetlb: 0 kB
Private_Hugetlb: 0 kB
Swap: 0 kB
SwapPss: 0 kB
Locked: 0 kB
THPeligible: 0
VmFlags: rd mr mw me dw ac