您的位置 首页 报告

uboot的relocation原理详细分析

所谓的relocation,就是重定位,uboot运行后会将自身代码拷贝到sdram的另一个位置继续运行,这个在uboot启动流程分析中说过。但基于以前的

所谓的relocation,便是重定位,uboot运转后会将本身代码复制到sdram的另一个方位持续运转,这个在uboot发动流程剖析中说过。

但依据曾经的了解,一个完好可运转的bin文件,link时指定的链接地址,load时的加载地址,运转时的运转地址,这3个地址应该是共同的

relocation后运转地址不同于加载地址特别是链接地址,ARM的寻址会不会出现问题?

新版uboot跟老版uboot不太相同的当地在于新版uboot不论uboot的load addr(entry pointer)在哪里,发动后会计算出一个接近sdram顶端的地址,将本身代码复制到该地址,持续运转。

个人感觉uboot这样改善意图有二,一是为kernel腾出低端空间,避免kernel解压掩盖uboot,二是关于由静态存储器(spiflash nandflash)发动,这个relocation是有必要的。

可是这样会有一个问题,relocation后uboot的运转地址跟其链接地址不共同,compiler会在link时确认了其间变量以及函数的肯定地址,链接地址加载地址 运转地址应该共同,

这样看来,arm在寻址这些变量函数时找到的应该是relocation之前的地址,这样relocation就没有意义了!

当然uboot不会这样,咱们来剖析一下uboot下relocation之后是怎么寻址的,开端学习之前我是有3个疑问,如下

(1)怎么对函数进行寻址调用

(2)怎么对大局变量进行寻址操作(读写)

(3)关于大局指针变量中存储的其他变量或函数地址在relocation之后怎么操作

搞清楚这3个问题,关于我来说relocation的原理就算是搞理解了。

为了搞清楚这些,在uboot的某一个文件中参加如下代码

[cpp] view plaincopyprint?
  1. voidtest_func(void)
  2. {
  3. printf(“testfunc\n”);
  4. }
  5. staticvoid*test_func_val=test_func;
  6. staticinttest_val=10;
  7. voidrel_dyn_test()
  8. {
  9. test_val=20;
  10. printf(“test=0x%x\n”,test_func);
  11. printf(“test_func=0x%x\n”,test_func_val);
  12. test_func();
  13. }
void test_func(void) { printf("test func\n"); } static void * test_func_val = test_func; static int test_val = 10; void rel_dyn_test() { test_val = 20; printf("test = 0x%x\n", test_func); printf("test_func = 0x%x\n", test_func_val); test_func(); }

rel_dyn_test函数中就包含了函数指针 变量赋值函数调用这3种状况,寻址肯定要汇编级的追寻才能够,编译完结后反汇编,得到u-boot.dump(objdump用-D选项,将一切section都disassemble出来)

找到rel_dyn_test函数,如下:

[cpp] view plaincopyprint?
  1. 80e9d3cc:
  2. 80e9d3cc:e59f0000ldrr0,[pc,#0];80e9d3d4
  3. 80e9d3d0:eaffc2fbb80e8dfc4
  4. 80e9d3d4:80eb1c39.word0x80eb1c39
  5. 80e9d3d8:
  6. 80e9d3d8:e59f202cldrr2,[pc,#44];80e9d40c
  7. 80e9d3dc:e3a03014movr3,#20;0x14
  8. 80e9d3e0:e92d4010push{r4,lr}
  9. 80e9d3e4:e59f1024ldrr1,[pc,#36];80e9d410
  10. 80e9d3e8:e5823000strr3,[r2]
  11. 80e9d3ec:e59f0020ldrr0,[pc,#32];80e9d414
  12. 80e9d3f0:ebffc2f3bl80e8dfc4
  13. 80e9d3f4:e59f301cldrr3,[pc,#28];80e9d418
  14. 80e9d3f8:e59f001cldrr0,[pc,#28];80e9d41c
  15. 80e9d3fc:e5931000ldrr1,[r3]
  16. 80e9d400:ebffc2efbl80e8dfc4
  17. 80e9d404:e8bd4010pop{r4,lr}
  18. 80e9d408:eaffffefb80e9d3cc
  19. 80e9d40c:80eb75c0.word0x80eb75c0
  20. 80e9d410:80e9d3cc.word0x80e9d3cc
  21. 80e9d414:80eb1c44.word0x80eb1c44
  22. 80e9d418:80eaa54c.word0x80eaa54c
  23. 80e9d41c:80eb1c51.word0x80eb1c51
80e9d3cc : 80e9d3cc: e59f0000 ldr r0, [pc, #0] ; 80e9d3d4 80e9d3d0: eaffc2fb b 80e8dfc4 80e9d3d4: 80eb1c39 .word 0x80eb1c39 80e9d3d8 : 80e9d3d8: e59f202c ldr r2, [pc, #44] ; 80e9d40c 80e9d3dc: e3a03014 mov r3, #20 ; 0x14 80e9d3e0: e92d4010 push {r4, lr} 80e9d3e4: e59f1024 ldr r1, [pc, #36] ; 80e9d410 80e9d3e8: e5823000 str r3, [r2] 80e9d3ec: e59f0020 ldr r0, [pc, #32] ; 80e9d414 80e9d3f0: ebffc2f3 bl 80e8dfc4 80e9d3f4: e59f301c ldr r3, [pc, #28] ; 80e9d418 80e9d3f8: e59f001c ldr r0, [pc, #28] ; 80e9d41c 80e9d3fc: e5931000 ldr r1, [r3] 80e9d400: ebffc2ef bl 80e8dfc4 80e9d404: e8bd4010 pop {r4, lr} 80e9d408: eaffffef b 80e9d3cc 80e9d40c: 80eb75c0 .word 0x80eb75c0 80e9d410: 80e9d3cc .word 0x80e9d3cc 80e9d414: 80eb1c44 .word 0x80eb1c44 80e9d418: 80eaa54c .word 0x80eaa54c 80e9d41c: 80eb1c51 .word 0x80eb1c51

。。。

data段中

[cpp] view plaincopyprint?
  1. 80eb75c0:
  2. 80eb75c0:0000000a.word0x0000000a
80eb75c0 : 80eb75c0: 0000000a .word 0x0000000a

。。。

[cpp] view plaincopyprint?
  1. 80eaa54c:
  2. 80eaa54c:80e9d3cc.word0x80e9d3cc
80eaa54c : 80eaa54c: 80e9d3cc .word 0x80e9d3cc

rel_dyn_test反汇编后,最终多了一部分从0x80e9d40c开端的内存空间,比照发现这部分内存空间地址上的值竟然是函数需求的变量test_val test_func_val的地址。

网上材料称这些函数结尾存储变量地址的内存空间为Label,(编译器主动分配)

一条条指令来剖析。

ldr r2, [pc, #44] ========> r2 = [pc + 0x2c]=======>r2 = [0x80e9d3e0 + 0x2c]=======>r2 =[0x80e9d40c]

需求留意,由于ARM的流水线机制,当时PC值为当时地址加8个字节

这样r2获取的是0x80e9d40c地址的值0x80eb75c0,这便是test_val的值嘛

mov r3, #20======> r3 = 20

对应C函数这应该是为test_val = 20做准备,先跳过后边2条指令,发现

str r3, [r2]

很明显了,将当即数20存入0x80eb75c0中也便是test_val中。

这3条指令阐明,ARM关于变量test_val的寻址如下:

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

(2)依据PC相对寻址获取函数尾端Label上的变量地址

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

再来看其间的几条指令

ldr r3, [pc, #28] =====> r3 = [0x80e9d3fc + 0x1c] =====> r3 = [0x80e9d418] ====> r3 = 0x80eaa54c

ldr r1, [r3] =====> r1 = [0x80eaa54c] ======> r1 = 0x80e9d3cc

0x80e9d3cc这个地址能够看出是test_func的进口地址,这儿是printf打印test_func_val的值
能够看出关于函数指针变量的寻址跟一般变量相同。

接下来来看函数的调用,能够看到关于printf以及test_func,运用的是指令bl以及b进行跳转,这2条指令都是相对寻址(pc + offset)

阐明ARM调用函数运用的是相对寻址指令bl或b,与函数的肯定地址无关

关于这3种状况的寻址办法现已知道了,那就需求考虑一下relocation之后会有什么改变。

将rel_dyn_test relocation之后能够幻想,函数的调用仍是没有问题的,由于运用了bl或b相对跳转指令。

可是关于变量的寻址就有问题了,寻址的前2步没有问题,相对寻址获取尾部Label中的变量地址,但获取的变量地址是在 link时就确认下来的肯定地址啊!

而关于指针变量的寻址呢,问题更多了,

首要跟一般变量寻址相同,尾部内存空间的变量地址是link时的肯定地址,再者,指针变量存储的变量指针或许函数指针也是在link时确认的肯定地址,relocation之后这个值也变了!

那uboot是怎么来处理这些状况的呢?更精确的说应该是compiler和uboot怎么一起来处理这些状况的呢?

这儿利用了PIC方位无关代码,通过为编译器指定编译选项-fpic或-fpie发生,
这样编译发生的方针文件包含了PIC所需求的信息,-fpic,-fpie是gcc的PIC编译选项。ld也有PIC衔接选项-pie,要取得一个完好的PIC可运转文件,衔接方针文件时有必要为ld指定-pie选项,

观察uboot的编译选项发现,在arch/arm/config.mk,如下:

[cpp] view plaincopyprint?
  1. #neededforrelocation
  2. LDFLAGS_u-boot+=-pie
# needed for relocation LDFLAGS_u-boot += -pie

uboot只指定了-pie给ld,而没有指定-fP%&&&&&%或-fPIE给gcc。

指定-pie后编译生成的uboot中就会有一个rel.dyn段,uboot便是靠rel.dyn段完结了完美的relocation!

观察u-boot.dump中的rel.dyn段,如下:

[cpp] view plaincopyprint?
  1. Disassemblyofsection.rel.dyn:
  2. 80eb7d54<__rel_dyn_end-0x5c10>:
  3. 80eb7d54:80e80020rschir0,r8,r0,lsr#32
  4. 80eb7d58:00000017andeqr0,r0,r7,lslr0
  5. 80eb7d5c:80e80024rschir0,r8,r4,lsr#32
  6. 80eb7d60:00000017andeqr0,r0,r7,lslr0
  7. 80eb7d64:80e80028rschir0,r8,r8,lsr#32
  8. 80eb7d68:00000017andeqr0,r0,r7,lslr0
  9. 。。。
  10. “color:#FF0000;”>80eba944:80e9d40crschisp,r9,ip,lsl#8
  11. 80eba948:00000017andeqr0,r0,r7,lslr0
  12. 80eba94c:80e9d410rschisp,r9,r0,lslr4
  13. 80eba950:00000017andeqr0,r0,r7,lslr0
  14. 80eba954:80e9d414rschisp,r9,r4,lslr4
  15. 80eba958:00000017andeqr0,r0,r7,lslr0
  16. 80eba95c:80e9d418rschisp,r9,r8,lslr4
  17. 80eba960:00000017andeqr0,r0,r7,lslr0
  18. 80eba964:80e9d41crschisp,r9,ip,lslr4
  19. 80eba968:00000017andeqr0,r0,r7,lslr0
  20. 。。。。
Disassembly of section .rel.dyn: 80eb7d54 <__rel_dyn_end-0x5c10>: 80eb7d54: 80e80020 rschi r0, r8, r0, lsr #32 80eb7d58: 00000017 andeq r0, r0, r7, lsl r0 80eb7d5c: 80e80024 rschi r0, r8, r4, lsr #32 80eb7d60: 00000017 andeq r0, r0, r7, lsl r0 80eb7d64: 80e80028 rschi r0, r8, r8, lsr #32 80eb7d68: 00000017 andeq r0, r0, r7, lsl r0 。。。 80eba944: 80e9d40c rschi sp, r9, ip, lsl #8 80eba948: 00000017 andeq r0, r0, r7, lsl r0 80eba94c: 80e9d410 rschi sp, r9, r0, lsl r4 80eba950: 00000017 andeq r0, r0, r7, lsl r0 80eba954: 80e9d414 rschi sp, r9, r4, lsl r4 80eba958: 00000017 andeq r0, r0, r7, lsl r0 80eba95c: 80e9d418 rschi sp, r9, r8, lsl r4 80eba960: 00000017 andeq r0, r0, r7, lsl r0 80eba964: 80e9d41c rschi sp, r9, ip, lsl r4 80eba968: 00000017 andeq r0, r0, r7, lsl r0 。。。。

有没有留意到,rel_dyn_test结尾存储大局变量地址的Label地址也存储在这儿,那有什么用呢,那就来看一下uboot的中心函数relocate_code是怎么完结本身的relocation的,

在arch/arm/lib/relocate.S中

[cpp] view plaincopyprint?
  1. ENTRY(relocate_code)
  2. ldrr1,=__image_copy_start
  3. subsr4,r0,r1
  4. beqrelocate_done
  5. ldrr2,=__image_copy_end
  6. copy_loop:
  7. ldmiar1!,{r10-r11}
  8. stmiar0!,{r10-r11}
  9. cmpr1,r2
  10. blocopy_loop
  11. ldrr2,=__rel_dyn_start
  12. ldrr3,=__rel_dyn_end
  13. fixloop:
  14. ldmiar2!,{r0-r1}
  15. andr1,r1,#0xff
  16. cmpr1,#23
  17. bnefixnext
  18. addr0,r0,r4
  19. ldrr1,[r0]
  20. addr1,r1,r4
  21. strr1,[r0]
  22. fixnext:
  23. cmpr2,r3
  24. blofixloop
  25. relocate_done:
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:

前半部分在uboot发动流程中讲过,将__image_copy_start到__image_copy_end之间的数据进行复制

来看一下arm的link script,在arch/arm/cpu/u-boot.lds,如下:

[cpp] view plaincopyprint?
  1. OUTPUT_FORMAT(“elf32-littlearm”,“elf32-littlearm”,“elf32-littlearm”)
  2. OUTPUT_ARCH(arm)
  3. ENTRY(_start)
  4. SECTIONS
  5. {
  6. .=0x00000000;
  7. .=ALIGN(4);
  8. .text:
  9. {
  10. *(.__image_copy_start)
  11. CPUDIR/start.o(.text*)
  12. *(.text*)
  13. }
  14. .=ALIGN(4);
  15. .rodata:{*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*)))}
  16. .=ALIGN(4);
  17. .data:{
  18. *(.data*)
  19. }
  20. .=ALIGN(4);
  21. .=.;
  22. .=ALIGN(4);
  23. .u_boot_list:{
  24. “code”class=”cpp”>KEEP(*(SORT(.u_boot_list*)));
  25. }
  26. .=ALIGN(4);
  27. .image_copy_end:
  28. {
  29. *(.__image_copy_end)
  30. }
  31. .rel_dyn_start:
  32. {
  33. *(.__rel_dyn_start)
  34. }
  35. .rel.dyn:{
  36. *(.rel*)
  37. }
  38. .rel_dyn_end:
  39. {
  40. *(.__rel_dyn_end)
  41. }
  42. .end:
  43. {
  44. *(.__end)
  45. }
  46. _image_binary_end=.;
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 : {
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段中有一段如下: 
[cpp] view plaincopyprint?
  1. 80eba944:80e9d40crschisp,r9,ip,lsl#8
  2. 80eba948:00000017andeqr0,r0,r7,lslr0
80eba944: 80e9d40c rschi sp, r9, ip, lsl #8 80eba948: 00000017 andeq r0, r0, r7, lsl r0
依照上面的剖析,判别第二个四字节为0x17,r0中存储为0x80e9d40c。这个是rel_dyn_test结尾Label的地址啊, 将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段中找到如下一段: 
[cpp] view plaincopyprint?
  1. 80ebc18c:80eaa54crschisl,sl,ip,asr#10
  2. 80ebc190:00000017andeqr0,r0,r7,lslr0
80ebc18c: 80eaa54c rschi sl, sl, ip, asr #10 80ebc190: 00000017 andeq r0, r0, r7, lsl r0
这上面存储的是test_func_val的地址,依照relocate_code的操作,完结后80eaa54c + offset上的值也应该+offset了。 这就处理了,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的值(大局变量地址 函数进口地址)的地址存储在rel.dyn段中,uboot运转中relocate_code遍历rel.dyn段,依据rel.dyn中存储的值,对以(这些值+offset)为地址上的值进行了relocate,完结对一切需求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便是如此!

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/ceping/baogao/263194.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部