当前位置: 首页 > news >正文

【Linux笔记】动态库与静态库的理解与加载

🔥个人主页🔥:孤寂大仙V
🌈收录专栏🌈:Linux
🌹往期回顾🌹:【Linux笔记】动态库与静态库的制作
🔖流水不争,争的是滔滔不


  • 一、ELF文件
  • 二、ELF的形成与加载
    • ELF的形成
    • ELF可执行文件加载
  • 三、理解加载与链接
    • 静态链接
    • ELF加载与进程地址空间
    • 动态链接与动态库加载
      • 全局偏移量表GOT(global offset table)

一、ELF文件

ELF 文件是一种二进制文件格式,它定义了文件的结构和内容,使得操作系统、链接器和调试器等工具能够正确地处理和解释文件。这种格式具有良好的可移植性和扩展性,能够适应不同的硬件平台和操作系统。

ELF 文件主要由以下几个部分组成:

  • ELF 头(ELF Header)
    位于文件的开头,包含了文件的基本信息,如文件类型(可执行文件、目标文件等)、机器架构(如 x86、ARM 等)、入口地址(程序开始执行的地址)等。ELF 头还指明了程序头表和节头表的位置和大小

  • 程序头表(Program Header Table)
    由多个程序头项组成,每个程序头项描述了一个需要加载到内存中的段(Segment)。段是一组具有相同属性(如访问权限)的节的集合,程序头表告诉操作系统的加载器如何将文件内容映射到内存中,并设置相应的内存访问权限。表里记着每个段的开始的位置和位移(offset)、长度。

  • 节头表(Section Header Table)
    由多个节头项组成,每个节头项对应文件中的一个节(Section)。节是文件中具有特定用途的数据块,如代码段(.text)存放程序的可执行代码,数据段(.data)存放已初始化的全局变量和静态变量,符号表(.symtab)存储文件中的符号信息等。节头表为链接器、调试器等工具提供了文件内部结构的详细信息。包含对节(sections)的描述。

  • 节(Sections)和段(Segments)
    节:是 ELF 文件中最基本的数据单元,每个节都有特定的用途。除了上述提到的代码段和数据段,还有.bss节存放未初始化的全局变量和静态变量,.rodata节存放只读数据,.rel.text节存放代码段的重定位信息等。
    段:是为了满足程序在内存中的加载和执行需求而对节进行的分组。例如,代码段通常包含.text节和.rodata节,它们都具有只读和可执行的属性;数据段包含.data节和.bss节,它们具有读写属性。


节头表(Section Header Table)

  • 面向文件内部:用于描述 ELF 文件自身的节(Section)结构,如代码段.text、数据段.data、符号表.symtab等。
  • 主要服务于工具链:链接器、调试器、反汇编工具等通过节头表解析文件内部的详细信息,例如符号地址、重定位信息等。
  • 存在于所有 ELF 文件:目标文件、可执行文件、共享库中均可能包含节头表(但发布版本的可执行文件可能会剥离节头表以减小体积)。

程序头表(Program Header Table)

  • 面向外部执行环境:用于描述 ELF 文件在内存中的布局和加载方式,将文件内容映射到进程地址空间。
  • 主要服务于操作系统:加载器(如 Linux 的ld-linux.so)通过程序头表确定哪些段(Segment)需要加载到内存、内存权限(可读 / 写 / 执行)等。
  • 仅存在于可执行文件和共享库:目标文件通常不含程序头表,因为其尚未确定最终的内存布局。

类比理解

  • 节头表:类似 “文件内部的目录”,记录文件内部各部分的详细信息(如房间的用途、大小、位置)。
  • 程序头表:类似 “建筑的框架图”,指导如何将文件内容 “搭建” 成可运行的程序(如承重墙的位置、楼层高度等)。
    通过这两个表,ELF 文件同时满足了编译链接(节头表)和运行加载(程序头表)的双重需求。
    在这里插入图片描述

二、ELF的形成与加载

ELF的形成

在这里插入图片描述
粗粒度的将ELF的合并理解为:

将多份 C/C++ 源代码,翻译成为目标 .o 文件
将多份 .o 文件section进行合并


ELF可执行文件加载

# 查看节(section)信息
$ readelf -S a.out[Nr] Name              Type             Address           OffsetSize              EntSize          Flags  Link  Info  Align[ 0] NULL              NULL             0000000000000000  000000000000000000000000  0000000000000000           0     0     0[ 1] .interp           PROGBITS         0000000000400238  00000238000000000000001c  0000000000000000   A       0     0     1[ 2] .note.ABI-tag     NOTE             0000000000400254  000002540000000000000020  0000000000000000   A       0     0     4[ 3] .note.gnu.build-i NOTE             0000000000400274  000002740000000000000024  0000000000000000   A       0     0     4[ 4] .gnu.hash         GNU_HASH         0000000000400298  00000298000000000000001c  0000000000000000   A       5     0     8[ 5] .dynsym           DYNSYM           00000000004002b8  000002b80000000000000048  0000000000000018   A       6     1     8[ 6] .dynstr           STRTAB           0000000000400300  000003000000000000000038  0000000000000000   A       0     0     1[ 7] .gnu.version      VERSYM           0000000000400338  000003380000000000000006  0000000000000002   A       5     0     2[ 8] .gnu.version_r    VERNEED          0000000000400340  000003400000000000000020  0000000000000000   A       6     1     8[ 9] .rela.dyn         RELA             0000000000400360  000003600000000000000018  0000000000000018   A       5     0     8[10] .rela.plt         RELA             0000000000400378  000003780000000000000018  0000000000000018  AI       5    24     8[11] .init             PROGBITS         0000000000400390  00000390...# 查看节合并后的段(segment)信息
$ readelf -l a.out
Elf file type is EXEC (Executable file)
Entry point 0x4003e0
There are 9 program headers, starting at offset 64Program Headers:Type           Offset             VirtAddr           PhysAddrFileSiz            MemSiz              Flags  AlignPHDR           0x0000000000000040  0x0000000000400040  0x00000000004000400x00000000000001f8  0x00000000000001f8  R E    8INTERP         0x0000000000000238  0x0000000000400238  0x00000000004002380x000000000000001c  0x000000000000001c  R      1[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]LOAD           0x0000000000000000  0x0000000000400000  0x00000000004000000x0000000000000744  0x0000000000000744  R E    0x200000LOAD           0x0000000000000e10  0x0000000000600e10  0x0000000000600e100x0000000000000218  0x0000000000000220  RW     0x200000DYNAMIC        0x0000000000000e28  0x0000000000600e28  0x0000000000600e280x00000000000001d0  0x00000000000001d0  RW     8NOTE           0x0000000000000254  0x0000000000400254  0x00000000004002540x0000000000000044  0x0000000000000044  R      4GNU_EH_FRAME   0x00000000000005a0  0x00000000004005a0  0x00000000004005a00x000000000000004c  0x000000000000004c  R      4GNU_STACK      0x0000000000000000  0x0000000000000000  0x00000000000000000x0000000000000000  0x0000000000000000  RW     0x10GNU_RELRO      0x0000000000000e10  0x0000000000600e10  0x0000000000600e100x00000000000001f0  0x00000000000001f0  R      1Section to Segment mapping:Segment Sections...0001     .interp02     .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame03     .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss04     .dynamic05     .note.ABI-tag .note.gnu.build-id06     .eh_frame_hdr0708     .init_array .fini_array .jcr .dynamic .got .got.plt

链接时,形成的section head table 把所有section数据节进行全部描述。program haeder table把相同属性的segment拿到,segment就是合并的相同属性的数据结。将来找到这个程序的时候,操作系统就会读取program header table,然后根据program header table所描述的内容,找到需要加载的内容在文件当中的位置把需要加载内容的若干个数据节加载到内存空间。

Section Header Table 和 Program Header Table 在 ELF文件中扮演着不同但又相互关联的角色。Section Header Table侧重于描述文件内部的结构,为链接器和调试器提供详细信息;Program Header Table则关注程序在内存中的布局和加载,帮助操作系统将程序正确加载到内存中并执行。

⼀个ELF会有多种不同的Section,在加载到内存的时候,也会进行Section合并,形成segment
合并原则:相同属性,比如:可读,可写,可执行,需要加载时申请空间等.
这样,即便是不同的Section,在加载到内存中,可能会以segment的形式,加载到⼀起
很显然,这个合并工作也已经在形成ELF的时候,合并方式已经确定了,具体合并原则被记录在了ELF的 程序头表(Program header table) 中


为什么要将section合并成为srgment?

  • Section合并的主要原因是为了减少页面碎片,提高内存使用效率。如果不进行合并,假设页面大小为4096字节(内存块基本大小,加载,管理的基本单位),如果.text部分为4097字节,.init部分为512字节,那么它们将占用3个页面,而合并后,它们只需2个页面。
  • 此外,操作系统在加载程序时,会将具有相同属性的section合并成⼀个大的segment,这样就可以实现不同的访问权限,从而优化内存管理和权限访问控制。

对于程序头表(Program Header Table)和节头表(Section Header Table)在和操作系统交互时的作用

上面说过一个是在链接时候起作用,一个是在运行加载时候起作用。

链接视图(Linking view) - 对应节头表 Section header table

  • 文件件结构的粒度更细,将文件按功能模块的差异进行划分,静态链接分析的时候⼀般关注的是链接视图,能够理解 ELF 文件中包含的各个部分的信息。
  • 为了空间布局上的效率,将来在链接目标文件时,链接器会把很多节(section)合并,规整成可执行的(segment)、可读写的段、只读段等。合并了后,空间利用率就高了,否则,都是很小的很小的⼀段,未来物理内存页浪费太大(物理内存页分配⼀般都是整数倍⼀块给你,比如4k),所以,链接器趁着链接就把小块们都合并了。

执行视图(execution view) - 对应程序头表 Program header table

  • 告诉操作系统,如何加载可执行文件,完成进程内存的初始化。⼀个可执行程序的格式中,⼀定有 program header table 。

在这里插入图片描述
链接视图

# 查看节(section)信息
$ readelf -S a.out[Nr] Name              Type             Address           OffsetSize              EntSize          Flags  Link  Info  Align[ 0] NULL              NULL             0000000000000000  000000000000000000000000  0000000000000000           0     0     0[ 1] .interp           PROGBITS         0000000000400238  00000238000000000000001c  0000000000000000   A       0     0     1[ 2] .note.ABI-tag     NOTE             0000000000400254  000002540000000000000020  0000000000000000   A       0     0     4[ 3] .note.gnu.build-i NOTE             0000000000400274  000002740000000000000024  0000000000000000   A       0     0     4[ 4] .gnu.hash         GNU_HASH         0000000000400298  00000298000000000000001c  0000000000000000   A       5     0     8[ 5] .dynsym           DYNSYM           00000000004002b8  000002b80000000000000048  0000000000000018   A       6     1     8[ 6] .dynstr           STRTAB           0000000000400300  000003000000000000000038  0000000000000000   A       0     0     1[ 7] .gnu.version      VERSYM           0000000000400338  000003380000000000000006  0000000000000002   A       5     0     2[ 8] .gnu.version_r    VERNEED          0000000000400340  000003400000000000000020  0000000000000000   A       6     1     8[ 9] .rela.dyn         RELA             0000000000400360  000003600000000000000018  0000000000000018   A       5     0     8[10] .rela.plt         RELA             0000000000400378  000003780000000000000018  0000000000000018  AI       5    24     8[11] .init             PROGBITS         0000000000400390  00000390...
  • .text节 :是保存了程序代码指令的代码节。
  • .data节 :保存了初始化的全局变量和局部静态变量等数据。
  • .rodata节 :保存了只读的数据,如⼀行C语言代码中的字符串。由于.rodata节是只读的,所以只能存在于⼀个可执行文件的只读段中。因此,只能是在text段(不是data段)中找到.rodata节。
  • .BSS节 :为未初始化的全局变量和局部静态变量预留位置
  • .symtab节 : Symbol Table 符号表,就是源码里面那些函数名、变量名和代码的对应关系。
  • .got.plt节 (全局偏移表-过程链接表):.got节保存了全局偏移表。.got节和.plt节⼀起提供了对导⼊的共享库函数的访问入口,由动态链接器在运行时进行修改。对于GOT的理解,我们后面聊。

执行视图

# 查看节合并后的段(segment)信息
$ readelf -l a.out
Elf file type is EXEC (Executable file)
Entry point 0x4003e0
There are 9 program headers, starting at offset 64Program Headers:Type           Offset             VirtAddr           PhysAddrFileSiz            MemSiz              Flags  AlignPHDR           0x0000000000000040  0x0000000000400040  0x00000000004000400x00000000000001f8  0x00000000000001f8  R E    8INTERP         0x0000000000000238  0x0000000000400238  0x00000000004002380x000000000000001c  0x000000000000001c  R      1[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]LOAD           0x0000000000000000  0x0000000000400000  0x00000000004000000x0000000000000744  0x0000000000000744  R E    0x200000LOAD           0x0000000000000e10  0x0000000000600e10  0x0000000000600e100x0000000000000218  0x0000000000000220  RW     0x200000DYNAMIC        0x0000000000000e28  0x0000000000600e28  0x0000000000600e280x00000000000001d0  0x00000000000001d0  RW     8NOTE           0x0000000000000254  0x0000000000400254  0x00000000004002540x0000000000000044  0x0000000000000044  R      4GNU_EH_FRAME   0x00000000000005a0  0x00000000004005a0  0x00000000004005a00x000000000000004c  0x000000000000004c  R      4GNU_STACK      0x0000000000000000  0x0000000000000000  0x00000000000000000x0000000000000000  0x0000000000000000  RW     0x10GNU_RELRO      0x0000000000000e10  0x0000000000600e10  0x0000000000600e100x00000000000001f0  0x00000000000001f0  R      1
  • 告诉操作系统哪些模块可以被加载进内存。
  • 加载进内存之后哪些分段是可读可写,哪些分段是只读,哪些分段是可执行的。

三、理解加载与链接

静态链接

无论是自己的.o, 还是静态库中的.o,本质都是把.o文件进行连接的过程

hello.o

#include<stdio.h>
void run();
int main()
{
printf("hello world!\n");
run();
return 0;
}

code.o

#include<stdio.h>
void run()
{
printf("running...\n");
}

code.o中包含hello.o中用的run方法的实现,这两个.o文件需要合并
在这里插入图片描述

objdump -d 命令:将代码段(.text)进行反汇编查看

$ objdump -d code.o
code.o: 	file format elf64-x86-64Disassembly of section .text:0000000000000000 <run>:Address | Machine Code | Disassembly          | Comments--------------------------------------------------------------0x0000 | f3 0f 1e fa    | endbr64              | 64位安全指令前缀0x0004 | 55             | push %rbp            | 保存基址指针0x0005 | 48 89 e5       | mov %rsp, %rbp       | 设置栈帧0x0008 | 48 8d 3d 00 00 00 00 | lea 0x0(%rip), %rdi | 计算字符串地址(实际地址=0x000f)0x000f | e8 00 00 00 00 | callq 0x14           | 调用函数(占位符,链接时重定位)0x0014 | 90             | nop                  | 空操作0x0015 | 5d             | pop %rbp             | 恢复基址指针0x0016 | c3             | retq                 | 返回
$ objdump -d hello.o
hello.o: 	file format elf64-x86-6Disassembly of section .text:0000000000000000 <main>:Address | Machine Code | Disassembly          | Comments--------------------------------------------------------------0x0000 | f3 0f 1e fa    | endbr64              | 64位安全指令前缀0x0004 | 55             | push %rbp            | 保存基址指针0x0005 | 48 89 e5       | mov %rsp, %rbp       | 设置栈帧0x0008 | 48 8d 3d 00 00 00 00 | lea 0x0(%rip), %rdi | 计算字符串地址(实际地址=0x000f)0x000f | e8 00 00 00 00 | callq 0x14           | 调用函数(占位符,链接时重定位)0x0014 | b8 00 00 00 00 | mov $0x0, %eax       | 设置返回值为00x0019 | e8 00 00 00 00 | callq 0x1e           | 调用函数(占位符)0x001e | b8 00 00 00 00 | mov $0x0, %eax       | 设置返回值为00x0023 | 5d             | pop %rbp             | 恢复基址指针0x0024 | c3             | retq                 | 返回

发现main函数里 调用函数调用的目标地址全是0
在这里插入图片描述

其实就是在编译 hello.c 的时候,编译器是完全不知道 printf 和 run 函数的存在的,比如他们位于内存的哪个区块,代码长什么样都是不知道的。因此,编辑器只能将这两个函数的跳转地址先暂时设为0。

这个地址会在链接的时候修正,为了让链接器将来在链接时能够正确定位到这些被修正的地址,在代码块(.data)中还存在⼀个重定位表,这张表将来在链接的时候,就会根据表里记录的地址将其修正。

在这里插入图片描述

最终形成的可执行程序的符号表,main函数和run函数等都被确定。两个.o进行合并之后,在最终的可执行程序中,就找到了run。下图为可执行程序的section信息
在这里插入图片描述
hello.o或者code.o call后面的00 00 00 00有没有被修改成为具体的最终函数地址呢

下面查看可执行程序文件的反汇编
objdump -d
在这里插入图片描述
两个.o的代码段合并到了一起,并进行了统一的编址
链接的时候,会修改.o中没有确定的函数地址,在合并完成之后,进行相关call地址,完成代码调用

静态链接就是把库中的.o进行合并,和上述过程⼀样
所以链接其实就是将编译之后的所有目标文件连同用到的一些静态库运行时库组合,拼装成⼀个独立
的可执行文件。其中就包括我们之前提到的地址修正,当所有模块组合在一起之后,链接器会根据我
们的.o文件或者静态库中的重定位表找到那些需要被重定位的函数全局变量,从而修正它们的地址。这
其实就是静态链接的过程。
在这里插入图片描述
一旦合并之后,形成一个大的ELF文件,目标函数就会进行统一编址因为一旦合并之后目标函数的位置就确定了。修改完之后call之后的地址全零改为最终指定函数的起始地址。
所有链接过程中会涉及到对.o中外部符号进行地址重定位。

ELF加载与进程地址空间

引入问题
一个ELF程序在没有加载到内存的时候,有没有地址?
有地址。

⼀个ELF程序,在没有被加载到内存的时候,本来就有地址,当代计算机工作的时候,都采用"平坦模式"进行工作。所以也要求ELF对自己的代码和数据进行统⼀编。下面是 objdump -S 反汇编之后的代码。

可执行程序在编译好之后已经是虚拟地址了。在这里插入图片描述

最左侧的就是ELF的虚拟地址,其实,严格意义上应该叫做逻辑地址(起始地址+偏移量), 但是我们
认为起始地址是0.也就是说,其实虚拟地址在我们的程序还没有加载到内存的时候,就已经把可执
行程序进行统一编址了。


在这里插入图片描述

可执行程序ELF文件在没有加载到内存之前,在编译链接节点就已经完成了线性编址,线性编址完成后虚拟地址也有了。现在把这个区域加载到物理内存中,(我们这里研究只代码区)。加载之后操作系统就会形成进程创建进程PCB和mm_struct,mm_struct中有了代码区就要找到代码区的起始地址,mm_struct代码区的起始地址用线性编址的起始位置做初始化,如图就有了1060做起点,然后根据线性编址的偏移量找到终点,start就是1060end就是108f。磁盘中的代码和数据加载到物理内存也会占用物理内存的物理空间,所有每一行代码也有其对应的物理地址。有了虚拟地址和物理地址,页表就会形成映射。然乎CPU开始执行程序。

Entry point adress 是程序的入口地址。Entry point adress是elf可执行程序的入口地址。CPU中的EIP拿到可执行程序的入口地址,CPU开始调度当前进程。CR3寄存器指向当前进程的页表,MMU硬件单元,通过这些通过虚拟地址找到物理地址,CPU把物理内存的指令拿到。
在这里插入图片描述
在这里插入图片描述

动态链接与动态库加载

进程如何看到动态库

在这里插入图片描述
这里的进程A数据和进程A代码也是,通过磁盘中的ELF可执行程序文件加载进内存的,和静态库的加载一样。磁盘中的库加载到内存中,库也是ELF也有物理地址和虚拟地址,会通过页表建立物理地址与虚拟地址的映射,然后把动态库映射到当前的地址空间上。动态库一旦映射到地址空间上的代码区,代码在调用库方法时只需要从代码区跳转到共享区中的某个地址处然后把库方法调用完然后再返回就完成了库方法的调用。

进程间如何共享库的
在这里插入图片描述
与上述过程一样,库映射到多个进程的地址空间,然后进行调用


动态链接其实远比静态链接要常用得多。比如我们查看下 hello 这个可执行程序依赖的动态库,会发现它就用到了⼀个c动态链接库:

$ ldd hellolinux-vdso.so.1 => (0x00007fffeb1ab000)libc.so.6 => /lib64/libc.so.6 (0x00007ff776af5000)/lib64/ld-linux-x86-64.so.2 (0x00007ff776ec3000)
# ldd命令⽤于打印程序或者库⽂件所依赖的共享库列表。

这里的 libc.so是C语言的运行时库,里面提供了常用的标准输入输出文件字符串处理等等这些功能。

那为什么编译器默认不使用静态链接呢?静态链接会将编译产生的所有目标文件,连同用到的各种库,合并形成⼀个独立的可执行文件,它不需要额外的依赖就可以运行。照理来说应该更加方便才对
是吧?
静态链接最大的问题在于生成的文件体积大,并且相当耗费内存资源。随着软件复杂度的提升,我们
的操作系统也越来越臃肿,不同的软件就有可能都包含了相同的功能和代码,显然会浪费大量的硬盘
空间。
这个时候,动态链接的优势就体现出来了,我们可以将需要共享的代码单独提取出来,保存成⼀个独
立的动态链接库,等到程序运行的时候再将它们加载到内存,这样不但可以节省空间,因为同一个模
块在内存中只需要保留⼀份副本,可以被不同的进程所共享。

动态链接实际上将链接的整个过程推迟到了程序加载的时候。 比如我们去运行⼀个程序,操作系统会首先将程序的数据代码连同它用到的⼀系列动态库先加载到内存,其中每个动态库的加载地址都是不固定的,操作系统会根据当前地址空间的使用情况为它们动态分配⼀段内存。当动态库被加载到内存以后,一旦它的内存地址被确定,我们就可以去修正动态库中的那些函数跳转地址了。

在C/C++程序中,当程序开始执行时,它首先并不会直接跳转到 main 函数。实际上,程序的入口点
是 _start ,这是⼀个由C运行时库(通常是glibc)或链接器(如ld)提供的特殊函数。 在 _start 函数中,会执行⼀系列初始化操作,这些操作包括:

  • 设置堆栈:为程序创建⼀个初始的堆栈环境。
  • 初始化数据段:将程序的数据段(如全局变量和静态变量)从初始化数据段复制到相应的内存位置,并清零未初始化的数据段
  • 动态链接:这是关键的⼀步, _start 函数会调用动态链接器的代码来解析和加载程序所依赖的动态库(shared libraries)。动态链接器会处理所有的符号解析和重定位,确保程序中的函数调用和变量访问能够正确地映射到动态库中的实际地址。

动态链接器: 动态链接器(如ld-linux.so)负责在程序运行时加载动态库。
当程序启动时,动态链接器会解析程序中的动态库依赖,并加载这些库到内存中。

环境变量和配置文件:

  • Linux系统通过环境变量(如LD_LIBRARY_PATH)和配置文件(如/etc/ld.so.conf及其子配置
    文件)来指定动态库的搜索路径。
  • 这些路径会被动态链接器在加载动态库时搜索。

缓存文件:

  • 为了提高动态库的加载效率,Linux系统会维护⼀个名为/etc/ld.so.cache的缓存文件。
  • 该文件包含了系统中所有已知动态库的路径和相关信息,动态链接器在加载动态库时会首先 搜索这个缓存文件
  • 调用__libc_start_main :一旦动态链接完成, _start 函数会调用__libc_start_main (这是glibc提供的⼀个函数)。 __libc_start_main 函数负责执行⼀些额外的初始化工作,比如设置信号处理函数、初始化线程库(如果使用了线程)等。
  • 调用 main 函数:最后, __libc_start_main 函数会调用程序的 main 函数,此时程序的执行控制权才正式交给用户编写的代码。
  • 处理 main 函数的返回值:当 main 函数返回时, __libc_start_main 会负责处理这个返回值,并最终调用 _exit 函数来中止程序。

动态库中的相对地址
动态库为了随时进行加载,为了支持并映射到任意进程的任意位置,对动态库中的方法,统⼀编址,采用相对编址的方案进行编制的(其实可执行程序也⼀样,都要遵守平坦模式,只不过exe是直接加载的)。


在这里插入图片描述
在库加载到内存的时候我们还拿到了库中方法的偏移量。

库已经被我们映射到了当前进程的地址空间中
库的虚拟起始地址我们也已经知道了
库中每⼀个方法的偏移量地址我们也知道
所有:访问库中任意方法,只需要知道库的起始虚拟地址+方法偏移量即可定位库中的方法
而且:整个调用过程,是从代码区跳转到共享区,调用完毕在返回到代码区,整个过程完全在进程地址空间中进行的.。
在这里插入图片描述

在动态链接的情况下,代码区的代码在找库中共享区的函数方法时,通常在编译链接阶段是不知道每个函数在共享区的具体地址的,但会知道库中方法相对于某个基地址的偏移量
在程序运行时,动态链接器会负责将动态链接库加载到内存中的合适位置,并根据偏移量来计算出每个函数的实际地址,然后将这些地址信息填充到程序的相关数据结构中,使得程序能够正确地调用共享区中的函数。这样,通过偏移量和动态链接器的工作,实现了代码对共享库函数的动态定位和调用。所以这时就需要全局偏移量表。

全局偏移量表GOT(global offset table)

动态链接采用的做法是在 .data (可执行程序或者库自己)中专门预留⼀片区域用来存放函数的跳转地址,它也被叫做全局偏移表GOT,表中每一项都是本运行模块要引用的⼀个全局变量或函数的地址。
在这里插入图片描述

  • 由于代码段只读,我们不能直接修改代码段。但有了GOT表,代码便可以被所有进程共享。但在不同进程的地址空间中,各动态库的绝对地址、相对位置都不同。反映到GOT表上,就是每个进程的每个动态库都有独立的GOT表,所以进程间不能共享GOT表。
  • 在单个.so下,由于GOT表与 .text 的相对位置是固定的,我们完全可以利⽤CPU的相对寻址来找
    到GOT表。
  • 在调用函数的时候会首先查表,然后根据表中的地址来进行跳转,这些地址在动态库加载的时候会被修改为真正的地址。
  • 这种方式实现的动态链接就被叫做 PIC 地址无关代码 。换句话说,我们的动态库不需要做任何修
    改,被加载到任意内存地址都能够正常运行,并且能够被所有进程共享,这也是为什么之前我们给编译器指定-fPIC参数的原因,PIC=相对编址+GOT。

那在代码区call的时候有这个表的起始地址,然后加上具体库函数的偏移量然后就可以实现调用了。

相关文章:

【Linux笔记】动态库与静态库的理解与加载

&#x1f525;个人主页&#x1f525;&#xff1a;孤寂大仙V &#x1f308;收录专栏&#x1f308;&#xff1a;Linux &#x1f339;往期回顾&#x1f339;&#xff1a;【Linux笔记】动态库与静态库的制作 &#x1f516;流水不争&#xff0c;争的是滔滔不 一、ELF文件二、ELF的形…...

ollama docker设置模型常驻显存

参考&#xff1a; https://github.com/ollama/ollama/issues/5272 https://deepseek.csdn.net/67cfd7c93b685529b708fdee.html 通过-e传入环境变量&#xff0c;ollama运行&#xff1a; docker run -d --gpusall -e OLLAMA_KEEP_ALIVE-1 -v ollama:/root/.ollama -p 11434:114…...

SAP-ABAP:SAP 主数据管理体系深度解析与学习路径介绍

Ⅰ. 主数据体系全景认知 1.1 主数据核心定位 #mermaid-svg-Lf3tZAfcROs5hlN4 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-Lf3tZAfcROs5hlN4 .error-icon{fill:#552222;}#mermaid-svg-Lf3tZAfcROs5hlN4 .error-t…...

Redis解决缓存击穿问题——两种方法

目录 引言 解决办法 互斥锁&#xff08;强一致&#xff0c;性能差&#xff09; 逻辑过期&#xff08;高可用&#xff0c;性能优&#xff09; 设计逻辑过期时间 引言 缓存击穿&#xff1a;给某一个key设置了过期时间&#xff0c;当key过期的时候&#xff0c;恰好这个时间点对…...

FGPA学习(二)实现LED流水灯

目录 一、6个LED灯实现流水灯 &#xff08;一&#xff09;实验逻辑 1、时钟和复位信号的处理 2、按键停止信号的处理 3、计数器的计数逻辑 4、LED 状态更新逻辑 &#xff08;二&#xff09;代码实现 &#xff08;三&#xff09;效果展示 二、vscode插件下载及其模块分…...

【蓝桥杯】每天一题,理解逻辑(4/90)【Leetcode 二进制求和】

题目描述 我们解析一下题目 我们可以理解到两个主要信息 给的是二进制的字符串返回他们的和 我们知道&#xff0c;十进制的加减法需要进位&#xff0c;例如&#xff1a;9716是因为91之后进了一位&#xff0c;二进制也是如此&#xff0c;只不过十进制是逢10进1&#xff0c;二…...

docker利用ollama +Open WebGUI在本地搭建部署一套Deepseek-r1模型

系统&#xff1a;没有限制&#xff0c;可以运行docker就行 磁盘空间&#xff1a;至少预留50GB; 内存&#xff1a;8GB docker版本&#xff1a;4.38.0 桌面版 下载ollama镜像 由于docker镜像地址&#xff0c;网络不太稳定&#xff0c;建议科学上网的一台服务器拉取ollama镜像&am…...

精准git动图拆解​

参考原文&#xff1a;精准git动图拆解​​ 该工具可精准识别并提取.git 动图的每一帧&#xff0c;无论是代码运行演示&#xff0c;还是项目流程展示的动图&#xff0c;都能完美处理。​ 快速格式转换​ 提取的动图帧会快速转换为 PNG 格式。PNG 无损压缩、支持透明背景&…...

让vscode远程开发也可以图形显示

目录 0. 摘要1. 保存查看2. jupyter内置inline渲染3. jupyter浏览器4. matplot修改后端5. SSH X11转发[※]6. 参考 0. 摘要 vscode登录远程服务器进行开发遇到图形显示需求时&#xff0c;该怎么处理&#xff1f;一般有几种方式&#xff1a; 保存下来查看jupyter内置的inline图…...

996引擎 - 红点系统

996引擎 - 红点系统 总结NPC 红点(TXT红点)Lua 红点1. Red_Point.lua2. UI_Ex.lua参考资料以下内容是在三端 lua 环境下测试的 总结 红点系统分几个部分组成。 M2中设置变量推送。 配置红点表。 Envir\Data\cfg_redpoint.xls 2.1. UI元素中找到ID填写 ids 列。 主界面挂载…...

Springboot List集合的校验方式

pom.xml 引入 <dependency><groupId>org.hibernate.validator</groupId><artifactId>hibernate-validator</artifactId><version>6.2.0.Final</version></dependency><dependency><groupId>org.springframework.b…...

基于图像识别的医学影像大数据诊断系统的设计与实现

标题:基于图像识别的医学影像大数据诊断系统的设计与实现 内容:1.摘要 随着医学影像技术的快速发展&#xff0c;医学影像数据量呈爆炸式增长&#xff0c;传统的人工诊断方式在处理海量数据时效率低下且容易出现误差。本研究的目的是设计并实现一个基于图像识别的医学影像大数据…...

黑马node.js教程(nodejs教程)——AJAX-Day01-04.案例_地区查询——查询某个省某个城市所有地区(代码示例)

文章目录 代码示例效果 代码示例 axiosTest.html <!DOCTYPE html> <!-- 文档类型声明&#xff0c;告诉浏览器这是一个HTML5文档 --> <html lang"en"> <!-- HTML根元素&#xff0c;设置文档语言为英语 --><head> <!-- 头部区域&am…...

PySide(PyQt),使用types.MethodType动态定义事件

以PySide(PyQt)的图片项为例&#xff0c;比如一个视窗的场景底图是一个QGraphicsPixmapItem&#xff0c;需要修改它的鼠标滚轮事件&#xff0c;以实现鼠标滚轮缩放显示的功能。为了达到这个目的&#xff0c;可以重新定义一个QGraphicsPixmapItem类&#xff0c;并重写它的wheelE…...

c语言基础编程入门练习题

[编程入门]成绩评定 题目描述 给出一百分制成绩&#xff0c;要求输出成绩等级‘A’、‘B’、‘C’、‘D’、‘E’。 90分以及90分以上为A&#xff0c;80-89分为B&#xff0c;70-79分为C&#xff0c;60-69分为D&#xff0c;60分以下为E。 输入格式 一个整数0&#xff0d;100…...

汽车安全确认等级-中国等保

1、概念解析 网络安全保证等级&#xff08;Cybersecurity Assurance Level&#xff09;通常指在不同标准或框架下&#xff0c;根据系统或数据的敏感性、重要性以及潜在风险划分的等级&#xff0c;用于指导组织采取相应的安全防护措施。以下是几个常见的网络安全保证等级体系及…...

Quartus + VScode 实现模块化流水灯

文章目录 一、通过VScode编写Verilog代码二、模块化编程三、代码示例 一、通过VScode编写Verilog代码 1、下载Vscode 2、下载相关插件 搜索Verilog就会弹出有如图所示的插件&#xff0c;下载并安装 3、创建Quartus项目 4、创建完成后点击Tools&#xff0c;选择Options 然后在…...

从两指到三指:Robotiq机器人自适应夹持器技术解析

工业自动化离不开高效工具的支持。Robotiq机器人工具凭借其模块化设计和智能化编程技术&#xff0c;提升了设备的灵活性和操作效率。Robotiq机器人工具精准的传感器和自适应夹持器技术&#xff0c;能够满足多样化的应用需求&#xff0c;为制造业、物流和科研等领域提供可靠的解…...

网络安全应急入门到实战

奇安信&#xff1a;95015网络安全应急响应分析报告&#xff08;2022-2024年&#xff09;官网可以下载 https://github.com/Bypass007/Emergency-Response-Notes 应急响应实战笔记 网络安全应急响应技术实战指南 .pdf 常见场景 第4章 勒索病毒网络安全应急响应 第5章 挖矿木…...

Flutter IconButton完全指南:高效使用与性能优化秘籍

目录 一、引言 二、IconButton 的基本用法 三、 进阶技巧 3.1 自定义形状与背景 3.2 带文本的 IconButton&#xff08;使用 Column 组合&#xff09; 3.3 自定义交互反馈 3.4 动态图标切换 3.5 组合式按钮&#xff08;图标 文字&#xff09; 四、高级应用 4.1 与主题…...

跨国生产制造企业:如何破解远距离数据传输难题?

在全球制造业数字化转型的背景下&#xff0c;跨国生产制造企业的文件传输需求正呈现指数级增长。无论是设计图纸、生产计划、质量控制数据&#xff0c;还是供应链协同信息&#xff0c;跨国文件传输已成为制造业高效运营的核心环节。 然而&#xff0c;制造业文件大数据传输具有文…...

大模型如何赋能安全防御?威胁检测与漏洞挖掘的“AI革命”

&#x1f680; 引言&#xff1a;大模型是“安全守护神”还是“双刃剑”&#xff1f; 当黑客用AI生成恶意代码&#xff0c;安全团队也能用大模型“魔法打败魔法”&#xff01; 划重点&#xff1a;大模型不仅是“生产力工具”&#xff0c;更是安全防御的“智能武器库”&#xff0…...

uniapp常用组件

写在前面 今天将uniapp中的组件都过了一遍&#xff0c;上手难度不大&#xff0c;但是还是遇到了一些问题&#xff1a; HBuilder实在是太难用&#xff0c;不管是插件生态还是设计之类的&#xff0c;总之就是用的哪哪不顺手虽然打开内置浏览器是挺方便的&#xff0c;但是不知道…...

Oracle OCP认证没落了吗?

Oracle OCP认证没落了吗? Oracle的OCP认证是数据库领域必考的一个认证&#xff0c;但随着国产化的发展&#xff0c;国内很多企业开发了自己的数据库产品&#xff0c;这种情况对很多人造成了错误的认识&#xff1a;OCP被淘汰了吗?不然&#xff0c;从行业需求、技术趋势、认证体…...

洛谷 P3986 斐波那契数列

P3986 斐波那契数列 题目描述 定义一个数列&#xff1a; f ( 0 ) a , f ( 1 ) b , f ( n ) f ( n − 1 ) f ( n − 2 ) f(0) a, f(1) b, f(n) f(n - 1) f(n - 2) f(0)a,f(1)b,f(n)f(n−1)f(n−2) 其中 a, b 均为正整数&#xff0c;n ≥ 2。 问有多少种 (a, b)&…...

使用fastapi部署stable diffusion模型

使用vscode运行stable diffusion模型&#xff0c;每次加载模型都需要10分钟&#xff0c;为算法及prompt调试带来了极大麻烦。使用jupyter解决自然是一个比较好的方案&#xff0c;但如果jupyter由于种种原因不能使用时&#xff0c;fastapi无疑成为了一个很好的选择。 参考github…...

PyTorch使用(3)-张量类型转换

文章目录 张量类型转换1. 张量转换为 numpy 数组1.1. 默认行为&#xff1a;共享内存1.2. 避免内存共享1.2.1. 使用 .copy()1.2.2. 使用 torch.clone() .numpy() 1.3. 处理 GPU 张量1.4. 分离梯度跟踪1.5. 代码示例1.6. 关键注意事项1.7. 总结 2. 标量张量和数字的转换2.1. tor…...

基于FPGA的DDS连续FFT 仿真验证

基于FPGA的 DDS连续FFT 仿真验证 1 摘要 本文聚焦 AMD LogiCORE IP Fast Fourier Transform (FFT) 核心,深入剖析其在 FPGA 设计中的应用。该 FFT 核心基于 Cooley - Tukey 算法,具备丰富特性,如支持多种数据精度、算术类型及灵活的运行时配置。文中详细介绍了其架构选项、…...

【Spring 默认是否管理 Request 和 Session Bean 的生命周期?】

要测试 Spring 默认是否管理 Request 和 Session 作用域的 Bean 的生命周期&#xff0c;可以通过以下步骤实现&#xff1a; 验证 Spring 是否创建了 Bean&#xff1a;检查 Spring 容器是否成功加载并管理了 Request 和 Session 作用域的 Bean。验证 Bean 的生命周期回调方法是…...

Git的基本指令

一、回滚 1.git init 在项目文件夹中打开bash生成一个.git的子目录&#xff0c;产生一个仓库 2.git status 查看当前目录下的所有文件的状态 3.git add . 将该目录下的所有文件提交到暂存区 4.git add 文件名 将该目录下的指定文件提交到暂存区 5.git commit -m 备注信…...

【微信小程序(云开发模式)变通实现DeepSeek支持语音】

整体架构 前端&#xff08;微信小程序&#xff09;&#xff1a; 使用微信小程序云开发能力&#xff0c;实现录音功能。将录音文件上传到云存储。调用云函数进行语音识别和 DeepSeek 处理。界面模仿 DeepSeek&#xff0c;支持文本编辑。 后端&#xff08;云函数 Node.js&#…...

前端使用 crypto-js库AES加解密

前端使用 crypto-js库AES加解密 为什么需要前端加密&#xff1f; 现在项目使用http协议&#xff0c;且登录界面的用户登录密码是明文传输&#xff0c;项目真正上线后&#xff0c;存在信息泄露风险。 所以准备用前端框架加密处理用户输入的密码再传输。 crypto-js 库 crypto…...

七天MySQL密集学习计划

七天MySQL密集学习计划 第1天&#xff1a;MySQL基础和环境搭建 上午&#xff08;理论安装&#xff09; 数据库基本概念MySQL是什么关系型数据库基础安装MySQL Windows/Mac下安装步骤MySQL Workbench安装 基本配置和连接 下午&#xff08;基础操作&#xff09; 数据库和表的…...

Python程序常用的配置文件格式及例子(上)

Python 中常用的配置文件格式有多种&#xff0c;每种格式都有其特点和适用场景。以下是常见的配置文件类型及简要说明&#xff1a; 1. INI 格式 特点&#xff1a;简单键值对&#xff0c;支持分节&#xff08;Section&#xff09;。文件扩展名&#xff1a;.ini, .cfgPython 库&…...

Go语言对于MySQL的基本操作

一.下载依赖 终端中输入&#xff1a; go get -u github.com/go-sql-driver/mysql 导入包 import ("database/sql"_ "github.com/go-sql-driver/mysql" ) 二.案例 package main//go get-u github.com/go-sql-driver/mysql 获取驱动 import ("databa…...

一键批量txt转DWG,DWG转txt——插件实现 CAD c#二次开发

如下图&#xff0c;我们有大量dwg需要转为txt格式&#xff0c;或txt格式坐标需要转为dwg格式&#xff0c;此插件可一键完成一个文件夹下所有文件的转换。 插件使用方式 命令行输入&#xff1a; netload 加载此dll插件&#xff0c; 输入&#xff1a; dwg2txt 可将dwg转为t…...

SpringBoot 集成 Minio (附带工具类)

Minio 官方文档&#xff1a; https://www.minio.org.cn/docs/minio/container/index.html MinIO是一个对象存储解决方案&#xff0c;它提供了与Amazon Web Services S3兼容的API&#xff0c;并支持所有核心S3功能。 MinIO有能力在任何地方部署 - 公有云或私有云&#xff0c;裸…...

图论——Prim算法

53. 寻宝(第七期模拟笔试) 题目描述 在世界的某个区域,有一些分散的神秘岛屿,每个岛屿上都有一种珍稀的资源或者宝藏。国王打算在这些岛屿上建公路,方便运输。 不同岛屿之间,路途距离不同,国王希望你可以规划建公路的方案,如何可以以最短的总公路距离将 所有岛屿联通…...

Linux系统上后门程序的原理细节,请仔细解释一下

在Linux系统上&#xff0c;后门程序通常通过隐蔽的方式绕过正常的安全机制&#xff0c;允许攻击者未经授权访问系统。以下是其工作原理的详细解释&#xff1a; 1. 隐蔽性 隐藏进程&#xff1a;后门程序常通过修改进程列表或使用rootkit技术隐藏自身&#xff0c;避免被ps、top…...

Cursor与Blender-MCP生成3D模型

随着DeepSeek的热度&#xff0c;各行各业接入AI智能&#xff0c;当然作为一个深受3D爱好者喜爱的软件——Blender&#xff0c;也接入了AI智能&#xff0c;通过Blender-MCP&#xff0c;开启一场Blender的智能化模型创建的世界之旅。 目录 1.准备工作2.环境配置2.1 Mac安装2.2 W…...

Object 转 JSONObject 并排除null和““字符串

public static JSONObject objToJSONObject(Object obj) throws Exception{//创建一个 HashMap 对象 map&#xff0c;用于存储对象的属性名和属性值。//key 是属性名&#xff08;String 类型&#xff09;&#xff0c;value 是属性值&#xff08;Object 类型&#xff09;Map<…...

物联网为什么用MQTT不用 HTTP 或 UDP?

先来两个代码对比&#xff0c;上传温度数据给服务器。 MQTT代码示例 // MQTT 客户端连接到 MQTT 服务器 mqttClient.connect("mqtt://broker.server.com:8883", clientId) // 订阅特定主题 mqttClient.subscribe("sensor/data", qos1) // …...

LeetCode135☞分糖果

关联LeetCode题号135 本题特点 贪心两次遍历&#xff0c;一次正序遍历&#xff0c;只比较左边&#xff0c;左边比右边大的情况 i-1 i一次倒序遍历&#xff0c;只比较右边的&#xff0c;右边比左边大 i1 i 本题思路 class Solution:def candy(self, ratings: List[int]) -&g…...

YOLO魔改之频率分割模块(FDM)

目标检测原理 目标检测是一种将目标分割和识别相结合的图像处理技术,旨在从图像中定位并识别特定目标。深度学习方法,如Faster R-CNN和YOLO系列,已成为主流解决方案。这些方法通常采用两阶段或单阶段策略,通过卷积神经网络(CNN)提取特征并进行分类和定位。 在小目标检测中…...

AI学习——卷积神经网络(CNN)入门

作为人类&#xff0c;我们天生擅长“看”东西&#xff1a;一眼就能认出猫狗、分辨红绿灯、读懂朋友的表情……但计算机的“眼睛”最初是一片空白。直到卷积神经网络&#xff08;CNN&#xff09;​的出现&#xff0c;计算机才真正开始理解图像。今天&#xff0c;我们就用最通俗的…...

【资源损坏类故障】:详细了解坏块

目录 1、物理坏块与逻辑坏块 1.1、物理坏块 1.2、逻辑坏块 2、两个坏块相关的参数 2.1、db_block_checksum 2.2、db_block_checking 3、检测坏块 3.1、告警日志 3.2、RMAN 3.3、ANALYZE 3.4、数据字典 3.5、DBVERIFY 4、修复坏块 4.1、RMAN修复 4.2、DBMS_REPA…...

Django系列教程(13)——Cookie和Session应用场景及案例

目录 什么是cookie&#xff0c;cookie的应用场景及缺点 Django中如何使用cookie Cookie使用示例 什么是session及session的工作原理 Django中如何使用会话session Session使用示例 小结 HTTP协议本身是”无状态”的&#xff0c;在一次请求和下一次请求之间没有任何状态保…...

给管理商场消防安全搭建消防安全培训小程序全过程

一、需求沟通 “我是管理商场消防安全的嘛&#xff0c;做这个的作用呢&#xff0c;1是商场的所有商户员工可以看平面或者视频随时自学&#xff0c; 2是我们定期培训必修课程、考试&#xff0c;这个需要留存他们的手签字的签到表确认我们讲给他们听了&#xff08;免责很重要&am…...

YOLOv11 目标检测

本文章不再赘述anaconda的下载以及虚拟环境的配置&#xff0c;博主使用的python版本为3.8 1.获取YOLOv11的源工程文件 链接&#xff1a;GitHub - ultralytics/ultralytics: Ultralytics YOLO11 &#x1f680; 直接下载解压 2.需要自己准备的文件 文件结构如下&#xff1a;红…...

数据库原理实验报告:Powerdesigner建模E-R模型并转换表

注&#xff1a;此实验并不完整&#xff0c;仅供参考&#xff0c;如需完整版请私我留言 一、实验目的&#xff1a; 二、实验工具&#xff1a; 三、实验要求&#xff1a; 四、实验过程&#xff1a; 图文并茂&#xff0c;每一步都包含详细图片&#xff0c;总共11页word&#xff01…...