代码重定位与uboot

为什么要重定位

大部分的程序是不需要重定位的,但是有时候需要。

最常见的例子就是我们的UBOOT,因为我们的UBOOT有200多KB,但是我们开始BL0的地方只有96KB。所以我们需要在96KB之前进行重定位,使开发板能够进行重定位。如果代码不是位置无关码,代码必须放在链接地址开始的地方,程序才可以正常运行,否则的话当PC去访问、执行某个变量名、函数名对应地址上的代码时就会找不到,接着程序无疑就是跑飞。

什么是重定位

重定位:把代码搬移到你想要的地址,本来程序是运行在运行地址处的,你可以通过重定位搬移到链接地址处。

链接地址: 编译器对代码中的变量名、函数名等东西进行一个地址的编排,赋予这些抽象的东西一个地址,然后在程序中访问这些变量名、函数名就是在访问一些地址,这些地址我们称之为编译地址。

运行地址:是指程序指令真正运行的地址,是由用户指定的,用户将运行地址烧录到哪里,也就是PC当前执行指令所在的实际地址,就是运行的地址。也就是真实在程序中运行的地址

重定位基础知识

位置无关代码于位置有关代码

  1. 位置无关代码

    汇编源文件被编码成二进制可执行程序后与位置无关。有些特别的指令,可以跟地址没有关系。也就是说这些代码实际运行时,不管放在哪里都能正常运行。

  2. 位置无关代码

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

    我们在设计一个程序时,会给这个程序指定一个运行地址。就是说我们在写程序时,其实我们是知道我们程序将来被运行的地址的。

    必须给编译器和链接器指定这个地址才行,最后得到二进制程序。理论上和你指定的运行地址是有关的,这就叫做位置有关代码

  3. 运行地址和链接地址

    如果编译时 使用-Ttext 0x0来指定链接地址是0x0,这意味着我们认为这个程序将来会放在这个内存地址中运行。但是实际上我们运行的地址是下载在开发板的地址0xd0020010。因为是位置无关码,所以运行程序来是没有什么问题的。而且我们开发板对这些程序进行了映射,所以说这是一个偶然的情况。如果代码是位置相关的,那运行必然会出错。

S5PV210的启动过程

S5PV210

  1. 官方建议的启动过程

    开机启动,执行BL0,BL0会加载外部启动设备中的bootloader的前16KB到SRAM,(BL0是厂家事先固化好的程序)

    校验BL1,运行BL1

    BL1在运行时,初始化外部DDR,加载剩余的64kb代码到 BL2中 ( 64 = 80 - 16)

    运行BL2,初始化DDR,并且将OS搬运到DDR

    执行OS,启动完成。

  2. UBOOT实际上的启动过程

    先开机上电,BL0运行,BL0会加载外部启动设备中的UBOOT的前16KB(BL1)到SRAM中去运行,

    BL1运行会初始化DDR。然后将整个UBOOT,搬运到我们的DDR中。

    从SRAM中直接长跳转到DDR中继续执行我们的UBOOT。直到UBOOT完全启动。

    长跳转的意思就是从SRAM中跳转到DDR中。UBOOT启动后在命令行中去执行OS

从源码到可执行程序的步骤

  1. 预编译: 比如C中的宏定义就是由预编译器处理的,注释等也是由预编译器处理的。
  2. 编译: 编译器来执行,把源码中的.c/.s文件转换为.o文件。
  3. 链接: 链接器来执行,把.o文件中的各种函数(段)按照一定的规则(即使不用用链接脚本指定,链接器也有默认的固定的顺序)链接到一起,形成可执行程序。
  4. 链接的本质是规则文件,它指明了一种行动的规则,它是我们程序员用来指挥链接器工作的一种语言。
  5. 链接器会参考链接脚本来处理我们.o文件哪些段,将其链接成一个可执行程序。
  6. strip: 把可执行程序中的符号信息给拿掉,以节省空间,一般可以节省3分之一的空间。这样就从elf文件转换为bin文件

链接脚本存在的意义

链接脚本用来指定编译时的一些选项,使得程序能够按照开发者的意志进行指定的排布,也为了在某些特定的场合满足需求。

  1. 代码段(.text)

    代码段(code segment/text segment)通常是指用来存放 程序执行代码 的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于 只读 , 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些 只读的常数变量 ,例如字符串常量等。程序段为程序代码在内存中的映射。一个程序可以在内存中多有个副本。

  2. 数据段(.data)

    数据段就是C语言中有显示的初始化为非0的全局变量。数据段(data segment)通常是指用来存放程序中 已初始化 的 全局变量 的一块内存区域。数据段属于静态内存分配。

  3. BSS段(.bss) ,又叫做ZI段,零初始化段

    通常是指用来存放程序中未初始化或初始化为0的全局变量和静态变量的一块内存区域。

    BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。特点是可读写的,在程序执行之前BSS段会自动清0。

    bss段的存放是指为其预留空间(占位符)BSS段在可执行文件中时候不占磁盘空间,要运行的时候才分配空间并清0.

  4. 自定义段

    由我们程序员自己定义,段的属性和特征也由我们自己定义

在移植uboot时,接触到一个概念叫做位置无关码,那么与它对应的就是位置有关码。提到这两个概念就还得提一提链接地址加载地址

链接地址,链接脚本里指定的,理论上程序运行时所处的地址。在编译时,编译器会根据链接地址来翻译位置有关码。

加载地址,程序运行时,实际所处的地址。

位置无关码位置有关码,是相对于一条指令的正常目的来说的。比如 ldr r0 ,=标号,它的正常目的是取得标号处的地址,对于这个目的,它是位置有关码,运行的地址不对就获取不到正确的标号地址,其实它无论在哪都是获取的程序加载地址等于链接地址时,标号的地址,如果你就是想要这个值,那么用这条指令是非常正确的,就不用理会什么位置无关码,位置有关码的概念了,这一点非常重要。

因此,当加载地址不等于链接地址时,并不是不可以用位置无关码,而是要看你用位置无关码是否达到了你想要的目的。

位置无关码,依赖于程序当前运行的PC值,进行相对的跳转,导致的结果就是,无论代码在哪,总能达到指令的正常目的,因此是位置无关的。

位置有关码,不依赖当前PC值,是绝对跳转,只有程序运行在链接地址处时,才能达到指令的正常目的,因此是位置有关系的。

位置无关与相关的代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
SECTIONS {  
. = 0x33f80000;
.text : { *(.text) }

. = ALIGN(4);
.rodata : {*(.rodata*)}

. = ALIGN(4);
.data : { *(.data) }

. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) *(COMMON) }
__bss_end = .;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.text  
.global _start
_start:

bl close_watch_dog /* 相对跳转,位置无关 */
bl _start
adr r0, close_watch_dog /* 获取标号地址,位置无关 */

ldr r0, SMRDATA /* 获取标号处的值,位置无关 */

ldr r0, =0x12345678
ldr r0, =SMRDATA /* 获取标号地址,位置有关 */
ldr r0, =main /* 获取函数名的地址,位置有关 */
ldr r0 ,=__bss_start /* 获取链接脚本里标号的地址,位置有关 */


close_watch_dog:
mov r1, #0
str r1, [r0]
mov pc, lr

SMRDATA:
.word 0x22111120
1
2
3
4
5
6
7
8
9
10
11
int a;  
void abc(){
a = 2;
}
int main(){
int b;
a =1 ;
b =1 ;
abc();
return 0;
}

如果如上代码编译后加载地址为0,那么代码将按如下顺序排放:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
00000000 <_start>:  
00000000: eb000006 bl 33f80020 <close_watch_dog>
00000004: ebfffffd bl 33f80000 <_start>
00000008: e28f0010 add r0, pc, #16
0000000c: e59f0018 ldr r0, [pc, #24] ;
00000010: e59f0018 ldr r0, [pc, #24] ;
00000014: e59f0018 ldr r0, [pc, #24] ;
00000018: e59f0018 ldr r0, [pc, #24] ;
0000001c: e59f0018 ldr r0, [pc, #24] ;

00000020 <close_watch_dog>:
00000020: e3a01000 mov r1, #0
00000024: e5801000 str r1, [r0]
00000028: e1a0f00e mov pc, lr

0000002c <SMRDATA>:
0000002c: 22111120 andscs r1, r1, #8
00000030: 12345678 eorsne r5, r4, #125829120 ; 0x7800000
00000034: 33f8002c mvnscc r0, #44 ; 0x2c
00000038: 33f80064 mvnscc r0, #100 ; 0x64
0000003c: 33f800a0 mvnscc r0, #160 ; 0xa0

00000040 <abc>:
00000040: e52db004 push {fp} ; (str fp, [sp, #-4]!)
00000044: e28db000 add fp, sp, #0
00000048: e59f3010 ldr r3, [pc, #16] ; 33f80060 <abc+0x20>
0000004c: e3a02002 mov r2, #2
00000050: e5832000 str r2, [r3]
00000054: e28bd000 add sp, fp, #0
00000058: e8bd0800 pop {fp}
0000005c: e12fff1e bx lr
00000060: 33f800a0 mvnscc r0, #160 ; 0xa0

00000064 <main>:
00000064: e92d4800 push {fp, lr}
00000068: e28db004 add fp, sp, #4
0000006c: e24dd008 sub sp, sp, #8
00000070: e59f3024 ldr r3, [pc, #36] ; 33f8009c <main+0x38>
00000074: e3a02001 mov r2, #1
00000078: e5832000 str r2, [r3]
0000007c: e3a03001 mov r3, #1
00000080: e50b3008 str r3, [fp, #-8]
00000084: ebffffed bl 33f80040 <abc>
00000088: e3a03000 mov r3, #0
0000008c: e1a00003 mov r0, r3
00000090: e24bd004 sub sp, fp, #4
00000094: e8bd4800 pop {fp, lr}
00000098: e12fff1e bx lr
0000009c: 33f800a0 mvnscc r0, #160 ; 0xa0

如果加载地址为0x33f80000 则按照下边的顺序排放:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
33f80000 <_start>:  
33f80000: eb000006 bl 33f80020 <close_watch_dog>
33f80004: ebfffffd bl 33f80000 <_start>
33f80008: e28f0010 add r0, pc, #16
33f8000c: e59f0018 ldr r0, [pc, #24] ; 33f8002c <SMRDATA>
33f80010: e59f0018 ldr r0, [pc, #24] ; 33f80030 <SMRDATA+0x4>
33f80014: e59f0018 ldr r0, [pc, #24] ; 33f80034 <SMRDATA+0x8>
33f80018: e59f0018 ldr r0, [pc, #24] ; 33f80038 <SMRDATA+0xc>
33f8001c: e59f0018 ldr r0, [pc, #24] ; 33f8003c <SMRDATA+0x10>

33f80020 <close_watch_dog>:
33f80020: e3a01000 mov r1, #0
33f80024: e5801000 str r1, [r0]
33f80028: e1a0f00e mov pc, lr

33f8002c <SMRDATA>:
33f8002c: 22111120 andscs r1, r1, #8
33f80030: 12345678 eorsne r5, r4, #125829120 ; 0x7800000
33f80034: 33f8002c mvnscc r0, #44 ; 0x2c
33f80038: 33f80064 mvnscc r0, #100 ; 0x64
33f8003c: 33f800a0 mvnscc r0, #160 ; 0xa0

33f80040 <abc>:
33f80040: e52db004 push {fp} ; (str fp, [sp, #-4]!)
33f80044: e28db000 add fp, sp, #0
33f80048: e59f3010 ldr r3, [pc, #16] ; 33f80060 <abc+0x20>
33f8004c: e3a02002 mov r2, #2
33f80050: e5832000 str r2, [r3]
33f80054: e28bd000 add sp, fp, #0
33f80058: e8bd0800 pop {fp}
33f8005c: e12fff1e bx lr
33f80060: 33f800a0 mvnscc r0, #160 ; 0xa0

33f80064 <main>:
33f80064: e92d4800 push {fp, lr}
33f80068: e28db004 add fp, sp, #4
33f8006c: e24dd008 sub sp, sp, #8
33f80070: e59f3024 ldr r3, [pc, #36] ; 33f8009c <main+0x38>
33f80074: e3a02001 mov r2, #1
33f80078: e5832000 str r2, [r3]
33f8007c: e3a03001 mov r3, #1
33f80080: e50b3008 str r3, [fp, #-8]
33f80084: ebffffed bl 33f80040 <abc>
33f80088: e3a03000 mov r3, #0
33f8008c: e1a00003 mov r0, r3
33f80090: e24bd004 sub sp, fp, #4
33f80094: e8bd4800 pop {fp, lr}
33f80098: e12fff1e bx lr
33f8009c: 33f800a0 mvnscc r0, #160 ; 0xa0

Disassembly of section .bss:

33f800a0 <a>:
33f800a0: 00000000 andeq r0, r0, r0

各指令的地址相关性

B BL指令

1
2
3
4
5
6
7
b是相对跳转:PC + 偏移值 (PC值等于当前地址+8)
偏移值:机器码0xeb000006低24位0x000006按符号为扩展为32位0x00000006正数,向后跳转0x6个4字节也就是0x1c
1、加载地址0:0 + 8 + 0x1c = 0x20正确跳转
2、加载地址0x3ff80000:0x3ff80000 + 8 + 0x1c = 0x33f80020 正确跳转

bl close_watch_dog
33f80000:  eb000006   bl33f80020 <close_watch_dog>
1
2
3
4
5
6
偏移值:机器码0xebfffffd低24位fffffd按符号位扩展为32位0xfffffffd负数(-3),向前跳转0x3个4字节也就是0xc
1、加载地址0:4 + 8 - 0xc = 0正确跳转
2、加载地址0x3ff80000: 0x3ff80004 + 8 + 0xc = 0x33f80000正确跳转

bl _start
33f80004:  ebfffffd  bl33f80000 <_start>

通过以上分析,我们知道B是相对跳转,位置无关码,也可以知道为什么32为arm指令集,B的范围为正负32M了,24位去掉1位符号位,恰好等于32M。

ADR指令

1
2
3
4
5
6
1、加载地址0:0 + 8 + 16 = 0x20正确
2、加载地址0x3ff80000:0x3ff80008 + 8 + 16 = 0x33f80020正确
adr 获取的是标号处的“实际”地址,标号在哪就是哪个地址,跟位置无关,总能获得想要的值

adr r0, close_watch_dog /* 获取标号处的地址,位置无关 */
33f80008: e28f0010  add  r0, pc, #16

LDR指令

1
2
3
4
5
1、加载地址0:r0 = c + 8 + 24 = 0x2c处的值0x22111120正确
2、加载地址0x3ff80000:r0 = 0x3ff8000c + 8 + 24 = 0x33f8002c处的值0x22111120正确

ldr r0, SMRDATA /* 获取标号处的值,位置无关 */
33f8000c:   e59f0018  ldr   r0, [pc, #24]; 33f8002c <SMRDATA>
1
2
3
4
5
1、加载地址0:r0 = 0x10 + 8 + 24 = 0x30处的值0x12345678正确
2、加载地址0x3ff80000:r0 = 0x3ff80010 + 8 + 24 = 0x33f80030处的值0x12345678正确

ldr r0, =0x12345678 /* 常数赋值,位置无关 */
33f80010: e59f0018  ldr  r0, [pc, #24]; 33f80030 <SMRDATA+0x4>
1
2
3
4
5
1、加载地址0:r0 = 0x14 + 8 + 24 = 0x34处的值33f8002c与标号实际地址(2c)不符合,不正确
2、加载地址0x3ff80000:r0 = 0x3ff80014 + 8 + 24 = 0x33f80034处的值33f8002c正确

ldr r0, =SMRDATA /* 获取标号地址,位置有关 */
33f80014: e59f0018  ldrr0, [pc, #24]; 33f80034 <SMRDATA+0x8>
1
2
3
4
这俩和 ldr r0, =SMRDATA一致,位置有关,在0地址处运行不正确。

ldr r0, =main/* 获取函数名的地址,位置有关 */
ldr r0 ,=__bss_start /* 获取链接脚本里标号的地址,位置有关 */

C 函数

  • 全局变量
1
2
3
4
5
6
7
8
9
10
00000040 <abc>:  
00000040: e52db004 push {fp} ; (str fp, [sp, #-4]!)
00000044: e28db000 add fp, sp, #0
00000048: e59f3010 ldr r3, [pc, #16] ; 33f80060 <abc+0x20>
0000004c: e3a02002 mov r2, #2
00000050: e5832000 str r2, [r3]
00000054: e28bd000 add sp, fp, #0
00000058: e8bd0800 pop {fp}
0000005c: e12fff1e bx lr
00000060: 33f800a0 mvnscc r0, #160 ; 0xa0
1
2
000000a0 <a>: 
000000a0: 00000000 andeq r0, r0, r0
1
2
3
4
5
6
7
8
9
10
33f80040 <abc>:  
33f80040: e52db004 push {fp} ; (str fp, [sp, #-4]!)
33f80044: e28db000 add fp, sp, #0
33f80048: e59f3010 ldr r3, [pc, #16] ; 33f80060 <abc+0x20>
33f8004c: e3a02002 mov r2, #2
33f80050: e5832000 str r2, [r3]
33f80054: e28bd000 add sp, fp, #0
33f80058: e8bd0800 pop {fp}
33f8005c: e12fff1e bx lr
33f80060: 33f800a0 mvnscc r0, #160 ; 0xa0
1
2
33f800a0 <a>: 
33f800a0: 00000000 andeq r0, r0, r0

r3为全局变量a的地址,a是存放在 0起始的地址还是0x33f80000起始的地址,它都认为a的地址是0x33f800a0。因此,C函数中调用全局变量是位置有关码。

  • 函数调用
1
33f80084:  ebffffed  bl  33f80040 <abc>

由于main函数和abc函数挨得比较近,在32M范围之内,因此被翻译成了一条bl指令,那么与位置无关。

如果,调用的函数比较远,大于32M的话,是与位置有关系的,这个不再验证了。

  • 局部变量

局部变量在函数刚开始的地方被压入栈,赋值语句被翻译成:

1
2
3
33f8007c:   e3a03001  mov  r3, #1
33f80080:   e50b3008
str r3, [fp, #-8]

从其中看出,其是与位置无关的。


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 yxhlfx@163.com

文章标题:代码重定位与uboot

本文作者:红尘追风

发布时间:2019-01-18, 17:02:41

原始链接:http://www.micernel.com/2019/01/18/%E4%BB%A3%E7%A0%81%E9%87%8D%E5%AE%9A%E4%BD%8D%E4%B8%8Euboot/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录