- 一个程序中含有那些段?
- 代码段:如果它不在链接地址上,就需要重定位
- 只读数据段:如果它不在链接地址上,就需要重定位
- 可读可写的数据段:如果它不在链接地址上,就需要重定位
- BSS段:不需要重定位,因为程序里根本不保存BSS段,使用前把BSS段对应的空间清零即可
- 代码段:如果它不在链接地址上,就需要重定位
- 谁来做重定位?
- 对于6ULL/157这些功能强大的芯片,可以让片内ROM程序/片内固件来进行重定位
- 大部分时候,是程序本身做重定位:它把自己复制到链接地址去
- 程序运行时,其前面的一小段代码需要将整个程序拷贝链接地址去;但是起始运行的一小段拷贝代码并不位于链接地址,为什么也可以正常的把自身复制到链接地址上呢?为什么也可以正常的执行重定位工作呢?
- 关键在于这一小段代码使用的是位置无关的指令码(位置无关码)编写的
- 什么叫位置无关? 就是说这一段代码在任何地方都可以运行,跟所处的位置没有关系
- 怎么写出位置无关码:
- 跳转:使用相对跳转指令,不能使用绝对跳转指令
- 只能使用branch指令(比如
bl main
),不能给PC直接复制,比如ldr pc, =main
- 直接给PC复制就相当于将函数的链接地址赋给PC,这样就不是位置无关了
- 只能使用branch指令(比如
- 不要访问全局变量、静态变量
- 不使用字符串
- 跳转:使用相对跳转指令,不能使用绝对跳转指令
- 对于F103/157/6ULL,实现一个memcpy函数来进行重定位即可
- 数据传输三要素:源、目的、长度
- 怎么做重定位和清除BSS段?
- 核心是: 复制
- 复制需要知道源、目的、长度
- 怎么知道代码段/数据段保存在哪?(加载地址)
- 怎么知道代码段/数据段要被复制到哪?(链接地址)
- 怎么知道代码段/数据段的长度?
- 怎么知道BSS段的地址范围:起始地址、长度?
- 这一切
- 在keil中使用散列文件(Scatter File)来描述
- 在GCC中使用链接脚本(Link Script)来描述
- 在keil中使用散列文件(Scatter File)来描述
- 加载地址和链接地址的区别
程序运行时,应该位于它的链接地址处,因为:
- 使用函数地址时用的是”函数的链接地址”,所以代码段应该位于链接地址处
- 去访问全局变量、静态变量时,用的是”变量的链接地址”,所以数据段应该位于链接地址处
但是: 程序一开始时可能并没有位于它的”链接地址”:
- 比如对于STM32F103,程序被烧录器烧写在Flash上,这个地址称为”加载地址”
- 比如对于IMX6ULL/STM32MP157,片内ROM根据头部信息把程序读入内存,这个地址称为“加载地址”
当加载地址 != 链接地址时,就需要重定位。