【LINUX操作系统】 动静态库的链接原理

初识linux(16) 动静态库(手搓动静态库!)-CSDN博客

完成了对动静态库使用的学习,现在浅显理解下动态库加载的原理。

1. 宏观认知

磁盘中的应用程序main和动态库libmystdio.so先加载到内存中

加载到内存后,OS会形成对应的task_struct结构体,task_struct中有对应的mm_struct来管理该进程的虚拟内存。

通过页表的虚拟地址将库映射到堆栈之间的共享区

这样,库中的所有方法就能在进程自己的mm_struct找到。

                             

当正文代码执行到库函数的时候,在共享区找到对应的内容,从而跳转到库中执行并返回。                                    

有了宏观的认识,就能具体学习这个过程了。


2. 可执行程序—ELF格式

size myexe

size + 可执行程序 ,查看一个可执行程序的属性。说明一个可执行程序也有自己的规范化的属性,也是被OS组织管理起来的。

ELF即可执行与可链接格式(Executable and Linkable Format),myexe就是一个ELF文件。类似于win中的.exe,ELF是Linux及Unix中可执行文件的主要可执行文件格式。

可重定位文件(xxx.o)、可执行文件(myexe)、共享文件(xxx.so)、内核转存文件(不作重点理解)都是ELF文件。这一点与win中的.exe不一样。

重点:进程=代码+数据+内核数据结构

text-代码

data-数据

  • text:可执行代码段的大小,这里是 2324 字节。它包含了程序的机器指令。

  • data:已初始化的全局变量和静态变量所占用的内存大小,这里是 588 字节。

  • bss:未初始化的全局变量和静态变量所占用的内存大小,这里是 4 字节。这个段在程序运行时会被初始化为0或空。

ELF文件 结构:

                               

其中最重要的是section(节)部分,ELF⽂件的各种信息和 数据都存储在不同的节中,如代码节存储了可执⾏代码,数据节存储了全局变量和静态数据等。

                                                        

而链接的过程,就是将各个文件相同属性的部分合体(包括静态库),3个.text形成一个更大的可执行代码段,3个.data形成一个更大的.data的section


ELF (ELF header) :描述⽂件的主要特性。其位于⽂件的开始位置,它的主要⽬的是定位⽂件的其他部分、以及整个可执行文件的整体信息。
readelf -h 可执行程序

以上指令用于查看可执行程序的elf header


Program Table Header

列举了所有有效的段(segments)和他们的属性。表⾥记着每个段的开始的位置和位移(offset)、⻓度,毕竟这些段,都是紧密的放在⼆进制⽂件中, 需要段表的描述信息,才能把他们每个段分割开。维护了程序的“执行时视图”,用于指导加载器如何将程序加载到内存中,以创建进程映像。这是一个维护“段”的结构
如果以上描述让你迷茫,不要着急,后文我们会进一步说明。

此处引出了一个重要的思想:在Linux中,对于任何一个文件来说,文件的内容就可以类比为一个一维数组,访问其内容就是通过「起始值+偏移量+内容大小(可选))」进行获取,所以对于上面所有的类型来说也是如此,当其需要加载到内存时也是通过对应的方式加载需要的内容。比如LOAD表示需要加载进内存的段,第一排表示从0000开始偏移,大小是0d24;

也就是说: 

任何一个文件的位置及区间都可以用偏移量+大小来标识

最后一块:

readelf -S(大写) myexe

阅读每个section及其对应的内容信息。

能看到好几个老朋友:

理解段与节:

⼀个ELF会有多种不同的Section,在加载到内存的时候,也会进⾏Section合并,形成segment(段) 也就是在mm_struct中看到的:代码段、已初始化数据区、未初始化数据区等等
合并原则:相同属性,⽐如:可读,可写,可执⾏,需要加载时申请空间等.
这样,即便是不同的Section,在加载到内存中,可能会以segment的形式,加载到⼀起
很显然,这个合并⼯作也已经在形成ELF的时候,合并⽅式已经确定了,具体合并原则被记录在了ELF的 程序头表(Program header table)
Section合并的主要原因是为了减少⻚⾯碎⽚,提⾼内存使⽤效率。如果不进⾏合并,
假设⻚⾯⼤⼩为4096字节(内存块基本⼤⼩,加载,管理的基本单位),如果.text部分
为4097字节,.init部分为512字节,那么它们将占⽤3个⻚⾯,⽽合并后,它们只需2个
⻚⾯。
此外,操作系统在加载程序时,会将具有相同属性的section合并成⼀个⼤的
segment,这样就可以实现不同的访问权限,从⽽优化内存管理和权限访问控制。

可以说,section是segment的一个小单位,所以在宏观上,段比节要大一点,数量要少一点


3. 地址空间与 程序从加载到执行

3.1 ELF加载到执行

     目前为止,我们对上图的认知不过是将以前加载进内存的文件的格式更加清晰了(ELF)

        CPU执行进程,需要知道进程下一步的虚拟地址(如PC指针和EIP寄存器都在使用),磁盘中的可执行文件中的每一条指令都是有地址的,其采用的编址方法(编译器编译代码的时候采用的)叫做“平坦模式”,其中整个地址空间被看作是单一的、连续的线性空间。在这种模式下,所有代码和数据都位于一个大的、平坦的地址范围内,没有分段或分区的概念。可以理解为每一个可执行程序都是从0x00000开始编码的。

类似于下图:

                      

磁盘中的内容加载到内存时,每一条指令也会具备真实的物理地址

可以通过以下代码反汇编查看汇编代码来查看可执行程序的虚拟地址。

objdump -S 可执行程序名

最左侧的就是ELF文件的虚拟地址,也叫逻辑地址(起始地址+偏移量),只不过此时的起始地址都是00000。

所以:虚拟地址机制,不光光OS要⽀持,编译器也要⽀持

   加载到内存之前会先建立PCB,建立PCB的时候需要初始化mm_struct和vm_area_struct(维护mm_struct中具体的内存指向)

                   

mm_struct和vm_area_struc的初始化数据是从哪里来的?

答:也是从ELF的各个segment来的!

理论上来说,虚拟内存中的地址和磁盘中被由编译器产生的逻辑地址在数值上是一样的。但是在实际执行中,数值可能会不一样,但是每一部分的数值的相对大小都是一样的。

每个segment有⾃⼰的起始地址和⾃⼰的⻓度,⽤来初始化内核结构中的[start, end]
等范围数据,另外在⽤详细地址,填充⻚表.

                                

同时,CPU在获取命令的时候,PC(EIP)指针拿到的其实也是虚拟地址。

                                               

这么说的理由是:

名字叫做_start的section作为整个代码区的开始,可以看到其对应的地址:4005d0

                  

myexe的ELF Header中的Entry point address也是这个地址。

当EIP拿到这条地址之后:

                 

                 

CR3存的是物理地址,EIP存的是虚拟地址

每一条指令都有自己的长度(翻译成机器码之后),在程序内部也都是直接使用虚拟地址。 

因此编译器在编译程序的时候,完全不用考虑整个真实物理地址,让操作系统和编译器解耦。


3.2 再理解vm_area_struct 

前面提到,虚拟地址空间初始化时会由ELF文件中的内容对指定区域进行初始化,但是并没有看到ELF文件中存在对栈、堆和共享区进行初始化的部分,这些部分如何进行的初始化就是下面需要讨论的问题

mm_struct中有一个vm_area_struct的链表,管理了许多vm_area_struct。理论上来说,每一个vm_area_struct就去维护一个段的内容。

             

真正的栈、堆、共享区等都是一个一个的vm_area_struct对象,由自己的vm_start和vm_end标记各个区域,所以,CPU在访问栈、堆和共享区时实际上访问的也是对应的vm_area_struct对象的虚拟地址,在页表中也存在着这些虚拟地址和物理地址的映射。

实际运行中,添加一个库就变成了链表中增加一个vm_area_struct;当一个段太大的时候,甚至可以一次只加载一部分的section,先形成vm_area_struct对象,最后再根据实际运行需求加载剩下的部分——以此实现section的懒加载。

甚至,整个系统可以什么都不加载,只加载一个可加载程序ELF Header的entry部分。 


4. 动态库的加载

4.1 地址重定位

.so文件本身也是ELF文件的一种,在形成.o并且被打包成.so的过程中,OS也要管理整个系统。

                                          

可能也会去形成一个类似的结构体,来管理各个库的信息,比如每当一个进程引用了一个库,ref_count就++等等。

                

内存中加载了一个库,堆栈之间的共享区就会维护一个新的libstart和libend来维护这个库的

虚拟地址

      

比如我们的代码中要用一个lib.so:printf,这个函数的位置是0x100

                                      

当要call这个函数的时候,就把libc.so换成库的虚拟起始地址,把prinf换成0x100,

通过起始地址+偏移量的方法来映射页表找到并执行这个方法。

                                                  

库中每⼀个⽅法的偏移量地址我们也知道
所以,想访问库中任意⽅法,只需要知道库的起始虚拟地址+⽅法偏移量即可定位库中的⽅

           

看似上面的思路好像没问题,实际上,虚拟地址空间的代码区是不可写的,也就是说,如果进程的代码加载到虚拟地址空间就不无法再更改其中的内容,那么此时又是如何做到使用动态库加载到内存之后的虚拟地址替换进程调用动态库代码的位置的内容呢?                           

补充,并不是库中的所有内容都会被加载,而是会通过file指针去加载会用到的部分。

 

4.2 GOT

GOT(Global Offset Table):是一个数据结构,它存储了动态链接符号(如函数或变量)的地址。当动态库首次加载时,动态链接器会更新GOT中的条目以指向正确的内存位置

got存在数据区.data中,即got本来是一个单独的section,在合并的时候和data合并成同一个seagment, 也可以说是专门在数据区中预留了一个区域来存这个全局偏移表。

 

可以说,got存的就是二次定位之后,该代码模块会用到的所有库函数的虚拟地址,这样在代码区执行call指令的时候就能去got表中查到lib.so的起始地址,这样一来虚拟地址就完整了。

所以,一个动态库之所以可以只加载一次而可以被任何进程所调用,本质就是因为这个GOT表,只需要知道这个GOT表的地址和对应库的下标,即可调用对应动态库中的内容

5.小结

静态链接的出现,提⾼了程序的模块化⽔平。对于⼀个⼤的项⽬,不同的⼈可以独⽴地测试和开发
⾃⼰的模块。通过静态链接,⽣成最终的可执⾏⽂件。
我们知道静态链接会将编译产⽣的所有⽬标⽂件,和⽤到的各种库合并成⼀个独⽴的可执⾏⽂件, 其中我们会去修正模块间函数的跳转地址,也被叫做编译重定位(也叫做静态重位)。
⽽动态链接实际上将链接的整个过程推迟到了程序加载的时候。⽐如我们去运⾏⼀个程序,操作系 统会⾸先将程序的数据代码连同它⽤到的⼀系列动态库先加载到内存,其中每个动态库的加载地址 都是不固定的,但是⽆论加载到什么地⽅,都要映射到进程对应的地址空间,然后通过.GOT⽅式进 ⾏调⽤(运⾏重定位,也叫做动态地址重定位)。

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

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

相关文章

广东启动“跨境电商+产业带”系列活动 三年打造30个产业振兴样板

大湾区经济网湾区财经快讯,近日,2025年广东省“跨境电商+产业带”助力“百千万工程”系列活动在中山市古镇镇启动。作为外贸领域新质生产力的重要载体,跨境电商将通过赋能县域特色产业带转型升级,为城乡融合与乡村振兴…

穿透递归的本质:从无限梦境到可控魔法的蜕变之路

穿透递归的本质:从无限梦境到可控魔法的蜕变之路(C实现) 一、递归:程序员的盗梦空间 在计算机科学的宇宙中,递归是最接近魔法本质的编程范式。它像一面镜子中的镜子,引导我们通过自我相似性破解复杂问题。…

基于django+vue的购物商城系统

开发语言:Python框架:djangoPython版本:python3.8数据库:mysql 5.7数据库工具:Navicat11开发软件:PyCharm 系统展示 系统首页 热卖商品 优惠资讯 个人中心 后台登录 管理员功能界面 用户管理 商品分类管理…

WordPress靶场攻略

后台修改模板 修改404.php为一句话木马 访问404.php,验证有没有成功 http://47.122.51.245:8080/wp-content/themes/twentyfifteen/404.php?cmdphpinfo(); 上传主题 创建6.php,写入图中代码 自己随便下载一个主题包,将1.php和主题包压缩在一起,提交上…

JVM常用概念之对象对齐

问题 对象对齐有什么规范吗?对象对齐是8个字节吗? 基础知识 许多硬件实现要求对数据的访问是对齐的,即确保所有 N 字节宽度的访问都在 N 的整数倍的地址上完成。即使对于普通的数据访问没有特别要求,特殊操作(特别是原子操作&#xff09…

K8S学习之基础三十七:prometheus监控node资源

Prometheus v2.2.1 ​ 编写yaml文件,包含创建ns、configmap、deployment、service # 创建monitoring空间 vi prometheus-ns.yaml apiVersion: v1 kind: Namespace metadata:name: monitor-sa# 创建SA并绑定权限 kubectl create serviceaccount monitor -n monito…

leetcode127.单词接龙

本题的思路就是将所有可转换的序列相连,构成图,然后选择起始词作为广度优先遍历的起点,那么就能找到转换的最小步骤数 而这里的两个单词是否相连不是真的把他们弄成一张图,而是采用暴力枚举,逐个尝试替换字母&#xf…

Tr0ll2靶机详解

一、主机发现 arp-scan -l靶机ip:192.168.55.164 二、端口扫描、漏洞扫描、目录枚举、指纹识别 2.1端口扫描 nmap --min-rate 10000 -p- 192.168.55.164发现21端口的ftp服务开启 以UDP协议进行扫描 使用参数-sU进行UDP扫描 nmap -sU --min-rate 10000 -p- 19…

Pycharm接入DeepSeek,提升自动化脚本的写作效率

一.效果展示: 二.实施步骤: 1.DeepSeek官网创建API key: 创建成功后,会生成一个API key: 2. PyCharm工具,打开文件->设置->插件,搜索“Continue”,点击安装 3.安装完成后&…

如何在 Node.js 中使用 .env 文件管理环境变量 ?

Node.js 应用程序通常依赖于环境变量来管理敏感信息或配置设置。.env 文件已经成为一种流行的本地管理这些变量的方法,而无需在代码存储库中公开它们。本文将探讨 .env 文件为什么重要,以及如何在 Node.js 应用程序中有效的使用它。 为什么使用 .env 文…

《视觉SLAM十四讲》ch13 设计SLAM系统 相机轨迹实现

前言 相信大家在slam学习中,一定会遇到slam系统的性能评估问题。虽然有EVO这样的开源评估工具,我们也需要自己了解系统生成的trajectory.txt的含义,方便我们更好的理解相机的运行跟踪过程。 项目配置如下: 数据解读: …

软考高级信息系统管理工程师通关100题(21-40)附记忆口诀

文章目录 21.常用存储模式的技术与应用对比22.物联网架构23.云计算服务提供的资源层次24.大数据25.区块链26.人工智能27.虚拟现实VR28.IT治理的内涵29.IT 治理活动30.IT治理本质31.IT审计目标32.IT审计方法33.治理系统设计34.数据管理能力成熟度评估模型35.项目管理原则36.管理…

Redisson 分布式锁原理

加锁原理 # 如果锁不存在 if (redis.call(exists, KEYS[1]) 0) then# hash结构,锁名称为key,线程唯一标识为itemKey,itemValue为一个计数器。支持相同客户端线程可重入,每次加锁计数器1.redis.call(hincrby, KEYS[1], ARGV[2], 1);# 设置过期时间redis.call(pexpi…

主流加固方案深度剖析(梆梆/腾讯/阿里)

1. 加固技术演进与核心原理 1.1 移动端加固技术图谱 graph TD A[代码防护] --> A1[混淆] A --> A2[虚拟化] A --> A3[动态加载] B[数据防护] --> B1[资源加密] B --> B2[协议加密] C[运行时防护] --> C1[反调试] C --> C2[环境检测] C --> C…

大模型之蒸馏模型

蒸馏模型(Distilled Model)是一种通过知识蒸馏(Knowledge Distillation)技术训练得到的轻量级模型,其核心思想是将一个复杂的大模型(称为教师模型)的知识“迁移”到一个更小、更高效的模型&…

iPaaS集成平台中的API可视化编排能给企业带来什么作用

随着企业数字化转型的加速,API(应用程序接口)作为企业数字化资产的核心组成部分,其数量和复杂性不断增加。为了满足业务敏捷化交付的要求,API可视化编排平台应运而生。谷云科技作为这一领域的领先者,其API可…

演员马晓琳正式加入创星演员出道计划,开启演艺事业新篇章

3月19日,演员马晓琳正式加入“创星演员出道计划”,不仅得到参演都市爱情喜剧《和我结婚吧》角色的机会,还获得文旅精品网剧《醉梦灵州》的出演机会,自此开启全新影视之路。对表演抱有极大热情的马晓琳,相信未来可以凭借…

绿盟科技春招面试

《网安面试指南》https://mp.weixin.qq.com/s/RIVYDmxI9g_TgGrpbdDKtA?token1860256701&langzh_CN 5000篇网安资料库https://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247486065&idx2&snb30ade8200e842743339d428f414475e&chksmc0e4732df793fa3bf39…

双碳战略下的电能质量革命:解码电力系统的健康密码

安科瑞顾强 在能源结构转型的深水区,电能质量正成为制约产业升级的隐形门槛。国家能源局数据显示,我国工业企业每年因电能质量问题造成的经济损失高达3000亿元,而新能源项目因并网质量问题导致的发电效率损失超过15%。在这场关乎能源安全的攻…