韦东山嵌入式Linux学习笔记之——代码重定位004_代码重定位与位置无关码

2019-07-12 18:34发布

将程序(包含可运行的代码和数据)从一个位置(flash)移动至另外一个位置运行或进行数据的读写成为代码重定位。代码重定位的根本原因是改善某个存储介质的某些缺陷(例如存取速度,读写限制等等)。在实际中我们可以只重定位程序的某一段(如数据data段或者代码text段),或者将整个程序进行重定位。现在考虑将整个程序重定位至SDRAM所需要的技术细节:① 从flash中将程序复制到(重定位)SDRAM,要在链接脚本里指定程序的运行地址(runtime addr)为SDRAM的地址。② 编译会得到一个bin文件,这个bin文件烧写至flash上后一上电要从flash的0地址开始运行,因此在data数据段之前的代码段text要完成将整个程序复制到SDRAM的任务。③ 注意:刚一上电的时候代码还存放在flash的0地址起始的空间中,这就随之带来一个问题:链接脚本本来指定了程序应该运行的地址(runtime addr)0x300000000,但是为什么刚上电时程序在flash的0地址依旧可以运行呢?也就是说flash中data段前面的那部分代码(重定位之前的代码)与运行地址无关,简称位置无关码。
下面开始写程序验证:1. 首先编写链接脚本,可参考uboot的连接脚本并简单修改其内容就可以适应我们自己的单板。SECTIONS { . = 0x30000000; //整个程序在运行时应该放在SDRAM的0x30000000地址上(也就是SDRAM的起始地址) . = ALIGN(4); .text : { *(.text) } . = ALIGN(4); .rodata : { *(.rodata) } . = ALIGN(4); .data : { *(.data) } . = ALIGN(4); __bss_start = .; .bss : { *(.bss) *(.COMMON)} //这里的COMMON段指的是没有初值的段 _end = .; } 今后编写链接脚本的时候大多采用这种链接脚本,而不使用以前那种分体式的链接脚本(这种链接脚本的代码段和数据段在运行时是分开的)。
分体式的链接脚本主要是针对有nor或者nand flash的单片机,因为单片机的内存资源比较有限,因此适合采用分体式链接脚本。还有一个原因就是JTAG一般只支持完整式的链接脚本。2. 接下来修改start.S文件,在其中完成整个程序(text、rodata、data)段的重定位。 /* 重定位text、rodata、data段整个程序 */ //这里的0地址在nor启动时时nor flash的0地址,在nand启动时是片内内存SRAM的0地址(nand启动是硬件自动将nand前4K复制到SRAM中 mov r1, #0 ldr r2, = _start /* 第1条指令运行时的地址 */ldr r3, = __bss_start /* BSS段的起始地址(BSS段不需要重定位) *//* 接下来将r1所指地址中的值拷贝至r2所指的地址空间 */cpy:ldr r4, [r1] //从r1地址处拷贝一个字节到r4str r4, [r2]add r1, r1, #4add r2, r2, #4cmp r2, r3ble cpy/* 清除BSS段 */ldr r1, = __bss_startldr r2, = _endmov r3, #0clean:str r3, [r1]add r1, r1, #4cmp r1, r2ble clean结合反汇编码分析启动过程:

 注意:① 反汇编dis文件中,B/BL指令后面的地址值0x3xxxxxxx只是起方便查看作用,而不是直接跳转至该地址处。链接脚本中虽然指定了程序的运行地址0x30000000,但是如果我们将运行地址改成0地址程序依旧可以正常运行,这是为什么?这是因为程序在跳转(B/BL)时并不是跳转至一个绝对地址,而是采用当前PC+offset 这种相对地址跳转的方式,这也是位置无关码的原理。位置无关码的通俗含义就是:这段代码在任何地址处都可以正常运行而不需要将其放在运行地址处。
接下来发现main函数也使用了bl指令“bl main”,可是我们已经重定位了代码(SDRAM: 0x30000000),这里的bl指令让main函数还是在nor flash/SRAM中的地址处执行,这是不合适的。这时应该使用绝对地址跳转,直接跳转至SDRAM中运行main函数。
使用指令:ldr pc, =main /* 绝对跳转,跳到SDRAM中执行 */总结:
怎么写位置无关的程序:
使用位置无关码! 不使用绝对地址! 最根本的办法是看反汇编
a. 调用程序时使用B/BL相对跳转指令
b. 重定位之前, 不可使用绝对地址,比如:
   不可访问全局变量/静态变量
   不可访问有初始值的数组(因为初始值放在rodata里,使用绝对地址来访问)
c. 重定位之后, 使用绝对跳转命令跳到Runtime Addr,比如:
    ldr pc, =main

举例:在初始化SDRAM时使用数组对内存控制器的寄存器进行赋值:
查看反汇编码寻找原因: