当前位置: 首页 > news >正文

查品牌的软件有什么百度seo优化方案

查品牌的软件有什么,百度seo优化方案,滴滴优惠券网站怎么做,郑州一建集团工程建设有限公司1、为什么要动态链接 1.1 空间浪费 对于静态链接来说#xff0c;在程序运行之前#xff0c;会将程序所需的所有模块编译、链接成一个可执行文件。这种情况下#xff0c;如果 Program1 和 Program2 都需要用到 Lib.o 模块#xff0c;那么#xff0c;内存中和磁盘中实际上就…1、为什么要动态链接 1.1 空间浪费 对于静态链接来说在程序运行之前会将程序所需的所有模块编译、链接成一个可执行文件。这种情况下如果 Program1 和 Program2 都需要用到 Lib.o 模块那么内存中和磁盘中实际上就存在了两份Lib.o的代码。当共享的模块基数变得很大时空间浪费无法想象。 1.2 更新困难 动态链接对程序的更新、部署和发布也会带来很多麻烦。比如 Program1 所使用的 Lib.o 是由一个第三方厂商提供的当该厂商更新了 Lib.o 的时候那么 Program1 的厂商就需要拿到最新版的 Lib.o然后将其与 Program.o 链接后将新的 Program1 整个发布给用户。这样做的缺点很明显即一旦程序中有任何模块更新整个程序就要重新链接、发布给用户。 1.3 动态链接 要解决空间浪费和更新困难这两个问题最简单的办法就是把程序的模块相互分割开来形成独立的文件而不再将它们静态地链接在一起。简单地讲就是不对那些组成程序地目标文件进行链接等到程序要运行时才进行链接。也就是说把链接这个过程推迟到了运行时再进行这就是动态链接Dynamic Linking的基本思想。 2、简单的动态链接例子 2.1 简单例子 我们先实现一个最简单的动态链接库的例子感受一下。 program1.c文件的内容 #include Lib.hint main() {foobar(1);return 0; }program2.c文件的内容 #include Lib.hint main() {foobar(2);return 0; }Lib.h文件的内容 #ifndef LIB_H #define LIB_Hvoid foobar(int i);#endifLib.c文件的内容 #include stdio.h void foobar(int i) {printf(Printing from Lib.so %d\n, i); }program1.c 和 program2.c 都调用了 Lib.c 里面的 foobar 函数。为了在内存中加载一次 Lib.c使 program1 和 program2 共享。我们可以将 Lib.c 编译成共享对象动态库。 这里需要强调一下这里所谓的共享并不是共享整个Lib.c的内容而是特指共享它的代码部分。 对于Lib.c中的数据部分每个进程都需要一份自己的拷贝因为它们可能需要独立地修改Lib.c中的数据。 先将Lib.c编译成共享对象 gcc -fPIC -shared -o Lib.so Lib.c-shared表示的是产生共享对象。 -fPIC的含义暂时先不用管待会儿再说。 现在我们来分别编译Program1和Program2 gcc -o Program1 Program1.c ./Lib.sogcc -o Program2 Program2.c ./Lib.so现在执行./Program1就可以执行并看到如下输出 Attention 注意上一步骤中我们使用了 ./Lib.so来指定编译链接时搜索库的路径、装载时指定的动态库搜索路径。 现代链接器在处理动态库时将 链接时路径Link-time path和 运行时路径Run-time path分开 实际上这一步骤可以拆解成以下 liangjieliangjie-virtual-machine:~/Desktop/cfp$ gcc -fPIC -shared -o libtest.so Lib.c liangjieliangjie-virtual-machine:~/Desktop/cfp$ gcc -o Program1 Program1.c -L./ -ltest -Wl,-rpath,./ liangjieliangjie-virtual-machine:~/Desktop/cfp$ ./Program1 Printing from Lib.so 1-Ldir制定链接时搜索库的路径。比如你自己的库可以用它制定目录不然链接器将只在标准库的目录找。这个dir就是目录的名称 -Wl,option此选项传递 option 给链接程序指定运行时动态库路径链接程序将动态库的路径包含在可执行文件中如果 option 中间有逗号, 就将 option 分成多个选项, 然后传递给会链接程序 可以使用 readelf 查看 dynamic 段其中会有可执行文件依赖的动态库NEEDED以及动态库的运行时路径RUNPATH liangjieliangjie-virtual-machine:~/Desktop/cfp$ readelf -d Program1Dynamic section at offset 0x2da8 contains 29 entries:Tag Type Name/Value0x0000000000000001 (NEEDED) Shared library: [libtest.so]0x0000000000000001 (NEEDED) Shared library: [libc.so.6]0x000000000000001d (RUNPATH) Library runpath: [./]0x000000000000000c (INIT) 0x10000x000000000000000d (FINI) 0x11640x0000000000000019 (INIT_ARRAY) 0x3d980x000000000000001b (INIT_ARRAYSZ) 8 (bytes)0x000000000000001a (FINI_ARRAY) 0x3da00x000000000000001c (FINI_ARRAYSZ) 8 (bytes)0x000000006ffffef5 (GNU_HASH) 0x3b00x0000000000000005 (STRTAB) 0x4800x0000000000000006 (SYMTAB) 0x3d80x000000000000000a (STRSZ) 157 (bytes)0x000000000000000b (SYMENT) 24 (bytes)0x0000000000000015 (DEBUG) 0x00x0000000000000003 (PLTGOT) 0x3fb80x0000000000000002 (PLTRELSZ) 24 (bytes)0x0000000000000014 (PLTREL) RELA0x0000000000000017 (JMPREL) 0x6200x0000000000000007 (RELA) 0x5600x0000000000000008 (RELASZ) 192 (bytes)0x0000000000000009 (RELAENT) 24 (bytes)0x000000000000001e (FLAGS) BIND_NOW0x000000006ffffffb (FLAGS_1) Flags: NOW PIE0x000000006ffffffe (VERNEED) 0x5300x000000006fffffff (VERNEEDNUM) 10x000000006ffffff0 (VERSYM) 0x51e0x000000006ffffff9 (RELACOUNT) 30x0000000000000000 (NULL) 0x0指定运行时动态库路径常见方法 1gcc参数指定 -Wl,-rpath ${LD_PATH} 2配置文件 /etc/ld.so.conf 文件中添加库的搜索路径 3设置环境变量 export LD_LIBRARY_PATH${LD_PATH} 执行 Program1 时操作系统会首先在我们的虚拟进程空间中加载进一个动态链接器动态链接器帮我们完成链接任务然后我们的程序就开始执行了。 解析   Lib.c 被编译成 libtest.so 共享对象文件Program1.c 被编译成 Program1.o 后链接成可执行程序 Program1。 上图中有一个步骤与静态链接不一样那就是 Program1.o 被链接成可执行文件这一步在静态链接中这一步链接过程会把 Program1.o和 Lib.o 链接到一起并且输出可执行文件 Program1。但在这里 Lib.o 没有被链接进来链接的输入目标文件只有 Program1.o 当然还有C语言运行库我们这里暂时忽略但是从前面的命令行中我们看到Lib.so也参与了链接过程这是怎么回事呢 让我们回到动态链接的机制上来当程序模块 Program1.c 被编译成 Program1.o 时编译器还不知道 foobar() 函数的地址。当连接器将 Program1.o 链接成可执行文件时这时候连接器必须确定 Program1.o 所引用的 foobar() 函数的性质。如果 foobar() 是一个定义在其静态目标模块中的函数那么链接器将会按照静态链接的规则将 Program1.o 中的 foobar 地址引用重定位如果 foobar() 是定义在某个动态共享对象中的函数那么链接器就会将这个符号的引用标记为一个动态链接的符号不对它进行地址重定位 而是在装载的时候再进行重定位。 可以使用 readelf 解析出 Program1 中的符号表其中 .dynsym 为动态符号表包含于 .symtab 全局符号表中 liangjieliangjie-virtual-machine:~/Desktop/cfp$ readelf -s Program1Symbol table .dynsym contains 7 entries:Num: Value Size Type Bind Vis Ndx Name0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _[...]GLIBC_2.34 (2)2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterT[...]3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND foobar4: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__5: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMC[...]6: 0000000000000000 0 FUNC WEAK DEFAULT UND [...]GLIBC_2.2.5 (3)Symbol table .symtab contains 36 entries:Num: Value Size Type Bind Vis Ndx Name0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FILE LOCAL DEFAULT ABS Scrt1.o2: 000000000000038c 32 OBJECT LOCAL DEFAULT 4 __abi_tag3: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c4: 0000000000001090 0 FUNC LOCAL DEFAULT 16 deregister_tm_clones5: 00000000000010c0 0 FUNC LOCAL DEFAULT 16 register_tm_clones6: 0000000000001100 0 FUNC LOCAL DEFAULT 16 __do_global_dtors_aux7: 0000000000004010 1 OBJECT LOCAL DEFAULT 26 completed.08: 0000000000003db0 0 OBJECT LOCAL DEFAULT 22 __do_global_dtor[...]9: 0000000000001140 0 FUNC LOCAL DEFAULT 16 frame_dummy10: 0000000000003da8 0 OBJECT LOCAL DEFAULT 21 __frame_dummy_in[...]11: 0000000000000000 0 FILE LOCAL DEFAULT ABS Program1.c12: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c13: 00000000000020e0 0 OBJECT LOCAL DEFAULT 20 __FRAME_END__14: 0000000000000000 0 FILE LOCAL DEFAULT ABS 15: 0000000000003db8 0 OBJECT LOCAL DEFAULT 23 _DYNAMIC16: 0000000000002004 0 NOTYPE LOCAL DEFAULT 19 __GNU_EH_FRAME_HDR17: 0000000000003fb8 0 OBJECT LOCAL DEFAULT 24 _GLOBAL_OFFSET_TABLE_18: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_mai[...]19: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterT[...]20: 0000000000004000 0 NOTYPE WEAK DEFAULT 25 data_start21: 0000000000004010 0 NOTYPE GLOBAL DEFAULT 25 _edata22: 0000000000001164 0 FUNC GLOBAL HIDDEN 17 _fini23: 0000000000000000 0 FUNC GLOBAL DEFAULT UND foobar24: 0000000000004000 0 NOTYPE GLOBAL DEFAULT 25 __data_start25: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__26: 0000000000004008 0 OBJECT GLOBAL HIDDEN 25 __dso_handle27: 0000000000002000 4 OBJECT GLOBAL DEFAULT 18 _IO_stdin_used28: 0000000000004018 0 NOTYPE GLOBAL DEFAULT 26 _end29: 0000000000001060 38 FUNC GLOBAL DEFAULT 16 _start30: 0000000000004010 0 NOTYPE GLOBAL DEFAULT 26 __bss_start31: 0000000000001149 25 FUNC GLOBAL DEFAULT 16 main32: 0000000000004010 0 OBJECT GLOBAL HIDDEN 25 __TMC_END__33: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMC[...]34: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalizeG[...]35: 0000000000001000 0 FUNC GLOBAL HIDDEN 12 _init 那么链接器如何知道 foobar 的引用是一个静态符号还是一个动态符号呢这就是为什么在编译的时候要用到 Lib.so 的原因。Lib.so 中保存了完整的符号信息把 Lib.so 作为链接的输入文件之一链接器在解析符号时就可以知道 foobar 是一个定义在 Lib.so 的动态符号这样链接器就可以对 foobar 的引用做特殊的处理使它成为一个动态符号的引用。 关于模块 在静态链接时整个程序最终只有一个可执行文件它是一个不可以分割的整体但是在动态链接下一个程序被分成了若干个文件有程序的主要部分 即可执行文件Program1和程序所依赖的共享对象Lib.so很多时候我们也把这部分称为模块 即动态链接下的可执行文件和共享对象都可以看作是程序的一个模块2.2 动态链接程序运行时地址空间分布 对于静态链接的可执行文件来说整个进程只有一个文件要被映射即可执行文件。而对于动态链接除了可执行文件还有它所依赖的共享目标文件。 还是以上面的 Program1 为例对 Lib.c 稍作修改 #include stdio.h void foobar(int i) {printf(Printing from Lib.so %d\n, i);sleep(-1); }然后就可以查看进程的虚拟地址空间分布 liangjieliangjie-virtual-machine:~/Desktop/cfp$ ./Program1 [1] 4801 Printing from Lib.so 1 liangjieliangjie-virtual-machine:~/Desktop/cfp$ cat /proc/4801/maps 5636a1ef2000-5636a1ef3000 r--p 00000000 08:03 2883771 /home/liangjie/Desktop/cfp/Program1 5636a1ef3000-5636a1ef4000 r-xp 00001000 08:03 2883771 /home/liangjie/Desktop/cfp/Program1 5636a1ef4000-5636a1ef5000 r--p 00002000 08:03 2883771 /home/liangjie/Desktop/cfp/Program1 5636a1ef5000-5636a1ef6000 r--p 00002000 08:03 2883771 /home/liangjie/Desktop/cfp/Program1 5636a1ef6000-5636a1ef7000 rw-p 00003000 08:03 2883771 /home/liangjie/Desktop/cfp/Program1 5636a1f8f000-5636a1fb0000 rw-p 00000000 00:00 0 [heap] 7fde22c00000-7fde22c28000 r--p 00000000 08:03 4988401 /usr/lib/x86_64-linux-gnu/libc.so.6 7fde22c28000-7fde22dbd000 r-xp 00028000 08:03 4988401 /usr/lib/x86_64-linux-gnu/libc.so.6 7fde22dbd000-7fde22e15000 r--p 001bd000 08:03 4988401 /usr/lib/x86_64-linux-gnu/libc.so.6 7fde22e15000-7fde22e19000 r--p 00214000 08:03 4988401 /usr/lib/x86_64-linux-gnu/libc.so.6 7fde22e19000-7fde22e1b000 rw-p 00218000 08:03 4988401 /usr/lib/x86_64-linux-gnu/libc.so.6 7fde22e1b000-7fde22e28000 rw-p 00000000 00:00 0 7fde22ea3000-7fde22ea6000 rw-p 00000000 00:00 0 7fde22eb5000-7fde22eb6000 r--p 00000000 08:03 2883703 /home/liangjie/Desktop/cfp/Lib.so 7fde22eb6000-7fde22eb7000 r-xp 00001000 08:03 2883703 /home/liangjie/Desktop/cfp/Lib.so 7fde22eb7000-7fde22eb8000 r--p 00002000 08:03 2883703 /home/liangjie/Desktop/cfp/Lib.so 7fde22eb8000-7fde22eb9000 r--p 00002000 08:03 2883703 /home/liangjie/Desktop/cfp/Lib.so 7fde22eb9000-7fde22eba000 rw-p 00003000 08:03 2883703 /home/liangjie/Desktop/cfp/Lib.so 7fde22eba000-7fde22ebc000 rw-p 00000000 00:00 0 7fde22ebc000-7fde22ebe000 r--p 00000000 08:03 4988059 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 7fde22ebe000-7fde22ee8000 r-xp 00002000 08:03 4988059 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 7fde22ee8000-7fde22ef3000 r--p 0002c000 08:03 4988059 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 7fde22ef4000-7fde22ef6000 r--p 00037000 08:03 4988059 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 7fde22ef6000-7fde22ef8000 rw-p 00039000 08:03 4988059 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 7ffe759f3000-7ffe75a14000 rw-p 00000000 00:00 0 [stack] 7ffe75b22000-7ffe75b26000 r--p 00000000 00:00 0 [vvar] 7ffe75b26000-7ffe75b28000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall] 我们可以看到整个进程虚拟地址空间中相比与静态链接多了几个文件的映射。Lib.so 和 Program1 一样它们都是被操作系统用同样的方法映射至进程的虚拟地址空间只是它们占据的虚拟地址和长度不同。Program1 除了使用 Lib.so之外它还用到了动态链接形式的C语言运行库 libc.so.6。另外还有一个值得关注的共享对象就是 ld-linux-x86-64.so.2它实际上是Linux下的动态链接器。动态链接器与普通共享对象一样被映射到了进程的地址空间在系统开始运行 Program1 之前这时候已经完成了装载首先会把控制权交给动态链接器由它完成所有的动态链接工作完成之后再把控制权交给 Program1然后 Program1 程序开始执行。 3、地址无关代码 3.1 固定装载地址的困扰 关于共享目标文件在内存中的地址分配主要有两种解决方案分别是 静态共享库Static Shared Library地址固定动态共享库Dynamic Shared Libary地址不固定 静态共享库   静态共享库的做法是将程序的各个模块统一交给操作系统进行管理操作系统在某个特定的地址划分出一些地址块为那些已知的模块预留足够的空间。因为这个地址对于不同的应用程序来说都是固定的所以称之为静态。 但是静态共享库的目标地址会导致地址冲突、升级等问题。 动态共享库 采用动态共享库的方式也称为装载时重定位Load Time Relocation。其基本思路是在链接时对所有绝对地址的引用都不作重定位而把这一步推迟到装载时再完成。一旦模块装载地址确定即目标地址确定那么系统就对程序中所有的绝对地址引用进行重定位。 3.2 装载时重定位 采用动态共享库的方式也称为装载时重定位Load Time Relocation。其基本思路是在链接时对所有绝对地址的引用都不作重定位而把这一步推迟到装载时再完成。一旦模块装载地址确定即目标地址确定那么系统就对程序中所有的绝对地址引用进行重定位。 我们前面在静态链接时提到过重定位那是的重定位叫做链接时重定位Link Time Relocation而现在这种情况经常被称为装载时重定位Load Time Relocation。在windows中又叫基址重置Rebasing区别于静态链接的链接时重定位-link time relocation 但是这种方式也存在一些问题。比如动态链接模块被装载映射至虚拟空间后指令部分是在多个进程间共享的由于装载时重定位的方法需要修改指令所以没有办法做到同一份指令被多个进程共享因为指令被重定位后对于每个进程来说都是不同的。 Attention关于上面一句话的理解 共享对象也就是动态链接库在被装载到物理内存后始终是只有一份的不管有多少个进程使用它。但是对于每一个进程共享对象会映射一次到虚拟地址空间也就是每个进程空间都有一份共享对象的映射此时对于不同的进程映射的地址基址是不一样的大部分情况下。紧接着进行装载时重定位。装载时重定位由动态链接器完成动态链接器会被一起映射到进程空间中。它根据共享对象在虚拟内存空间中的地址修改在物理内存中的共享对象中的指令为什么会修改指令原因在于绝对地址访问如模块内的变量访问是直接用mov指令完成的也就是直接将地址打入寄存器所以此时的重定位会直接修改指令。进一步共享对象中修改的指令是根据共享对象被映射到虚拟空间中的地址基址决定的而每个进程对共享对象的映射不可能都是在相同地址。所以也就无法完成这一部分代码的共享 虽然动态链接库中的代码是共享的但是其中的可修改数据部分对于不同进程来说是由多个副本的所以它们可以采用装载时重定位的方法来解决。基于此一种名为地址无关代码的技术被提出以克服这个问题。 Linux 和 GCC 支持这种装载时重定位的方法我们前面在产生共享对象时时殷弘了两个 GCC 参数“-shared”和“-fPIC”如果只使用“-shared”那么输出共享对象就是使用了装载时重定位的方法。 3.3 地址无关码 基本思路是把指令中那些需要被修改的部分分离出来跟数据部分放到一起这样剩下的指令就可以保持不变而数据部分在每个进程中拥有一个副本。ELF 针对各种可能的访问类型模块内部指令调用、模块内部数据访问、模块间指令调用、模块间数据访问实现了对应地址引用方式从而实现了PICPosition-independent Code。 共享对象模块中的地址引用按照是否为跨模块分为两类模块内部引用、模块外部引用。按照不同的引用方式又可分为指令引用、数据引用。以如下代码为例可得出如下四种类型 /** pic.c*/static int a; extern int b; extern void ext();void bar() {a 1;b 2; }void foo() {bar();ext(); }liangjieliangjie-virtual-machine:~/Desktop/cfp$ gcc -fPIC -shared -o libpic.so pic.c类型1模块内部的函数调用   由于被调用的函数与调用者都处于同一模块它们之间的相对位置是固定的。对于现代的系统来说模块内部的调用都可以是相对地址调用或者是基于寄存器的相对调用所以对于这种指令是不需要重定位的。 liangjieliangjie-virtual-machine:~/Desktop/cfp$ objdump -d libpic.so ...... 0000000000001070 barplt:1070: f3 0f 1e fa endbr64 1074: f2 ff 25 a5 2f 00 00 bnd jmp *0x2fa5(%rip) # 4020 bar0x2ee7107b: 0f 1f 44 00 00 ...... 000000000000115b foo:115b: f3 0f 1e fa endbr64 115f: 55 push %rbp1160: 48 89 e5 mov %rsp,%rbp1163: b8 00 00 00 00 mov $0x0,%eax1168: e8 03 ff ff ff call 1070 barplt116d: b8 00 00 00 00 mov $0x0,%eax1172: e8 e9 fe ff ff call 1060 extplt1177: 90 nop1178: 5d pop %rbp1179: c3 ret ......foo 中对 bar 的调用的那条指令实际上是一条相对地址调用指令。只要 bar 和 foo 的相对位置不变这条指令是地址无关的。即无论模块被装载到哪个位置这条指令都是有效的这种相对地址的方式对于 jmp 指令也有效。 注这里面的关于 “ barplt ”在后面的 PLT 章节会去详细讲解这里就把它理解成 bar 就行了 类型2模块内部的数据访问如模块中定义的全局变量、静态变量   一个模块前面一般是若干个页的代码后面紧跟着若干个页的数据这些页之间的相对位置是固定的即任何一条指令与它需要访问的模块内部数据之间的相对位置是固定的所以只需要相对于当前指令加上固定的偏移量就可以访问模块内部数据了。 反汇编 libpic.so liangjieliangjie-virtual-machine:~/Desktop/cfp$ objdump -d libpic.so 0000000000001139 bar:1139: f3 0f 1e fa endbr64 113d: 55 push %rbp113e: 48 89 e5 mov %rsp,%rbp1141: c7 05 e9 2e 00 00 01 movl $0x1,0x2ee9(%rip) # 4034 a1148: 00 00 00 114b: 48 8b 05 86 2e 00 00 mov 0x2e86(%rip),%rax # 3fd8 b1152: c7 00 02 00 00 00 movl $0x2,(%rax)1158: 90 nop1159: 5d pop %rbp115a: c3 ret 以访问 a 变量为例 1141: c7 05 e9 2e 00 00 01 movl $0x1,0x2ee9(%rip) # 4034 a%rip 寄存器保存的是下一条指令的地址”0x114b”(因为这是个相对地址所以用引号扩住) a 的访问地址为这里是基于模块 装载地址为 0 来计算的0x2ee9固定偏移量 0x114b当前指令即 PC 值 0x4034 固定偏移量 0x2ee9 是模块 libpic.so 在编译时就算好的 我们使用 readelf -S 查看 libpic.so 中各个 section 的地址发现 0x4034 刚好在 .bss 段符合未初始化的静态变量在 .bss 段事实。 liangjieliangjie-virtual-machine:~/Desktop/cfp$ readelf -S libpic.so There are 27 section headers, starting at offset 0x3568:Section Headers:[Nr] Name Type Address OffsetSize EntSize Flags Link Info Align......[21] .data PROGBITS 0000000000004028 000030280000000000000008 0000000000000000 WA 0 0 8[22] .bss NOBITS 0000000000004030 000030300000000000000008 0000000000000000 WA 0 0 4......当然了模块 libpic.so 的装载地址肯定不是0。在实际装载时会确定模块的装载地址那么变量 a 的访问地址为 装载地址 0x4034类型3模块间数据访问   模块间的数据访问比模块内部稍微麻烦一些因为模块间的数据访问目标地址要等到装载时才决定。此时动态链接需要使用代码无关地址技术其基本思想是把地址相关的部分放到数据段。ELF 的实现方法是在数据段中建立一个指向这些变量的指针数组也称为全局偏移表Global Offset TableGOT当代码需要引用该全局变量时可以通过 GOT 中相对应的项间接引用。过程示意图如下所示 当指令中需要访问变量 b 时程序会先找到 GOT然后根据 GOT 中变量所对应的项找到变量的目标地址。每个变量都对应一个4字节的地址链接器在装载模块的时候会查找每个变量所在的地址然后填充GOT中的各个项以确保每个指针所指向的地址正确。由于 GOT 本身是放在数据段的所以它可以在模块装载时被修改并且每个进程都可以由独立的副本相互不受影响。 我们回顾刚才函数 bar的反汇编代码。为访问变量 b 我们程序首先计算出变量 b 在 got 中的位置即 0x1152%rip也就是 PC 值 0x2e86 固定偏移 0x3fd8 然后使用寄存器间接寻址方式给变量 b 赋值2。 0000000000001139 bar:1139: f3 0f 1e fa endbr64 113d: 55 push %rbp113e: 48 89 e5 mov %rsp,%rbp1141: c7 05 e9 2e 00 00 01 movl $0x1,0x2ee9(%rip) # 4034 a1148: 00 00 00 114b: 48 8b 05 86 2e 00 00 mov 0x2e86(%rip),%rax # 3fd8 b1152: c7 00 02 00 00 00 movl $0x2,(%rax)1158: 90 nop1159: 5d pop %rbp115a: c3 ret 我们可以用 objdump 来查看 got 表位置 liangjieliangjie-virtual-machine:~/Desktop/cfp$ objdump -h libpic.so ......18 .got 00000028 0000000000003fd8 0000000000003fd8 00002fd8 2**3CONTENTS, ALLOC, LOAD, DATA19 .got.plt 00000028 0000000000004000 0000000000004000 00003000 2**3CONTENTS, ALLOC, LOAD, DATA ......可以看到 got 在文件中的偏移是 0x3fd8我们再来看看 libpic.so 的需要在动态链接时的重定位项 liangjieliangjie-virtual-machine:~/Desktop/cfp$ objdump -R libpic.so ...... 0000000000003fd8 R_X86_64_GLOB_DAT b .....这里的 R_X86_64_GLOB_DAT 含义一旦知道变量 b 的运行时地址就把它放入 0x3fd8 处。 可以看到变量 b 的地址需要重定位它的地址位于 0x3fd8也就是 got 中偏移0相当于是 GOT 中的第一项每4字节一项。这也就有上面反汇编中的对 b 赋值语句 将 0x2e86 PC 的值就是变量 b 在 got 表中的地址写到寄存器 rax 中将立即数 2赋值给 rax 寄存器中地址指向的值也就是变量 b ......114b: 48 8b 05 86 2e 00 00 mov 0x2e86(%rip),%rax # 3fd8 b1152: c7 00 02 00 00 00 movl $0x2,(%rax)......类型4模块间调用、跳转   对于模块间函数调用同样可以采用类型3的方法来解决。与上面的类型有所不同的是GOT中响应的项保存的是目标函数的地址当模块需要调用目标函数时可以通过GOT中的项进行间接跳转。 总结   指令中的有些地址要在装载时才能确定也就是不同的进程可能有不同的地址。   之前我们已经解释过共享对象的数据段是每个进程一份的。而数据段和代码段的相对位置又是确定的。   由此我们就可以在数据段中建立一个指针数组称其为GOTglobal offset table。里面存放跨模块的数据的地址当然可以在装载时动态填入。然后共享对象指令中对跨模块数据的访问可以通过GOT中的指针间接访问。   这样的好处是指令中的地址就从跨模块数据的地址变成了got中指针的地址而这个地址是相对代码段确定的。   以上就是动态链接最最最核心的思想 3.5 共享模块全局变量问题 共享对象代码段中对模块内全局数据的访问也是通过 got 实现的。 既然是模块内为啥不用相对地址呢 因为其他的模块可能会使用全局数据。比如 module.c 中这样的代码 extern int global;int foo() {global 1; }int main() {foo();return 0; }我们对 module.c 进行编译将他编译成一个目标文件 module.o liangliang-virtual-machine:~/cfp$ gcc -c -fno-stack-protector module.c随后使用 ld 对其进行链接链接对象是一个动态库且动态库中定义了全局变量 global如下 liangliang-virtual-machine:~/cfp$ gcc -fPIC -shared -o libtest.so libtest.c liangliang-virtual-machine:~/cfp$ cat ./libtest.c int global 4;int add(int a, int b) {global a b;return global; } liangliang-virtual-machine:~/cfp$使用 ld 对其进行链接链接成可执行文件 module liangliang-virtual-machine:~/cfp$ ld -e main module.o -o module ./libtest.so liangliang-virtual-machine:~/cfp$ 在 module.c 这个代码中对 global 进行了赋值既然是赋值肯定需要 global 的地址但是编译时 这里只进行了编译没有链接gcc 并不知道它在共享对象中定义了。因此gcc会在 bss 段中定义 global也就是说在编译 module.c 时就为 global 分配了虚拟内存地址。这样如果加载共享模块 libtest.so 后加载的模块中数据段也有该变量的副本肯定会产生矛盾。 既然有可能出现这种情况干脆让共享对象访问自身的全局变量时也通过 got 的方式就避免了进程中存在多个 global 的可能即在装载时将 global 的虚拟内存地址存入共享变量中的 got 中。 这样如果运行时动态加载的时候发现可执行文件中也有该变量则会统一在 GOT 表中重定位填充为可执行文件 bss 段中该变量副本的地址。如果在共享库中对该变量进行了初始化动态装载器还得负责将初始化的值拷贝到可执行文件bss中该变量的副本位置。如果可执行文件中没有该变量则 GOT 表中重定位后指向自己模块内的该变量。这样就意味着对模块内的变量访问也采用了 GOT 表。也就是说对于共享库中的全局对象无论是否是内部的还是无法决定是否是内部的都得作为外部模块访问那样使用 GOT 表进行访问。 我们可以使用 objdump 工具验证上述结论共享对象访问自身的全局变量时也是通过 got 的方式 可以看到got 表的范围为 0x200fd0 - 0x201000 liangliang-virtual-machine:~/cfp$ objdump -h libtest.so ......18 .got 00000030 0000000000200fd0 0000000000200fd0 00000fd0 2**3CONTENTS, ALLOC, LOAD, DATA19 .got.plt 00000018 0000000000201000 0000000000201000 00001000 2**3CONTENTS, ALLOC, LOAD, DATA ......我们再查看 动态可重定位表我们发现需要重定位项 global需要修复的地址刚好是 got 范围内且刚好是 got[1] 条目1.got section的大小为0x30——即.got中的条目个数为6.got的每个条目占8字节。 liangliang-virtual-machine:~/cfp$ objdump -R libtest.so libtest.so: file format elf64-x86-64DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE 0000000000200e28 R_X86_64_RELATIVE *ABS*0x0000000000000660 0000000000200e30 R_X86_64_RELATIVE *ABS*0x0000000000000620 0000000000201018 R_X86_64_RELATIVE *ABS*0x0000000000201018 0000000000200fd0 R_X86_64_GLOB_DAT _ITM_deregisterTMCloneTable 0000000000200fd8 R_X86_64_GLOB_DAT globalBase 0000000000200fe0 R_X86_64_GLOB_DAT __gmon_start__ 0000000000200fe8 R_X86_64_GLOB_DAT _Jv_RegisterClasses 0000000000200ff0 R_X86_64_GLOB_DAT _ITM_registerTMCloneTable 0000000000200ff8 R_X86_64_GLOB_DAT __cxa_finalizeGLIBC_2.2.5R_X86_64_GLOB_DAT的含义一旦知道 global 的运行时地址就把它放入 0x200fd8 处。 我们还可以反汇编 add 函数代码看看是如何访问 global 变量的 0000000000000690 add:690: 55 push %rbp691: 48 89 e5 mov %rsp,%rbp694: 89 7d fc mov %edi,-0x4(%rbp)697: 89 75 f8 mov %esi,-0x8(%rbp)69a: 8b 55 fc mov -0x4(%rbp),%edx69d: 8b 45 f8 mov -0x8(%rbp),%eax6a0: 01 c2 add %eax,%edx6a2: 48 8b 05 2f 09 20 00 mov 0x20092f(%rip),%rax # 200fd8 _DYNAMIC0x1986a9: 89 10 mov %edx,(%rax)6ab: 48 8b 05 26 09 20 00 mov 0x200926(%rip),%rax # 200fd8 _DYNAMIC0x1986b2: 8b 00 mov (%rax),%eax6b4: 5d pop %rbp6b5: c3 retq 由上可见对于 global 变量的访问实际上是访问地址 0x200fd8 中的地址所指向的值 3.6 数据段地址无关性 通过上面的方法我们能保证共享对象中的代码部分地址无关但是数据部分是不是也有绝对地址引用的问题呢   这里我们还是用上面地址无关码的例子稍加改动 static int a; extern int b; extern void ext();static int*pa;void bar() {a 1;b 2;b *p; }void foo() {bar();ext(); }上面的地址无关码的例子里面加了这样一段代码的话 static int*pa;那么指针 p 指向就是一个绝对地址它指向变量 a而变量 a 的地址会随着共享对象的装载地址改变而改变。那么有什么办法解决这个问题呢? 对于数据段来说它在每个进程都有一份独立的副本所以并不担心被进程改变。从这点来看我们可以选择装载时重定位的方法来解决数据段中绝对地址引用问题。对于共享对象来说如果数据段中有绝对地址引用那么编译器和链接器就会产生一个重定位表叫做rela.dyn这个重定位表里面包含了 “R_X86_64_RELATIVE” 类型的重定位入口用于解决上述问题。当动态链接器装载共享对象时如果发现该共享对象有这样的重定位入口动态链接重定位表那么动态链接器就会对该共享对象进行重定位。   通过 objdump 工具得到共享目标文件的动态重定位表如下 liangjieliangjie-virtual-machine:~/Desktop/cfp$ objdump -R libpic.so libpic.so: file format elf64-x86-64DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE 0000000000003e48 R_X86_64_RELATIVE *ABS*0x0000000000001130 0000000000003e50 R_X86_64_RELATIVE *ABS*0x00000000000010f0 0000000000004028 R_X86_64_RELATIVE *ABS*0x0000000000004028 0000000000004030 R_X86_64_RELATIVE *ABS*0x000000000000403c 0000000000003fd8 R_X86_64_GLOB_DAT b 0000000000003fe0 R_X86_64_GLOB_DAT __cxa_finalize 0000000000003fe8 R_X86_64_GLOB_DAT _ITM_registerTMCloneTable 0000000000003ff0 R_X86_64_GLOB_DAT _ITM_deregisterTMCloneTable 0000000000003ff8 R_X86_64_GLOB_DAT __gmon_start__ 0000000000004018 R_X86_64_JUMP_SLOT ext 0000000000004020 R_X86_64_JUMP_SLOT bar查看 section 信息我们发现一个重定位项 0000000000004030 R_X86_64_RELATIVE *ABS*0x000000000000403c根据下面的段表信息以及需要重定位的符号地址可以判断出这一项需要重定位地址位于 .data 段。根据动态重定位表中的 VALUE 值可以知道重定位符号需要被修复成目的值为0x403c liangjieliangjie-virtual-machine:~/Desktop/cfp$ readelf -S libsta.so There are 24 section headers, starting at offset 0x34c0:Section Headers:......[19] .got PROGBITS 0000000000003fd8 00002fd80000000000000028 0000000000000008 WA 0 0 8[20] .got.plt PROGBITS 0000000000004000 000030000000000000000028 0000000000000008 WA 0 0 8[21] .data PROGBITS 0000000000004028 000030280000000000000010 0000000000000000 WA 0 0 8[22] .bss NOBITS 0000000000004038 000030380000000000000008 0000000000000000 WA 0 0 4......反汇编 libpic.so 我们看到0x403c刚好是变量 a 的地址这刚好与代码想要表达的意思相符。 0000000000001139 bar:1139: f3 0f 1e fa endbr64 113d: 55 push %rbp113e: 48 89 e5 mov %rsp,%rbp1141: c7 05 f1 2e 00 00 01 movl $0x1,0x2ef1(%rip) # 403c a1148: 00 00 00 114b: 48 8b 05 86 2e 00 00 mov 0x2e86(%rip),%rax # 3fd8 b1152: c7 00 02 00 00 00 movl $0x2,(%rax)1158: 48 8b 05 d1 2e 00 00 mov 0x2ed1(%rip),%rax # 4030 p115f: 8b 10 mov (%rax),%edx1161: 48 8b 05 70 2e 00 00 mov 0x2e70(%rip),%rax # 3fd8 b1168: 89 10 mov %edx,(%rax)116a: 90 nop116b: 5d pop %rbp116c: c3 ret 实际上我们甚至可以让代码段也使用这种装载时重定位的方法而不使用地址无关代码。从前面的例子中我们看到我们在编译共享对象时使用了“-PIC”参数这个参数表示产生地址无关的代码段。如果我们不使用这个参数来产生共享对象又会怎么样呢? $gcc -shared pic. c -o pic. so上面这个命令就会产生一个不使用地址无关代码而使用装载时重定位的共享对象。但正如我们前面分析过的一样如果代码不是地址无关的它就不能被多个进程之间共享于是也就失去了节省内存的优点。但是装载时重定位的共享对象的运行速度要比使用地址无关代码的共享对象快因为它省去了地址无关代码中每次访问全局数据和函数时需要做一次计算当前地址以及间接地址寻址的过程。 对于可执行文件来说默认情况下如果可执行文件是动态链接的那么 GCC 会使用 PIC 的方法来产生可执行文件的代码段部分以便于不同的进程能够共享代码段节省内存。所以我们可以看到动态链接的可执行文件中存在“got”这样的段。 4、PLT 4.1 PLT 在之前的《静态链接与动态链接》中我们介绍了这两者的优缺点动态链接的缺点主要就是动态链接的程序执行速度会比静态链接的程度略慢一些。 原因就在于动态链接的可执行程序对于模块间的变量以及函数访问都需要通过 GOT 表进行间接跳转。如此一来程序的运行速度肯定会有所减慢。 另一个很重要的原因就是动态链接的链接工作是在程序运行时来完成的即程序开始执行前动态链接器会去寻找并且装载程序所需的动态共享对象然后完成一系列的符号重定位操作。这部分动作肯定会减慢程序的启动速度。 针对这种情况链接工作是在程序运行时来完成的一种称为“延迟绑定(Lazy Binding)”的解决办法出现了。延迟绑定的核心思想就是在程序启动时并不完成所有模块间函数调用的符号重定位操作只有当目标程序需要调用某个模块外函数时才进行地址绑定(即符号查找、符号重定位)。 要实现以上的目标ELF文件采用了 PLT(PProcedure Linkage Table) 的结构这种结构内包含了一些很精妙的指令序列这也是接下来所要讲解的内容。 PTL 原理当调用外部模块的函数时通过 PTL 新增加的一层间接跳转。调用函数并不直接通过 GOT 跳转而是通过一个叫作 PLT 项的结构来进行跳转。每个外部函数在 PLT 中都有一个相应的项 4.2 大体逻辑思考 在讲解 PLT 具体细节之前我们可以从自顶向下的角度来思考一下如何完成这一项工作。假设目标程序需要调用某个动态共享对象 liba.so内的函数foo()那么第一次调用该函数的时候动态链接器就需要一个寻找 foo 函数地址的查找函数来完成绑定的工作。 那么这个查找函数需要哪些信息呢首先要知道绑定行为发生在哪个模块内(目标程序主模块内)其次我们要知道具体要绑定哪个函数(foo()函数)。在 Glibc 中这个查找函数的名字就叫做 _dl_runtime_resolve()。把这个过程用伪代码描述出来就如以下所示 void DSOFunctionplt(){if (DSOFunctiongot[index] RELOCATED) { //如果该函数是第一次调用GOT表内还没有该函数的地址让查找函数根据模块ID和被调用函数的ID来获取被调用函数的地址并且填入GOT的对应表项之中DSOFunctiongot[index] RELOCATED;}else{//GOT表内已经有了该函数地址直接跳转到该函数地址jmp *DSOFunctiongot[index];}}这一段伪代码就是 PLT 结构之中的模块外函数的对应表项。将伪代码整理一下我们就可以得到汇编语言级别的 PLT 表项的内容如下所示 foopltjmp *(foogot)push npush moduleIDjmp _dl_runtime_resolve第一条指令就是跳转到 foo() 函数所对应的 GOT 表项如果该 GOT 表项已经被绑定好了那就可以直接跳转到正确的函数地址。如果是第一次调用该函数其 GOT 表项内的内容是第二条指令“push n”的地址这一步就实现伪代码中的 if 判断。 第二条指令就是将 foo() 函数所对应的函数 ID 压入栈内这个 ID 是 foo 函数在重定位表中的下标。 第三条指令就是将该模块的 ID 压入栈中 第四条指令就是跳转到我们上文所说的查找函数_dl_runtime_resolve()。_dl_runtime_resolve()进行一系列查找之后会将 foo() 函数的绝对地址填入 GOT 的对应表项中然后将控制流转到 foo() 函数上。 一旦 foo() 函数地址被成功绑定之后再次调用 foo() 在 PLT 的表项就是直接通过 GOT 表项跳转到正确的地址上。以上就是 GOT 和 PLT 出现的大体逻辑。接下来讲解具体的工作流程。 4.3 GOT 与 PLT ELF 文件将 got 分为两部分分别是 .got 和 .got.plt前者用于储存全局变量后者用于保存 DSO 中的函数引用地址。 这里要说明一点PLT 位于可执行程序的代码段是可读不可写的而 GOT 位于可执行程序的数据段是可读可写的。另外 .got.plt 还有一个特别之处在于它的前三项都是有特定含义的含义分别如下所示 第一项保存了.dynamic段的地址这其中描述了本模块动态链接的相关信息第二项保存本模块的 ID第三项保存了_dl_runtime_resolve的地址 我们还是以 libpic.so 为例弄清 .got.plt 段的含义 liangliang-virtual-machine:~/cfp$ objdump -s -d libpic.so ...... Contents of section .got.plt:201000 100e2000 00000000 00000000 00000000 .. .............201010 00000000 00000000 e6050000 00000000 ................201020 f6050000 00000000 ........ ......liangliang-virtual-machine:~/cfp$ readelf -S libpic.so ......[19] .dynamic DYNAMIC 0000000000200e10 00000e1000000000000001c0 0000000000000010 WA 4 0 8 ......[10] .plt PROGBITS 00000000000005d0 000005d00000000000000030 0000000000000010 AX 0 0 16[11] .plt.got PROGBITS 0000000000000600 000006000000000000000010 0000000000000000 AX 0 0 8 ......liangliang-virtual-machine:~/cfp$ objdump -S libpic.so libpic.so: file format elf64-x86-64Disassembly of section .plt:00000000000005d0 barplt-0x10:5d0: ff 35 32 0a 20 00 pushq 0x200a32(%rip) # 201008 _GLOBAL_OFFSET_TABLE_0x85d6: ff 25 34 0a 20 00 jmpq *0x200a34(%rip) # 201010 _GLOBAL_OFFSET_TABLE_0x105dc: 0f 1f 40 00 nopl 0x0(%rax)00000000000005e0 barplt:5e0: ff 25 32 0a 20 00 jmpq *0x200a32(%rip) # 201018 _GLOBAL_OFFSET_TABLE_0x185e6: 68 00 00 00 00 pushq $0x05eb: e9 e0 ff ff ff jmpq 5d0 _init0x2000000000000005f0 extplt:5f0: ff 25 2a 0a 20 00 jmpq *0x200a2a(%rip) # 201020 _GLOBAL_OFFSET_TABLE_0x205f6: 68 01 00 00 00 pushq $0x15fb: e9 d0 ff ff ff jmpq 5d0 _init0x20 ......64 位系统中地址长度是 64 比特也就是 8 字节。按 8 字节一项并调整字节序后可得 .got.plt 的内容是 第几项地址内容备注00x2010000x0000000000200e10.dynamic 段地址10x2010080x0000000000000000本镜像的link_map数据结构地址未运行无法确定故以全 0 填充20x2010100x0000000000000000_dl_runtime_resolve 函数地址未运行无法确定故以全 0 填充30x2010180x000000000000005e6bar 对应的 .got.plt 表项内容是 bar 的 PLT 表项地址加 640x2010200x000000000000005f6ext 对应的 .got.plt 表项内容是 ext 的 PLT 表项地址加 6 00000000000005e0 barplt:5e0: ff 25 32 0a 20 00 jmpq *0x200a32(%rip) # 201018 _GLOBAL_OFFSET_TABLE_0x185e6: 68 00 00 00 00 pushq $0x05eb: e9 e0 ff ff ff jmpq 5d0 _init0x20看到它跳转到了 0x200a32(%rip) 指向的地址0x200a32(%rip) 的内容在反汇编结果的注释中给出了是 0x201018 。0x201018 正是 bar 函数的 .got.plt 表项的地址其内容是 0x00000000000005e6这个地址实际上是 bar 的 PLT 表项地址加 6。可见 5e0 处的 jmpq 指令实际上跳到了 0x5e6 处相当于没有跳转。0x5e6 处的 pushq 指令将 0x00 压栈可以理解为接下来要调用的函数的参数。接着 0x5eb 处的 jmpq 指令跳转到了 0x5d0 即 PLT 表的第 0 项 00000000000005d0 barplt-0x10:5d0: ff 35 32 0a 20 00 pushq 0x200a32(%rip) # 201008 _GLOBAL_OFFSET_TABLE_0x85d6: ff 25 34 0a 20 00 jmpq *0x200a34(%rip) # 201010 _GLOBAL_OFFSET_TABLE_0x105dc: 0f 1f 40 00 nopl 0x0(%rax)先是把 0x201008 即 .got.plt 表的第 1 项压栈接着跳转到 201010 即 .got.plt 表的第 2 项亦即 _dl_runtime_resolve 函数解析 bar 函数真正的地址。之后会执行 bar并将 bar 函数真正的地址写到 bar 对应的 .got.plt 表项中。这样下次调用 bar 数时 0x5e0 处的 jmpq 指令会直接跳转到 bar 函数真正的地址不用再调用 _dl_runtime_resolve。 4.4 .plt、.plt.got、.got 和 .got.plt 之间的区别 通过上一小节的分析 section所在 segmentsection 属性用途.plt代码段RE可读可执行.plt section 实际就是通常所说的过程链接表Procedure Linkage Table, PLT.plt.got代码段RE.plt.got section 用于存放 __cxa_finalize 函数对应的 PLT 条目.got数据段RW可读可写.got section 中可以用于存放全局变量的地址.got section 中也可以用于存放不需要延迟绑定的函数的地址.got.plt数据段RW.got.plt section 用于存放需要延迟绑定的函数的地址 5、动态链接相关结构 5.1 “.interp”段 动态链接器的位置由 ELF 可执行文件决定。在动态链接的 ELF 可执行文件中有一个专门的段叫做”.interp”段。 “.interp”段的内容很简单里面保存的就是一个字符串这个字符串就是可执行文件所需要的动态链接器路径。 动态链接器在Linux下是Glibc的一部分也就是属于系统库级别。 liangjieliangjie-virtual-machine:~/Desktop/cfp$ objdump -s Program1Program1: file format elf64-x86-64Contents of section .interp:0318 2f6c6962 36342f6c 642d6c69 6e75782d /lib64/ld-linux-0328 7838362d 36342e73 6f2e3200 x86-64.so.2. Contents of section .note.gnu.property:0338 04000000 20000000 05000000 474e5500 .... .......GNU.0348 020000c0 04000000 03000000 00000000 ................0358 028000c0 04000000 01000000 00000000 ................ Contents of section .note.gnu.build-id:0368 04000000 14000000 03000000 474e5500 ............GNU.0378 e24d7e65 dfac3356 97a6b11b d2524780 .M~e..3V.....RG.0388 e12f526d ./Rm Contents of section .note.ABI-tag:038c 04000000 10000000 01000000 474e5500 ............GNU.039c 00000000 03000000 02000000 00000000 ................ Contents of section .gnu.hash:03b0 02000000 06000000 01000000 06000000 ................03c0 00008100 00000000 06000000 00000000 ................03d0 d165ce6d .e.m Contents of section .dynsym:03d8 00000000 00000000 00000000 00000000 ................03e8 00000000 00000000 5c000000 12000000 ........\.......03f8 00000000 00000000 00000000 00000000 ................0408 01000000 20000000 00000000 00000000 .... ...........0418 00000000 00000000 46000000 12000000 ........F.......0428 00000000 00000000 00000000 00000000 ................0438 1d000000 20000000 00000000 00000000 .... ...........0448 00000000 00000000 2c000000 20000000 ........,... ...0458 00000000 00000000 00000000 00000000 ................0468 4d000000 22000000 00000000 00000000 M..............0478 00000000 00000000 ........ ...... ......5.2 “.dynamic”段 类似于“.interp”这样的段ELF中还有几个段也是专门用于动态链接的比如 “.dynamic” 段和 .dynsym段等。要了解动态链接器如何完成链接过程跟前面一样从了解ELF文件中跟动态链接相关的结构入手将会是一个很好的途径。ELF文件中跟动态链接相关的段有好几个相互之间的关系也比较复杂我们先从 “.dynamic” 段入手 动态链接ELF中最重要的结构应该是“ .dynamic”段,这个段里面保存了动态链接器所需要的基本信息比如依赖于哪些共享对象、动态链接符号表的位置、动态链接重定位表的位置、共享对象初始化代码的地址等。“ .dynamic”段的结构很经典,就是我们已经碰到过的ELF中眼熟的结构数组,结构定义在“elf.h”中 typedef struct {Elf32_Sword d_tag;union {Elf32_Word d_val;Elf32_Addr d_ptr;} d_un; } Elf32_Dyn;//常见类型值#define DT_NULL 0 /* Marks end of dynamic section */ #define DT_NEEDED 1 /* Name of needed library */ #define DT_HASH 4 /* Address of symbol hash table */ #define DT_STRTAB 5 /* Address of string table */ #define DT_SYMTAB 6 /* Address of symbol table */ #define DT_RELA 7 /* Address of Rela relocs */ #define DT_RELAENT 9 /* Size of one Rela reloc */ #define DT_STRSZ 10 /* Size of string table */ #define DT_INIT 12 /* Address of init function */ #define DT_FINI 13 /* Address of termination function */ #define DT_SONAME 14 /* Name of shared object */ #define DT_RPATH 15 /* Library search path (deprecated) */ #define DT_REL 17 /* Address of Rel relocs */ #define DT_RELENT 19 /* Size of one Rel reloc */Elf32_Dyn 结构由一个类型值加上一个附加的数值或指针对于不同类型后面附加的数值或者指针有着不同含义。我们这里列举几个比较常见的类型值这些值都是定义在“elf.h”里面的宏如表7-2所示 d_tag 类型d_un 的含义DT_SYMTAB动态连接符号表的地址d_ptr 表示 “.dynsym” 的地址DT_STRTAB动态链接字符串表地址d_ptr 表示 “.dynstr” 的地址DT_STRSZ动态链接字符串表大小d_val 表示大小DT_HASH动态链接哈希表地址d_ptr 表示“.hash”地址DT_SONAME本共享对象的“SO-NAME”我们在后面会介绍“SO-NAME”DT_INIT初始化代码地址DT_FINI结束代码地址DT_NEEDED依赖的共享对象文件d_ptr 表示所依赖的共享对象文件名DT_REL动态链接重定位表入口DT_RELENT动态重读位表入口数量 .dynamic 段可以看成是动态链接下ELF文件的“文件头”只是我们前面看到的 ELF 文件头中保存的是静态链接时相关的内容比如静态链接时用到的符号表、重定位表等这里换成了动态链接下所使用的相应信息了。使用 readelf 查看“.dynamic”段的内容 liangjieliangjie-virtual-machine:~/Desktop/cfp$ readelf -d Lib.so Dynamic section at offset 0x2e20 contains 24 entries:Tag Type Name/Value0x0000000000000001 (NEEDED) Shared library: [libc.so.6]0x000000000000000c (INIT) 0x10000x000000000000000d (FINI) 0x11740x0000000000000019 (INIT_ARRAY) 0x3e100x000000000000001b (INIT_ARRAYSZ) 8 (bytes)0x000000000000001a (FINI_ARRAY) 0x3e180x000000000000001c (FINI_ARRAYSZ) 8 (bytes)0x000000006ffffef5 (GNU_HASH) 0x2f00x0000000000000005 (STRTAB) 0x3d80x0000000000000006 (SYMTAB) 0x3180x000000000000000a (STRSZ) 127 (bytes)0x000000000000000b (SYMENT) 24 (bytes)0x0000000000000003 (PLTGOT) 0x40000x0000000000000002 (PLTRELSZ) 48 (bytes)0x0000000000000014 (PLTREL) RELA0x0000000000000017 (JMPREL) 0x5300x0000000000000007 (RELA) 0x4880x0000000000000008 (RELASZ) 168 (bytes)0x0000000000000009 (RELAENT) 24 (bytes)0x000000006ffffffe (VERNEED) 0x4680x000000006fffffff (VERNEEDNUM) 10x000000006ffffff0 (VERSYM) 0x4580x000000006ffffff9 (RELACOUNT) 30x0000000000000000 (NULL) 0x0 Linux还提供了ldd命令查看一个程序主模块或一个共享库依赖于哪些共享库 liangjieliangjie-virtual-machine:~/Desktop/cfp$ ldd Program1linux-vdso.so.1 (0x00007ffdf2b5e000)./Lib.so (0x00007f13d8c69000)libc.so.6 /lib/x86_64-linux-gnu/libc.so.6 (0x00007f13d8a00000)/lib64/ld-linux-x86-64.so.2 (0x00007f13d8c75000)5.3 动态符号表 动态符号表段名通常叫做 .dynsym用于表示模块之间的符号导入导出关系。.dynsym 只保存了与动态链接相关的符号.symtab 中往往保存了所有符号包括 .dynsym 中的符号。一般动态链接的模块同时拥有 .dynsym 和 .symtab 两个表。 与 .symtab 类似动态符号表也需要一些辅助的表比如动态符号字符串表 .dynstr。 由于动态链接在程序运行时查找符号为了加快符号的查找过程往往还有辅助的符号哈希表. hash。 我们可以使用 readelf 查看ELF文件的动态符号表及它的哈希表 liangjieliangjie-virtual-machine:~/Desktop/cfp$ readelf -sD Lib.so Symbol table for image contains 7 entries:Num: Value Size Type Bind Vis Ndx Name0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterT[...]2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND [...]GLIBC_2.2.5 (2)3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__4: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMC[...]5: 0000000000000000 0 FUNC WEAK DEFAULT UND [...]GLIBC_2.2.5 (2)6: 0000000000001119 43 FUNC GLOBAL DEFAULT 14 foobar动态链接符号表的结构与静态链接的符号表几乎一样。 5.3 动态链接重定位表 在静态链接中目标文件里面包含有专门用于表示重定位信息的重定位表比如 “.rel.text” 表示的是代码段的重定位表“.rel.data” 是数据段的重定位表。在动态链接中也有重定位表 “.rela.dyn” 是对数据引用的修正他所修正的位置位于 “.got” 以及数据段而 “.rela.plt” 是对函数引用的修正他所修正的位置位于 “.got.plt” 共享对象需要重定位的主要原因是导入符号的存在。 动态链接下无论是可执行文件或共享对象一旦它依赖于其他共享对象也就是说有导入的符号那么它的代码或数据中就会有对于导入符号的引用。在编译时这些导入符号地址未知。在静态连接中这些未知的地址引用在最终链接时被修正。但是在动态链接中导入符号的地址在运行时才确定所以需要在运行时将这些导入符号引用修正即需要重定位。 可以使用 readelf 或者 objdump 查看重定位表中的信息 liangjieliangjie-virtual-machine:~/Desktop/cfp$ readelf -r Program1Relocation section .rela.dyn at offset 0x558 contains 8 entries:Offset Info Type Sym. Value Sym. Name Addend 000000003da8 000000000008 R_X86_64_RELATIVE 1140 000000003db0 000000000008 R_X86_64_RELATIVE 1100 000000004008 000000000008 R_X86_64_RELATIVE 4008 000000003fd8 000100000006 R_X86_64_GLOB_DAT 0000000000000000 __libc_start_mainGLIBC_2.34 0 000000003fe0 000200000006 R_X86_64_GLOB_DAT 0000000000000000 _ITM_deregisterTM[...] 0 000000003fe8 000400000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ 0 000000003ff0 000500000006 R_X86_64_GLOB_DAT 0000000000000000 _ITM_registerTMCl[...] 0 000000003ff8 000600000006 R_X86_64_GLOB_DAT 0000000000000000 __cxa_finalizeGLIBC_2.2.5 0Relocation section .rela.plt at offset 0x618 contains 1 entry:Offset Info Type Sym. Value Sym. Name Addend 000000003fd0 000300000007 R_X86_64_JUMP_SLO 0000000000000000 foobar 0 liangjieliangjie-virtual-machine:~ liangjieliangjie-virtual-machine:~/Desktop/cfp$ objdump -R Program1Program1: file format elf64-x86-64DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE 0000000000003da8 R_X86_64_RELATIVE *ABS*0x0000000000001140 0000000000003db0 R_X86_64_RELATIVE *ABS*0x0000000000001100 0000000000004008 R_X86_64_RELATIVE *ABS*0x0000000000004008 0000000000003fd8 R_X86_64_GLOB_DAT __libc_start_mainGLIBC_2.34 0000000000003fe0 R_X86_64_GLOB_DAT _ITM_deregisterTMCloneTableBase 0000000000003fe8 R_X86_64_GLOB_DAT __gmon_start__Base 0000000000003ff0 R_X86_64_GLOB_DAT _ITM_registerTMCloneTableBase 0000000000003ff8 R_X86_64_GLOB_DAT __cxa_finalizeGLIBC_2.2.5 0000000000003fd0 R_X86_64_JUMP_SLOT foobarBase我们看到有几种重定位入口类型R_X86_64_RELATIVE、R_X86_64_GLOB_DAT、R_X86_64_JUMP_SLOT。不同的重定位类型表示重定位时有不同的地址计算方法其中 R_X86_64_GLOB_DAT、R_X86_64_JUMP_SLOT 这两种类型表示被修正的位置只需要直接填入符号的地址即可
http://www.hkea.cn/news/14290741/

相关文章:

  • 在线模版下载网站灰色行业推广渠道
  • 网站开发知识培训网站建设与规划试卷
  • 合理规划网站简洁的企业网站源码
  • 德州哪里有学做网站的owl WordPress
  • 网站显示建设中页面四川住房城乡和城乡建设厅网站
  • 下载类网站开发条件邢台视频优化
  • 帮网站做推广赚钱中国的网站域名
  • 域名注册多少钱网页优化包括
  • 电子商务网站建设实践报告wordpress 附件预览
  • 网站地图对seo的影响wordpress中文官网上
  • iis怎么做网站微信开发公司怎么样
  • 简述创建一个网站的过程反向代理
  • 站酷网页温岭专业自适应网站建设
  • 中山网站优化wordpress编辑器代码
  • 昆明网站开发培训百度收录wordpress
  • 重庆点优建设网站公司吗合肥做一个网站要多少钱
  • 餐饮加盟网站建设方案扁平化wordpress主题
  • 如何将自己做的网站发布到网上做网站用啥软件
  • 广州模板建站多少钱成都网站建设 今网科技
  • 网站建设制作设计营销 中山网站排名快速提升
  • 郑州做网站公司yooker北京 网站开发
  • 网站能实现什么功能常见的静态网站开发技术
  • 购物网站 英文介绍进销存管理软件
  • 福建建设执业注册中心网站图片生成链接
  • 网站建设专利申请成都品牌logo设计公司
  • 适合建设网站的国外服务器网站点击率高
  • 江西响应式网站建设大理网站设计
  • 怎样在建设部网站下载规范做php网站前端价格
  • 做网站 视频加载太慢wordpress主题 mnews1.9
  • 网站怎么制作视频教程江苏网站建设开发