Dynamic link library, GOT, PLT

Dynamic link library

What is dynamic link library

The following is my own understanding, which may not be correct, but it should be roughly the same

Long ago, without the concept of library, every time I wrote code, I was building wheels from scratch
Later, people found that some functions are common to most programs and have nothing to do with specific programs, such as printing a character to the console. Therefore, in order to reduce the cost of writing code, people gather these codes that realize common functions, and use methods such as include or import to refer to them every time they need to be used.
Later, the amount of such code is increasing. This leads to two problems: 1. The consumption of each compilation is increasing; 2. In fact, not all the code is used, and the extra part becomes a waste. This part of the code has nothing to do with the specific program, but every new program has to compile them. Even if you only use a simple printing function, you should include all the code. What about including only part of the code? There is still no fundamental solution to the problem. What people hope most is that this part of the code can be directly integrated into a new program without compiling.

Therefore, in order to solve this problem, people invented static link library.
First, compile all the code into object files, compress these object files into a file, and give these files a directory. Next, when compiling, you can get these compiled parts on demand and embed them into the program. For example, to use a function foo, when linking, find the location of the foo in the static link library directory, and then plug it into the program.
This not only solves the problem of repeated compilation, but also solves the problem of too large scale of executable program.

But there are still shortcomings in this way: some code is executed so frequently that almost all programs have such a code embedded in it. As a result, there are hundreds of copies of the same code in memory - which could have been used for more useful things, such as running more programs. The second and more difficult part is that if the original static link library has a bug, all programs containing this part of the code must be recompiled - as for which programs contain this code and others do not, programmers and gods know before writing, and only gods know after writing.

Therefore, in order to make up for this deficiency, dynamic link library appears.
The so-called dynamic link library means that it is dynamically linked - you can link one copy if you want to use it, and you can't link it if you don't use it.
All such reusable code is placed in a dynamic link library and occupies a part of memory. When a program needs to use some of its functions, go there to find the method you want. In this way, there is only one backup of this kind of code in memory, and when you need to update a bug, you only need to simply update the version of the dynamic link library.

Is dynamic link library better than static link library? not always.
The dynamic link library is linked only when it is running. When the program uses this part of things, it must stop and link it (that is, find out where this part of the code is placed) before it can go on.
In addition, the system must also run a dynamic linker to help our program find the code it needs.

However, with the rapid development of hardware, this loss has long been insignificant.

GOT,PLT

Reference blog: https://blog.csdn.net/u011987514/article/details/67716639?utm_medium=distribute.pc_relevant_t0.none -task-blog-BlogCommendFromBaidu-1. control&depth_ 1-utm_ source=distribute. pc_ relevant_ t0. none-task-blog-BlogCommendFromBaidu-1. control

Since the program needs to use the dynamic link library, you must know which parts of the dynamic link library you need to use, so as to accurately link the corresponding parts to its address space when loading. These address related parts are placed in the data segment, which is called GOT (Gloabl Offset Table). When the code needs to refer to the global variable, it can directly refer to the corresponding item in GOT for indirect reference.
This part of space is often dynamically allocated to avoid conflict with the space solidified by the original executable at compile time. In this way, the reference problem of absolute address is solved.

You can use objdump -h and objdump -R to view the position of GOT and the offset of data items in GOT

Because the data end is in the data segment, and this part can be modified during loading, each process can have an independent copy of this part of the code - both an independent data part and a common instruction part

For the jump and call between modules, the address of the objective function is saved in GOT. When this function needs to be executed, jump to the corresponding address in GOT.

As mentioned earlier, dynamic linking is a way to gain flexibility at the expense of some performance. Although the performance of this part is insignificant, there are still ways to improve it.

Under dynamic linking, there will be performance loss because there are a large number of function references in the program, so it will take a lot of time to link before execution and during loading.
Most functions have little chance to use. Therefore, ELF adopts a technology called delayed binding (PLT).

The so-called delayed binding means that you don't need to bind it in a hurry and bind it when you need it. Just like I usually catch up with my homework, the school committee doesn't urge me to do it until I urge it.

The implementation method of this delayed binding is:

  1. When using the function of external module, it does not jump directly through GOT, but through PLT
  2. Each external function has a specific item in the PLT
  3. At the beginning, the address item corresponding to GOT is not the corresponding address
  4. In order to find the location of the corresponding function, first, you need to know the corresponding module ID, and then you need to know the corresponding ID of this function
  5. When the information is sufficient, call a function for parsing. After parsing, fill in the address of the corresponding part of GOT
  6. The next time you use it, jump directly to the corresponding function position

For example, parse an executable file, which contains these things:

  • GOT
 23 .got          00000088  0000000000003f78  0000000000003f78  00002f78  2**3
                  CONTENTS, ALLOC, LOAD, DATA
DYNAMIC RELOCATION RECORDS
OFFSET           TYPE              VALUE 
0000000000003d78 R_X86_64_RELATIVE  *ABS*+0x0000000000001240
0000000000003d80 R_X86_64_RELATIVE  *ABS*+0x0000000000001200
0000000000004008 R_X86_64_RELATIVE  *ABS*+0x0000000000004008
0000000000003fd8 R_X86_64_GLOB_DAT  _ITM_deregisterTMCloneTable
0000000000003fe0 R_X86_64_GLOB_DAT  __libc_start_main@GLIBC_2.2.5
0000000000003fe8 R_X86_64_GLOB_DAT  __gmon_start__
0000000000003ff0 R_X86_64_GLOB_DAT  _ITM_registerTMCloneTable
0000000000003ff8 R_X86_64_GLOB_DAT  __cxa_finalize@GLIBC_2.2.5
0000000000003f90 R_X86_64_JUMP_SLOT  free@GLIBC_2.2.5
0000000000003f98 R_X86_64_JUMP_SLOT  open_memstream@GLIBC_2.2.5
0000000000003fa0 R_X86_64_JUMP_SLOT  fclose@GLIBC_2.2.5
0000000000003fa8 R_X86_64_JUMP_SLOT  __stack_chk_fail@GLIBC_2.4
0000000000003fb0 R_X86_64_JUMP_SLOT  printf@GLIBC_2.2.5
0000000000003fb8 R_X86_64_JUMP_SLOT  fputs@GLIBC_2.2.5
0000000000003fc0 R_X86_64_JUMP_SLOT  fflush@GLIBC_2.2.5
0000000000003fc8 R_X86_64_JUMP_SLOT  perror@GLIBC_2.2.5
0000000000003fd0 R_X86_64_JUMP_SLOT  exit@GLIBC_2.2.5
  • .plt.got
......

00000000000010e0 <open_memstream@plt>:
    10e0:	f3 0f 1e fa          	endbr64 
    10e4:	f2 ff 25 ad 2e 00 00 	bnd jmpq *0x2ead(%rip)        # 3f98 <open_memstream@GLIBC_2.2.5>
    10eb:	0f 1f 44 00 00       	nopl   0x0(%rax,%rax,1)

......
  • .plt
Disassembly of section .plt:

0000000000001020 <.plt>:
    1020:	ff 35 5a 2f 00 00    	pushq  0x2f5a(%rip)        # 3f80 <_GLOBAL_OFFSET_TABLE_+0x8>
    1026:	f2 ff 25 5b 2f 00 00 	bnd jmpq *0x2f5b(%rip)        # 3f88 <_GLOBAL_OFFSET_TABLE_+0x10>
    102d:	0f 1f 00             	nopl   (%rax)
    1030:	f3 0f 1e fa          	endbr64 
    1034:	68 00 00 00 00       	pushq  $0x0
    1039:	f2 e9 e1 ff ff ff    	bnd jmpq 1020 <.plt>
    103f:	90                   	nop
    1040:	f3 0f 1e fa          	endbr64 
    1044:	68 01 00 00 00       	pushq  $0x1
    1049:	f2 e9 d1 ff ff ff    	bnd jmpq 1020 <.plt>
    104f:	90                   	nop
    1050:	f3 0f 1e fa          	endbr64 
    1054:	68 02 00 00 00       	pushq  $0x2
    1059:	f2 e9 c1 ff ff ff    	bnd jmpq 1020 <.plt>
    105f:	90                   	nop
    1060:	f3 0f 1e fa          	endbr64 
    1064:	68 03 00 00 00       	pushq  $0x3
    1069:	f2 e9 b1 ff ff ff    	bnd jmpq 1020 <.plt>
    106f:	90                   	nop
    1070:	f3 0f 1e fa          	endbr64 
    1074:	68 04 00 00 00       	pushq  $0x4
    1079:	f2 e9 a1 ff ff ff    	bnd jmpq 1020 <.plt>
    107f:	90                   	nop
    1080:	f3 0f 1e fa          	endbr64 
    1084:	68 05 00 00 00       	pushq  $0x5
    1089:	f2 e9 91 ff ff ff    	bnd jmpq 1020 <.plt>
    108f:	90                   	nop
    1090:	f3 0f 1e fa          	endbr64 
    1094:	68 06 00 00 00       	pushq  $0x6
    1099:	f2 e9 81 ff ff ff    	bnd jmpq 1020 <.plt>
    109f:	90                   	nop
    10a0:	f3 0f 1e fa          	endbr64 
    10a4:	68 07 00 00 00       	pushq  $0x7
    10a9:	f2 e9 71 ff ff ff    	bnd jmpq 1020 <.plt>
    10af:	90                   	nop
    10b0:	f3 0f 1e fa          	endbr64 
    10b4:	68 08 00 00 00       	pushq  $0x8
    10b9:	f2 e9 61 ff ff ff    	bnd jmpq 1020 <.plt>
    10bf:	90                   	nop

To call the function open_ Take the memstream function as an example:

callq  10e0 <open_memstream@plt>

First call the function at 10e0:

00000000000010e0 <open_memstream@plt>:
    10e0:	f3 0f 1e fa          	endbr64 
    10e4:	f2 ff 25 ad 2e 00 00 	bnd jmpq *0x2ead(%rip)        # 3f98 <open_memstream@GLIBC_2.2.5>
    10eb:	0f 1f 44 00 00       	nopl   0x0(%rax,%rax,1)

It indirectly jumps to the address of 3f98 records:

0000000000003f98 R_X86_64_JUMP_SLOT  open_memstream@GLIBC_2.2.5

At the beginning, the address filled in here is an address, such as 1050:

    1050:	f3 0f 1e fa          	endbr64 
    1054:	68 02 00 00 00       	pushq  $0x2
    1059:	f2 e9 c1 ff ff ff    	bnd jmpq 1020 <.plt>
    105f:	90                   	nop

It just took 16 bytes
It pushes func ID=2 onto the stack and jumps to 1020

0000000000001020 <.plt>:
    1020:	ff 35 5a 2f 00 00    	pushq  0x2f5a(%rip)        # 3f80 <_GLOBAL_OFFSET_TABLE_+0x8>
    1026:	f2 ff 25 5b 2f 00 00 	bnd jmpq *0x2f5b(%rip)        # 3f88 <_GLOBAL_OFFSET_TABLE_+0x10>
    102d:	0f 1f 00             	nopl   (%rax)

Another moudule ID is pressed in here=_ GLOBAL_ OFFSET_ TABLE_+ 0x8, that is, the ID of the libc module recorded at 3f80
Then, an address is called in. GLOBAL_ OFFSET_ TABLE_+ 0x10 > is the function at 3f88, which is used to dynamically link and find the function address of external modules. It can be called lookup function
Now, two parameters are pushed into our stack, one is the ID corresponding to the function and the other is the ID corresponding to the module. In the lookup function, these two parameters will be popped up - this is also the basic method of parameter transfer for 32-bit system functions.
After the lookup function performs the linking function, it will put the corresponding jump in GOT_ Modify the slot to the address of the corresponding function in memory, and jump to the position of the function to start execution

In this way, the next time when executing, after indirectly jumping to the GOT position, the above process will not be executed, but the function will be executed directly

That is, the general calling process is as follows:
When Unbound:
.plt.got => .plt => lookup => func
After binding:
.plt.got => func

How to use dynamic link library

Only the methods under Linux are discussed

When using GCC, use this parameter: - fPIC, - shared to generate dynamic link library
-The function of fPIC is to act on the compilation stage and tell the compiler to generate location independent code
-shared is used to compile dynamic libraries

Adding the link library to / usr/lib is not enough. You need to
Call the ldconfig command

reference resources: http://linux-wiki.cn/wiki/zh-hans/%E5%8A%A8%E6%80%81%E5%BA%93(.so)

Each so file has a file name, such as libabc so. x. Y.z, where ABC is the library name_ x.y.z_ Is the version number of the file
First_ x_ Indicates compatibility_ x_ Different so files are not compatible.
Second place_ y_ The change indicates that new features may be introduced, but they are generally compatible.
Third place_ z_ The change of generally means that only the Bug has been corrected.
Not all so files follow this rule, but its application is indeed very common.

In the system, there will be some problems Symbolic link , such as [3]:

libpam.so -> libpam.so.0.83.0
libpam.so.0 -> libpam.so.0.83.0

The first one is mainly used when developing other programs using this library. For example, gcc wants to connect to PAM library and libpam directly Just so
The second one is mainly used at runtime, because the same libraries as the first version are compatible with each other, so when the program runs, just try to connect libpam so. 0 is enough

ldconfig can automatically generate these links.

Here is a book for reference:
https://akkadia.org/drepper/dsohowto.pdf

Keywords: C Linux gcc

Added by timclaason on Tue, 01 Feb 2022 02:26:18 +0200