动态链接(8/11)

news2024/11/25 22:24:45

静态链接的缺点:生成的可执行文件体积较大,当多个程序引用相同的公共代码时,这些公共代码会多次加载到内存,浪费内存资源。

为了解决这个问题,动态链接对静态链接做了一些优化:对一些公用的代码,如库,在链接期间暂不链接,而是推迟到程序运行时再进行链接。这些在程序运行时才参与链接的库被称为动态链接库。程序运行时,除了可执行文件,这些动态链接库也要跟着一起加载到内存,参与链接和重定位过程,否则程序可能就会报未定义错误,无法运行。

动态链接的好处是节省了内存资源:加载到内存的动态链接库可以被多个运行的程序共享,使用动态链接可以运行更大的程序、更多的程序,升级也更加简单方便。

在 Windows 下解压一个软件安装包,里面的 .dll 后缀的文件就是动态链接库,需要和可执行文件一起安装到系统中。程序在运行前会首先把它们加载到内存,链接成功后程序才能运行。

在 Linux 环境下,动态库文件的后缀为 .so。

gcc -fPIC -shared add.c sub.c mul.c div.c -o libtest.so
gcc main.c libtest.so
cp libtest.so /usr/lib/ # 程序就可以运行了

在上面的程序中,可执行文件 a.out 是动态链接生成的,所以在运行 a.out 之前,libtest.so 这个动态链接库要放到 /lib、/usr/lib 等系统默认的库路径下,否则 a.out 就会链接失败,无法正常运行。

在 Linux 环境下,当我们运行一个程序时,操作系统首先会给程序 fork 一个子进程,接着动态链接器被加载到内存,操作系统将控制权交给动态链接器,让动态链接器完成动态库的加载和重定位操作,最后跳转到要运行的程序。

动态链接器本身也是一个动态库(/lib/ld-linux.so文件),动态链接器被加载到内存后,会首先给自己重定位,然后才能运行。像这种自己给自己重定位然后自动运行的行为,一般称为自举。

动态链接器解析可执行文件中未确定的符号及需要链接的动态库信息,将对应的动态库加载到内存,并进行重定位操作。这个过程其实和静态链接的重定位过程一样,只不过推迟到了运行阶段而已。重定位结束后,程序中要引用的所有符号都有了地址和定位,动态链接器将控制权交给要执行的程序,跳转到该程序运行。

在这里插入图片描述
动态链接需要考虑的一个重要问题是加载地址。静态链接时,加载地址等于链接地址,这个地址是固定的。动态链接过程中,类似静态链接的重定位,动态链接库被加载到内存后,目标文件的起始地址也发生了变化,需要重定位。一个可执行文件对动态链接库的符号引用,要等动态链接库加载到内存后地址才能确定,然后对可执行文件中的这些符号修改即可。

main() 函数调用了 add() 函数,但 add() 函数的地址还不能确定,等到 libtest.so 加载到内存后,add() 函数的地址才能确定下来。加载器通过动态链接、重定位操作,更新了符号表中 add() 函数的实际地址,并修正 main() 函数指令中引用 add() 函数的地址,然后程序才可以正常运行。

这种装载时重定位的操作,虽然解决了可执行文件中绝对地址的引用问题,但也带来了另外一个问题:对于每个进程,动态库被加载到了内存的不同地址,也只能被进程自身共享,无法在多个进程间共享,无法节省内存,违背了动态库的设计初衷。如果有一种方法,将动态库设计成无论放到哪里,都可以执行,而且可以被多个进程共享,那么这个问题就迎刃而解了。

与地址无关的代码

如果想让我们的动态库放到内存的任何位置都可以运行,都可以被多个进程共享,一种比较好的方法是将我们的动态库设计成与地址无关的代码。将指令中需要修改的部分(如绝对地址符号的引用)分离出来,剩余的部分就和地址无关了。需要被修改的指令(符号)和数据在每个进程中都有一个副本,互不影响各自的运行。

编译代码时加上 -fPIC 参数(Position-Independent Code)就可以实现代码与地址无关:把这段代码放在内存中的任何位置,都无须重定位,直接运行即可(使用相对跳转指令代替对绝对地址的访问)。

全局偏移表

在动态库的设计中,对于模块内的符号相互引用,通过相对寻址很容易实现代码与地址无关。但是当动态库作为第三方模块被不同的应用程序引用时,库中的一些绝对地址符号(如函数名)将不可避免地被多次调用,需要重定位。动态库中的这些绝对地址符号,如何能做到同时被不同的应用程序引用呢?

每个应用程序将引用的动态库(绝对地址)符号收集起来,保存到一个表中,这个表用来记录各个引用符号的地址。当程序在运行过程中需要引用这些符号时,可以通过这个表查询各个符号的地址。这个表被称为全局偏移表(Global Offset Table,GOT)。

在一个可执行文件中,其引用的动态库中的绝对地址符号会被分离出来,单独保存到 GOT 表中,GOT 表以 section 的形式保存在可执行文件中,这个表的地址在编译阶段已经确定了。当程序运行需要引用动态库中的函数时,会将动态库加载到内存,根据动态库被加载到内存中的具体地址,更新 GOT 表中的各个符号(函数)的地址。等下次该符号被引用时,程序可以直接跳到 GOT 表查询该符号的地址,因为 GOT 表在可执行文件中的位置是固定不变的,所以程序中访问 GOT 表的指令也是固定不变的,唯一需要变化的是:动态库加载到内存后,库中的各个函数的位置确定,在 GOT 表中实时更新各个符号在内存中的真实地址就可以了。

这样做的好处是:在内存中只需要加载一份动态库,当不同的程序运行时,只要修改各自的 GOT 表,它们引用的符号都可以指向同一份动态库。

延迟绑定

动态连接通过使用与地址无关这一技术,加载到内存任意地址都可以运行。与地址无关这一技术在 ARM 平台可以使用相对寻址来实现。ARM 相对寻址的本质其实就是寄存器间接寻址,只不过基址换成了 PC 而已,访问效率还是比较低的,包括程序运行之前的动态链接和重定位操作,也会对程序的及时响应和性能造成一定的影响。可执行文件一般都采用延迟绑定:程序在运行时,并不急着把所有的动态库都加载到内存中并对它们进行重定位。当动态库中的函数第一次被调用时,才会把用到的动态库加载到内存并进行重定位。

C 标准库起始就是以动态库的封装形式保存在 Linux 系统中的,不同的应用程序都会调用 printf() 函数,当它们在内存中运行时,只需要加载一份 printf() 函数代码到内存就可以了。各个应用程序在引用 printf 这个符号时,就会启动链接器,将这份代码映射到各自进程的地址空间,更新各自 GOT 表中 printf() 函数的实际地址,然后通过查询 GOT 表找到 printf() 在内存中的实际地址,就可通过间接访问跳转执行。

共享库

现在大多数软件都是采用动态链接的方式开发的,不仅可以节省内存空间,升级维护也比较方便。在发布软件包时,可执行文件及其以来的动态链接共享库被一起打包发布,如果你依赖的是系统默认自带的共享库,如 C 标准库,则不需要跟软件一起打包。程序安装时,可执行文件会复制到 Linux 系统的默认路径下,如 /bin、/sbin、/usr/bin、/usr/local/bin 等,这些路径由环境变量 PATH 管理和维护。可执行文件依赖的共享库一般要放到库的默认路径下面:如 /lib、/usr/lib 等。当程序运行时,动态链接器首先被加载到内存运行,动态链接器会分析可执行文件,从可执行文件的 .dynamic 段中查询该程序运行需要依赖的动态共享库,然后到库的默认路径下查找这些共享库,加载到内存中并进行动态链接,链接成功后将 CPU 的控制权交给可执行程序,程序就可以正常运行了。

动态链接器在查找共享库的过程中,除了到系统默认的路径下查找,也会到用户指定的一些路径下去查找,用户可以在 /etc/ld.so.conf 文件中添加自己的共享库路径。为了减少每次查找文件的时间消耗,/etc/ld.so.conf 修改后,可以使用 ldconfig 命令生成一个缓存 /etc/ld.so.cache 以提高查找效率。每当我们新增、删除或修改共享库的路径时,使用 ldconfig 更新一下缓存就可以了。

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

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

相关文章

邻接表创建无向表(C++ 代码)

#include<iostream>//邻接表创建无向表 #define MVNum 100 using namespace std; typedef char VerTexType; typedef struct Arcnode//边节点 {int adjvex;//该边所指向的顶点的位置struct Arcnode* nextarc;//指向下一条边的指针 }Arcnode; typedef struct vnode//顶点节…

【数据结构】二叉树篇| 纲领思路02+刷题

博主简介&#xff1a;努力学习的22级计算机科学与技术本科生一枚&#x1f338;博主主页&#xff1a; 是瑶瑶子啦每日一言&#x1f33c;: 所谓自由&#xff0c;不是随心所欲&#xff0c;而是自我主宰。——康德 目录 一、前言二、刷题1、翻转二叉树 2、二叉树的层序遍历✨3、 二…

SpringMVC 详细教程及源码讲解

目录 一、SpringMVC简介1. 什么是MVC&#xff1f;2.什么是SpringMVC?3.SpringMVC的特点&#xff1f; 二、SpringMVC入门案列1. 开发环境2. 创建Maven工程2.1 添加web模块2.2 引入依赖 3. 配置web.xml3.1 默认配置方式3.2 扩展配置方式 4.创建请求控制器5. 创建SpringMVC的配置…

基于ChatGLM的Deepin文档问答Bot

一、背景介绍 题目来源&#xff1a;2023全国大学生计算机系统能力大赛操作系统设计赛-功能挑战赛题目地址&#xff1a;proj225-document-question-answering-bot题目描述&#xff1a;https://wiki.deepin.org 上有900多条deepin系统相关的中文教程和词条&#xff0c;请编写能根…

【第358场周赛】翻倍以链表形式表示的数字,Java解密。

LeetCode 第358场周赛 恒生专场。 文章目录 剑指Offer:翻倍以链表形式表示的数字示例:限制:解题思路:剑指Offer:翻倍以链表形式表示的数字 【题目描述】 给你一个 非空 链表的头节点 head ,表示一个不含前导零的非负数整数。 将链表 翻倍 后,返回头节点 head 。 示例…

django——创建 Django 项目和 APP

2.创建 Django 项目和 APP 命令&#xff1a; 创建Django项目 django-admin startproject name 创建子应用 python manager.py startapp name 2.1 创建工程 在使用Flask框架时&#xff0c;项目工程目录的组织与创建是需要我们自己手动创建完成的。 在django中&#xff0c;…

第四课 学习动词短语

文章目录 前言动词短语动副词组及物动副词组实义动词副词介词动宾词组固定特殊可分开动词短语时态变化规则 一、动副词组1、go ahead 先走&#xff0c;进行 不及物go along 前进&#xff0c;向前走&#xff0c;与....一道去go away 走开&#xff0c;离去&#xff0c;逃走&#…

全网最全的接口自动化测试教程

为什么要做接口自动化 相对于UI自动化而言&#xff0c;接口自动化具有更大的价值。 为了优化转化路径或者提升用户体验&#xff0c;APP/web界面的按钮控件和布局几乎每个版本都会发生一次变化&#xff0c;导致自动化的代码频繁变更&#xff0c;没有起到减少工作量的效果。 而…

用友 U8 CRM 任意文件上传+读取漏洞复现(HW0day)

0x01 产品简介 用友U8 CRM客户关系管理系统是一款专业的企业级CRM软件&#xff0c;旨在帮助企业高效管理客户关系、提升销售业绩和提供优质的客户服务。 0x02 漏洞概述 用友 U8 CRM客户关系管理系统 getemaildata.php 文件存在任意文件上传和任意文件读取漏洞&#xff0c;攻击…

RK3399平台开发系列讲解(入门篇)Linux内核常见的规则

🚀返回专栏总目录 文章目录 一、编码风格二、内核结构分配和初始化三、面向对象的思想沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 本篇将介绍在内核代码的演化过程中应该遵守标准规则 一、编码风格 参考一下内核编码风格手册,它位于内核源代码树的 Documentat…

Flink 火焰图

方式一 使用 Flink Web UI 的 Flame Graph Flink 自己也支持了 Task 粒度的 Flame Graphs 功能,并且可以细化到 subtask 粒度。 第一步:配置启用功能 Flink 作业动态参数里增加配置:“rest.flamegraph.enabled”: “true” 并重启作业。当前该功能没有默认开启,因为这个功…

ROS2 学习(一)介绍,环境搭建,以及个人安装的一些建议

ROS2 学习 学习自b站课程&#xff1a;https://www.bilibili.com/video/BV16B4y1Q7jQ?p1 &#xff08;up主&#xff1a;古月居GYH&#xff09; ROS 介绍 Robot OS&#xff0c;为机器人开发提供了相对完善的 middleware&#xff0c;工具&#xff0c;软件等。 ROS1 对嵌入式设…

ProsperEx 的野望:借势 RWA 浪潮,构建全新的链上衍生品体系

真实资产代币化&#xff08;RWA&#xff09;并不是一个新概念了&#xff0c;以 USDT、USDC、DAI 等一系列美元稳定币是行业内最早的 RWA 概念资产&#xff0c;这些资产以美元为价值基础通过不同信用的机制&#xff0c;将其价值映射至链上&#xff0c;并以加密货币的形式体现&am…

关于memset的小实验

关于memset的小实验 memset是包含在<string.h>的函数&#xff0c;用来给字符数组赋值。然而人们常常把它拿来给整型变量赋值。 void *MEMSET (void *dstpp, int c, size_t len)memset是一个返回通用指针的函数&#xff0c;返回的地址便是输入的地址 int c表示对这块内…

Linux学习之awk函数

awk里边的函数分为内置函数和自定义函数。 内置函数有下边的几种&#xff1a; 算术函数&#xff08;arithmetic&#xff09; 字符串函数&#xff08;string&#xff09; 输入/输出函数和通用函数&#xff08;input/output, and general&#xff09; 自定义函数格式如下&#xf…

企业计算机服务器中了360后缀勒索病毒怎么办,勒索病毒解密数据恢复

随着计算机技术的不断发展&#xff0c;企业的办公系统得到了很大提升&#xff0c;但是随之而来的网络安全威胁也不断增加&#xff0c;勒索病毒的攻击事件时有发生。近期&#xff0c;我们收到某地连锁超市的求助&#xff0c;企业的计算机服务器遭到了360后缀勒索病毒攻击&#x…

【算法题】螺旋矩阵III (求解n阶蛇形矩阵)

一、问题的提出 n阶蛇形矩阵的特点是按照图1所示的方式排列元素。n阶蛇形矩阵是指矩阵的大小为nn&#xff0c;其中n为正整数。 题目背景 一个 n 行 n 列的螺旋矩阵可由如图1所示的方法生成&#xff0c;观察图片&#xff0c;找出填数规律。填数规则为从 1 开始填到 nn。 图1 …

SPI协议简介

什么是SPI&#xff1f; SPI是串行外设接口&#xff08;Serial Peripheral Interface&#xff09;的缩写&#xff0c;是美国摩托罗拉公司&#xff08;Motorola&#xff09;最先推出的一种同步串行传输规范&#xff0c;也是一种单片机外设芯片串行扩展接口&#xff0c;是一种高速…

[管理与领导-13]:IT基层管理者 - 激励 - 除了薪资奖金,还有哪些激励手段?

目录 前言 第1章 问题现象&#xff1a;对激励的误解 第一个误解&#xff1a;就是激励就是给钱 第二个误解&#xff1a;就是激励当成了升职 第三个误解&#xff1a;把激励当成忽悠 第2章 背后原因 1 没有找到和满足员工真正的需求&#xff1a;不同人&#xff0c;需求不同…

【数据结构与算法——TypeScript】树结构Tree

【数据结构与算法——TypeScript】 树结构(Tree) 认识树结构以及特性 什么是树? &#x1f332; 真实的树&#xff1a;相信每个人对现实生活中的树都会非常熟悉 &#x1f332; 我们来看一下树有什么特点&#xff1f; ▫️ 树通常有一个根。连接着根的是树干。 ▫️ 树干到…