C/C++动态链接及地址无关代码(PIC)

C/C++动态链接及地址无关代码(PIC)本文简要介绍了动态链接库中地址无关代码 Position independent code 的实现原理 并利用 GDB 等工具对此过程进行了验证 1 测试代码 add

大家好,欢迎来到IT知识分享网。

本文简要介绍了动态链接库中地址无关代码(Position independent code)的实现原理,并利用GDB等工具对此过程进行了验证。

1. 测试代码

 /*add.c*/ int global_extern_int = 2; void foo() { } int add(int a_, int b_) { foo(); return global_extern_int+a_+ b_; }
 /*main.c*/ int add(int a_, int b_); extern int global_extern_int; int global_int = 3; int main() { static int a = 19; global_int = 5; int rtv = 0; rtv = add(global_int, global_extern_int); return rtv; }

代码的编译和链接,

 gcc -g -fno-pie -no-pie -m32 -fPIC -c add.c gcc -g -fno-pie -no-pie -m32 -shared add.o -o libadd.so gcc -g -fno-pie -no-pie -m32 -fPIC -c main.c gcc -g -fno-pie -no-pie -m32 -o main main.o -L . -ladd

2. 为什么需要动态链接

动态链接是相对于静态链接提出来的,这里仅简单分析两者的区别,

C/C++动态链接及地址无关代码(PIC)

  1. 静态库,是由许多的.o打包而成,会在链接阶段链接到程序中,链接后的应用程序可以直接运行,不依赖其他任何运行时的元素。但是如果有多个应用程序都需要使用这个库,此时每个应用程序都需要把静态库打包进自己的应用程序,浪费了磁盘空间,同时如果多个进程同时运行,每个进程中都有一份“几乎相同的代码”,也浪费了内存;
  2. 动态库和应用程序是分开的,因此,当动态库的代码升级了以后,只要保证库的接口不变,应用程序依然可以正常运行。但是对于静态库,应用程序就必须重新编译,才能使用最新的库代码;
  3. 如果当前系统有多个进程使用了同一个动态库,整个内存中只需要加载一份动态库,共享给调用其的所有进程。

3.什么是Position independent code?

Position-independent code (PIC) is code that uses no hard-coded addresses for either code or data. by using relative addressing

PIC不使用绝对地址对data或者fun进行寻址,而是利用一些相对地址的手段进行,PIC一般而言是针对共享库的。

4.如何做到地址无关的代码

对于一个共享库,以代码中的add.c为例,它对外提供了,

  • 全局的变量,符号,也就是数据;
  • 函数,符号;

PIC的目的是让我们的代码段对所有的使用者来说都是一样的(不能对代码段进行修改,例如使用前面文章中介绍的符号重定位的方法,这种方法其实修改了最终的代码段),其具体的实现比较巧妙,核心的思想是通过增加一个间接层,也就是GOT(Global Offset Table)和PLT(Procedure Linkage Table),来完成对数据和函数的间接寻址。

C/C++动态链接及地址无关代码(PIC)

PIC的一个核心思想是借助数据段和代码段之间的确定的偏移量。因为对于链接器而言,在将许多的目标文件进行合并的时候,它明确的知道所有段的大小和他们之间的偏移量。如上图所示,代码段紧跟着数据段,因此任何代码段中的某条指令到数据段起始的偏移量都可以很容易的计算出来,用代码段的大小减去该指令距离代码段起始点的偏移量。假设距离代码段起始点偏移0x80的指令想取获取数据段中的数据,此时链接器知道相对偏移量(0xEF80),可以用相对偏移量去进行相对寻址。

5.GOT

C/C++动态链接及地址无关代码(PIC)

如上图所示,GOT是一个保存了各种地址的一个数组,前三个元素都是动态链接器的一些信息,后面的元素都是一些符号的地址,也就是变量和函数的地址。

C/C++动态链接及地址无关代码(PIC)

GOT保存在数据段,数据段是可以修改的,这个很重要,如果代码段中有指令要去获取变量的地址,代码段中的指令不是通过直接去获取绝对地址,而是指向了一个确定的GOT中的位置,动态链接器可以在找到变量或者函数的地址后,然后修改对应的地址值。这就保证了代码段的地址无关,保证了代码段的稳定不变。

GOT在ELF文件中有两个对应的段,分别为.got和.got.plt,其实是一个,只不过根据功能的不同,分成了两个:

  • .got和.got.plt紧挨着,一般在汇编代码中,当前.text中的某条指令+一个固定的偏移量会定位到.got.plt段的起始点;
  • .got是为了变量重定位服务,.got.plt为了函数重定位服务;
  • 所以,给.got.plt段起始地址加个正值,对应的就是.got.plt段,而加个负值,对应的就是.got段。

6.GOT在获取变量地址时的例子

下面分析一下,

 lyf@liuyifei:~/test$ objdump -d -Mintel ./libadd.so ./libadd.so: file format elf32-i386 ...... 0000116d <add>: 116d: 55 push ebp 116e: 89 e5 mov ebp,esp 1170: 53 push ebx 1171: 83 ec 04 sub esp,0x4 1174: e8 e7 fe ff ff call 1060 <__x86.get_pc_thunk.bx> 1179: 81 c3 87 2e 00 00 add ebx,0x2e87 117f: e8 bc fe ff ff call 1040 <foo@plt> 1184: 8b 83 f8 ff ff ff mov eax,DWORD PTR [ebx-0x8] 118a: 8b 10 mov edx,DWORD PTR [eax] 118c: 8b 45 08 mov eax,DWORD PTR [ebp+0x8] 118f: 01 c2 add edx,eax 1191: 8b 45 0c mov eax,DWORD PTR [ebp+0xc] 1194: 01 d0 add eax,edx 1196: 8b 5d fc mov ebx,DWORD PTR [ebp-0x4] 1199: c9 leave 119a: c3 ret 0000119b <__x86.get_pc_thunk.ax>: 119b: 8b 04 24 mov eax,DWORD PTR [esp] 119e: c3 ret 

call 116e <__x86.get_pc_thunk.ax>的作用是获取到下一条指令的地址(32位特有的,64位上有专门的寄存器),即0x1179。

  • 调用call的时候,会将下一条指令的地址入栈;
  • 在__x86.get_pc_thunk.ax函数中mov eax,DWORD PTR [esp]将栈顶元素,也就是下一条指令的地址保存在eax寄存器中;
  • 函数调用结束后,eax寄存器中保存的就是下一条指令的地址。

eax加上0x2e87为0x4000,那么这个地址对应的是什么地方呢?这个0x2e87就是链接器在链接时候知道的当前代码地址距离GOT的OFFSET。

 lyf@liuyifei:~/test$ readelf -S ./libadd.so There are 31 section headers, starting at offset 0x3710: Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .note.gnu.bu[...] NOTE 00000154 000154 000024 00 A 0 0 4 [ 2] .gnu.hash GNU_HASH 00000178 000178 00002c 04 A 3 0 4 [ 3] .dynsym DYNSYM 000001a4 0001a4 000080 10 A 4 1 4 [ 4] .dynstr STRTAB 00000224 000224 00006f 00 A 0 0 1 [ 5] .rel.dyn REL 00000294 000294 000040 08 A 3 0 4 [ 6] .rel.plt REL 000002d4 0002d4 000008 08 AI 3 18 4 [ 7] .init PROGBITS 00001000 001000 000024 00 AX 0 0 4 [ 8] .plt PROGBITS 00001030 001030 000020 04 AX 0 0 16 [ 9] .plt.got PROGBITS 00001050 001050 000008 08 AX 0 0 8 [10] .text PROGBITS 00001060 001060 00013f 00 AX 0 0 16 [11] .fini PROGBITS 000011a0 0011a0 000018 00 AX 0 0 4 [12] .eh_frame_hdr PROGBITS 00002000 002000 000034 00 A 0 0 4 [13] .eh_frame PROGBITS 00002034 002034 0000ac 00 A 0 0 4 [14] .init_array INIT_ARRAY 00003f24 002f24 000004 04 WA 0 0 4 [15] .fini_array FINI_ARRAY 00003f28 002f28 000004 04 WA 0 0 4 [16] .dynamic DYNAMIC 00003f2c 002f2c 0000c0 08 WA 4 0 4 [17] .got PROGBITS 00003fec 002fec 000014 04 WA 0 0 4 [18] .got.plt PROGBITS 00004000 003000 000010 04 WA 0 0 4 [19] .data PROGBITS 00004010 003010 000008 00 WA 0 0 4 [20] .bss NOBITS 00004018 003018 000004 00 WA 0 0 1 [21] .comment PROGBITS 00000000 003018 00002b 01 MS 0 0 1 [22] .debug_aranges PROGBITS 00000000 003043 000020 00 0 0 1 [23] .debug_info PROGBITS 00000000 003063 000085 00 0 0 1 [24] .debug_abbrev PROGBITS 00000000 0030e8 000079 00 0 0 1 [25] .debug_line PROGBITS 00000000 003161 000054 00 0 0 1 [26] .debug_str PROGBITS 00000000 0031b5 000099 01 MS 0 0 1 [27] .debug_line_str PROGBITS 00000000 00324e 000015 01 MS 0 0 1 [28] .symtab SYMTAB 00000000 003264 0001e0 10 29 23 4 [29] .strtab STRTAB 00000000 003444 0001b0 00 0 0 1 [30] .shstrtab STRTAB 00000000 0035f4 00011b 00 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), I (info), L (link order), O (extra OS processing required), G (group), T (TLS), C (compressed), x (unknown), o (OS specific), E (exclude), D (mbind), p (processor specific)

从libadd.so的段表中,我们能够看到0x4000对应的是段.got.plt。

1184: 8b 83 f8 ff ff ff mov eax,DWORD PTR [ebx-0x8]
  • 找到.got.plt后,取[ebx-0x8]位置的值(数据,加的负值),放在eax中,其实这里取的就是global_extern_int的值,然后执行几个数的加法([ebp+0x8]和[ebp+0xc],输入参数,入栈,从栈中取对应的值)。

下面是实际运行时gdb的例子,

 lyf@liuyifei:~/test$ gdb main ... Reading symbols from main... (gdb) set environment LD_LIBRARY_PATH=. (gdb) break add Breakpoint 1 at 0x (gdb) run Starting program: /home/lyf/test/main [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Breakpoint 1, add (a_=5, b_=2) at add.c:10 10 foo(); (gdb) set disassembly-flavor intel (gdb) disas add Dump of assembler code for function add: 0xf7fba16d <+0>: push ebp 0xf7fba16e <+1>: mov ebp,esp 0xf7fba170 <+3>: push ebx 0xf7fba171 <+4>: sub esp,0x4 0xf7fba174 <+7>: call 0xf7fba060 <__x86.get_pc_thunk.bx> 0xf7fba179 <+12>: add ebx,0x2e87 => 0xf7fba17f <+18>: call 0xf7fba040 <foo@plt> 0xf7fba184 <+23>: mov eax,DWORD PTR [ebx-0x8] 0xf7fba18a <+29>: mov edx,DWORD PTR [eax] 0xf7fba18c <+31>: mov eax,DWORD PTR [ebp+0x8] 0xf7fba18f <+34>: add edx,eax 0xf7fba191 <+36>: mov eax,DWORD PTR [ebp+0xc] 0xf7fba194 <+39>: add eax,edx 0xf7fba196 <+41>: mov ebx,DWORD PTR [ebp-0x4] 0xf7fba199 <+44>: leave 0xf7fba19a <+45>: ret End of assembler dump. (gdb) i registers eax 0x804c000  ecx 0x2 2 edx 0x5 5 ebx 0xf7fbd000 - esp 0xffffd170 0xffffd170 ebp 0xffffd178 0xffffd178 esi 0xffffd274 -11660 edi 0xf7ffcb80 - eip 0xf7fba17f 0xf7fba17f <add+18> eflags 0x296 [ PF AF SF IF ] cs 0x23 35 ss 0x2b 43 ds 0x2b 43 es 0x2b 43 fs 0x0 0 gs 0x63 99 (gdb) print/x *(void)0xF7FBCFF8 $1 = 0xf7fbd014 (gdb) print/x &global_extern_int $2 = 0xf7fbd014
  • 0xf7fba179距离.got.plt的偏移量为0x2e87,相加后为0xf7fbd000,ebx-0x8=0xF7FBCFF8;
  • 打印0xF7FBCFF8对应的值,此处应为global_extern_int的地址,我们发现和实际是相符合的。

7.PIC中的函数地址的获取

如上述介绍了PIC中对变量地址的获取,对于函数调用,我们可以用同样的方法来实现,即将动态库中函数的加载在内存中的真实地址放在GOT中,然后利用间接引用的方式去获取。但是这里有个问题,目前很多C/C++库都包含了大量的函数,而这些库函数的真实地址直到动态链接库解析后才能获取到,如果采用上述GOT的做法,在动态库加载时,将所有函数的地址都先解析后,填充在GOT表中,可以想象GOT表得有多大。此外我们调用动态库,可能有时候只调用了一个printf函数,其他的函数根本就没有被调用。因此编译系统采用了名叫延时绑定(Lazy binding)的技术,只有在真正调用某个函数时,才去解析其内存中真实的地址,其核心原理依然是通过添加一个间接层,也就是PLT(Procedure Linkage Table),来完成对函数的间接寻址以及延时绑定。

PLT位于代码段,也是类似于GOT一样的一个数组结构,如下图所示,

C/C++动态链接及地址无关代码(PIC)

  • 当代码段中有去调用printf函数时,编译器将其转化为去调用printf@plt,printf@plt是PLT的第N个元素。PLT中除了第一个元素也就是PLT[0]外,其他每个元素都表示一个要去寻址的函数的相关信息,PLT[0]是动态链接器的一些程序,用来去做真实的运行时的地址解析的事情;
  • 每个PLT元素都包含了以下三个部分,
    • 首先是每个PLT项都对应了一个GOT项,GOT是数据段,是可写的,因此真实的运行时地址肯定就是保存在对应的GOT中的吧?
    • 准备调用resolver时候的参数;
    • 调用resolver,它是PLT[0],固定的地方,这个地方不固定这个方案就没法实现;
  • 如上图中4,当函数的地址还未解析出来之前,GOT[n]中保存的是PLT[n]中第二条指令的地址

当第一次调用printf函数时,

  • 转到调用PLT[n],进而跳转到*GOT[n],如上图中3;
  • 因为GOT[n]目前是指向PLT[n]中第二条指令的地址,解引用,跳转到*GOT[n],进而就跳转到PLT[n]的第二条指令,如上图中4;
  • 接着调用动态链接器resolver,如上图中5;
  • resolver负责解析printf加载在内存中的真实地址,然后将解析的地址放在GOT[n]中,如上图中6,然后调用printf函数。

当本进程中再次调用printf函数时,

  • 此时GOT[n]已经保存了printf函数的地址,jmp跳转到printf函数,控制权将转移给printf函数,因此不会再次调用resolver。

通过上面的步骤,我们可以看到

  • 通过PLT+GOT,使得共享库的代码段(.text)是真正的地址无关,因为地址解析后是保存在GOT中,其在数据段(.data)。
  • 通过延迟绑定,只有在真正调用函数时,动态链接器才会去解析其真实地址。

8.PLT+GOT在获取函数地址时的例子

main链接了PIC的动态库libadd.so,查看其包含的段,

 lyf@liuyifei:~/test$ readelf -S ./main There are 35 section headers, starting at offset 0x3874: Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .interp PROGBITS 0 000194 000013 00 A 0 0 1 [ 2] .note.gnu.bu[...] NOTE 080481a8 0001a8 000024 00 A 0 0 4 [ 3] .note.ABI-tag NOTE 080481cc 0001cc 000020 00 A 0 0 4 [ 4] .gnu.hash GNU_HASH 080481ec 0001ec 000020 04 A 5 0 4 [ 5] .dynsym DYNSYM 0c 00020c 000060 10 A 6 1 4 [ 6] .dynstr STRTAB 0c 00026c 000066 00 A 0 0 1 [ 7] .gnu.version VERSYM 080482d2 0002d2 00000c 02 A 5 0 2 [ 8] .gnu.version_r VERNEED 080482e0 0002e0 000020 00 A 6 1 4 [ 9] .rel.dyn REL 0 000300 000010 08 A 5 0 4 [10] .rel.plt REL 0 000310 000010 08 AI 5 22 4 [11] .init PROGBITS 0 001000 000024 00 AX 0 0 4 [12] .plt PROGBITS 0 001030 000030 04 AX 0 0 16 [13] .text PROGBITS 0 001060 000178 00 AX 0 0 16 [14] .fini PROGBITS 080491d8 0011d8 000018 00 AX 0 0 4 [15] .rodata PROGBITS 0804a000 002000 000008 00 A 0 0 4 [16] .eh_frame_hdr PROGBITS 0804a008 002008 000034 00 A 0 0 4 [17] .eh_frame PROGBITS 0804a03c 00203c 0000b0 00 A 0 0 4 [18] .init_array INIT_ARRAY 0804bf00 002f00 000004 04 WA 0 0 4 [19] .fini_array FINI_ARRAY 0804bf04 002f04 000004 04 WA 0 0 4 [20] .dynamic DYNAMIC 0804bf08 002f08 0000f0 08 WA 6 0 4 [21] .got PROGBITS 0804bff8 002ff8 000008 04 WA 0 0 4 [22] .got.plt PROGBITS 0804c000 003000 000014 04 WA 0 0 4 [23] .data PROGBITS 0804c014 003014 000010 00 WA 0 0 4 [24] .bss NOBITS 0804c024 003024 000004 00 WA 0 0 1 [25] .comment PROGBITS 00000000 003024 00002b 01 MS 0 0 1 [26] .debug_aranges PROGBITS 00000000 00304f 000020 00 0 0 1 [27] .debug_info PROGBITS 00000000 00306f 000098 00 0 0 1 [28] .debug_abbrev PROGBITS 00000000 003107 00009e 00 0 0 1 [29] .debug_line PROGBITS 00000000 0031a5 000057 00 0 0 1 [30] .debug_str PROGBITS 00000000 0031fc 0000a9 01 MS 0 0 1 [31] .debug_line_str PROGBITS 00000000 0032a5 000016 01 MS 0 0 1 [32] .symtab SYMTAB 00000000 0032bc 000280 10 33 19 4 [33] .strtab STRTAB 00000000 00353c 0001e7 00 0 0 1 [34] .shstrtab STRTAB 00000000 003723 000151 00 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), I (info), L (link order), O (extra OS processing required), G (group), T (TLS), C (compressed), x (unknown), o (OS specific), E (exclude), D (mbind), p (processor specific)
  • .plt位于0,属性为可执行,和.text属性相同;
  • .got.plt位于0804c000,属性为可写,和.data属性相同;

反汇编main,

 lyf@liuyifei:~/test$ objdump -d -Mintel ./main ./main: file format elf32-i386 Disassembly of section .init: 0 <_init>: : f3 0f 1e fb endbr32 : 53 push ebx : 83 ec 08 sub esp,0x8 : e8 a3 00 00 00 call 80490b0 <__x86.get_pc_thunk.bx> d: 81 c3 f3 2f 00 00 add ebx,0x2ff3 : 8b 83 fc ff ff ff mov eax,DWORD PTR [ebx-0x4] : 85 c0 test eax,eax b: 74 02 je f <_init+0x1f> d: ff d0 call eax f: 83 c4 08 add esp,0x8 : 5b pop ebx : c3 ret Disassembly of section .plt: 0 <__libc_start_main@plt-0x10>: : ff 35 04 c0 04 08 push DWORD PTR ds:0x804c004 : ff 25 08 c0 04 08 jmp DWORD PTR ds:0x804c008 c: 00 00 add BYTE PTR [eax],al ... 0 <__libc_start_main@plt>: : ff 25 0c c0 04 08 jmp DWORD PTR ds:0x804c00c : 68 00 00 00 00 push 0x0 b: e9 e0 ff ff ff jmp  <_init+0x30> 0 <add@plt>: : ff 25 10 c0 04 08 jmp DWORD PTR ds:0x804c010 : 68 08 00 00 00 push 0x8 b: e9 d0 ff ff ff jmp  <_init+0x30> 0 <main>: : 8d 4c 24 04 lea ecx,[esp+0x4] a: 83 e4 f0 and esp,0xfffffff0 d: ff 71 fc push DWORD PTR [ecx-0x4] : 55 push ebp : 89 e5 mov ebp,esp : 53 push ebx : 51 push ecx : 83 ec 10 sub esp,0x10 : e8 47 00 00 00 call 80491d4 <__x86.get_pc_thunk.ax> d: 05 73 2e 00 00 add eax,0x2e73 : c7 c2 1c c0 04 08 mov edx,0x804c01c : c7 02 05 00 00 00 mov DWORD PTR [edx],0x5 e: c7 45 f4 00 00 00 00 mov DWORD PTR [ebp-0xc],0x0 80491a5: 8b 90 f8 ff ff ff mov edx,DWORD PTR [eax-0x8] 80491ab: 8b 0a mov ecx,DWORD PTR [edx] 80491ad: c7 c2 1c c0 04 08 mov edx,0x804c01c 80491b3: 8b 12 mov edx,DWORD PTR [edx] 80491b5: 83 ec 08 sub esp,0x8 80491b8: 51 push ecx 80491b9: 52 push edx 80491ba: 89 c3 mov ebx,eax 80491bc: e8 8f fe ff ff call  <add@plt> 80491c1: 83 c4 10 add esp,0x10 80491c4: 89 45 f4 mov DWORD PTR [ebp-0xc],eax 80491c7: 8b 45 f4 mov eax,DWORD PTR [ebp-0xc] 80491ca: 8d 65 f8 lea esp,[ebp-0x8] 80491cd: 59 pop ecx 80491ce: 5b pop ebx 80491cf: 5d pop ebp 80491d0: 8d 61 fc lea esp,[ecx-0x4] 80491d3: c3 ret 080491d4 <__x86.get_pc_thunk.ax>: 80491d4: 8b 04 24 mov eax,DWORD PTR [esp] 80491d7: c3 ret 
  • 0x和0xd的作用,eax=0xd+0x2e73=0x804C000,这个是.got.plt的起始点;
  • 0x80491bc行,调用add@plt;
0 <add@plt>: : ff 25 10 c0 04 08 jmp DWORD PTR ds:0x804c010 : 68 08 00 00 00 push 0x8 b: e9 d0 ff ff ff jmp  <_init+0x30>
  • add@plt,跳转到0x804c010,在.got.plt段中,也就是从PLT跳转到了GOT;

看看0x804c010地址处的值,

lyf@liuyifei:~/test$ readelf -x .got.plt ./main Hex dump of section '.got.plt': NOTE: This section has relocations against it, but these have NOT been applied to this dump. 0x0804c000 08bf0408 00000000 00000000  ............F... 0x0804c010  V...
  • 小端,所以其值为0x,又回到了add@plt的第二条指令,然后去调用jmp <_init+0x30>。

以上分析的结果和上一节的结果吻合,下面通过gdb实际运行,看看lazy binding的效果,

lyf@liuyifei:~/test$ gdb ./main ... Reading symbols from ./main... (gdb) set environment LD_LIBRARY_PATH=. (gdb) set disassembly-flavor intel (gdb) break main.c:9 Breakpoint 1 at 0xe: file main.c, line 9. (gdb) run Starting program: /home/lyf/test/main [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Breakpoint 1, main () at main.c:9 9 int rtv = 0; (gdb) disas main Dump of assembler code for function main: 0x0 <+0>: lea ecx,[esp+0x4] 0x0a <+4>: and esp,0xfffffff0 0x0d <+7>: push DWORD PTR [ecx-0x4] 0x0 <+10>: push ebp 0x0 <+11>: mov ebp,esp 0x0 <+13>: push ebx 0x0 <+14>: push ecx 0x0 <+15>: sub esp,0x10 0x0 <+18>: call 0x80491d4 <__x86.get_pc_thunk.ax> 0x0d <+23>: add eax,0x2e73 0x0 <+28>: mov edx,0x804c01c 0x0 <+34>: mov DWORD PTR [edx],0x5 => 0x0e <+40>: mov DWORD PTR [ebp-0xc],0x0 0x080491a5 <+47>: mov edx,DWORD PTR [eax-0x8] 0x080491ab <+53>: mov ecx,DWORD PTR [edx] 0x080491ad <+55>: mov edx,0x804c01c 0x080491b3 <+61>: mov edx,DWORD PTR [edx] 0x080491b5 <+63>: sub esp,0x8 0x080491b8 <+66>: push ecx 0x080491b9 <+67>: push edx 0x080491ba <+68>: mov ebx,eax 0x080491bc <+70>: call 0x <add@plt> 0x080491c1 <+75>: add esp,0x10 0x080491c4 <+78>: mov DWORD PTR [ebp-0xc],eax 0x080491c7 <+81>: mov eax,DWORD PTR [ebp-0xc] 0x080491ca <+84>: lea esp,[ebp-0x8] 0x080491cd <+87>: pop ecx 0x080491ce <+88>: pop ebx 0x080491cf <+89>: pop ebp 0x080491d0 <+90>: lea esp,[ecx-0x4] 0x080491d3 <+93>: ret End of assembler dump. (gdb) print/x *(void)0x804c010 $1 = 0x (gdb) print &add $2 = (int (*)(int, int)) 0xf7fba16d <add> (gdb) step 10 rtv = add(global_int, global_extern_int); (gdb) x/w 0x804c010 0x804c010 <>: 0x0 (gdb) step add (a_=5, b_=2) at add.c:10 10 foo(); (gdb) x/w 0x804c010 0x804c010 <>: 0xf7fba16d (gdb) print &add $4 = (int (*)(int, int)) 0xf7fba16d <add>
  • 在真正执行add之前,add@plt对应的GOT中的地址为0x804c010,而*0x804c010又跳转回了0x,也就是add@plt的第二条指令,这时候肯定动态链接器要去解析真正的add函数的地址了,其实add函数的地址是已经确定了(0xf7fba16d),这里的解析指的是通过索引找到其地址,然后填充在PLT对应的GOT中
  • 当step进入add函数后,此时add@plt对应的GOT肯定已经填充了,我们发现查询GOT对应处的值x/w 0x804c010,已经变成了add函数的真实地址0xf7fba16d。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/166738.html

(0)
上一篇 2025-01-08 07:00
下一篇 2025-01-08 07:26

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信