LV04-06-重定位-01-段与链接脚本基础
本文主要是重定位——段与链接脚本相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
Windows版本 | windows11 |
Ubuntu版本 | Ubuntu16.04的64位版本 |
VMware® Workstation 16 Pro | 16.2.3 build-19376536 |
终端软件 | MobaXterm(Professional Edition v23.0 Build 5042 (license)) |
Linux开发板 | 正点原子 i.MX6ULL Linux 阿尔法开发板 |
uboot | NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03) |
linux内核 | linux-4.15(NXP官方提供) |
Win32DiskImager | Win32DiskImager v1.0 |
点击查看本文参考资料
分类 | 网址 | 说明 |
官方网站 | https://www.arm.com/ | ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档 |
https://www.nxp.com.cn/ | NXP官方网站 | |
https://www.nxpic.org.cn/ | NXP 官方社区 | |
https://u-boot.readthedocs.io/en/latest/ | u-boot官网 | |
https://www.kernel.org/ | linux内核官网 |
点击查看相关文件下载
分类 | 网址 | 说明 |
NXP | https://github.com/nxp-imx | NXP imx开发资源GitHub组织,里边会有u-boot和linux内核的仓库 |
https://elixir.bootlin.com/linux/latest/source | 在线阅读linux kernel源码 | |
nxp-imx/linux-imx/releases/tag/rel_imx_4.1.15_2.1.0_ga | NXP linux内核仓库tags中的rel_imx_4.1.15_2.1.0_ga | |
nxp-imx/uboot-imx/releases/tag/rel_imx_4.1.15_2.1.0_ga | NXP u-boot仓库tags中的rel_imx_4.1.15_2.1.0_ga | |
I.MX6ULL | i.MX 6ULL Applications Processors for Industrial Products | I.MX6ULL 芯片手册(datasheet,可以在线查看) |
i.MX 6ULL Applications ProcessorReference Manual | I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网) | |
ARM | Cortex-A7 MPCore Technical Reference Manual | Cortex-A7 MPCore技术参考手册 |
ARM Architecture Reference Manual ARMv7-A and ARMv7-R edition | ARM架构参考手册ARMv7-A和ARMv7-R版 | |
Arm Generic Interrupt Controller Architecture Specification- version 3 and version 4 | Arm通用中断控制器架构规范-版本3和版本4 | |
ARM Generic Interrupt Controller Architecture Specification - Version 2.0 | Arm通用中断控制器架构规范-版本2.0 | |
ARM Cortex-A Series Programmer's Guide for ARMv7-A | Cortex-A系列ARMv7-A编程指南 |
一、段的概念
1. 基本概念
段是程序的组成元素。将整个程序分成一个一个段,并且给每个段起一个名字,然后在链接时就可以用这个名字来指示这些段,使得这些段排布在合适的位置。程序的段包括以下几部分:
(1)代码段(.text):存放代码指令
(2)只读数据段(.rodata):存放有初始值并且 const 修饰的全局类变量(全局变量或 static 修饰的局部变量)
(3)数据段(.data):存放有初始值的全局类变量
(4)零初始化段(.bss):存放没有初始值或初始值为 0 的全局类变量
(5)注释段(.comment):存放注释
需要注意的是:bss 段和注释段不保存在 bin或者elf 文件中。注释段里面的机器码是用来表示文字的。
2. 在代码中的体现
这里用到之前的工程代码,具体修改了哪些内容可以看这里:。
- (1)在主函数 main.c 文件中创建不同属性的全局变量
1 | char g_charA = 'A'; // 存储在 .data段 |
- (2)创建链接脚本 imx6ull.lds
1 | SECTIONS { |
- (3)在 Makefile 文件中指明使用链接脚本 imx6ull.lds 控制链接过程
1 | # 使用链接脚本链接 |
- (4)然后我们编译程序,会得到一个dis文件,我们打开这个文件会发现以下内容:
在反汇编文件中程序的地址从 0x80100000 开始
整个程序被分为不同的段,每个段以 Disassembly of section 作为开始
段落之间的地址是连续的,并且从低地址到高地址,段依次为:代码段、只读数据段、数据段、 bss 段、注释段(注意 bss 段和注释段不包含在elf/bin 文件中。那我们定义的几个变量都在哪?这个就进去搜一搜吧。
二、链接脚本
链接脚本控制程序的链接过程,它规定如何把输入文件内的段放入输出文件, 并控制输出文件内的各部分在程序地址空间内的布局。
1. 刚才的链接脚本?
1 | SECTIONS { |
这就是刚才的链接脚本,具体使用的时候我们需要再编译的过程中使用-T filename.lds
指定,否则在编译时将使用默认的链接脚本(默认的链接脚本无法进行一些段的复杂操作):
需要注意,对于结构较为简单的程序,也可以使用默认的链接脚本,并手动指定不同段在输出文件中的位置,例如:
1 | 将所有程序的.text段放在一起,起始地址设置为0x80100000 |
2. 链接脚本语法
相关的语法,我们其实可以看GNU的官方文档:Using LD, the GNU linker
1 | SECTIONS { |
- secname:段的名称
- start:段的运行地址( runtime addr),也称为重定位地址( relocation addr)
- AT ( ldadr ): ldadr 是段的加载地址( load addr); AT 是链接脚本函数,用于将该段的加载地址设定为 ldadr;如果不添加这个选项,默认的加载地址等于运行地址。
- 其他的链接脚本函数我们之后用到了再学习,想进一步了解可以参考上面的官方文档。
- { contents }: { } 用来表示段的起始结束; content 为该段包含的内容,可以由用户自己指定。
- BLOCK(align) (NOLOAD), >region :phdr =fill:很少用到,可以不做深入学习。
3. 解析链接脚本
我们来分析一下上边用测试的链接脚本:
1 | SECTIONS { |
根据上述链接脚本的配置, .bin 文件中的数据结构如下图所示:
上面我们写的链接脚本称为一体式链接脚本,与之相对的是分体式链接脚本,区别在于代码段(.text)和数据段(.data)的存放位置是否是分开的。例如现在的一体式链接脚本的代码段后面依次就是只读数据段、数据段、bss 段,都是连续在一起的。 分体式链接脚本则是代码段、只读数据段,中间间隔很远之后才是数据段、 bss 段。分体式链接脚本实例如下:
1 | SECTIONS { |
之后的代码更多的采用一体式链接脚本,原因如下:
(1)分体式链接脚本适合单片机,因为单片机自带有 flash,不需要将代码复制到内存占用空间。而我们的嵌入式系统内存非常大,没必要节省这点空间,并且有些嵌入式系统没有可以直接运行代码的 Flash,就需要从存储设备如 Nand Flash 或者 SD 卡复制整个代码到内存;
(2)JTAG 等调试器一般只支持一体式链接脚本;
4. 清除bss段
之前提到过 bin 文件中并不会保存 bss 段的值,因为这些值都是 0,保存这些值没有意义并会使得 bin 文件臃肿。当程序运行涉及到 bss 段上的数据时, CPU 会从 bss 段对应的内存地址去读取对应的值,为了确保从这段内存地址上读取到的 bss 段数值为 0,在程序运行前需要将这一段内存地址上的数据清零,即清除 bss 段。
4.1 start.S
1 | .text |
4.2 imx6ull.lds
1 | SECTIONS { |
4.3 main.c
1 | int main(int argc, const char * argv[]) |
编译过后,烧写到板子执行,保存在 bss 段中的变量 g_intA, g_intB 的值都为 0,表明清除 bss 段成功(但是吧,我没清的话)。