深圳龙华做网站公司,网络平面设计包括哪些,兰州网站优化,青岛房产网签查询系统库用于将相似函数打包在一个单元中。Linux支持两种类型的库#xff1a;静态库#xff08;在编译时静态绑定到程序#xff09;和动态库#xff08;在运行时绑定到程序#xff09;。Linux系统使用的动态库是ELF格式#xff0c;后缀名为so。
1 加载
动态库内部划分为段静态库在编译时静态绑定到程序和动态库在运行时绑定到程序。Linux系统使用的动态库是ELF格式后缀名为so。
1 加载
动态库内部划分为段段分为不同的类型 PT_LOAD段包含代码或数据是需要被映射到内存中的每个段有不同的访问权限读、些、执行 PT_DYNAMIC段包含动态链接信息如符号表、重定位表、引用的其他库等。
其他段类型暂不说明。
加载器将库文件第一个PT_LOAD段和最后一个PT_LOAD段之间的内容映射到一段连续的内存地址空间好处是任意代码和数据的相对地址固定其首地址称为基地址如图。
库的加载只是把文件内容映射到内存地址但没有真正读取文件数据在发生内存缺页异常时才由操作系统读入对应的文件数据到内存。延迟读取文件可以加快库的加载速度。
1.1 预链接
一般来说映射的基地址是不固定的但如果动态库使用了预链接prelink技术则会被映射到预定的地址保存在文件上。如果预定的地址范围已经被占用了则加载失败Androidlinker是这样其他加载器可能不同。Prelink的好处是简化重定位加快加载速度。
2 重定位
2.1 内部函数和变量
在没有使用prelink的情况下库的基地址不是固定的运行时才确定其全局变量和函数的绝对地址也不是固定的。由于库加载之后任意代码和数据的相对地址是固定的如前一节所述因此一些系统如x86可以使用相对地址来访问全局变量和函数。ARM系统由于指令长度限制32位无法在指令中直接使用大范围的偏移量但可通过寄存器指定另外绝对地址在执行效率上要优于相对地址因此还是需要重定位。
如这个例子 [cpp] view plaincopyprint? __attribute__((visibility(hidden)))int errBase 1; void setErr(){ errBase 0x999; } __attribute__((visibility(hidden)))int errBase 1;
void setErr(){ errBase 0x999; }
编译得到so然后反编译ARM架构 [plain] view plaincopyprint? $ gcc -shared-nostdlib -o libtest.so test.c $ objdump -dlibtest.so 000002c4setErr: 2c4: mov ip,sp 2c8: push {fp,ip, lr, pc} 2cc: sub fp,ip, #4 2d0: ldr r2,[pc, #12] ; r2 errBase 2d4: mov r3,#2448 ; r3 990 2d8: add r3,r3, #9 ; r3 9 2dc: str r3,[r2] ; *r2 r3 2e0: ldm sp,{fp, sp, pc} 2e4: .word 0x0000109c ; 这里保存着errBase变量的地址 $ gcc -shared-nostdlib -o libtest.so test.c
$ objdump -dlibtest.so
000002c4setErr:
2c4: mov ip,sp
2c8: push {fp,ip, lr, pc}
2cc: sub fp,ip, #4
2d0: ldr r2,[pc, #12] ; r2 errBase
2d4: mov r3,#2448 ; r3 990
2d8: add r3,r3, #9 ; r3 9
2dc: str r3,[r2] ; *r2 r3
2e0: ldm sp,{fp, sp, pc}
2e4: .word 0x0000109c ; 这里保存着errBase变量的地址
查看重定位表 [plain] view plaincopyprint? $ readelf -rlibtest.so Relocationsection .rel.dyn at offset 0x2bc contains 1 entries: Offset Info Type Sym.Value Sym. Name 000002e4 00000017 R_ARM_RELATIVE $ readelf -rlibtest.so
Relocationsection .rel.dyn at offset 0x2bc contains 1 entries:
Offset Info Type Sym.Value Sym. Name
000002e4 00000017 R_ARM_RELATIVE
对比汇编代码和重定位表2e4即是保存errBase变量地址的偏移量。
重定位表中一个RELATIVE类型的表项指向变量和函数的相对地址加载器把它加上基地址使成为绝对地址。如果使用了prelink则不需要进行重定位。
2.2 外部函数和变量
外部变量和函数是指目标库引用依赖库的变量和函数需要加载器在依赖库的符号表查找对应的名称和绝对地址然后写入目标库的全局偏移量表GlobalOffset Table简称GOT。目标库通过GOT来访问外部变量和函数。
外部变量重定位对应一个GLOB_DAT类型的表项外部函数重定位对应一个JMP_SLOT类型表项表项的值是外部变量或函数的绝对地址由加载器进行设置。
如这个例子 [cpp] view plaincopyprint? extern interrBase; void setErr(){ errBase 0x999; } extern interrBase;
void setErr(){ errBase 0x999; }
编译得到so然后反编译ARM架构 [cpp] view plaincopyprint? $ gcc -shared-nostdlib -o libtest.so test.c $ objdump -dlibtest.so 00000218setErr: 218: push {fp} 21c: add fp,sp, #0 220: ldr r3,[pc, #28] ; r3GOT偏移 224: add r3,pc, r3 ; r3GOT地址 228: ldr r2,[pc, #24] ; r2errBase项在GOT的偏移 22c: ldr r3,[r3, r2] ; r3errBase的地址 230: ldr r2,[pc, #20] ; r20x999 234: str r2,[r3] ; r3r2 238: add sp,fp, #0 23c: pop {fp} 240: bx lr 244: .word 0x00008dc4 ; GOT偏移 248: .word 0x0000000c ; errBase在GOT的偏移 24c: .word 0x00000999 $ gcc -shared-nostdlib -o libtest.so test.c
$ objdump -dlibtest.so
00000218setErr:
218: push {fp}
21c: add fp,sp, #0
220: ldr r3,[pc, #28] ; r3GOT偏移
224: add r3,pc, r3 ; r3GOT地址
228: ldr r2,[pc, #24] ; r2errBase项在GOT的偏移
22c: ldr r3,[r3, r2] ; r3errBase的地址
230: ldr r2,[pc, #20] ; r20x999
234: str r2,[r3] ; r3r2
238: add sp,fp, #0
23c: pop {fp}
240: bx lr
244: .word 0x00008dc4 ; GOT偏移
248: .word 0x0000000c ; errBase在GOT的偏移
24c: .word 0x00000999
查看重定位表 [cpp] view plaincopyprint? $readelf -rlibtest.so Relocationsection .rel.dyn at offset 0x210 contains 1 entries: Offset Info Type Sym.Value Sym.Name 00008ffc 00000415 R_ARM_GLOB_DAT 00000000 errBase $readelf -rlibtest.so
Relocationsection .rel.dyn at offset 0x210 contains 1 entries:
Offset Info Type Sym.Value Sym.Name
00008ffc 00000415 R_ARM_GLOB_DAT 00000000 errBase
8ffcc正好对应errBase的GOT表项地址。
2.3 延迟绑定
外部函数和变量的重定位需要查找依赖库的符号表并进行字符串比较效率较低不过一般一个库使用的外部变量和函数都不会太多。如果使用了较多的外部函数为了加快动态库加载速度可以使用过程链接表ProcedureLinkageTable简称PLT把外部函数的定位延迟到第一次调用的时候称为延迟绑定。函数延迟绑定需要编译器对函数调用生成额外的代码主要由编译器实现。
看这个例子 [cpp] view plaincopyprint? voidprintf1(const char*, ...); void setErr(){ printf1(setErr\n); } voidprintf1(const char*, ...);
void setErr(){ printf1(setErr\n); }
对应汇编代码x86-64 [html] view plaincopyprint? 4c0printf1plt: 4c0: jmpq *0x200b3a(%rip) 4c6: pushq $0x0 4cb: jmpq 4b0 _init0x18 5ac setErr: 5ac: push %rbp 5ad: mov %rsp,%rbp 5b0: lea 0x5f(%rip),%rdi 5b7: mov $0x0,%eax 5bc: callq 4c0 5c1: pop %rbp 5c2: retq 4c0printf1plt:
4c0: jmpq *0x200b3a(%rip)
4c6: pushq $0x0
4cb: jmpq 4b0 _init0x18
5ac setErr:
5ac: push %rbp
5ad: mov %rsp,%rbp
5b0: lea 0x5f(%rip),%rdi
5b7: mov $0x0,%eax
5bc: callq 4c0
5c1: pop %rbp
5c2: retq
调用printf1会调用printf1plt然后跳转到*0x200b3a(%rip)即*(基地址0x201000。
如果是第一次执行*0x(基地址0x201000)的值是(基地址4c6)后面的代码会进行函数绑定对应的重定位项是 [plain] view plaincopyprint? $ readelf -rlibtest.so Relocationsection .rela.plt at offset 0x468 contains 2 entries: 201000 000300000007 R_X86_64_JUMP_SLO printf1 0 $ readelf -rlibtest.so
Relocationsection .rela.plt at offset 0x468 contains 2 entries:
201000 000300000007 R_X86_64_JUMP_SLO printf1 0
绑定之后*0x(基地址0x201000)会对应printf1函数的地址下次再进入printf1plt就可以直接跳转到printf1函数了。
2.4 位置无关代码
一般来说程序和动态库的代码和只读数据被加载到内存之后可以被多个进程共享但被写的脏数据则不能被多个进程共享。RELATIVE类型的重定位会修改代码段的变量地址导致代码段被污染从而不能被多个进程共享。为了让动态库的代码段可以在进程间共享可以让编译器编译出位置无关代码简称PIC通过GOT来访问变量和函数。
PIC使代码段可在进程间共享从而节省了内存但是通过GOT表来访问变量和函数会比相对定位慢一点如果没有需要则可以不使用PIC。