uboot-relocation学习总结

来源:互联网 发布:php curl exec返回空 编辑:程序博客网 时间:2024/06/06 18:23

近期学习uboot,在relocation部分看了不少大神的分析,下面将自己的理解写下来,也是对此部分功能的学习巩固。

 

1.1 为什么要relocation

 

ubootSDRAM低位拷贝到高位后,各指令存储地址改变,对位置无法指令的执行,不受影响;但位置相关指令的执行,比如说读取一个全局变量(存储在一个绝对地址处),按照之前的寻址无法找到拷贝后的变量值,导致程序执行错误,这时即需要relocation来解决。

 

通过下面这个例子说明:

 

 


Static int G_test = 1;Uboot中执行函数:

Void Fun_test(void)

{

G_test = 0xff;

}

图中标识说明:

Fun_test: uboot中函数;

G_test: 全局变量,被函数Fun_test使用;

G_test_lable: 函数Fun_test尾端lable,其值是变量G_test的地址。

Fun_test_addr:  函数Fun_test拷贝前存储地址;

G_test_lable_addrG_test_lable拷贝前存储地址;

G_test_addr变量G_test拷贝前存储地址;

Fun_test_rel_addr函数Fun_test拷贝后存储地址;

G_test_lable_rel_addrG_test_lable拷贝后存储地址;

G_test_rel_addr变量G_test拷贝后存储地址;

 

 

此处插入一个知识点,arm如何对变量G_test进行寻址:

1)将变量G_test的地址存储在函数尾端的Label中(这段内存空间是由编译器自动分配的,而非人为)

2)基于PC相对寻址获取函数尾端Label上的变量地址

3)对G_test变量地址进行读写操作

 

下面看uboot在拷贝前函数Fun_test的执行:

1、调用函数Fun_test

2、通过函数尾端lableG_test_lable(存储地址是G_test_lable_addr)得到变量G_test存储地址G_test_addr,即[G_test_lable_addr] = G_test_addr

3、根据变量G_test的存储地址对其进行读写操作,例子中将其赋值为0xFF

4、函数执行结束,返回。

 

再看uboot拷贝后函数Fun_test的执行:

1、调用函数Fun_test

2、通过函数尾端lableG_test_lable(存储地址是G_test_lable_rel_addr)得到变量G_test存储地址仍然是G_test_addr,而不是我们期望的G_test_rel_addr,这样既无法准确获取变量的存储地址,导致程序执行出错。

 

relocation就是解决这个问题,使得uboot在拷贝后,仍能准确的寻址,避免程序执行出错,这就是relocation的原因。

 

由上面描述我们可以看出,问题出现在变量G_test的寻址上,不难看出只要将拷贝后的G_test_lable保存的值更新成拷贝后的变量存储地址G_test_rel_addr,问题就解决了,那如何操作呢,下面就是如何进行relocation

 

1.2 如何relocation

 

直接看源码(uboot2014.10  arch/arm/lib/relocate.S)

ENTRY(relocate_code)ldrr1, =__image_copy_start/* r1 <- SRC &__image_copy_start */subsr4, r0, r1/* r4 <- relocation offset */beqrelocate_done/* skip relocation */ldrr2, =__image_copy_end/* r2 <- SRC &__image_copy_end */ copy_loop:ldmiar1!, {r10-r11}/* copy from source address [r1]    */stmiar0!, {r10-r11}/* copy to   target address [r0]    */cmpr1, r2/* until source end address [r2]    */blocopy_loop/*-------------------------------------dividing line-------------------------------------------*//* * fix .rel.dyn relocations */ldrr2, =__rel_dyn_start/* r2 <- SRC &__rel_dyn_start */ldrr3, =__rel_dyn_end/* r3 <- SRC &__rel_dyn_end */fixloop:ldmiar2!, {r0-r1}/* (r0,r1) <- (SRC location,fixup) */andr1, r1, #0xffcmpr1, #23/* relative fixup? */bnefixnext /* relative fix: increase location by offset */addr0, r0, r4ldrr1, [r0]addr1, r1, r4strr1, [r0]fixnext:cmpr2, r3blofixloop


分界线前面是uboot数据拷贝,后面是relocation,从代码可以看到,relocation__rel_dyn_start__rel_dyn_end之间数据进行了处理,专为relocation使用,其结构如下:

连续24字节(共8字节)组成一relocation处理部分,其中前4字节保存需要relocation的存储地址,后面4个字节是一个lable,标记前一地址需要relocation__rel_dyn_start__rel_dyn_end这段数据编译器在编译时产生,编译器讲uboot拷贝后需要relocation的存储地址(即例中的G_test_lable_addr)均记录在此段中,然后由上述代码进行遍历,实现relocation,下面看代码实现: 

 ldrr2, =__rel_dyn_start/* r2 <- SRC &__rel_dyn_start */ldrr3, =__rel_dyn_end/* r3 <- SRC &__rel_dyn_end */fixloop:ldmiar2!, {r0-r1}//从__rel_dyn_start开始,每次连续读取8字节andr1, r1, #0xffcmpr1, #23//判断后面四字节低字节是否为0x17bnefixnext//不是不进行relocation,跳至fixnext /* relative fix: increase location by offset *///是0x17,开始进行relocation//结合前面举例分析此段代码//r4保存拷贝偏移量offsetaddr0, r0, r4//r0=r0+offset,即例中,r0=G_test_lable_rel_addrldrr1, [r0]//将r0中的值(变量拷贝前的存储地址G_test_addr)赋值r1addr1, r1, r4//将存储地址+offset,即r1=G_test_addr+offset=G_test_rel_addrstrr1, [r0]//将变量存储地址存储到lable中(G_test_lable_rel_addr) fixnext:cmpr2, r3//判断遍历是否结束blofixloop 


从上面代码可以看出,relocation处理即是例中所说,将拷贝后的变量lable存储的变量地址更新成拷贝后的变量存储地址。

除了全局变量以外,还有存储变量或函数的指针变量也需要relocation,其实道理是一样的,只是指针变量处理起来复杂一点。

1)首先根据上面方法将指针变量的lable存储的地址更新为拷贝后的存储地址;

2)再将存储地址的值(即变量或函数的地址)更新为拷贝后的存储地址。

此处的更新即是在原先的存储地址增加偏移量offset,类似于拷贝中的处理。

 

下面引用一位大神的总结:

总结一下,可以看出,

使用-pie选项的compiler,将需要relocate的值(全局变量地址 函数入口地址)的地址存储在rel.dyn段中,uboot运行中relocate_code遍历rel.dyn段,根据rel.dyn中存储的值,对以(这些值+offset)为地址上的值进行了relocate,完成对所有需要relocate的变量的修改!


需要注意的是,在uboot的整个relocate_coderel.dyn不仅没有拷贝,也没有修改,修改只是针对rel.dyn中值+offset为地址上的值!


本文参考:《uboot的relocation原理详细分析

更详细的分析请看此篇博客。





原创粉丝点击