uboot的relocation原理详细分析(1)


标签:
佛学 |
有没有注意到,rel_dyn_test末尾存储全局变量地址的Label地址也存储在这里,那有什么用呢,那就来看一下uboot的核心函数relocate_code是如何实现自身的relocation的,
在arch/arm/lib/relocate.S中
-
ENTRY(relocate_code)
-
ldr r1, =__image_copy_start -
subs r4, r0, r1 -
beq relocate_done -
ldr r2, =__image_copy_end -
-
copy_loop:
-
ldmia r1!, {r10-r11} -
stmia r0!, {r10-r11} -
cmp r1, r2 -
blo copy_loop -
-
-
ldr r2, =__rel_dyn_start -
ldr r3, =__rel_dyn_end -
fixloop:
-
ldmia r2!, {r0-r1} -
and r1, r1, #0xff -
cmp r1, #23 -
bne fixnext -
-
-
add r0, r0, r4 -
ldr r1, [r0] -
add r1, r1, r4 -
str r1, [r0] -
fixnext:
-
cmp r2, r3 -
blo fixloop -
-
relocate_done:
来看一下arm的link script,在arch/arm/cpu/u-boot.lds,如下:
-
OUTPUT_FORMAT("elf32-littlearm",
"elf32-littlearm", "elf32-littlearm") -
OUTPUT_ARCH(arm)
-
ENTRY(_start)
-
SECTIONS
-
{
-
. = 0x00000000; -
-
. = ALIGN(4); -
.text : -
{ -
*(.__image_copy_start) -
CPUDIR/start.o (.text*) -
*(.text*) -
} -
-
. = ALIGN(4); -
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) } -
-
. = ALIGN(4); -
.data : { -
*(.data*) -
} -
-
. = ALIGN(4); -
-
. = .; -
-
. = ALIGN(4); -
.u_boot_list : { -
"code"
class="cpp"> KEEP(*(SORT(.u_boot_list*))); -
} -
-
. = ALIGN(4); -
-
.image_copy_end : -
{ -
*(.__image_copy_end) -
} -
-
.rel_dyn_start : -
{ -
*(.__rel_dyn_start) -
} -
-
.rel.dyn : { -
*(.rel*) -
} -
-
.rel_dyn_end : -
{ -
*(.__rel_dyn_end) -
} -
-
.end : -
{ -
*(.__end) -
} -
-
_image_binary_end = .;
可以看出__image_copy_start---end之间包括了text data rodata段,但是没有包括rel_dyn。
继续看relocate_code函数,拷贝__image_copy_start----end之间的数据,但没有拷贝rel.dyn段。
首先获取__rel_dyn_start地址到r2,将start地址上连续2个4字节地址的值存在r0 r1中
判断r1中的值低8位,如果为0x17,则将r0中的值加relocation offset。
获取以此r0中值为地址上的值,存到r1中
将r1中值加relocation offset,再存回以r0中值为地址上。
以此循环,直到__rel_dyn_end。
这样读有些拗口。来以咱们的rel_dyn_test举例子。
上面rel.dyn段中有一段如下:
-
80eba944:
80e9d40c rschi sp, r9, ip, lsl #8 -
80eba948:
00000017 andeq r0, r0, r7, lsl r0
将r0加上relocation offset,则到了relocation之后rel_dyn_test的末尾Label。
获取r0为地址上的值到r1中,0x80eb75c0,可以看到,这个值就是变量test_val的首地址啊。
最后将r1加上relocation offset,写回以r0为地址上。意思是将变量test_val地址加offset后写回到relocation之后rel_dyn_test的末尾Label中。
这样relocate_code完成后,再来看对test_val的寻址。寻址第三步获取到的是修改之后的relocation addr啊,这样就可以获取到relocation之后的test_val值!
对于普通变量寻址是这样,那对于指针变量呢,如test_func_val呢?
获取test_func_val relocation后地址的步骤跟上面一样,但是我们在获取test_func_val的值时要注意,这个变量存储的是函数test_func指针,之前是0x80e9d3cc,relocation之后就变化了,所以test_func_val的值也应该变化,这个该怎么办?
方法是一样的,可以在rel.dyn段中找到如下一段:
-
80ebc18c:
80eaa54c rschi sl, sl, ip, asr #10 -
80ebc190:
00000017 andeq r0, r0, r7, lsl r0
这就解决了,test_func_val的值也就是test_func的地址也被修改为relocation之后的地址了。
网上查阅资料,这里对于rel.dyn段中每一个rel section(8个字节)第二个4字节,0x17,是一种label的类型R_ARM_RELATIVE,
经过上面uboot的relocate_code后,我们提出的3个问题的寻址都可以正常工作。
还有一个疑问,是谁来决定哪些label放到rel.dyn中,特别是对于存储指针的变量,如何分辨,这样看来,是compiler的ld来完成的这个工作,将所有需要relocate的label放到rel.dyn段中,真是牛逼的compiler啊!
总结一下,可以看出,
使用-pie选项的compiler,将需要relocate的值(全局变量地址
需要注意的是,在uboot的整个relocate_code中rel.dyn不仅没有拷贝,也没有修改,修改只是针对rel.dyn中值+offset为地址上的值!
查阅网上资料,compiler在cc时加入-fPIC或-fPIE选项,会在目标文件中生成GOT(global offset table),将本文件中需要relocate的值存放在GOT中,函数尾部的Label来存储GOT的offset以及其中变量的offset,变量寻址首先根据尾部Label相对寻址找到GOT地址,以及变量地址在GOT中的位置,从而确定变量地址,这样对于目标文件统一修改GOT中的值,就修改了变量地址的offset,完成了relocation。
ld时加入-pie选项,就会将GOT并入到rel.dyn段中,uboot在relocate_code中统一根据rel.dyn段修改需要relocation的数值。
uboot中ld使用-pie而cc没有使用-fPIC或-fPIE,目标文件中就不会生成GOT,函数中寻址还是在尾部Label中直接存储变量的绝对地址,但这个Label同样存在rel.dyn中,uboot根据rel.dyn段修改Label上的值,就完成了relocation。
这样不仅节省了每个目标文件的GOT段,而且不需要去相对寻址GOT,直接修改函数尾部Label所存储的变量地址就可以啦!
uboot的relocation就是如此!