静态链接与动态链接

news2024/11/24 11:26:21

目录

静态链接

地址空间分配

静态链接的详细过程

静态链接库

动态链接

位置无关代码

延迟绑定机制


本篇会重点介绍静态链接,动态链接,延迟绑定机制

问:两个或者多个不同的目标文件是如何组成一个可执行文件的呢?

答:这就需要进行链接( linking )。链接由链接器( linker )完成,根据发生的时间不同,可分为编译时链接( compile time)、加载时链接( load time )和运行时链接(runtime )。

静态链接

测试代码

main.c

extern int shared;
extern void func(int * a, int * b);
int main()
{
	int a = 100;
	func(&a, &shared);
	return 0;
}

func.c

int shared = 1;
int tmp = 0;
void func(int * a, int * b)
{
	tmp = *a;
	*a = *b;
	*b = tmp;
}

地址空间分配

把两个目标文件链接成一个可执行文件

gcc -static -fno-stack-protector main.c func.c -save-temps --verbose -o func.ELF

在将main.o和func.o这两个目标文件链接成一个可执行文件时,最简单的方法是按序叠加这种方案的弊端是,如果参与链接的目标文件过多,那么输出的可执行文件会非常零散。而段的装载地址和空间以页为单位对齐,不足一页的代码节或数据节也要占用一页,这样就造成了内存空间的浪费。

另一种方案是相似节合并,将不同目标文件相同属性的节合并为一个节,如将main.o与func.o的.text节合并为新的.text 节,将main.o与 func.o中的.data节合并为新的.data节,这种方案被当前的链接器所采用,首先对各个节的长度、属性和偏移进行分析,然后将输入目标文件中符号表的符号定义与符号引用统一生成全局符号表,最后读取输入文件的各类信息对符号进行解析、重定位等操作。相似节的合并就发生在重定位时。完成后,程序中的每条指令和全局变量就都有唯一的运行时内存地址了。

静态链接的详细过程

为了构造可执行文件,链接器必须完成两个重要工作:符号解析( symbol resolution )和重定位( relocation )。

  • 符号解析是将每个符号(函数、全局变量、静态变量)的引用与其定义进行关联。
  • 重定位则是将每个符号的定义与一个内存地址进行关联,然后修改这些符号的引用,使其指向这个内存地址。

对比一下静态链接可执行文件 func.ELF和中间产物main.o的区别。使用objdump可以查看文件各个节的详细信息,这里我们重点关注.text、.data和.bss节。

objdump -h main.o

objdump -h func.ELF

5 .text         0008f480  00000000004004d0  00000000004004d0  000004d0  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
20 .data         00001af0  00000000006b90e0  00000000006b90e0  000b90e0  2**5
                  CONTENTS, ALLOC, LOAD, DATA
25 .bss          000016f8  00000000006bb2e0  00000000006bb2e0  000bb2d8  2**5
                  ALLOC

  • VMA( Virtual Memory Address )是虚拟地址
  • LMA( Load Memory Address )是加载地址,一般情况下两者是相同的。

尚未进行链接的目标文件 main.o的VMA都是0。而在链接完成后的 func.ELF中,相似节被合并,且完成了虚拟地址的分配。
使用objdump查看main.o的反汇编代码,参数“-mi386:intel”表示以intel格式输出。

objdump -d -M intel --section=.text main.o

main()函数的地址从0开始。其中,对func()函数的调用在偏移0x20处,0xe8是CALL指令的操作码,后四个字节是被调用函数相对于调用指令的下一条指令的偏移量。

由于还没有重定位,编译器并不知道func函数的位置以及变量shared的位置,所以其地址用0x0代替,之后的地址替换工作是交给链接器完成
查看func.ELF的符号地址

objdump -d -M intel --section=.text func.ELF | grep -A 16 "<main>"

调用func()函数的指令CALL位于0x4009c9,其下一条指令MOV位于0x4009ce,因此相对于MOV指令偏移量为0x07的地址为0x4009ce+0x07=0x4009d5,刚好就是func()函数的地址。同时,0x4009c1处也已经改成了shared 的地址0x6ca090。

查看main.o里的可重定位表

objdump -r main.o

可重定位文件中最重要的就是要包含重定位表,用于告诉链接器如何修改节的内容。每一个重定位表对应一个需要被重定位的节。

例如名为.rel.text的节用于保存.text节的重定位表。.rel.text包含两个重定位入口:

  • shared 的类型R_X86_64_32用于绝对寻址,CPU 将直接使用在指令中编码的32位值作为有效地址。
  • func的类型R_X86_64_PC32用于相对寻址,CPU将指令中编码的32位值加上PC (下一条指令地址)的值得到有效地址。需要注意的是,func-0x0000000000000004 中的-0x4是r_addend域的值,是对偏移的调整
     


静态链接库

后缀名为.a的文件是静态链接库文件,如常见的libc.a。一个静态链接库可以视为一组目标文件
经过压缩打包后形成的集合。执行各种编译任务时,需要许多个同的目标文件,比如输入输出有printf.o、scanf.o,内存管理有malloc.o等。为了方便管理,人们使用ar 工具将这些目标文件进行了压缩、编号和索引,就形成了libc.a。

动态链接

静态链接产生问题:

  • 内存空间的浪费,大部分可执行文件都需要glibc,在静态链接时需要把libc.a和编写的代码链接进去。多个可执行程序在内存中都包含这一部分,相同的库被多次链接,内存空间浪费。
  • 标准函数只要有改动,就需要重新编译整个源文件

动态链接:系统库和自己写的代码先不链接在一起,都是独立的模块,等到程序执行时在内存中完成链接。而且内存一个系统库可以被多个程序一起使用。这些被共享的库被称作共享库,或者共享对象,这个过程由动态链接器完成。

  • 当运行func1.ELF时,系统将func1.o和依赖的testLib.o装载入内存,然后进行动态链接。完成后系统将控制权交给程序人口点,程序开始执行。
  • 当运行func2.ELF想要执行时,由于内存中已经有testLib.o,因此不再重复加载,直接进行链接即可。

GCC默认使用动态链接编译,通过下面的命令我们将func.c编译为共享库,然后使用这个库编译main.c。

gcc -shared -fpic -o func.so func.c

gcc -fno-stack-protector -o func.ELF2 main.c ./func.so

参数-shared表示生成共享库, -fpic 表示生成与位置无关的代码。这样可执行文件 func.ELF2就会在加载时与func.so进行动态链接。另外动态加载器ld-linux.so本身就是一个共享库,因此加载器会加载并运行动态加载器,并由动态加载器来完成其他共享库以及符号的重定位。
 

位置无关代码

可以加载而不需要重定位的代码被称为位置无关代码(PIC),这个共享库的基本属性,通过给gcc传递 -fpic 参数可以生成 PIC 。这样一个共享库就可以被所有进程使用。

一个程序(或共享库)的数据段和代码段的相对距离不变,与绝对内存地址无关。于是就由了全局偏移量表(GOT),位于数据段的开头,用于保存全局变量和库函数的引用,每个条目占8个字节,加载时会进行重定位并填入符号的绝对地址。

因为引入了RELRO保护机制,GOT被拆分为 .got 和 .got.plt节两个部分:

  1. 前者不需要延迟绑定机制用于保存全局变量引用,加载内存后标记为只读;
  2. 后者需要延迟绑定用于保存函数引用,具有读写权限。

看一下 func.so 

objdump -h func.so

readelf -r func.so | grep tmp

objdump -d -M intel --section=.text func.so | grep -A 20 "<func>"

 全局变量 tmp 位于GOT上,R_X86_64_GLOB_DAT 表示需要动态链接器找到 tmp 的值并填充到0x200fd8。在func()函数需要取出 tmp时,计算符号相对PC的偏移 rip+0x2009e5 ,也就是0x6c9+0x2009e5=0x200fd8。

延迟绑定机制

由于动态链接是由动态链接器在程序加载时进行的,如果有很多个程序需要加载,会影响到动态链接器的性能。延迟绑定,其思想是当函数第一次被调用时,动态链接器才进行符号查找,重定位等操作,没被调用就不进行绑定。

ELF文件通过过程链接表(Procedure Linkage Table,PLT )和GOT的配合来实现延迟绑定,每个被调用的库函数都有一组对应的PLT和GOT。

位于代码段.plt节的PLT是一个数组,每个条目占16个字节。其中 PLT[0]用于跳转到动态链接器,PLT[1]用于调用系统启动函数_libc_start_main(),我们熟悉的main()函数就是在这里面调用的,从PLT[2]开始就是被调用的各个函数条目。

位于数据段.got.plt节的GOT也是一个数组,每个条目占8个字节。其中 GOT[0]和 GOT[1]包含动态链接器在解析函数地址时所需要的两个地址(.dynamic和 relor条目),GOT[2]是动态链接器ld-linux.so 的人口点,从GOT[3]开始就是被调用的各个函数条目,这些条目默认指向对应PLT条目的第二条指令,完成绑定后才会被修改为函数的实际地址。

当func.ELF2调用库函数 func()为例

  1. 当main调用func时,会进入0x4005b0这个地址,也就是PLT[2];
  2. jmp指令跳会找到0x601020这个地址,也就是GOT[4],取地址中的值,跳转到0x4005b6,也就是PLT[2],把0x1压入栈(func在.rel.plt中的下标)压栈;
  3. 之后jmp跳转到0x400590,也就是PLT[0],把GOT[1]压入栈;
  4. 调用GOT[2],也就是动态链接器的_dl_runtime_resolve()函数,完成符号解析和重定位工作,并把func的真实地址填入func@got.plt,也就是GOT[4],最后程序控制权给func(),延迟绑定完成。
  5. 之后如果需要调用func(),直接跳转到func@got.plt,控制去哪交给func()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1044178.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【计算机网络】 基于UDP的简单通讯(客户端)

文章目录 客户端流程代码实现添加头文件以及库依赖加载库创建套接字发送接收数据关闭套接字、卸载库 测试 客户端 流程 客户端跟服务端差不多&#xff0c;也要先加载库&#xff0c;在加载库之后也要创建套接字&#xff0c;但是客户端一定是没有绑定ip地址的&#xff0c;之后是…

【Java 基础篇】Java 注解详解

在 Java 编程中&#xff0c;注解&#xff08;Annotation&#xff09;是一种元数据&#xff0c;它提供了关于程序代码的额外信息。注解不直接影响程序的执行&#xff0c;但可以在运行时提供有关程序的信息&#xff0c;或者让编译器执行额外的检查。 本文将详细介绍 Java 注解的…

Mac电脑剪切键Command-X键失灵

在Mac上&#xff0c;Command-X键的剪切功能失效可能是由于键盘快捷键设置出现错误或者剪切的目标文件处于只读状态。 可以尝试以下方法进行解决&#xff1a; 1.检查键盘快捷键设置&#xff1a;转到Apple菜单 > 系统偏好设置 > 辅助功能 > 键盘 > 快捷键&#xff0c…

Pytorch梯度累积实现

前言 主要用于解决显卡内存不足的问题。 梯度累积可以使用单卡实现增大batchsize的效果 梯度累积原理 按顺序执行Mini-Batch&#xff0c;同时对梯度进行累积&#xff0c;累积的结果在最后一个Mini-Batch计算后求平均更新模型变量。 a c c u m u l a t e d ∑ i 0 N g r a…

nat综合实验

路漫漫其修远兮,吾将上下而求索。 实验目的如图 实验思路&#xff1a;配置内网&#xff0c;再配置外网&#xff0c;再做nat clien1配置 clien2配置 pc3配置 lsw1配置 sysname lsw1 # vlan batch 10 20 30 # interface MEth0/0/1 # interface Eth-Trunk1port link-type trunkp…

【Linux】IO操作

IO 典型 IO 模型阻塞 IO非阻塞 IO信号驱动 IO异步 IO常见问题 多路转接模型select 模型poll 模型epoll 模型 典型 IO 模型 IO 操作指的就是数据的输入输出操作&#xff1b;IO 过程可以分为两个步骤&#xff1a;等待 IO 就绪、数据拷贝 阻塞 IO 发起 IO 操作&#xff0c;若当…

【面试高高手】 —— Java基础(36题)

文章目录 1. 八大基本数据类型分类2. 重写和重载的区别3. int和integer区别4. Java的关键字5. 什么是自动装箱和拆箱&#xff1f;6. 什么是Java的多态性&#xff1f;7. 接口和抽象类的区别&#xff1f;8. Java中如何处理异常&#xff1f;9. Java中的final关键字有什么作用&…

iview 的table表格组件使单元格可编辑和输入

表格的列定义中&#xff0c;在需要编辑的字段下使用render函数 template表格组件 <Table border :data"data" :columns"tableColumns" :loading"loading"></Table>data中定义table对象 table: {tableColumns: [{title: 商品序号,k…

服务断路器_Resilience4j的断路器

断路器&#xff08;CircuitBreaker&#xff09;相对于前面几个熔断机制更复杂&#xff0c;CircuitBreaker通常存在三种状态&#xff08;CLOSE、OPEN、HALF_OPEN&#xff09;&#xff0c;并通过一个时间或数量窗口来记录当前的请求成功率或慢速率&#xff0c;从而根据这些指标来…

【JVM】第三篇 JVM对象创建与内存分配机制深度剖析

目录 一. JVM对象创建过程详解1. 类加载检查2. 分配内存2.1 如何划分内存?2.2 并发问题 3. 初始化4. 设置对象头5. 执行<init>方法 二. 对象头和指针压缩详解三. JVM对象内存分配详解四.逃逸分析 & 栈上分配 & 标量替换详解1. 逃逸分析 & 栈上分配2. 标量替…

用纹理图集优化3D场景性能【Texture Atlas】

推荐&#xff1a;用 NSDT编辑器 快速搭建可编程3D场景 在 Unity 中开发移动应用程序时&#xff0c;确保一切都得到优化始终至关重要。 最大化帧速率使我们能够专注于优化脚本、烘焙灯光、修改对象等。 当我们将移动应用程序带入虚拟现实时&#xff0c;这一点变得更加重要。 虽…

嵌入式Linux应用开发-文件 IO

嵌入式Linux应用开发-文件 IO 第四章 文件 IO4.1 文件从哪来&#xff1f;4.2 怎么访问文件&#xff1f;4.2.1 通用的 IO 模型&#xff1a;open/read/write/lseek/close4.2.2 不是通用的函数&#xff1a;ioctl/mmap 4.3 怎么知道这些函数的用法&#xff1f;4.4 系统调用函数怎么…

基于微信小程序的健身小助手打卡预约教学系统(源码+lw+部署文档+讲解等)

文章目录 前言系统主要功能&#xff1a;用户的功能设计为&#xff1a;管理员的功能设计为&#xff1a;健身房的功能设计为&#xff1a;具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获…

QFrame类学习笔记

1、QFrame的作用 QFrame类继承于QWidget类&#xff0c;被QAbstractScrollArea, QLabel, QLCDNumber, QSplitter, QStackedWidget, and QToolBox等类继承。 QFrame作为许多基础控件的基类&#xff0c;提供许多成员方法给子类&#xff0c;实现子类的框架样式的设计。框架样式主要…

Android 13 定制化开发--开启相机或麦克风时,去掉状态栏上的绿色图标

Android 12 或更高版本的设备上&#xff0c;当应用使用麦克风或相机时&#xff0c;图标会出现在状态栏中。如果应用处于沉浸模式&#xff0c;图标会出现在屏幕的右上角。用户可以打开“快捷设置”&#xff0c;并选择图标以查看哪些应用当前正在使用麦克风或摄像头。图 1 显示了…

Ubuntu 安装Kafka

在本指南中&#xff0c;我们将逐步演示如何在 Ubuntu 22.04 上安装 Apache Kafka。 在大数据中&#xff0c;数以百万计的数据源生成了大量的数据记录流&#xff0c;这些数据源包括社交媒体平台、企业系统、移动应用程序和物联网设备等。如此庞大的数据带来的主要挑战有两个方面…

【数据结构】插入排序:直接插入排序、折半插入排序、希尔排序的学习知识总结

目录 1、排序的基本概念 2、直接插入排序 2.1 算法思想 2.2 代码实现 3、折半插入排序 3.1 算法思想 3.2 代码实现 4、希尔排序 4.1 算法思想 4..2 代码实现 1、排序的基本概念 排序是将一组数据按照预定的顺序排列的过程&#xff0c;排序的基本概念包括以下内容…

自学WEB后端01-安装Express+Node.js框架完成Hello World!

一、前言&#xff0c;网站开发扫盲知识 1.网站搭建开发包括什么&#xff1f; 前端 前端开发主要涉及用户界面&#xff08;UI&#xff09;和用户体验&#xff08;UX&#xff09;&#xff0c;负责实现网站的外观和交互逻辑。前端开发使用HTML、CSS和JavaScript等技术来构建网页…

数据结构--快速排序

文章目录 快速排序的概念Hoare版本挖坑法前后指针法快速排序的优化三数取中法小区间用插入排序 非递归的快速排序 快速排序的概念 快速排序是通过二叉树的思想&#xff0c;先设定一个值&#xff0c;通过比较&#xff0c;比它大的放在它的右边&#xff0c;比它小的放在它的左边…