C/C++动态链接及地址无关代码(PIC)
bigegpt 2024-11-17 07:10 44 浏览
本文简要介绍了动态链接库中地址无关代码(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. 为什么需要动态链接
动态链接是相对于静态链接提出来的,这里仅简单分析两者的区别,
?
- 静态库,是由许多的.o打包而成,会在链接阶段链接到程序中,链接后的应用程序可以直接运行,不依赖其他任何运行时的元素。但是如果有多个应用程序都需要使用这个库,此时每个应用程序都需要把静态库打包进自己的应用程序,浪费了磁盘空间,同时如果多个进程同时运行,每个进程中都有一份“几乎相同的代码”,也浪费了内存;
- 动态库和应用程序是分开的,因此,当动态库的代码升级了以后,只要保证库的接口不变,应用程序依然可以正常运行。但是对于静态库,应用程序就必须重新编译,才能使用最新的库代码;
- 如果当前系统有多个进程使用了同一个动态库,整个内存中只需要加载一份动态库,共享给调用其的所有进程。
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),来完成对数据和函数的间接寻址。
?
?
PIC的一个核心思想是借助数据段和代码段之间的确定的偏移量。因为对于链接器而言,在将许多的目标文件进行合并的时候,它明确的知道所有段的大小和他们之间的偏移量。如上图所示,代码段紧跟着数据段,因此任何代码段中的某条指令到数据段起始的偏移量都可以很容易的计算出来,用代码段的大小减去该指令距离代码段起始点的偏移量。假设距离代码段起始点偏移0x80的指令想取获取数据段中的数据,此时链接器知道相对偏移量(0xEF80),可以用相对偏移量去进行相对寻址。
5.GOT
?
?
如上图所示,GOT是一个保存了各种地址的一个数组,前三个元素都是动态链接器的一些信息,后面的元素都是一些符号的地址,也就是变量和函数的地址。
?
?
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 0x8049050
(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 134529024
ecx 0x2 2
edx 0x5 5
ebx 0xf7fbd000 -134492160
esp 0xffffd170 0xffffd170
ebp 0xffffd178 0xffffd178
esi 0xffffd274 -11660
edi 0xf7ffcb80 -134231168
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一样的一个数组结构,如下图所示,
?
?
- 当代码段中有去调用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 08048194 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 0804820c 00020c 000060 10 A 6 1 4
[ 6] .dynstr STRTAB 0804826c 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 08048300 000300 000010 08 A 5 0 4
[10] .rel.plt REL 08048310 000310 000010 08 AI 5 22 4
[11] .init PROGBITS 08049000 001000 000024 00 AX 0 0 4
[12] .plt PROGBITS 08049030 001030 000030 04 AX 0 0 16
[13] .text PROGBITS 08049060 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位于08049030,属性为可执行,和.text属性相同;
- .got.plt位于0804c000,属性为可写,和.data属性相同;
反汇编main,
lyf@liuyifei:~/test$ objdump -d -Mintel ./main
./main: file format elf32-i386
Disassembly of section .init:
08049000 <_init>:
8049000: f3 0f 1e fb endbr32
8049004: 53 push ebx
8049005: 83 ec 08 sub esp,0x8
8049008: e8 a3 00 00 00 call 80490b0 <__x86.get_pc_thunk.bx>
804900d: 81 c3 f3 2f 00 00 add ebx,0x2ff3
8049013: 8b 83 fc ff ff ff mov eax,DWORD PTR [ebx-0x4]
8049019: 85 c0 test eax,eax
804901b: 74 02 je 804901f <_init+0x1f>
804901d: ff d0 call eax
804901f: 83 c4 08 add esp,0x8
8049022: 5b pop ebx
8049023: c3 ret
Disassembly of section .plt:
08049030 <__libc_start_main@plt-0x10>:
8049030: ff 35 04 c0 04 08 push DWORD PTR ds:0x804c004
8049036: ff 25 08 c0 04 08 jmp DWORD PTR ds:0x804c008
804903c: 00 00 add BYTE PTR [eax],al
...
08049040 <__libc_start_main@plt>:
8049040: ff 25 0c c0 04 08 jmp DWORD PTR ds:0x804c00c
8049046: 68 00 00 00 00 push 0x0
804904b: e9 e0 ff ff ff jmp 8049030 <_init+0x30>
08049050 <add@plt>:
8049050: ff 25 10 c0 04 08 jmp DWORD PTR ds:0x804c010
8049056: 68 08 00 00 00 push 0x8
804905b: e9 d0 ff ff ff jmp 8049030 <_init+0x30>
08049176 <main>:
8049176: 8d 4c 24 04 lea ecx,[esp+0x4]
804917a: 83 e4 f0 and esp,0xfffffff0
804917d: ff 71 fc push DWORD PTR [ecx-0x4]
8049180: 55 push ebp
8049181: 89 e5 mov ebp,esp
8049183: 53 push ebx
8049184: 51 push ecx
8049185: 83 ec 10 sub esp,0x10
8049188: e8 47 00 00 00 call 80491d4 <__x86.get_pc_thunk.ax>
804918d: 05 73 2e 00 00 add eax,0x2e73
8049192: c7 c2 1c c0 04 08 mov edx,0x804c01c
8049198: c7 02 05 00 00 00 mov DWORD PTR [edx],0x5
804919e: 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 8049050 <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
- 0x8049188和0x804918d的作用,eax=0x804918d+0x2e73=0x804C000,这个是.got.plt的起始点;
- 0x80491bc行,调用add@plt;
08049050 <add@plt>:
8049050: ff 25 10 c0 04 08 jmp DWORD PTR ds:0x804c010
8049056: 68 08 00 00 00 push 0x8
804905b: e9 d0 ff ff ff jmp 8049030 <_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 46900408 ............F...
0x0804c010 56900408 V...
- 小端,所以其值为0x8049056,又回到了add@plt的第二条指令,然后去调用jmp 8049030 <_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 0x804919e: 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:
0x08049176 <+0>: lea ecx,[esp+0x4]
0x0804917a <+4>: and esp,0xfffffff0
0x0804917d <+7>: push DWORD PTR [ecx-0x4]
0x08049180 <+10>: push ebp
0x08049181 <+11>: mov ebp,esp
0x08049183 <+13>: push ebx
0x08049184 <+14>: push ecx
0x08049185 <+15>: sub esp,0x10
0x08049188 <+18>: call 0x80491d4 <__x86.get_pc_thunk.ax>
0x0804918d <+23>: add eax,0x2e73
0x08049192 <+28>: mov edx,0x804c01c
0x08049198 <+34>: mov DWORD PTR [edx],0x5
=> 0x0804919e <+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 0x8049050 <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 = 0x8049056
(gdb) print &add
$2 = (int (*)(int, int)) 0xf7fba16d <add>
(gdb) step
10 rtv = add(global_int, global_extern_int);
(gdb) x/w 0x804c010
0x804c010 <add@got.plt>: 0x08049056
(gdb) step
add (a_=5, b_=2) at add.c:10
10 foo();
(gdb) x/w 0x804c010
0x804c010 <add@got.plt>: 0xf7fba16d
(gdb) print &add
$4 = (int (*)(int, int)) 0xf7fba16d <add>
- 在真正执行add之前,add@plt对应的GOT中的地址为0x804c010,而*0x804c010又跳转回了0x8049056,也就是add@plt的第二条指令,这时候肯定动态链接器要去解析真正的add函数的地址了,其实add函数的地址是已经确定了(0xf7fba16d),这里的解析指的是通过索引找到其地址,然后填充在PLT对应的GOT中;
- 当step进入add函数后,此时add@plt对应的GOT肯定已经填充了,我们发现查询GOT对应处的值x/w 0x804c010,已经变成了add函数的真实地址0xf7fba16d。
相关推荐
- 悠悠万事,吃饭为大(悠悠万事吃饭为大,什么意思)
-
新媒体编辑:杜岷赵蕾初审:程秀娟审核:汤小俊审签:周星...
- 高铁扒门事件升级版!婚宴上‘冲喜’老人团:我们抢的是社会资源
-
凌晨两点改方案时,突然收到婚庆团队发来的视频——胶东某酒店宴会厅,三个穿大红棉袄的中年妇女跟敢死队似的往前冲,眼瞅着就要扑到新娘的高额钻石项链上。要不是门口小伙及时阻拦,这婚礼造型团队熬了三个月的方案...
- 微服务架构实战:商家管理后台与sso设计,SSO客户端设计
-
SSO客户端设计下面通过模块merchant-security对SSO客户端安全认证部分的实现进行封装,以便各个接入SSO的客户端应用进行引用。安全认证的项目管理配置SSO客户端安全认证的项目管理使...
- 还在为 Spring Boot 配置类加载机制困惑?一文为你彻底解惑
-
在当今微服务架构盛行、项目复杂度不断攀升的开发环境下,SpringBoot作为Java后端开发的主流框架,无疑是我们手中的得力武器。然而,当我们在享受其自动配置带来的便捷时,是否曾被配置类加载...
- Seata源码—6.Seata AT模式的数据源代理二
-
大纲1.Seata的Resource资源接口源码2.Seata数据源连接池代理的实现源码3.Client向Server发起注册RM的源码4.Client向Server注册RM时的交互源码5.数据源连接...
- 30分钟了解K8S(30分钟了解微积分)
-
微服务演进方向o面向分布式设计(Distribution):容器、微服务、API驱动的开发;o面向配置设计(Configuration):一个镜像,多个环境配置;o面向韧性设计(Resista...
- SpringBoot条件化配置(@Conditional)全面解析与实战指南
-
一、条件化配置基础概念1.1什么是条件化配置条件化配置是Spring框架提供的一种基于特定条件来决定是否注册Bean或加载配置的机制。在SpringBoot中,这一机制通过@Conditional...
- 一招解决所有依赖冲突(克服依赖)
-
背景介绍最近遇到了这样一个问题,我们有一个jar包common-tool,作为基础工具包,被各个项目在引用。突然某一天发现日志很多报错。一看是NoSuchMethodError,意思是Dis...
- 你读过Mybatis的源码?说说它用到了几种设计模式
-
学习设计模式时,很多人都有类似的困扰——明明概念背得滚瓜烂熟,一到写代码就完全想不起来怎么用。就像学了一堆游泳技巧,却从没下过水实践,很难真正掌握。其实理解一个知识点,就像看立体模型,单角度观察总...
- golang对接阿里云私有Bucket上传图片、授权访问图片
-
1、为什么要设置私有bucket公共读写:互联网上任何用户都可以对该Bucket内的文件进行访问,并且向该Bucket写入数据。这有可能造成您数据的外泄以及费用激增,若被人恶意写入违法信息还可...
- spring中的资源的加载(spring加载原理)
-
最近在网上看到有人问@ContextConfiguration("classpath:/bean.xml")中除了classpath这种还有其他的写法么,看他的意思是想从本地文件...
- Android资源使用(android资源文件)
-
Android资源管理机制在Android的开发中,需要使用到各式各样的资源,这些资源往往是一些静态资源,比如位图,颜色,布局定义,用户界面使用到的字符串,动画等。这些资源统统放在项目的res/独立子...
- 如何深度理解mybatis?(如何深度理解康乐服务质量管理的5个维度)
-
深度自定义mybatis回顾mybatis的操作的核心步骤编写核心类SqlSessionFacotryBuild进行解析配置文件深度分析解析SqlSessionFacotryBuild干的核心工作编写...
- @Autowired与@Resource原理知识点详解
-
springIOCAOP的不多做赘述了,说下IOC:SpringIOC解决的是对象管理和对象依赖的问题,IOC容器可以理解为一个对象工厂,我们都把该对象交给工厂,工厂管理这些对象的创建以及依赖关系...
- java的redis连接工具篇(java redis client)
-
在Java里,有不少用于连接Redis的工具,下面为你介绍一些主流的工具及其特点:JedisJedis是Redis官方推荐的Java连接工具,它提供了全面的Redis命令支持,且...
- 一周热门
- 最近发表
- 标签列表
-
- mybatiscollection (79)
- mqtt服务器 (88)
- keyerror (78)
- c#map (65)
- resize函数 (64)
- xftp6 (83)
- bt搜索 (75)
- c#var (76)
- mybatis大于等于 (64)
- xcode-select (66)
- mysql授权 (74)
- 下载测试 (70)
- linuxlink (65)
- pythonwget (67)
- androidinclude (65)
- logstashinput (65)
- hadoop端口 (65)
- vue阻止冒泡 (67)
- oracle时间戳转换日期 (64)
- jquery跨域 (68)
- php写入文件 (73)
- kafkatools (66)
- mysql导出数据库 (66)
- jquery鼠标移入移出 (71)
- 取小数点后两位的函数 (73)