前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >编译、链接到载入、运行的大致过程----3.载入

编译、链接到载入、运行的大致过程----3.载入

作者头像
qsjs
发布2020-06-08 12:16:41
2.3K0
发布2020-06-08 12:16:41
举报
文章被收录于专栏:MyPanda的学习笔记

在Linux下,elf文件有三类,分别是: relocatable , shared object, executable. 见下面的例子:

代码语言:javascript
复制
[root@www ~]# file main.obj  /usr/bin/cat /lib/librt-2.17.so 
main.obj:           ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
/usr/bin/cat:       ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=8ac8b57ae50762a4a0480486839107e87b3c284d, stripped
/lib/librt-2.17.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, BuildID[sha1]=e9050e3a9543278c0fe04e541644287e67356ff1, for GNU/Linux 2.6.32, not stripped
[root@www ~]# 

其中的relocatable的文件,就是编译后生成的“目标文件”; 而 "shared object", 就是Linux下的动态链接库文件; executable 文件是 可以直接运行的 程序文件; 在这里要注意的是: 部分“shared object” 也是可以直接运行的,并不是所有的“动态链接库文件” 都不可以运行;

从上面的结果中还可以看到: executable的elf文件最后有一个“stripped” 的说明,而另外两种elf文件所对应的是"not stripped", 这表示什么意思呢?

编译的时候,每个文件中涉及到的对其他文件中函数的引用,都会用符号来进行表示,这是因为 当前文件文件中无法确定被引用函数的地址,所以就采用了符号来表示;而在链接的时候,会进行 地址重载,这时候,被引用的符号就被解析成了地址,并生成了最终的程序文件;而"stripped" 就表示 符号 已经被解析为 程序虚拟地址,而"not stripped" 就表示 符号没有被解析为 程序虚拟地址;所以 编译后的文件,其属于"not stripped"的类型,而 链接后的文件又分为两类: "shared object" 类型的文件,一般对外提供 程序接口的,这些对外提供的接口是以符号的方式提供的,而不是以 程序虚拟地址来提供,毕竟我们写代码的时候调用的都是函数名称,而不是用一串地址来调用的;所以 "shared object"的程序是 "not stripped"的,但是也有"shared object"是stripped, 而“executable ” 的elf文件,因为并不对外提供函数的接口,所以其符号 在编译的过程中已经被替换为程序虚拟地址;因此“executable” 的elf文件是"stripped"的;

程序的载入过程:

当我们运行一个程序的时候,操作系统打开程序文件做完相应的处理后,会把控制权交给 程序解释器(比如:/lib/ld-linux.so.2 就是程序解释器的一种 ), 程序解释器根据程序的头部信息,生成程序的 内存虚拟地址的入口,并从程序需要的动态链接库文件中查找对应的符号地址,这些找到的符号地址,被加载器进行了重定向,然后加入到当前程序的虚拟内存地址空间中合适的位置,从而完成 当前程序中的符号解析,至此完成程序虚拟地址到 内存虚拟地址的转换工作;然后程序解释器创建程序的进程映像,创建进程映像之后,会把控制权交给程序的入口地址,从而开始程序的执行;

在这个过程中:

1. 无论是程序本身,还是其依赖的动态链接库,被载入的都是 type=LOAD的segment;其他segment不会在程序的正常加载过程中被载入内存; 2. 载入内存后,在运行时候,访问的地址是: 内存虚拟地址。这个内存虚拟地址 并不是 “程序虚拟地址”,也不是“内存物理地址”;但是 这三者之间是有关系的: A. “程序虚拟地址“是源代码被编译链接之后生成的;这其中要关注的是type=LOAD的segment 对应的地址范围,因为这些segment会被加载到内存;通过 readelf -l 命令来查看segment的地址范围,也可以通过 readelf -S 来获取对应section的地址,从而计算出segment的地址; B. “内存虚拟地址” 是程序被加载后,其进程映像对应的 虚拟地址,它一般是由加载器分配的;如果程序运行过程中发生了或者启动时候发生了常见的segment报错,那么这个segment 地址一般都是 "内存虚拟地址",查看特定文件的内存虚拟地址可以通过命令: cat /proc/{PID_VALUE}/maps | grep FILE_PATH 来获得; C. type=LOAD的segment的地址是以程序虚拟地址来表示的 ,对于executable文件来说,它和内存虚拟地址是一致的,因为编译后的程序中的部分代码可能是地址相关的,所以为了保证 程序能够可靠运行,一般对于"executable"的elf文件来说,其 内存虚拟地址 和程序虚拟地址是相同的, 而 "shared object"的程序虚拟地址(TYPE=LOAD的segment) 总是从0开始,这个地址 和 内存虚拟地址是不同的,即便是 同一个“shared object” 文件,在不同的进程中 映射对应的 内存虚拟地址也是不同的,因为这个地址是 加载器 分配的. 这一点在ldd 命令的结果中体现的非常明显,我们知道ldd的输出结果表示对相应的共享库的依赖,其输出结果的最后一节是一个地址,而这个地址就是:内存虚拟地址;每次用ldd去查看其依赖的时候,这个地址都是发生变化的,因为是 加载器分配的; D. ”内存虚拟地址“和“程序虚拟地址” 都是虚拟地址,并且都采用分页的机制(默认page大小为4KB,也就是0x1000对齐),所以 对于 type=LOAD的segment 尽管在 “程序虚拟地址” 和“内存虚拟地址” 可能并不相同,但是对应的segment 的大小一定是相同的; E. “程序虚拟地址” 通过分析文件获得,依赖于程序文件;而“内存虚拟地址” 是 程序加载器 分配的,所以每次运行程序可能都会发生变化,实际上没有不发生变化的,程序的运行访问的都是"内存虚拟地址" ,所以"内存虚拟地址" 到 “内存物理地址”之间存在mapping, 这个mapping的工作是操作系统来完成的;

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 从上面的结果中还可以看到: executable的elf文件最后有一个“stripped” 的说明,而另外两种elf文件所对应的是"not stripped", 这表示什么意思呢?
  • 程序的载入过程:
  • 在这个过程中:
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档