欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 教育 > 高考 > Linux:ELF加载_动态库

Linux:ELF加载_动态库

2025/2/25 21:28:53 来源:https://blog.csdn.net/2301_80153885/article/details/145513507  浏览:    关键词:Linux:ELF加载_动态库

在这里插入图片描述
hello,各位小伙伴,本篇文章跟大家一起学习《Linux:ELF加载_动态库_进程通信》,感谢大家对我上一篇的支持,如有什么问题,还请多多指教 !
如果本篇文章对你有帮助,还请各位点点赞!!!

话不多说,开始正题:

文章目录

    • 可执行程序格式
    • 重谈地址空间 ---- 可执行程序,加载
      • CPU在执行的时候,使用的是什么地址?
      • 重谈区域划分
    • 动态库加载的理解
      • 真实做法

可执行程序格式

下图是可执行程序main
在这里插入图片描述
下图是可执行程序的格式ELF格式:
在这里插入图片描述
maintext、data、bss……就是上图的Section

不仅仅是我们的可执行程序是这个格式,我们的库(动静态库)、目标文件(.o文件)都是这样的格式的!

.o文件的Section都有自己的text、data……

动静态库实际上就是所有的.o文件链接起来,所谓的链接其实就是将我们一个个相同属性的Section进行合并!
合并最终变成一个大的ELF就是可执行程序

对于ELF HeaderELF表头,Program Header Table optional是程序的表头,Section等等也有自己的大小,每一个ELF都不一样,那是如何分配好空间的呢?

对于任何一个文件,文件的内容就是一个巨大的“一维数组”,标识文件任何一个区域,采用偏移量+大小的方式,所以我们只需要知道偏移量就可以了,大小在文件属性里有

重谈地址空间 ---- 可执行程序,加载

  • mm_struct是由谁来初始化的?
  • 可执行程序有没有地址?

先谈第二个问题,可执行程序是有地址的,我们反汇编一下:

objdump -d main

在这里插入图片描述
可以看到每一条指令都是有地址的,再加上每一条指令都有自己的长度,所以下一条指令的地址减去上一条指令的地址,就是上一条指令的长度!从另一个角度来讲:当前地址+当前指令长度 = 下一条指令的地址

这种地址叫做逻辑地址(逻辑地址 = 起始地址 + 偏移量),在ELF,没有加载到内存时就已经形成的了,并且都是按照[0000……00,FFFF……FF]进行编址,没错,这实际上就是虚拟地址

所以,编译器编译,就已经形成了虚拟地址

那么第一个问题也就解决了,mm_struct的初始化是从磁盘中的可执行程序ELF中得来的!

在CPU里有一个指针叫做pc指针,指向的是下一条指令的地址,那么CPU如何获取main函数的地址的呢?
在这里插入图片描述
实际上main函数什么都交代了,连程序入口地址也说明了

CPU在执行的时候,使用的是什么地址?

答案是:虚拟地址

没错,CPU也是读取虚拟地址的,那是怎么实现的?

在磁盘里存放着可执行程序的ELF格式,虚拟地址加上指令
在这里插入图片描述
加载到物理内存:
在这里插入图片描述

物理内存也有地址,也就是物理地址,这样物理地址有了、虚拟地址也有了,页表就完善了:
在这里插入图片描述
这样CPU读到虚拟地址再通过MMU硬件CR3进行查表,就可以实现虚拟内存到物理内存的转换!!
在这里插入图片描述
对于MMU硬件是通过硬件电路进行查表的,CR3指向的是页表的起始地址,这里的起始地址是物理地址!

在CPU里还有一个EIP,是用来读取物理地址的指令的:
在这里插入图片描述
pc将虚拟地址给CPU查表后,得到物理地址,通过EIP读取并且解析该物理地址上的指令长度,那么pc就可以在原先虚拟地址加上指令长度得到下一条指令的地址,如此循环往复,程序就跑起来了!

总结:
虚拟地址空间是操作系统,CPU,编译器共同协作下的产物!!

所以为什么要有虚拟地址和虚拟空间?

有了之后,编译器就再也不用考虑物理地址了,操作系统就能够以线性角度去看待程序,实现了操作系统与物理内存的解耦!

重谈区域划分

为什么mm_struct能够帮助我们形成虚拟内存,但是,当数据虚拟内存中时,是怎么控制的?任意加载吗?很明显不可能,这样虚拟空间会非常乱!

其实mm_struct并非这么简单,里面有着一个结构体struct vm_area_struct,这个结构体里存放着struct vm_area_struct*指针,用来指向下一个struct vm_area_struct,该结构体里存放着start、end用来实现空间的开辟,实际上这是用链表的形式来维护的,从struct vm_area_struct*指针就可以得出结论

所以啊,假设你加载一个动态库,只需要创建一个struct vm_area_struct节点维护即可,MMU硬件所谓的查表,实际上就是查找你所寻找的在哪一个节点

动态库加载的理解

对于库,我们也可以认为是基地址 + 偏移量

那么库里的方法本质就是偏移量!

动态库加载到内存中也是需要被操作系统管理的,也是先描述再组织!同样是在mm_struct里进行管理,通过页表进行映射关系

将正文执行到库里的某个方法的时候call libc.so:printf,直接将libc.so虚拟地址转换为物理地址,此时printf就会转变为偏移量0xXXX而不再是printf

问题来了,代码区不是只读的吗?

真实做法

对于动态库,ELF里还有一张表叫做.GOT
在这里插入图片描述
这张表加载到物理内存里,正文代码区只需要看其物理地址,也就是说正文区实际上放的就是物理地址,为并非虚拟地址,物理地址存放着.GOT这张表,这就能够转到虚拟地址,然后在mm_struct共享区找到方法,就不用改正文代码区

.GOT + 库方法偏移量 = 与地址无关

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词