当前位置: 首页 > news >正文

ELF加载,进程地址空间与可执行程序的关系

1,可执行程序的格式

粗略概况

操作系统要如何认识可执行程序?我们的可执行程序是有格式的:

用指令size 加可执行程序名:

其中test就是代码块,data就是数据块,不仅可执行程序有格式,动态库,静态库也有格式,他们的格式有一个统一的命名叫做ELF,我在网上找了一些关于ELF的结构图:

其中先讲中间黄色这一段section,我们称之为节,用来分别存放不同属性的内容,其中在size看到的多个属性中,就分别是里面的某个节。

我们知道,程序编译的过程就算先编译成.o文件,再链接最后形成可执行程序,宏观上的流程就是:将不同文件中相同属性的section合并在一起形成可执行程序:

这就是为什么在实现函数的时候不能出现同名函数的原因,要是在合并的时候发现命名冲突了就不知道该听谁的了。

elfheader

用指令readelf -h 加可执行程序或.o文件就可查看:

可以发现,里面存的都是一些类似指示文件内容的东西。

Program Header Tableoptional

这一行存的是elf的整体布局,查看指令为readdlf -l 加名字

其中可以看到左边的LOAD区域,这部分地区就算存放代码段和数据段的。

前面学了inode与磁盘我们知道,对于任何一个文件,文件的内容就算一个巨大的“一维数组“,标识文件任何一个区域就等于偏移量加大小的方式

这也就是os寻找程序的方法,所以在readelf -l中,

LOAD区域可以看到有两段区间,就是偏移量加大小的方式来寻址。

如何查看表中更细节的信息?

指令:readelf -f  +名字可以查看:

左边【】中数字代表多少节,可以在其中找到对应的data,bss等:

2,重谈地址空间与可执行程序,加载的话题

1,可执行程序有没有地址的存在?

有!

可以通过反汇编的形式看到!

指令:objdump -S +名字:

可以看到红色框起来的部分,这就是每个指令的地址,在其中还可以找到section/.test等字段,也可以找到自己完成的函数名:

这就是存在磁盘中未运行的地址,我们叫他为逻辑地址,逻辑地址=起始地址+偏移量,其中区间为全零到全f

那么我们看到为什么main函数不是从零开始?

因为main函数上面还有一些指令,同时我们知道了指令也是有长度的,因为地址等于偏移量加长度,下一个地址减去上一个地址的值就是这条指令的大小。

所以在逻辑上我们找到第一个地址就可以找到全部地址,

地址+长度=下一个地址

而且这个事实还让我们知道,ELF在没加载到内存的时候就已经按【000,FFF】进行编址了,这就是虚拟地址!

编译器在编译的时候就已经形成虚拟地址了,所以磁盘的逻辑地址等于内存的虚拟地址

2,mm_struct由谁来初始化?

mm_struct中正文,初始化数据,未初始化数据都是由可执行程序中的各个数据节初始化而来

其中,readelf -h中的entry point address中记录着虚拟地址从哪里开始,是整个可执行程序的入口,放到cpu中的pc寄存器才可以运行,这也解释了os是怎么找到可执行程序从哪里开始的。

3,cpu与进程地址空间与物理内存的关系

问,cpu中pc执行程序的时候,使用的是什么地址?

虚拟地址!我们cpu也被骗啦

当可执行程序加载到内存中的时候,在物理内存中也有自己的地址:

其中在框外面的是物理地址,在框里面的是虚拟地址和指令,这时页表左侧就以框内虚拟地址初始化,页表右侧就以物理地址做初始化:

这个时候 ,可以认识cpu中两个东西,一个是CR3,一个是MMU硬件,CR3是用来存放页表的物理地址,MMU硬件可以读取物理内存中的指令,这时CR3+MMU就等于一个查表工具,并将查到的指令给到EIP寄存器中执行指令,当执行完时pc指针加加,这时就完成了程序运行过程的闭环:

、所以虚拟地址是操作系统,cpu,编译器共同协作下的产物,同时可以实现各个进程的独立性。

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

编译器在编译的时候就不用考虑虚拟地址的情况了

在mm_struct中,还有一个vm_area_struct结构体,以链表的形式管理起来,里面由start和end来标记正文,栈,堆区,共享区等偏移量地址,用来定位代码或数据的位置。

4,动态库加载的理解

刚刚讲过的·vm_area_struc结构体中,有一个libso的int ret_count的引用计数,当lib加载进os时此libso的引用计数加加,然后将库函数映射到共享区中,与正文区的代码进行替换,正文会call物理内存,将映射过来的地址覆盖到正文代码中。

但是,正文去的代码不是只读的吗?

这时,elf中有一个节中的GOT可以解决,当我们发生库函数替换的时候,只需要把got映射到内存中的位置替换成虚拟地址位置就可以了,正文区会通过got表,找到对应的共享区。所以对地址重定位时,就改got表就行了。

同时,got属于进程自己的东西,所以可以做到重定位与地址无关

其中,我们可以通过readelf -S 看到got:


http://www.mrgr.cn/news/67198.html

相关文章:

  • Vue3版本的uniapp项目运行至鸿蒙系统
  • 小语言模型介绍与LLM的比较
  • Jmeter5.X性能测试
  • 【商用存储】希捷磁盘阵列部署实践
  • Linux 经典面试八股文
  • yolov8涨点系列之引入CBAM注意力机制
  • C++转义序列
  • 【ETL:概念、流程与应用】
  • 基本开关电源(DCDC)电路分析
  • 4.1 WINDOWS XP,ReactOS对象与对象目录----1
  • Java的Object类常用的方法(详述版本)
  • 11.6学习日志
  • 数学建模启发式算法篇(一)---遗传算法
  • Oracle OCP认证考试考点详解082系列14
  • LDO电路分析
  • 1.3 自然语言处理的应用
  • 【启程Golang之旅】深入理解 Protocol Buffers 及其应用
  • Spring 配置绑定原理分析
  • 复合查询【MySQL】
  • 蓝牙协议的前世今生
  • 复现LLM——带你从零认识自注意力
  • L6.【LeetCode笔记】合并两个有序链表
  • 【机器学习】k最近邻分类
  • Android中Activity启动的模式
  • python验证码滑块图像识别
  • 基于SSM的校园美食交流系统【附源码】