Libraries are used to package similar functions in a single unit. Linux supports two types of libraries: static libraries (statically bound to programs at compile time) and dynamic libraries (bound to programs at run time). The dynamic libraries used by Linux systems are in ELF format, with the suffix named so. 1 Load
The dynamic library is divided into segments, and segments are divided into different types:
Pt_load segment: Contains code or data that needs to be mapped into memory, each segment has different access rights (read, some, execute);
Pt_dynamic: Contains dynamic link information, such as symbol table, relocation table, referenced other library, and so on.
Other segment types are temporarily not indicated.
The loader maps the contents of the first pt_load segment of the library file to the last Pt_load segment to a contiguous memory address space (the benefit is that arbitrary code and the relative address of the data are fixed), and its first address is called the base site (pictured).
The loading of the library only maps the contents of the file to the memory address, but does not actually read the file data, and the corresponding file data is read into memory by the operating system when the memory page fault is abnormal. Deferred read files can speed up the loading of a library. 1.1 Pre-link
In general, the base address of the map is not fixed, but if the dynamic Library uses theprelink technique, it is mapped to the intended address (saved on the file). If the intended address range is already occupied, the load fails (Androidlinker is so that other loaders may be different). The advantage of PreLink is that it simplifies relocation and speeds up loading. 2 Relocation 2.1 Internal functions and variables
In the absence of the use of PreLink, the base address of the library is not fixed (the runtime is determined) and its global variables and the absolute addresses of the functions are not fixed. Because the relative addresses of arbitrary code and data are fixed after Kuga (as described in the previous section), some systems (such as x86) can use relative addresses to access global variables and functions. The arm system cannot use a large range of offsets directly in the instruction (but can be specified by registers) because of the instruction length limitation (32 bits), and the absolute address is superior to the relative address in execution efficiency, so it still needs relocation.
As in this example:
__ATTRIBUTE__ ((Visibility ("hidden")) int errbase = 1;
void Seterr () {errbase = 0x999;}
Compile so, then decompile (ARM architecture):
$ gcc-shared-nostdlib-o libtest.so test.c
$ objdump-dlibtest.so
000002c4<seterr>:
2c4: mov ip,sp
2c8: push {fp,ip, LR, PC}
2cc: Sub fp,ip, #4
2d0: Ldr r2,[pc, #12] ; r2 = &errbase
2d4: mov R3, #2448 ; r3 = 990 2d8
: add r3,r3, #9 R3 + + 9
2dc: str R3,[R2] ; *r2 = R3
2e0: ldm sp,{fp, SP, PC}
2e4: . Word 0x0000109c ; This holds the address of the errbase variable.
To view the relocation table:
$ readelf-rlibtest.so
relocationsection '. Rel.dyn ' at offset 0X2BC contains 1 entries:
offset Info Type sym.value Sym. Name
000002e4 00000017 r_arm_relative
Comparing the assembly code with the relocation table, 2e4 is the offset that holds the address of the errbase variable.
Reposition a table entry in the table with a relative type, pointing to the relative address of the variable and function, and the loader adds it to the base address, making it an absolute address. If you use PreLink, you do not need to relocate. 2.2 External functions and variables
External variables and functions refer to the variables and functions of the target library referencing dependent libraries, requiring the loader to find the corresponding name and absolute address in the symbol table of the dependent library, and then write to the Global offset tableof the target library (globaloffset table , referred to as GOT). The target library accesses external variables and functions through got.
External variable reposition table entry corresponding to a glob_dat type , external function relocation corresponds to a jmp_slot type table entry, the value of the table entry is an absolute address of an external variable or function , which is set by the loader.
As in this example:
extern interrbase;
void Seterr () {errbase = 0x999;}
Compile so, then decompile (ARM architecture):
$ gcc-shared-nostdlib-o libtest.so test.c
$ objdump-dlibtest.so
00000218<seterr>:
218: push {fp}
21c: add fp,sp, #0
: ldr r3,[pc, #28] ; r3=got offset
224: Add r3,pc, R3 ; R3=got address
228: ldr r2,[pc, #24] r2=errbase entry at got offset
22c: ldr R3,[R3, R2] ; R3=errbase address
230: ldr r2,[pc, #20]; r2=0x999 234
: str R2,[R3] ; r3=r2
238: add sp,fp, #0
23c: pop {fp}
: bx LR
244: . Word 0X00008DC4 ; Got offset
248: . Word 0x0000000c errbase at got offset
24c: . Word 0x00000999
To view the relocation table:
$readelf-rlibtest.so
relocationsection '. Rel.dyn ' at offset 0x210 contains 1 entries:
offset Info Type Sym.value sym.name
00008ffc 00000415 r_arm_glob_dat 00000000 errbase
8FFCC exactly corresponds to the Errbase got table entry address. 2.3 Deferred binding
The relocation of external functions and variables needs to find the symbol table of the dependent library, and it is less efficient to do string comparisons, although generally one library uses fewer external variables and functions. If more external functions are used, the Process link table (procedurelinkagetable, or PLT)can be used to speed up dynamic library loading. Delays the positioning of external functions to the first call (called deferred binding). Function delay binding requires the compiler to generate additional code for the function call, which is implemented primarily by the compiler.
Look at this example:
VOIDPRINTF1 (const char*, ...);
void Seterr () {PRINTF1 ("seterr\n");}
Corresponding assembly code (X86-64):
4c0<printf1@plt>:
4c0: jmpq *0x200b3a (%rip)
4c6: Pushq $0x0
4CB: JMPQ 4b0 <_init+0x18>
5ac <seterr>:
5ac: push %rbp
5ad: mov %RSP,%RBP
5b0: Lea 0x5f (%rip),%rdi
5b7: mov $0x0,%eax
5BC: callq 4C0
5c1: pop %rbp
5c2: retq
Calling PRINTF1 calls Printf1@plt, and then jumps to *0x200b3a (%rip), which is * (base address +0x201000).
If this is the first execution, the value of the *0x (base address +0x201000) is (base address +4c6), and the following code performs the function binding, and the corresponding relocation entry is:
$ readelf-rlibtest.so
relocationsection '. Rela.plt ' at offset 0x468 contains 2 entries:
201000 000300000007 r_x8 6_64_jump_slo PRINTF1 + 0
After binding *0x (base address +0x201000) corresponds to the PRINTF1 function's address, the next time you enter the PRINTF1@PLT, you can jump directly to the PRINTF1 function. 2.4-bit independent code
In general, code and read-only data for programs and dynamic libraries can be shared by multiple processes after they are loaded into memory, but dirty data that is written cannot be shared by multiple processes. Relative type relocation modifies the variable address of the code snippet, causing the code snippet to be contaminated so that it cannot be shared by multiple processes. In order for the code snippets of a dynamic library to be shared between processes, you can have the compiler compile the location-independent code ( PIC)by got to access the variables and functions.
PIC Allows code snippets to be shared between processes, saving memory, but accessing variables and functions by got tables is a bit slower than relative positioning, and you can not use pic If you don't need to.