• ARM 重定位引入和链接脚本


    一、一个事实:大部分指令是位置有关编码

    位置无关编码(PIC,position independent code):汇编源文件被编码成二进制可执行程序时,编码方式与位置(内存地址)无关。

    位置有关编码:汇编源码编码成二进制可执行程序后和内存地址是有关的。


    我们在设计一个程序时,会给这个程序指定一个运行地址(链接地址)。就是说我们在编译程序时,其实心里是知道我们程序将来被运行时的地址(运行地址)的,而且必须给编译器链接器指定这个地址(链接地址)才行。最后得到的二进制程序,理论上是和你指定的运行地址有关的,将来这个程序被执行时必须放在当时编译链接时给定的那个地址(链接地址)下才行,否则不能运行(就叫位置有关代码)。但是有个别特别的指令,他可以跟指定的地址(链接地址)没有关系,也就是说这些代码实际运行时不管放在哪里都能正常运行。


    对比:位置无关代码要好一些,适应性强,放在哪里都能正常运行;位置有关代码就必须运行在链接时指定的地址上,适应性差。位置无关码有一些限制,不能完成所有功能,有时候不得不使用位置有关代码。


    二、链接地址和运行地址:可能相同也可能不同

    对于位置有关代码来说:最终执行时的运行地址和编译链接时给定的链接地址必须相同,否则一定出错

    我们之前的裸机程序中,Makefile 中用 -Ttext 0x0 来指定链接地址是0x0。这意味着我们认为这个程序将来会放在0x0这个内存地址去运行。

    但是实际上,我们运行时的地址是 0xd0020010 (我们用 dnw 下载时指定的下载地址)。这两个地址看似不同,但是实际相同。这是因为 S5PV210 内部做了映射,把 SRAM 映射到了 0x0 地址去。

    在这里插入图片描述


    分清楚这两个概念
    链接地址:链接时指定的地址(指定方式为:Makefile中用-Ttext,或者链接脚本)
    运行地址:程序实际运行时地址(指定方式:由实际运行时被加载到内存的哪个位置说了算)


    三、再解 S5PV210的启动过程

    三星推荐方式和uboot的实现方式是不同的

    三星推荐的启动方式中:bootloader 必须小于 96KB 并大于 16KB,假定 bootloader 为 80KB,启动过程是这样子:先开机上电后 BL0 运行,BL0 会加载外部启动设备中的 bootloader 的前 16KB(BL1) 到 SRAM 中去运行,BL1 运行时会加载 BL2(bootloader 中 80-16=64KB )到 SRAM 中(从 SRAM 的1 6KB 处开始用)去运行;BL2 运行时会初始化 DDR 并且将 OS 搬运到 DDR 去执行 OS,启动完成。

    uboot 实际使用的方式:uboot 大小随意,假定为 200KB。启动过程是这样子:先开机上电后 BL0 运行,BL0 会加载外部启动设备中的 uboot 的前 16KB(BL1)到 SRAM 中去运行,BL1 运行时会初始化 DDR,然后将整个 uboot 搬运到 DDR 中,然后用一句长跳转(从 SRAM 跳转到DDR )指令从 SRAM 中直接跳转到 DDR 中继续执行 uboot 直到 uboot 完全启动。uboot 启动后在 uboot 命令行中去启动 OS


    四、现在明白为什么要重定位了吧?

    原因

    链接地址和运行地址有时候必须不相同,而且还不能全部用位置无关码,这时候只能重定位。

    扩展

    分散加载:把 uboot 分成 2 部分(BL1 和整个 uboot),两部分分别指定不同的链接地址。启动时将两部分加载到不同的地址(BL1 加载到 SRAM,整个 uboot 加载到 DDR ),这时候不用重定位也能启动。


    评价:分散加载其实相当于手工重定位。重定位是用代码来进行重定位,分散加载是手工操作重定位的。


    五、运行时地址由什么决定?

    运行时的地址是由运行时决定的(编译链接时是无法绝对确定运行时地址的)


    链接地址由什么决定?

    链接地址是由程序员在编译链接的过程中,通过 Makefile 中 -Ttext xxx 或者在链接脚本中指定的。程序员事先会预知自己的程序的执行要求,并且有一个期望的执行地址,并且会用这个地址来做链接地址。


    举例

    1、linux 中的应用程序。gcc hello.c -o hello,这时使用默认的链接地址就是 0x0,所以应用程序都是链接在 0 地址的。因为应用程序运行在操作系统的一个进程中,在这个进程中这个应用程序独享 4G 的虚拟地址空间。所以应用程序都可以链接到 0 地址,因为每个进程都是从 0 地址开始的。(编译时可以不给定链接地址而都使用 0)

    2、210 中的裸机程序。运行地址由我们下载时确定,下载时下载到 0xd0020010,所以就从这里开始运行。(这个下载地址也不是我们随意定的,是 iROM 中的 BL0 加载 BL1 时事先指定好的地址,这是由 CPU 的设计决定的)。所以理论上我们编译链接时应该将地址指定到 0xd0020010,但是实际上我们在之前裸机程序中都是使用位置无关码 PIC,所以链接地址可以是 0。


    六、从源码到可执行程序的步骤:预编译、编译、链接、strip

    预编译:预编译器执行。譬如 C 中的宏定义就是由预编译器处理,注释等也是由预编译器处理的。
    编译:  编译器来执行。把源码 .c .S 编程机器码 .o 文件。
    链接:	链接器来执行。把 .o 文件中的各函数(段)按照一定规则(链接脚本来指定)累积在一起,
    		形成可执行文件。
    strip: strip 是把可执行程序中的符号信息给拿掉,以节省空间。(Debug 版本和 Release 版本)
    objcopy:由可执行程序生成可烧录的镜像 bin 文件。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    程序段的概念: 代码段、数据段、bss段(ZI段)、自定义段

    段就是程序的一部分,我们把整个程序的所有东西分成了一个一个的段,给每个段起个名字,然后在链接时就可以用这个名字来指示这些段。也就是说给段命名就是为了在链接脚本中用段名来让段站在合适的位置。

    段名分为 2 种:一种是编译器链接器内部定好的,先天性的名字;一种是程序员自己指定的、自定义的段名。
    先天性段名:
    	代码段:(.text),又叫文本段,代码段其实就是函数编译后生成的东西
    	数据段:(.data),数据段就是C语言中有显式初始化为非0的全局变量
    	bss段:(.bss),又叫ZI(zero initial)段,就是零初始化段,对应C语言中初始化为0的全局变量。
    	
    后天性段名:
    	段名由程序员自己定义,段的属性和特征也由程序员自己定义。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    分析一些问题,跟这里结合,然后试图明白一些本质:
    1、C 语言中全局变量如果未显式初始化,值是 0。本质就是 C 语言把这类全局变量放在了 bss 段,从而保证了为 0 。
    2、C 运行时环境如何保证显式初始化为非 0 的全局变量的值在 main 之前就被赋值了?就是因为它把这类变量放在了.data 段中,而 .data 段会在 main 执行之前被处理(初始化)。


    链接脚本究竟要做什么?

    链接脚本其实是个规则文件,他是程序员用来指挥链接器工作的。

    链接器会参考链接脚本,并且使用其中规定的规则来处理 .o 文件中那些段,将其链接成一个可执行程序。

    链接脚本的关键内容有2部分:段名 + 地址(作为链接地址的内存地址)
    链接脚本的理解:
    SECTIONS {}	这个是整个链接脚本
    . 点号在链接脚本中代表当前位置。
    = 等号代表赋值
    
    • 1
    • 2
    • 3
    • 4
    • 5

    源自朱有鹏老师.

  • 相关阅读:
    主流开发环境和开发语言介绍
    OpenGL 单色
    Vue2 slot插槽学习笔记
    虹科分享 | 带您全面认识“CAN总线错误”(一)——CAN总线错误与错误帧
    【力扣】26. 删除有序数组中的重复项
    【工程师整活】Ai-WB1-A1S实现离线语音+APP+天猫精灵控制风扇
    众佰诚:现在的抖音小店赚钱是真的吗
    【乐吾乐3D可视化组态编辑器】灯光
    Python【验证大于九点数与九的联系】
    springboot+vue+java校园交友信息推荐网站
  • 原文地址:https://blog.csdn.net/weixin_42109053/article/details/128175949