动静态链接与加载

目录

静态链接

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.mzph.cn/web/70236.shtml

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

相关文章

Web 后端 请求与响应

一 请求响应 1. 请求(Request) 客户端向服务器发送的HTTP请求,通常包含以下内容: 请求行:HTTP方法(GET/POST等)、请求的URL、协议版本。 请求头(Headers):…

【Excel笔记_6】条件格式和自定义格式设置表中数值超过100保留1位,超过1000保留0位,低于100为默认

方法一:自定义格式 选中需要设置格式的单元格区域。右键选择设置单元格格式,或者在工具栏中选择开始 -> 数字 -> 自定义格式。在类型框中输入以下自定义格式: [>1000]0;[>100]0.0;G/通用格式解释: [>1000]0&…

排序与算法:希尔排序

执行效果 希尔排序的执行效果是这样的: 呃……看不懂吗?没关系,接着往下看介绍 算法介绍 希尔排序算法(Shell Sort)是按其设计者希尔(Donald Shell)的名字命名,该算法由 1959 年公布…

Python HTTP 请求工具类 HttpUtils:简化 HTTP 请求的高效工具

在现代的 Web 开发和 API 集成中,HTTP 请求是最常见的操作之一。无论是获取数据、提交表单,还是与 RESTful API 交互,我们都需要频繁地发送 HTTP 请求。为了简化这些操作,提升代码的可读性和可维护性,我们可以使用一个高效的工具类——HttpUtils。本文将详细介绍 HttpUtil…

亲测Windows部署Ollama+WebUI可视化

一. Ollama下载 登录Ollama官网(Ollama)点击Download进行下载 如果下载很慢可用以下地址下载: https://github.com/ollama/ollama/releases/download/v0.5.7/OllamaSetup.exe 在DeepSeek官网上,你可以直接点击【model】 到达这个界面之后,…

用xml配置spring, bean标签有哪些属性?

用xml配置spring, bean标签有哪些属性? 在Spring框架中&#xff0c;使用XML配置文件时&#xff0c;<bean>标签用于定义一个Bean。以下是一些常用的<bean>标签属性&#xff1a; 1. class 描述&#xff1a;指定Bean的类名。示例&#xff1a;<bean id"myBe…

50页PDF|数字化转型成熟度模型与评估(附下载)

一、前言 这份报告依据GBT 43439-2023标准&#xff0c;详细介绍了数字化转型的成熟度模型和评估方法。报告将成熟度分为五个等级&#xff0c;从一级的基础转型意识&#xff0c;到五级的基于数据的生态价值构建与创新&#xff0c;涵盖了组织、技术、数据、资源、数字化运营等多…

golang panic信息捕获

背景 我们的日志接入阿里云sls平台&#xff0c;但是&#xff0c;日志是以json的格式存储在阿里云sls平台上&#xff0c;程序中产生的error,info等日志都可以实现以json的格式打印。但是&#xff0c;golang程序中产生的panic信息本身不是以json的格式输出&#xff0c;这就导致p…

拦截器VS过滤器:Spring Boot中请求处理的艺术!

目录 一、拦截器&#xff08;Interceptor&#xff09;和过滤器&#xff08;Filter&#xff09;&#xff1a;都是“守门员”&#xff01;二、如何实现拦截器和过滤器&#xff1f;三、拦截器和过滤器的区别四、执行顺序五、真实的应用场景六、总结 &#x1f31f;如果喜欢作者的讲…

FastGPT及大模型API(Docker)私有化部署指南

​​欢迎关注【AI技术开发者】 ​ 经过优化&#xff0c;在不影响FastGPT功能的情况下&#xff0c;大幅降低了部署的设备配置要求&#xff0c;仅需1c1h即可正常部署使用。 官方要求配置&#xff1a; ​ ​ 优化后的实际占用情况&#xff1a; 运行内存仅需370M&#xff08…

解决 WSL Ubuntu 中 /etc/resolv.conf 自动重置问题

解决 WSL Ubuntu 中 /etc/resolv.conf 自动重置问题 前言问题描述问题原因尝试过的命令及分析解决方案&#xff1a;修改 wsl.conf 禁用自动生成总结 前言 在使用 Windows Subsystem for Linux (WSL) 的 Ubuntu 子系统时&#xff0c;你可能会遇到 /etc/resolv.conf 文件被自动重…

【第15章:量子深度学习与未来趋势—15.3 量子深度学习在图像处理、自然语言处理等领域的应用潜力分析】

一、开篇:为什么我们需要关注这场"量子+AI"的世纪联姻? 各位技术爱好者们,今天我们要聊的这个话题,可能是未来十年最值得押注的技术革命——量子深度学习。这不是简单的"1+1=2"的物理叠加,而是一场可能彻底改写AI发展轨迹的范式转移。 想象这样一个…

企业软件合规性管理:构建高效、安全的软件资产生态

引言 在数字化转型的浪潮下&#xff0c;企业的软件使用方式日益多元化&#xff0c;涉及云端、订阅制、永久授权及浮动许可等多种模式。然而&#xff0c;随着软件资产的增多&#xff0c;企业面临着合规性管理的严峻挑战&#xff1a;非法软件使用、许可证管理不当、软件资产闲置…

python学习笔记,python处理 Excel、Word、PPT 以及邮件自动化办公

文章目录 前言一、环境搭建1. 下载 Python2. 安装 Python 二、处理 Excel 文件&#xff08;openpyxl库&#xff09;三、 处理 Word 文件&#xff08;python-docx库&#xff09;四、 处理 PPT 文件&#xff08;python-pptx库&#xff09;五、 自动发送邮件&#xff08;smtplib和…

Python 基础-循环

目录 简介 break continue 小结 简介 要计算123&#xff0c;我们可以直接写表达式&#xff1a; >>> 1 2 3 6要计算123...10&#xff0c;勉强也能写出来。 但是&#xff0c;要计算123...10000&#xff0c;直接写表达式就不可能了。 为了让计算机能计算成千上…

简单易懂,解析Go语言中的Channel管道

Channel 管道 1 初始化 可用var声明nil管道&#xff1b;用make初始化管道&#xff1b; len()&#xff1a; 缓冲区中元素个数&#xff0c; cap()&#xff1a; 缓冲区大小 //变量声明 var a chan int //使用make初始化 b : make(chan int) //不带缓冲区 c : make(chan stri…

python-leetcode 36.二叉树的最大深度

题目&#xff1a; 给定一个二叉树root,返回其最大深度 二叉树的最大深度是指从根节点到最远叶子节点的最长路径上的节点数 方法一&#xff1a;深度优先搜索 知道了左子树和右子树的最大深度l和r&#xff0c;那么该二叉树的最大深度即为:max(l,r)1 而左子树和右子树的最大深…

RESTful 的特点与普通 Web API 的区别

RESTful 是一种设计风格&#xff0c;而不仅仅是普通的 Web API。它遵循一些特定的原则和约束&#xff0c;使得 API 更加简洁、可扩展和易于理解。以下是 RESTful 的特点&#xff0c;以及与普通 Web API 的区别&#xff1a; RESTful 的特点 1. 资源导向 RESTful API 的核心是资…

结构风荷载理论与Matlab计算

结构风荷载理论与matlab计算的实例程序&#xff0c;适合初学者理解matlab风荷载计算 资源文件列表 程序_结构风荷载理论与Matlab计算/chapter1/exam_simWind_1_1.m , 1035 程序_结构风荷载理论与Matlab计算/chapter1/Extrmv.m , 303 程序_结构风荷载理论与Matlab计算/chapter1…

numpy(02 数据类型和数据类型转换)

numpy(01 入门) 目录 一、Python NumPy 数据类型 1.1 NumPy 基本类型 1.2 数据类型对象 (dtype) 1.3 具体实例 二、Numpy数据类型转换 2.1 浮点数据转换 2.2 整型数据转换 2.3 浮点数转整数 一、Python NumPy 数据类型 1.1 NumPy 基本类型 下表列举了常用 NumPy 基…