程序编译时候我们会指定一个链接地址(也就是程序定位的运行地址),编译后,cpu的pc指针指向这个地址开始执行,就时没为题的。

但是我们现在想把该段程序运行时(没法对该段程序进行再次指定地址的编译了)挪动到其它内存区域(程序动态加载功能),要想该段代码仍然能正常运行,就需要对该段代码进行重定位,而不是简单的拷贝该段代码到指定内存区域即可。

来看段简单的程序:

1 static int rel_a = 0;
2
3 void rel_test(void)
4 {
5         rel_a = 100;
6         printf("rel_test\r\n");
7 }

8 int board_init(void)

9 {

10         rel_test();

11 }

反汇编后如下:

1 87804184 <rel_test>:
2 87804184: e59f300c ldr r3, [pc, #12] ; 87804198 <rel_test+0x14>
3 87804188: e3a02064 mov r2, #100 ; 0x64
4 8780418c: e59f0008 ldr r0, [pc, #8] ; 8780419c <rel_test+0x18>
5 87804190: e5832000 str r2, [r3]
6 87804194: ea00d668 b 87839b3c <printf>
7 87804198: 8785da50 ; <UNDEFINED> instruction: 0x8785da50
8 8780419c: 878426a2 strhi r2, [r4, r2, lsr #13]

10 878041a0 <board_init>:
11 878041a0: e92d4010 push {r4, lr}
12 878041a4: ebfffff6 bl 87804184 <rel_test>
13
14 ......
15
16 8785da50 <rel_a>:
17 8785da50: 00000000 andeq r0, r0, r0

明白一个知识点:bl 指令时位置无关指令,bl 指令是相对寻址的(pc+offset),即bl的跳转总是按照与当前pc指针的偏移差来跳转到指定地址,因此程序存放首地址无论在哪,bl都能正确跳转,因此bl也称为位置无关指令(具体看这篇文章b/bl与ldr区别 位置无关码),因此函数调用,属于位置无关代码。

再来看一下函数 rel_test 对于全局变量 rel_a 的调用,第 2 行设置 r3 的值为 pc+12 地址处的
值,因为ARM流水线的原因,pc寄存器的值为当前地址+8,因此pc=0X87804184+8=0X8780418C,
r3=0X8780418C+12=0X87804198,第 7 行就是 0X87804198 这个地址,0X87804198 处的值为
0X8785DA50。根据第 17 行可知,0X8785DA50 正是变量 rel_a 的地址,最终 r3=0X8785DA50。

总结一下 rel_a=100 的汇编执行过程:
①、在函数 rel_test 末尾处有一个地址为 0X87804198 的内存空间(示例代码 32.2.6.3 第 7
行),此内存空间保存着变量 rel_a 的地址。
②、函数 rel_test 要想访问变量 rel_a,首先访问末尾的 0X87804198 来获取变量 rel_a 的地
址,而访问 0X87804198 是通过偏移来访问的,很明显是个位置无关的操作。
③、通过 0X87804198 获取到变量 rel_a 的地址,对变量 rel_a 进行操作。
④、可以看出,函数 rel_test 对变量 rel_a 的访问没有直接进行,而是使用了一个第三方偏
移地址 0X87804198
,专业术语叫做 Label。这个第三方偏移地址就是实现重定位后运行不会出
错的重要原因!

因此,把上面程序搬运到另外一个内存区域,全局变量是地址相关的,我们需要特别的重定位(即把红色内容自己修正为正确的值即可)一下即可,而其它代码,直接搬运过去即可

问题是,我们在代码搬运时(程序运行时我们自己写程序做的事)怎么知道到底哪些代码需要重定位呢,那就是,在使用 ld 进行链接的时候使用选项“-pie”生成位置无关的可执行文件。LDFLAGS_u-boot += -pie

使用“-pie”选项以后会生成一个.rel.dyn 段(相当于编译器自动把需要重定位的段自动帮我们提取出来了,我们搬运代码的时候,单独对该段处理一下即可,就好像我们的程序自动能提取出我们程序的text段(仅仅存放指令,CPU进行访问),data段一样(该区域的数据会动态变化,比如变量内容)),我们就是靠这个.rel.dyn 来解决重定位问题
的,的.rel.dyn 段中有如下所示内容:

1 Disassembly of section .rel.dyn:
2
3 8785da44 <__rel_dyn_end-0x8ba0>:
4 8785da44: 87800020 strhi r0, [r0, r0, lsr #32]

5 8785da48: 00000017 andeq r0, r0, r7, lsl r0
6 ......
7 8785dfb4: 87804198 ; <UNDEFINED> instruction: 0x87804198
8 8785dfb8: 00000017 andeq r0, r0, r7, lsl r0

第 7 行和第 8 行这样的是一组,也就是两个 4 字节数据
为一组。高 4 字节是 Label 地址标识 0X17低 4 字节就是 Label 的地址,首先判断 Label 地址
标识是否正确,也就是判断高 4 字节是否为 0X17,如果是的话低 4 字节就是 Label 地址值。

第 7 行值为 0X87804198,第 8 行为 0X00000017,说明第 7 行的 0X87804198 是个 Label,
这个正是示例代码 32.2.6.3 中存放变量 rel_a 地址的那个 Label。根据前面的分析,只要将地址
0X87804198+offset 处的值改为重定位后的变量 rel_a 地址即可

具体的搬运程序的代码如下(代码是顺序往后执行的(无论是否遇到标签),除非跳转):

91         /*
92         * fix .rel.dyn relocations
93         */
94         ldr r2, =__rel_dyn_start /* 这两个标签标识符编译完后在程序中可以直接读取 */
95         ldr r3, =__rel_dyn_end /*  */
96 fixloop:
97         ldmia r2!, {r0-r1} /* (r0,r1) <- (SRC location,fixup) */
98         and r1, r1, #0xff
99         cmp r1, #23 /* relative fixup? */
100         bne fixnext
101
102         /* relative fix: increase location by offset */
103         add r0, r0, r4
104         ldr r1, [r0]
105         add r1, r1, r4
106         str r1, [r0]
107 fixnext:
108         cmp r2, r3
109         blo fixloop

第 94 行,r2=__rel_dyn_start,也就是.rel.dyn 段的起始地址。
第 95 行,r3=__rel_dyn_end,也就是.rel.dyn 段的终止地址。
第 97 行,从.rel.dyn 段起始地址开始,每次读取两个 4 字节的数据存放到 r0 和 r1 寄存器
中,r0 存放低 4 字节的数据,也就是 Label 地址;r1 存放高 4 字节的数据,也就是 Label 标志。
第 98 行,r1 中给的值与 0xff 进行与运算,其实就是取 r1 的低 8 位。
第 99 行,判断 r1 中的值是否等于 23(0X17)。
第 100 行,如果 r1 不等于 23 的话就说明不是描述 Label 的,执行函数 fixnext,否则的话
继续执行下面的代码。
第 103 行,r0 保存着 Label 值,r4 保存着重定位后的地址偏移,r0+r4 就得到了重定位后的
Label 值。此时 r0 保存着重定位后的 Label 值,相当于 0X87804198+0X18747000=0X9FF4B198。
第 104,读取重定位后 Label 所保存的变量地址,此时这个变量地址还是重定位前的(相当
于 rel_a 重定位前的地址 0X8785DA50),将得到的值放到 r1 寄存器中。
第 105 行 , r1+r4 即 可 得 到 重 定 位 后 的 变 量 地 址 , 相 当 于 rel_a 重 定 位 后 的
0X8785DA50+0X18747000=0X9FFA4A50。
第 106 行,重定位后的变量地址写入到重定位后的 Label 中,相等于设置地址 0X9FF4B198
处的值为 0X9FFA4A50。
第 108 行,比较 r2 和 r3,查看.rel.dyn 段重定位是否完成。
第 109 行,如果 r2 和 r3 不相等,说明.rel.dyn 重定位还未完成,因此跳到 fixloop 继续重定
位.rel.dyn 段。

这样就完成了程序的地址相关部分的搬运功能,地址都定位修改正确了。而其它地址无关的部分,直接搬运到目的地址即可。这样整个程序就实现了搬运后的正常运行。