加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

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

(2015-04-26 15:59:23)
标签:

佛学


有没有注意到,rel_dyn_test末尾存储全局变量地址的Label地址也存储在这里,那有什么用呢,那就来看一下uboot的核心函数relocate_code是如何实现自身的relocation的,

 

在arch/arm/lib/relocate.S中

 

  1. ENTRY(relocate_code)  
  2.         ldr     r1, =__image_copy_start   
  3.         subs    r4, r0, r1                
  4.         beq     relocate_done             
  5.         ldr     r2, =__image_copy_end     
  6.   
  7. copy_loop:  
  8.         ldmia   r1!, {r10-r11}            
  9.         stmia   r0!, {r10-r11}            
  10.         cmp     r1, r2                    
  11.         blo     copy_loop  
  12.   
  13.           
  14.         ldr     r2, =__rel_dyn_start      
  15.         ldr     r3, =__rel_dyn_end        
  16. fixloop:  
  17.         ldmia   r2!, {r0-r1}              
  18.         and     r1, r1, #0xff  
  19.         cmp     r1, #23                   
  20.         bne     fixnext  
  21.   
  22.           
  23.         add     r0, r0, r4  
  24.         ldr     r1, [r0]  
  25.         add     r1, r1, r4  
  26.         str     r1, [r0]  
  27. fixnext:  
  28.         cmp     r2, r3  
  29.         blo     fixloop  
  30.   
  31. relocate_done:  
前半部分在uboot启动流程中讲过,将__image_copy_start到__image_copy_end之间的数据进行拷贝

 

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

 

  1. OUTPUT_FORMAT("elf32-littlearm""elf32-littlearm""elf32-littlearm" 
  2. OUTPUT_ARCH(arm)  
  3. ENTRY(_start)  
  4. SECTIONS  
  5.  
  6.         0x00000000;  
  7.   
  8.         ALIGN(4);  
  9.         .text  
  10.          
  11.                 *(.__image_copy_start)  
  12.                 CPUDIR/start.o (.text*)  
  13.                 *(.text*)  
  14.          
  15.   
  16.         ALIGN(4);  
  17.         .rodata *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*)))  
  18.   
  19.         ALIGN(4);  
  20.         .data  
  21.                 *(.data*)  
  22.          
  23.   
  24.         ALIGN(4);  
  25.   
  26.         .;  
  27.   
  28.         ALIGN(4);  
  29.         .u_boot_list  
  30. "code" class="cpp"               KEEP(*(SORT(.u_boot_list*)));  
  31.          
  32.   
  33.         ALIGN(4);  
  34.   
  35.         .image_copy_end  
  36.          
  37.                 *(.__image_copy_end)  
  38.          
  39.   
  40.         .rel_dyn_start  
  41.          
  42.                 *(.__rel_dyn_start)  
  43.          
  44.   
  45.         .rel.dyn  
  46.                 *(.rel*)  
  47.          
  48.   
  49.         .rel_dyn_end  
  50.          
  51.                 *(.__rel_dyn_end)  
  52.          
  53.   
  54.         .end  
  55.          
  56.                 *(.__end)  
  57.          
  58.   
  59.         _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段中有一段如下:

 

  1. 80eba944:       80e9d40c        rschi   sp, r9, ip, lsl #8  
  2. 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段中找到如下一段:

 

  1. 80ebc18c:       80eaa54c        rschi   sl, sl, ip, asr #10  
  2. 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就是如此!

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有