动静态链接与加载

目录

静态链接

ELF加载与进程地址空间(静态链接)

动态链接与动态库加载

GOT表


静态链接

对于多个.o文件在没有链接之前互相是不知到对方存在的,也就是说这个.o文件中调用函数的的跳转地址都会被设定为0(当然这个函数是在其他.o文件中定义的)这个地址会在哪个时候被修正?链接的时候!为了让链接器将来在链接时能够正确定位到这些被修正 的地址,在代码块(.data)中还存在⼀个重定位表,这张表将来在链接的时候,就会根据表⾥记录的地址将其修正。这也就是为什么.o文件叫做可重定位文件。

所以,链接过程中会涉及到对.o中外部符号进⾏地址重定位。

ELF加载与进程地址空间(静态链接)

从上面的连接过程可以看到,在我们链接完成的之后形成的可执行程序中是有地址的,这个时候程序显然没有加载到内存中那这个地址就不可能是内存中的物理地址。事实上这个地址是一种逻辑地址,其思想与虚拟地址类似,也与虚拟地址对应,也就是说磁盘上的逻辑地址就是以后运行可执行程序时的虚拟地址。在当代计算机内部,这个逻辑地址采用平坦模式进行编址(也就是从0开始编址)。所以也要求ELF文件对自己的代码和数据进行统一编址。

简直巧妙,原来虚拟地址跟磁盘中可执行文件的逻辑地址是对应的。我们知道可执行程序的执行需要os创建子进程来执行,那么mm_struct、vm_area_struct在进程刚刚创建的时候,初始化数据从哪⾥来?就从逻辑地址来。从ELF各个segment来,每个segment有⾃⼰的起始地址和⾃⼰的⻓度,⽤来初始化内核结构中的[start, end] 等范围数据,另外再⽤详细地址,填充⻚表。

mm_struct  描述进程的整个虚拟地址空间,包含所有 vm_area_struct 的链表或红黑树。例如:

struct mm_struct {struct vm_area_struct *mmap;  // 虚拟内存区域链表unsigned long start_code;      // 代码段起始地址(ELF的 .text)unsigned long end_code;unsigned long start_data;     // 数据段起始地址(ELF的 .data)unsigned long end_data;// ...
};

vm_area_struct   描述一个连续的虚拟内存区域(如一个ELF段),包括权限、文件映射信息等。

struct vm_area_struct {unsigned long vm_start;        // 起始虚拟地址(ELF的 p_vaddr)unsigned long vm_end;          // 结束虚拟地址struct file *vm_file;          // 关联的ELF文件unsigned long vm_pgoff;        // 文件中的偏移(对应ELF段在文件中的位置)pgprot_t vm_page_prot;         // 访问权限(如可读、可执行)// ...
};

示例:ELF加载到虚拟地址空间

假设一个ELF文件有两个可加载段:

  1. 代码段:.textp_vaddr = 0x400000p_memsz = 0x1000

  2. 数据段:.datap_vaddr = 0x401000p_memsz = 0x2000

进程创建时,内核会:

  1. 创建两个 vm_area_struct

    • 代码段:vm_start=0x400000vm_end=0x401000, 权限为 RX(读+执行)。

    • 数据段:vm_start=0x401000vm_end=0x403000, 权限为 RW(读+写)。

  2. 通过 mmap 将这两个段映射到虚拟地址空间,但物理内存尚未分配。

  3. 程序先加载到内存,用虚拟地址初始化了mm_struct,当进程首次执行 0x400000 处的指令时,触发缺页中断,内核将 .text 段的内容从磁盘加载到物理内存,并更新页表。

问题是cpu怎么知道从哪里开始执行呢?ELF文件的LEF Header中有一个Entry point address 这个就是程序的入口地址。cpu中有一个寄存器EIP其中存放的是当前执行指令的下一条指令的地址,CR3寄存器执行页表。所以当程序开始执行的就时候就将Entry point address中的地址load到cpu中的EIP寄存器中,然后程序从入口开始执行。

动态链接与动态库加载

我们知道动态库跟我们编译链接好的可执行和程序之间是独立的存在于磁盘的。

我们的所有依赖于动态库的可执行文件都依赖于一个这个库:/lib64/ld-linux-x86-64.so.2,lib64/ld-linux-x86-64.so.2 是 Linux 系统中的一个动态链接器库文件,主要用于在程序运行时动态加载和链接共享库(.so 文件)

在我们要运行可执行程序时,我们先是跟静态库一样的过程,先通过Entry point address找到程序的入口,事实上程序的入口就是_start函数,这是一个由C运⾏时库(通常是glibc)或链接器(如ld)提供的特殊函数。在_start函数中会执行一下一系列操作:

1.设置堆栈:为程序设置一个初始的堆栈环境

2.初始化数据段:将程序的数据段(全局变量和静态变量)从初始化数据段复制到相应的内存位置,并清零未初始化的数据段。

3.动态链接:_start函数会调用动态链接器的代码来解析和加载程序运行所需要的动态库,动态连接器会处理所有的符号解析和重定位,确保程序中的调用函数和变量访问能够正确的映射到动态库中的实际地址。(动态链接实际上将链接的整个过程推迟到了程序加载的时候

动态连接的优点:可以看到对于不同的进程如果需要同一个库中的函数,我们只需要在内存中加载一份动态库,分配一份物理地址即可,但是对于静态库来说,其可执行文件就是已经包含静态库中的函数的了,所以其磁盘空间和内存空间都是会产生浪费的。

动态链接器                                                                                                                                      动态链接器(如ld-linux.so)负责在程序运⾏时加载动态库

当程序启动时,动态链接器会解析程序中的动态库依赖,并加载这些库到内存中。
环境变量和配置⽂件
Linux系统通过 环境变量(如LD_LIBRARY_PATH) 配置⽂件(如/etc/ld.so.conf 及其⼦配置
⽂件)来指定动态库的搜索路径。 这些路径会被动态链接器在加载动态库时搜索。
缓存文件
为了提⾼动态库的加载效率,Linux系统会维护⼀个名为 /etc/ld.so.cache的缓存⽂件 。 该⽂件包含了系统中所有已知动态库的路径和相关信息,动态链接器在加载动态库时会⾸先 搜索这个缓存⽂件。
4.调⽤ __libc_start_main :⼀旦动态链接完成, _start 函数会调⽤ __libc_start_main (这是glibc提供的⼀个函数)。 __libc_start_main 函数负责执⾏ ⼀些额外的初始化⼯作,⽐如设置信号处理函数、初始化线程库(如果使⽤了线程)等。
5. 调⽤ main 函数:最后, __libc_start_main 函数会调⽤程序的 main 函数,此时程序的执⾏控制权才正式交给⽤⼾编写的代码。
6. 处理 main 函数的返回值:当 main 函数返回时, __libc_start_main 会负责处理这个返回
值,并最终调⽤ _exit 函数来终⽌程序。
上述过程描述了C/C++程序在 main 函数之前执⾏的⼀系列操作,但这些操作对于⼤多数程序员来说 是透明的。程序员通常只需要关注 main 函数中的代码,⽽不需要关⼼底层的初始化过程。然⽽,了解这些底层细节有助于更好地理解程序的执⾏流程和调试问题。

但是我们的程序具体是怎么和库映射起来的?

首先可执行程序中存有依赖的动态库的路径,通过这个路径可以将动态库加载到物理内存。动态库也是采用了平坦模式进行编址,我们叫做库中方法的偏移量。然后通过创建新的mm_area_struct用库的大小开辟一段新的进程地址空间,就能得到库的虚拟地址,并建立页表映射关系。通过库的虚拟地址和库中的偏移量就能找到对应的方法。


 所以库函数的调用机制如下:                                                                                                         库已经被我们映射到了当前进程的地址空间中 库的虚拟起始地址我们也已经知道了,库中每⼀个   ⽅法的偏移量地址我们也知道

 所有:访问库中任意⽅法,只需要知道库的起始虚拟地址+⽅法偏移量即可定位库中的⽅法

 ⽽且:整个调⽤过程,是从 代码区跳转到共享区,调⽤完毕在返回到代码区 ,整个过程完全在进   程地址空间中进⾏的。

GOT表

也就是说,我们的程序运⾏之前,先把所有库加载并映射,所有库的起始虚拟地址都应该提前知道
然后对我们加载到内存中的程序的库函数调⽤进⾏地址修改,在内存中⼆次完成地址设置 (这个叫做加载地址重定位) 但是内存中的代码段是不可写的。所以:动态链接采⽤的做法是在.data (可执⾏程序或者库⾃⼰)中专⻔预留⼀⽚区域⽤来存放函数的跳转地址,它也被叫做全局偏移表GOT,表中每⼀项都是本运⾏模块要引⽤的⼀个全局变量或函数的地址。

那GOT具体是怎么工作的呢?     比如,程序在编译时,对于外部函数比如printf,编译器并不知道它运行时的具体地址,所以会在GOT中生成一个条目。当程序第一次调用printf时,动态链接器(如ld-linux.so)会找到printf的实际地址并填入GOT中,之后的调用就直接使用这个地址了。这样可以实现延迟绑定,也就是PLT(Procedure Linkage Table)和GOT配合使用。PLT负责跳转到GOT中的地址,而GOT存储实际的地址。第一次调用时,GOT中的地址可能指向PLT中的解析代码,由动态链接器完成地址解析后,GOT中的条目会被更新为正确的地址。另外,GOT还可能用于全局变量的访问,因为动态库中的全局变量地址在加载时确定,也需要通过GOT来间接访问。

但在不 同进程的地址空间中,各动态库的绝对地址、相对位置都不同。反映到GOT表上 ,就是每个进程的 每个动态库都有独⽴的GOT表 ,所以进程间不能共享GOT表。
在单个.so下,由于GOT表与 .text 的相对位置是固定的,我们完全可以利⽤CPU的相对寻址来找
到GOT表。
在调⽤函数的时候会⾸先查表,然后根据表中的地址来进⾏跳转,这些地址在动态库加载的时候会
被修改为真正的地址。
这种⽅式实现的动态链接就被叫做 PIC 地址⽆关代码 。换句话说,我们的动态库不需要做任何修
改,被加载到任意内存地址都能够正常运⾏,并且能够被所有进程共享,这也是为什么之前我们给
编译器指定-fPIC参数的原因,PIC=相对编址+GOT。
总结: GOT表中存储的地址应该是虚拟地址。当程序执行跳转指令时,使用这个虚拟地址,然后由MMU通过页表将其转换为物理地址,从而访问实际的内存位置其他时候都是直接通过页表维持虚拟地址跟物理地址之间的关系。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python-leetcode 33.排序链表

题目: 给定链表的头结点head,请将其按升序排列,并返回排序后的链表 方法一:自顶向下归并排序 链表自顶向下归并排序的过程: 1.找到链表的中点,以中点为分界,将链表拆分成两个子链表。寻找链表的中点可以…

PyQt加载UI文件

1.动态加载 import sys from PySide6 import QtCore,QtWidgets from PySide6.QtWidgets import * from PySide6.QtUiTools import QUiLoaderclass readfile(QWidget):def __init__(self):super().__init__()self.uiQUiLoader().load("test.ui",self) self.__c…

深入解析NoSQL数据库:从文档存储到图数据库的全场景实践

title: 深入解析NoSQL数据库:从文档存储到图数据库的全场景实践 date: 2025/2/19 updated: 2025/2/19 author: cmdragon excerpt: 通过电商、社交网络、物联网等12个行业场景,结合MongoDB聚合管道、Redis Stream实时处理、Cassandra SSTable存储引擎、Neo4j路径遍历算法等42…

EasyRTC:开启智能硬件与全平台互动新时代

在当今数字化时代,实时音视频互动已成为企业与用户沟通、协作和娱乐的关键技术。无论是在线教育、视频会议、远程医疗还是互动直播,流畅、高效的互动体验都是成功的关键。然而,实现跨平台、低延迟且功能丰富的音视频互动并非易事——直到 Eas…

【前端框架】vue2和vue3的区别详细介绍

Vue 3 作为 Vue 2 的迭代版本,在性能、语法、架构设计等多个维度均有显著的变革与优化。以下详细剖析二者的区别: 响应式系统 Vue 2 实现原理:基于 Object.defineProperty() 方法实现响应式。当一个 Vue 实例创建时,Vue 会遍历…

springboot-ffmpeg-m3u8-convertor nplayer视频播放弹幕 artplayer视频弹幕

学习链接 ffmpeg-cli-wrapper - 内部封装了操作ffmpeg命令的java类库,它提供了一些类和方法,可以方便地构建和执行 ffmpeg 命令,而不需要直接操作字符串或进程。并且支持异步执行和进度监听 springboot-ffmpeg-m3u8-convertor - gitee代码 …

Linux下centos系统中使用docker容器中的ollama下载deepseek速度太慢解决办法

以下是使用shell脚本实现的一个示例,该脚本会尝试下载一个名为"deepseek-r1:32b"的模型。通过每隔60秒中断一次下载操作,从何恢复下载速度。亲测有效,其中需要将模型改为你自己要下载的模型 #!/bin/bashwhile true; do# 检查模型是否已下载完…

自动创建spring boot应用(eclipse版本)

使用spring starter project创建项目 设置Service URL 把Service URL设置为 https://start.aliyun.com/ 如下图: 使用这个网址,创建项目更快。 选择Spring Web依赖 项目结构 mvnw和mvnw.cmd:这是maven包装器(wrapper)脚本&…

基于flask+vue的租房信息可视化系统

✔️本项目利用 python 网络爬虫抓取某租房网站的租房信息,完成数据清洗和结构化,存储到数据库中,搭建web系统对各个市区的租金、房源信息进行展示,根据各种条件对租金进行预测。 1、数据概览 ​ 将爬取到的数据进行展示&#xff…

uniapp 滚动尺

scale组件代码&#xff08;部分class样式使用到了uview1.0的样式&#xff09; <template><view><view class"scale"><view class"pointer u-flex-col u-col-center"><u-icon name"arrow-down-fill" size"26&qu…

分布式大语言模型服务引擎vLLM论文解读

论文地址&#xff1a;Efficient Memory Management for Large Language Model Serving with PagedAttention 摘要 大语言模型&#xff08;LLMs&#xff09;的高吞吐量服务需要一次对足够多的请求进行批处理。然而&#xff0c;现有系统面临困境&#xff0c;因为每个请求的键值…

【HeadFirst系列之HeadFirst设计模式】第5天之工厂模式:比萨店的秘密武器,轻松搞定对象创建!

工厂模式&#xff1a;比萨店的秘密武器&#xff0c;轻松搞定对象创建&#xff01; 大家好&#xff0c;今天我们来聊聊设计模式中的工厂模式。如果你曾经为对象的创建感到头疼&#xff0c;或者觉得代码中到处都是 new 关键字&#xff0c;那么工厂模式就是你的救星&#xff01;本…

CSS基本选择器

1. 通配选择器 作用&#xff1a;可以选中所有的 HTML 元素。 语法&#xff1a; * { 属性名: 属性值; } 举例&#xff1a; <!DOCTYPE html> <html lang"zh-cn"> <head><meta charset"UTF-8"><meta name"viewport" …

Idea24.3 如何设置Git忽略某一个文件

文章目录 左上角找到commit选中你要忽略的文件 右键New Changelist给这个文件夹名称和描述 点击ok将要忽略的文件添加到这个文件夹 左上角找到commit 选中你要忽略的文件 右键New Changelist 给这个文件夹名称和描述 点击ok 将要忽略的文件添加到这个文件夹

ctfshow web入门 web11-web24

web11 web12 进来浏览网站&#xff0c;底部有一串数字&#xff0c;根据提示可能有用&#xff0c;访问robots.txt&#xff0c;发现禁止访问/admin/&#xff0c;进去看看发现需要输入用户名和密码&#xff0c;刚想爆破就猜对了&#xff0c;用户名是admin&#xff0c;密码是页面下…

MySQL笔记-对max_allowed_packet的进一步理解(2024-10-28)

背景 最近不仅仅在做开发&#xff0c;还在不停的做实施&#xff0c;运维。以前都不太喜欢做实施&#xff0c;运维&#xff0c;但是工作6年后&#xff0c;对这些还是比较感兴趣了&#xff0c;毕竟计算机这块不仅仅是开发&#xff0c;还有很多岗位&#xff0c;并且实施和运维会从…

Elasticsearch:探索 CLIP 替代方案

作者&#xff1a;来自 Elastic Jeffrey Rengifo 及 Toms Mura 分析图像到图像和文本到图像搜索的 CLIP 模型的替代方案。 在本文中&#xff0c;我们将通过一个模拟房地产网站的实际示例介绍 CLIP 多模态模型&#xff0c;探索替代方案&#xff0c;并分析它们的优缺点&#xff0c…

Spring中的日志

日志 了解一下 (有个印象) 门面模式 (外观模式) 含有两种角色&#xff1a; Facade (外观角色 / 门面角色): 系统对外的统一接口。SubSystem (子系统角色): 可以含有多个子系统&#xff0c;每个子系统都不是单独的类&#xff0c;而是一个类的集合。 Facade 对 SubSystem 是…

uniapp邪门事件

很久之前在这篇《THREEJS 在 uni-app 中使用&#xff08;微信小程序&#xff09;》&#xff1a;THREEJS 在 uni-app 中使用&#xff08;微信小程序&#xff09;_uni-app_帶刺的小葡萄-华为开发者空间 中学到了如何在uniapp的微信小程序里接入three.js的3d模型 由于小程序自身很…

C#项目04——递归求和

实现逻辑 利用递归&#xff0c;求取1~N以内的和 知识点 正常情况下&#xff0c;C#每条线程都会分配1MB的地址空间&#xff0c;因此执行递归的层次不能太深&#xff0c;否则就会出现溢出的风险&#xff0c; 业务设计 程序代码 private void button1_Click(object sender, E…