可重定位文件介绍
目标文件是可重定位文件,可以根据链接脚本把section内的代码映射到实际运行的地址。
.line:原始C源程序中的行号和 .text节中机器指令之间的映射
查看符号表举例如下:
重定位含义
重定位地址举例:
左侧全部使用 0 1 来表示程序, 0010 表示“跳转”指令,指令后接的就是跳转的地址,现在跳转的地址是 0101 (即 5),但是如果在 3 的位置插入一条指令的话,那么需要跳转的地址可能就不是 0101 了,从而导致程序的修改。
1 重定位
链接器在完成了符号解析之后,就可以进行重定位了。
不过由于重定位的内容比较复杂,当然如果你是做编译器或者逆向的话,重定位的原理是必须要求掌握的,但是作为应用开发者来说这不需要,因为了解详细原理的意义不大,因此有关重定位,我们这里只介绍基本内容。
1.1 重定位的种类
重定位的种类其实有两种,
(1)第一种:动态重定位
所谓动态重定位就是,重定位的动作是在程序运行的过程中动态完成的,因此被称为动态重定位。加载动态链接库就是动态重定位。
(2)第二种:静态重定位
所谓静态重定位就是,由链接阶段完成的重定位,因为是在程序运行之前做的重定位,因此被称为静态重定位。
所以我们现在要讲的由链接器完成的静态重定位。
1.2 静态重定位做什么事情
做两件很重要的事情:
· 将同名节整合为新的同名聚合节
· 将可执行目标文件中各聚合节的地址,重定位为实际运行的地址
(1)将同名节整合为新的同名聚合节
其中重点是将.text和.data节聚合为新的同名聚合节,为了能够将.text和.data节聚合为新的聚合节,需要依赖一些信息,这些信息存储在了.rel.text和.rel.data中。
.rel.text中的信息:实现.text的聚合
.rel.data中的信息:实现.data的聚合
(2)将可执行目标文件中各聚合节的地址,重定位为实际运行的地址
其实就是将程序在内存中实际运行时的内存地址赋给聚合节,程序在内存中运行时,就是按照重定位的地址来运行的,至于重定位的地址具体是多少,要分裸机和OS两种情况来看。
1)如果程序是直接裸机运行的话(没有OS)
程序是直接运行在物理内存上的,所以重定位的运行地址就是物理地址,所以CPU取指时所取得的指令地址就是物理地址。
2)如果程序是基于OS运行的话
大部分的OS都有提供虚拟内存机制,所以程序是运行在OS虚拟内存上的,虚拟内存所提供的地址就是虚拟地址,所以CPU取指时,所取得的指令地址就是虚拟地址。
虚拟内存是基于真实的物理存储器构建出来的,所以程序最终其实还是运行在物理内存上的,因此虚拟地址最终还是会被转为物理地址,然后到物理内存上该物理地址所指定的位置读取指令。
疑问:反正程序最终都是运行在物理内存上的,为什么要在真实物理存储器上弄出一个虚拟内存呢?
有关这个原因,请大家看《计算机体系结构———操作系统》的课程,里面介绍虚拟内存时有详细介绍。
我们都知道现在的OS基本都支持多进程并发运行,其实多进程并发运行与虚拟内存机制有着莫大关系。
疑问:什么是进程,与程序什么关系?
· 进程:动态运行的程序,进程有生有死,是一个动态运行的过程
· 程序:存放在硬盘上,没有运行的静态的可执行目标文件。
疑问:什么是进程的并发运行?
多个进程同时运行,这就并发运行,比如酷狗/浏览器/wps同时运行着,这几个程序就是并发运行的。
我们这里简单认为,单个程序对应单个的进程,实际上一程序可以是多进程的,有关这一点,请大家看《Linux系统编程、网络编程》这门课,有详细介绍。
为什么能够并发运行?
实际上与虚拟内存有很大关系。因为有虚拟内存这个东西,所以每个程序运行时,虚拟内存机制会给每一个进程都弄一个虚拟内存,也就是说每一个进程都是运行在自己独立的虚拟内存上的。
由于进程是运行在虚拟内存上的,因此“虚拟内存空间”也被称为了“进程空间”。
进程运行在自己的虚拟内存上时,每个进程运行了一个短暂的时间片(ms)后,快速切换到其它进程上运行,此时cpu就会取指运行不同进程的指令。
如此在宏观上,我们会感觉所有的进程都是同时运行的,这就是进程的并发运行,从介绍可以看出,进程间的并发运行与虚拟内存机制有着密切的关系。
与并发运行相对应的还有一个概念,那就是“并行运行”,有关这两个概念的异同请看《Linux系统编程、网络编程》的课程。
我们这里所面对的OS是Linux,Linux有虚拟内存机制,所以gcc编译得到的程序基于Linux运行时,就是运行在虚拟内存上的。
(3)如何指定重定位运行地址
实际上是通过“链接脚本文件”来指定的,“链接脚本文件”里面会说明实际的运行地址是多少,重定位时会把实际的运行地址赋值给新的聚合节,如此一来,函数和全局变量就有了真正可以运行的运行地址。
实际运行时,将程序拷贝到运行地址所指定的内存位置,cpu的pc存放第一条指令地址(指向第一条指令_start),然后整个程序就运行起来了。
至于重定位时,给聚合节具体指定的运行地址应该是多少,这里要分裸机和OS两种情况来定。
1)裸机运行
运行地址(物理地址)是多少,可以由程序员自己来定,比如我们一般可以指定为0,表示程序需要拷贝到物理内存的0地址处,从0地址处开始存放。
我们将0地址写到链接脚本文件中,gcc编译时给他指定链接脚本,重定位后运行地址就从0开始。
裸机运行时,整个计算机上就一个程序,由于没有OS虚拟内存的参与,所以裸机只能运行一个程序(单进程)。
2)基于OS运行
运行地址(虚拟地址)为一个固定值,不同OS这个固定值不一样,比如在Linux这边
32位OS:从0x08048000开始
64位OS:从0x0000000000400000开始
程序运行时,程序会被拷贝到虚拟内存的0x08048000或者0x0000000000400000位置处,然后pc指向_start,程序就运行起来了。
这个地址也是指定在链接脚本中的,gcc编译基于Linux运行的程序时,这个链接脚本不需要我们自己给,是自动给的,这个脚本中会指定0x08048000或者0x0000000000400000的地址。
而且gcc编译每一个程序,链接重定位所指定的地址都是0x08048000或者0x0000000000400000。
疑问:重定位时,如果每个程序都是相同的0x08048000或者0x0000000000400000的话,运行时不会冲突吗?
不会冲突,因为每个进程的虚拟内存是独立的,虽然都是相同的地址,但是底层实际对应的都是不同的物理空间,
将虚拟地址转换为物理地址后,得到的物理地址是不同的,所以不同的物理地址所指向的物理空间不同,自然不同物理内存空间中存放的是不同程序的指令。
标签:定位,可重,文件,程序,地址,运行,OS,虚拟内存 From: https://www.cnblogs.com/god-of-death/p/16841209.html