首页 /  技术专区  /  C 宽屏模式 >

可执行目标文件——地址重定位

1 重定位

链接器在完成了符号解析之后,就可以进行重定位了。

不过由于重定位的内容比较复杂,当然如果你是做编译器或者逆向的话,重定位的原理是必须要求掌握的,但是作为应用开发者来说这不需要,因为了解详细原理的意义不大,因此有关重定位,我们这里只介绍基本内容。

1.1 重定位的种类

重定位的种类其实有两种,

(1)第一种:动态重定位

        所谓动态重定位就是,重定位的动作是在程序运行的过程中动态完成的,因此被称为动态重定位。

        有关动态重定位,在讲Uboot和内核移植时会介绍。

(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开始。

我们后面讲到arm裸机时,会介绍如何修改这个链接脚本。

裸机运行时,整个计算机上就一个程序,由于没有OS虚拟内存的参与,所以裸机只能运行一个程序(单进程)。

2)基于OS运行

运行地址(虚拟地址)为一个固定值,不同OS这个固定值不一样,比如在Linux这边

        32位OS:从0x08048000开始

        64位OS:从0x0000000000400000开始

程序运行时,程序会被拷贝到虚拟内存的0x08048000或者0x0000000000400000位置处,然后pc指向_start,程序就运行起来了。

这个地址也是指定在链接脚本中的,gcc编译基于Linux运行的程序时,这个链接脚本不需要我们自己给,是自动给的,这个脚本中会指定0x08048000或者0x0000000000400000的地址。

而且gcc编译每一个程序,链接重定位所指定的地址都是0x08048000或者0x0000000000400000。

疑问:重定位时,如果每个程序都是相同的0x08048000或者0x0000000000400000的话,运行时不会冲突吗?

不会冲突,因为每个进程的虚拟内存是独立的,虽然都是相同的地址,但是底层实际对应的都是不同的物理空间,

将虚拟地址转换为物理地址后,得到的物理地址是不同的,所以不同的物理地址所指向的物理空间不同,自然不同物理内存空间中存放的是不同程序的指令。


头像
0/200
图片验证码