Analysis of the so file palm used inside the _ i686.get _ pc_thunk.bx, so Reprinted from a http://www.cs.virginia.edu /~ Wh5a/blog/blogs.
Ian Lance Taylor is writing a serialized article about linkers.
I always wanted to know the Dynamic Linking process, so I just learned about it.
The specific working procedures of program loader, program linker, and dynamic linker are not discussed at the moment.
First, write a small program and use objdump to check it:
$ Objdump-D main. o
00000000:
0: 8d 4C 24 04 Lea 0x4 (% ESP), % ECx
4: 83 E4 F0 and $0xfffffff0, % ESP
7: FF 71 FC pushl 0 xfffffffc (% ECx)
A: 55 push % EBP
B: 89 E5 mov % ESP, % EBP
D: 51 push % ECx
E: 83 EC 14 sub $0x14, % ESP
11: E8 fc ff call 12
16: 89 04 24 mov % eax, (% ESP)
19: E8 fc ff call 1A
1e: E8 fc ff call 1f
23: 89 44 24 04 mov % eax, 0x4 (% ESP)
27: C7 04 24 00 00 00 00 movl $0x0, (% ESP)
2e: E8 fc ff call 2f
33: 83 C4 14 add $0x14, % ESP
36: 59 pop % ECx
37: 5D pop % EBP
38: 8d 61 FC Lea 0 xfffffffc (% ECx), % ESP
3B: C3 RET
We can see some functions in main call, but the addresses are all numbers such as 12 and 1A. These numbers represent the offset in this. text section and need to be patched by linker. You can use readelf-R to view the relocations:
$ Readelf-r main. o
Relocation section '. Rel. text' at offset 0x388 contains 5 entries:
Offset info type sym. Value sym. Name
00000012 00000902 r_1__pc32 00000000 foo
2017001a 00000a02 r_0000_pc32 00000000 printf
0000001f 00000b02 r_0000_pc32 00000000 bar
2017002a 00000501 r_0000_32 00000000. rodata
0000002f 00000a02 r_0000_pc32 00000000 printf
These function addresses will be patched into a real address (static join) or an address in PLT (Dynamic join) during link ). We can see the following information in readelf-r a. Out, which we will see later:
$ Readelf-r a. Out
Relocation section '. Rel. dyn' at offset 0x34c contains 1 entries:
Offset info type sym. Value sym. Name
08049744 00000206 r_1__glob_dat 00000000 _ gmon_start __
Relocation section '. Rel. PLT' At offset 0x354 contains 5 entries:
Offset info type sym. Value sym. Name
08049754 00000107 r_0000_jump_slot 00000000 bar
08049758 00000207 r_0000_jump_slot 00000000 _ gmon_start __
0804975c 00000407 r_1__jump_slot 00000000 _ libc_start_main
08049760 00000507 r_1__jump_slot 00000000 foo
08049764 00000607 r_0000_jump_slot 00000000 printf
The executable files generated in the dynamic connection mode are written to the dynamic loader path in the. interp section. For example:
Wh5a @ power3/tmp/Dyn $ readelf-s a. Out | grep interp
[1]. interp progbits 08048134 000134 000013 00 A 0 0 1
Wh5a @ power3/tmp/Dyn $ GDB-q a. Out
(GDB) x/S 0x8048134
0x8048134: "/lib/ld-linux.so.2"
In this case, when the program is loaded, dynamic loader will be called to load the dynamic library on which the program depends.
The following describes how the foo function is called:
0x080484e5: Call 0x80483d4
We mentioned earlier that the call function addresses in main need to be patched. This process is the first step of static linker, so that the call command points to PLT. The patch in the second step will be executed dynamically on demand, that is, lazy symbol binding. This line can be modified through the Environment Variable ld_bind_now = 1, which will be helpful in debugging. The base address of PLT can be viewed using readelf-S. Got is also an important information, which is listed here:
[11]. PLT progbits 08048394 000394 000060 04 ax 0 0 4
[21]. Got progbits 08049744 000744 000004 04 wa 0 0 4
[22]. Got. PLT progbits 08049748 000748 000020 04 wa 0 0 4
Each entry in PLT is 16 bytes, and each entry in got is 4 bytes.
The plt0 information is special (the specific address is listed here for understanding ):
Push got [1]; 0x804974c
JMP got [2]; * 0x8049750.
0x00000000; padding
The following PLT table items correspond to each dynamic function. They are listed in the same order as readelf-R. We know that foo is a 4th function, that is, PLT [4]. Through the computing address, we know that it is 0x80483d4, which is indeed the address of the main function call.
The content is as follows:
Plt4:
080483d4:
80483d4: FF 25 60 97 04 08 JMP * 0x8049760; got [6]
80483da: 68 18 00 00 00 push $0x18; Foo's relocation offset
80483df: E9 B0 ff jmp 8048394; plt0
The first command of the PLT entry jumps to got, which means that got plays an indirection role. Each table item in got is initialized as the second instruction pointing to the PLT entry.
Here plt4 corresponds to got6 (Do you still remember the information displayed by readelf-r? Have you found 0x8049760 ?) Because got [0 .. 2] has a special effect (got [0] seems to point to. Dynamic section, which stores some useful information for dynamic loader [5]). So how does this got base address come from? It corresponds to the section. Got. PLT .. Got section stores global variables, which will be available for further research.
In the initial state, foo has not been resolve, so got has not been patched by dynamic linker. In this way, the got6 command causes 0x80483da to be executed. This command is used to press the offset stack corresponding to foo. Next, jump to plt0 to continue execution.
Plt0 first presses the content in got1 (pointing to a link_map structure) on the stack, and then jumps to got2 to continue execution. Got [2] points to the _ dl_runtime_resolve function. This function is provided by dynamic linker. by viewing the maps file of the proc file system, we can also see that got [2] does point to the address space of/lib/ld-2.6.so. This function is a very simple piece of Assembly used to build the necessary stack environment so that _ dl_fixup (source code in glibc/Elf/dl-runtime.c) to complete the real work. We have pushed two parameters in front. One is the content of got [1], that is, the address of link_map, and the other is the offset of the symbol to be solved, which is 0x18. For more information about this process, see the source code and [2]. This function will eventually Patch got [6] so that the real address of Foo can be obtained directly when plt4 (that is, foo) is called again. Next, _ dl_fixup returns the real address of Foo to _ dl_runtime_resolve, which puts the returned value to the top of the stack, xchg % eax, (% ESP), and then jumps to foo directly with a ret.
Finally, we will summarize the role of. Got. PLT:
. Got. PLT (0x8049748)
0x0804966c got [0],. Dynamic
0x00ba3650 got [1], the Link Map
0x00b9a2b0 got [2], always jump here to resolve symbols./lib/ld-linux.so.2 is loaded here.
...
0x080483da got [6], not resolved yet, so points right back to the instruction after the JMP
References:
[1] linkers Part 4
[2] elf dynamic symbolic parsing process (revision)
[3] How to hijack the global offset table with pointers for root shells
[4] The elf object file format: Introduction
[5] The elf object file format by dissection
[6] before Main () Analysis
The previous analysis shows how to call a dynamic library from an application. How does a dynamic library complete symbolic parsing?
According to Ian Lance Taylor, it is best to compile shared Lib in PIC (-FPIC) mode. This is not the case, but it will increase the workload of dynamic linker for relocation. Libraries compiled in PIC mode can greatly reduce the necessary relocation info, however, when you call non-static functions and access global/static variables, you must use PLT/got to indirectly call all problems in computer science can be solved by another level of indirection .)
If libfoo. so is compiled in PIC mode, and an external function bar is called, bar will appear in. rel. in the PLT section (readelf-R), bar will appear in. rel. dyn section. If it is the latter, dynamic linker will load libfoo. so, the offset information provided by this section is used to directly patch the reference bar, this means that the command itself has been modified (should the command be read-only again after dynamic linker ?), Therefore, the Features shared by multiple processes are lost. In the PIC mode, there will be an indirect process. What we analyze now is this process. Unlike function calls, we found that no matter which method the global variables are compiled, their relocation information is placed. rel. in Dyn, I think this is because the data access is not completed through several layers of redirection like control transfer, so lazy binding cannot be performed and must be completed during load.
The biggest difference between the PIC mode and the non-pic mode is that the former does not directly patch the command, but Patch got. All commands access got to achieve position independence, which is very similar to the idea of the dynamic Parsing Library Function in the previous article. But since it is address-independent, how do you know the location of got? The key is that each shared lib has its own got, and the entire Lib is loaded to the memory as a whole. Therefore, the relative offset between the got base address and each command is always determined. In this way, when a command accesses got, it only needs to calculate its current IP address and add the offset determined by the static state, you can find the got entry of the symbol you want to access.
The function used to calculate the current IP address is usually _ i686.get _ pc_thunk.bx, which is included in each PIC module. Therefore, the relative offset between it and the function called is fixed. It is very simple:
MoV (% ESP), % EBX
RET
In this way, the return address, that is, the IP address of caller function, is put into EBX. Sometimes we can see _ i686.get _ pc_thunk.cx and write it into ECx because EBX is callee saved Reg and ECx is caller saved, therefore, if a function needs to call other functions, it is better to use EBX; otherwise, it is better to use other registers. The following command (0x00113458) adds a fixed offset to its own IP address to obtain the got address of this Lib. This value is usually always cached in the register (EBX.
(GDB) disassemble
Dump of worker er code for function FOO:
0x0011344c: Push % EBP
0x0011344d: mov % ESP, % EBP
0x0011344f: Push % EBX
0x00113450: Sub $0x4, % ESP
0x00113453: Call 0x113447 <__i686.get _ pc_thunk.bx>
0x00113458: add $0x1180, % EBX
0x0011345e: mov 0xfffffff0 (% EBX), % eax; a negative number because we are accessing. Got from. Got. PLT
0x00113464: movb $0x32, (% eax)
0x00113467: Call 0x113320
0x00111_c: Lea 0xffffef08 (% EBX), % eax
...
0x00113458 how is the $0x1180 command obtained?
Run readelf-r Foo. O to get:
Relocation section '. Rel. text' at offset 0x484 contains 7 entries:
Offset info type sym. Value sym. Name
00000008 00000b02 r_1__pc32 00000000 _ i686.get _ pc_thunk.bx
0000000e 00000c0a r_0000_gotpc 00000000 _ global_offset_table _
...
It indicates that linker should remove the real address of _ global_offset_table _ on the patch from 0xe of. text section. 0xe corresponds to the number of the add command in the. text section of foo. O:
$ Objdump-D Foo. o
Disassembly of section. Text:
00000000:
0: 55 push % EBP
1: 89 E5 mov % ESP, % EBP
3: 53 push % EBX
4: 83 EC 04 Sub $0x4, % ESP
7: E8 fc ff call 8; 0x8: _ i686.get _ pc_thunk.bx
C: 81 C3 02 00 00 00 add $0x2, % EBX; 0xe: _ global_offset_table _
...
Let's take a look at the value of the symbol _ global_offset_table _ in libfoo. So.
$ Nm libfoo. So | grep _ global_offset_table _
201715d8 A _ global_offset_table _
$ Readelf-s libfoo. So | grep 15d8
[20]. Got. PLT progbits limit 15d8 0005d8 00001c 04 wa 0 0 4
Exactly the address of. Got. PLT! Therefore, it can be seen that all the commands that access got leave a blank value, telling linker to determine the got address (in fact, that is, the offset rather than the absolute address ), enter the offset of got to this command.
Note that the got address obtained after half-day calculation is only used to access global variables, because the absolute address (0x0011345e) is required ). However, calling a function in Plt only requires a relative address. Therefore, you do not need to use EBX for indirect access (0x00113467 ).