PIC(与位置无关代码)在u-boot上的实现
2019-04-15 12:55发布
生成海报
1.1 原理介绍
u-boot通常都是存在ROM或者Flash上,以保证CPU启动后可以直接运行u-boot。但ROM的问题是只能读不能写,不利于程序的执行。如:全局变量读写,地址空间限制等问题。因此u-boot会先把自己拷贝到RAM中去执行。这一拷贝带来的问题是执行地址的混乱。代码的执行地址通常都是在编译时有链接地址指定的,如何保证拷贝前后都可以执行呢?一个办法是使用拷贝到RAM后的地址作为编译时的链接地址,拷贝前所有函数与全局变量的调用都增加偏移量。(如VxWorks的bootloader)尽量减少拷贝前需要执行的代码量。两一个地址是把image编译成与地址无关的程需,也就是PIC - Position independent code。编译器无法保证代码的独立性,它需要与加载器配合起来。U-boot自己加载自己,所以她自己就是加载器。域代码无关代码依赖于下面两种技术:1) 使用相对地址2) 加载器可以自动更新涉及到绝对地址的指令PIC的实现方式不止一种,不对CPU架构下的实现也有区别。这里主要结合搜啊ARM架构下u-boot中PIC的实现方式1.2 一个简单例子
先通过一个简单例子介绍编译成PIC与非PIC的区别
1.2.1 源码
test0.1cint foo(int a); int ttt = 1;int xxx = 5; int boo(){ foo(xxx); return foo(ttt);}这是一个简单的程序。Foo函数的实现在另一个文件,boo函数调用了foo函数与全局变量ttt与xxx。1.2.2 Compile
对于PowerPC架构,u-boot只是在编译时使用了-fpic。这种方式会生成一个.got段来存储绝对地址符号(位置无关(PIC)代码原理剖析)。对与ARM架构,则是在编译时使用-mword-relocations,生成与位置无关代码;链接时使用-pie生成.rel.dyn段,该段中的每个条目被称为一个LABEL,用来存储绝对地址符号的地址。1.2.3 PIC如何找到所有的绝对地址符号
Non-PICPICarm-qhao-linux-gnueabi-gcc -c test01.carm-qhao-linux-gnueabi-ld -o a.out test01.o test02.o arm-qhao-linux-gnueabi-gcc -c -mword-relocations test01.carm-qhao-linux-gnueabi-ld -pie -o a.out test01.o test02.o SYMBOL TABLE:00008094 l d .text 00000000 .text000100f8 l d .data 00000000 .data000100fc g O .data 00000004 xxx000100f8 g O .data 00000004 ttt SYMBOL TABLE:00000220 l d .rel.dyn 00000000 .rel.dyn00000230 l d .text 00000000 .text00008320 l d .data 00000000 .data00008324 g O .data 00000004 xxx00008320 g O .data 00000004 ttt Disassembly of section .text: 00008094 : 8094: e92d4800 push {fp, lr} 8098: e28db004 add fp, sp, #4 809c: e30030fc movw r3, #252 ; 0xfc 80a0: e3403001 movt r3, #1 80a4: e5933000 ldr r3, [r3] 80a8: e1a00003 mov r0, r3 80ac: eb000007 bl 80d0 80b0: e30030f8 movw r3, #248 ; 0xf8 80b4: e3403001 movt r3, #1 80b8: e5933000 ldr r3, [r3] 80bc: e1a00003 mov r0, r3 80c0: eb000002 bl 80d0 80c4: e1a03000 mov r3, r0 80c8: e1a00003 mov r0, r3 80cc: e8bd8800 pop {fp, pc} Disassembly of section .text: 00000230 : 230: e92d4800 push {fp, lr} 234: e28db004 add fp, sp, #4 238: e59f3024 ldr r3, [pc, #36] ; 264 23c: e5933000 ldr r3, [r3] 240: e1a00003 mov r0, r3 244: eb000008 bl 26c 248: e59f3018 ldr r3, [pc, #24] ; 268 24c: e5933000 ldr r3, [r3] 250: e1a00003 mov r0, r3 254: eb000004 bl 26c 258: e1a03000 mov r3, r0 25c: e1a00003 mov r0, r3 260: e8bd8800 pop {fp, pc} 264: 00008324 andeq r8, r0, r4, lsr #6 ; this is called as Lable 268: 00008320 andeq r8, r0, r0, lsr #6Disassembly of section .data: 000100f8 : 100f8: 00000001 andeq r0, r0, r1 000100fc : 100fc: 00000005 andeq r0, r0, r5 Disassembly of section .data: 00008320 : 8320: 00000001 andeq r0, r0, r1 00008324 :8324: 00000005 andeq r0, r0, r5 Disassembly of section .rel.dyn: 00000220 <.rel.dyn>: 220: 00000264 andeq r0, r0, r4, ror #4 224: 00000017 andeq r0, r0, r7, lsl r0 228: 00000268 andeq r0, r0, r8, ror #4 22c: 00000017 andeq r0, r0, r7, lsl r0以符号ttt为例,在non-PIC中,#809c与#80a通过绝对地址获得xxx的地址,如果要搬移到新地址,则必须找到并修改调用绝对地址的指令。PIC中,每个函数在最后都附加了绝对地址表,#238通过PC寄存器加偏移的方式找到地址表,根据地址表中存储的绝对地址找到xxx实际的地址。如果要搬移到新地址,需要修改每一个函数后面的绝对地址表。1.2.4 加载器如何发现绝对地址符号
下图展示了加载器如何对代码进行重定向:
从前一节的表格中可以发现,PIC代码多了一个 。.rel.dyn段,该段有-pic参数产生,被称为LABLE表格,表格中每一项对应着一个函数后绝对地址表中的LABEL。程序被拷贝到新地址后,加载器通过.rel.dyn段找到所有的LABEL,利用新的启示地址来更新所有的LABEL。1.3 U-boot中加载器的实现
u-boot加载自身的过程有被称为重定向(relocate)下表中左侧是u-boot中的源码,右侧是用C语言写的伪代码。
AssemblyPseudo C codeENTRY(_main)…… adr lr, here ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */ add lr, lr, r0 ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */ b relocatehere: void _main(void){ lr = here; r0 = gd->relocaddr relocate(here, gd->relocaddr)} ENTRY(relocate_code) ldr r1, =__image_copy_start /* r1 <- SRC &__image_copy_start */ subs r4, r0, r1 /* r4 <- relocation offset */ beq relocate_done /* skip relocation */ ldr r2, =__image_copy_end /* r2 <- SRC &__image_copy_end */ copy_loop: ldmia r1!, {r10-r11} /* copy from source address [r1] */ stmia r0!, {r10-r11} /* copy to target address [r0] */ cmp r1, r2 /* until source end address [r2] */ blo copy_loop /* * fix .rel.dyn relocations */ ldr r2, =__rel_dyn_start /* r2 <- __rel_dyn_start */ ldr r3, =__rel_dyn_end /* r3 <- __rel_dyn_end */fixloop: ldmia r2!, {r0-r1} /* (r0,r1) <- (location,fixup) */ and r1, r1, #0xff cmp r1, #23 /* relative fixup? */ bne fixnext /* relative fix: increase location by offset */ add r0, r0, r4 ldr r1, [r0] add r1, r1, r4 str r1, [r0]fixnext: cmp r2, r3 blo fixloop relocate_done: bx lr ENDPROC(relocate_code) #define R_ARM_RELATIVE 0x17typedef struct tagRelocItem{unsigned long address;unsigned long reloc_code;} RelocItem; void relocate(int lr, int relocaddr){ RelocItem *relocItem;unsigned long offset;unsigned long addr; /* new address of Label */ /* copy image */memcpy(__image_copy_start, __image_copy_end, (__image_copy_end - __image_copy_start)); /* fix .rel.dyn relocations */relocItem = __rel_dyn_start; offset = relocaddr - __image_copy_start; while (relocItem >= __rel_dyn_start) { if (relocItem->reloc_code == R_ARM_RELATIVE) { addr = relocItem ->address + offset; *(unsigned long *)addr += offset; } relocItem++; } goto lr;} /* clear .bss segment */ ldr r0, =__bss_start /* this is auto-relocated! */ ldr r1, =__bss_end /* this is auto-relocated! */ mov r2, #0x00000000 /* prepare zero to clear BSS */ clbss_l: cmp r0, r1 /* while not at end of BSS */ strlo r2, [r0] /* clear 32-bit BSS word */ addlo r0, r0, #4 /* move to next */ blo clbss_l memset(__bss_start, 0, __bss_end); /* attention: that will cover .rel.dyn section */ /* call board_init_r(gd_t *id, ulong dest_addr) */ mov r0, r9 /* gd_t */ ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */ /* call board_init_r */ldr pc, =board_init_r /* this is auto-relocated! *//* we should not return here. */board_init_r(gd, gd->dest_addr); 上述实现的最后一步是.bss段清零。看下面的u-boot段列表: 6 .rel.dyn 000040f8 0003155c 0003155c 0002955c 2**2 8 .bss 000358a0 0003155c 0003155c 00000000 2**6.rel.dyn与.bss段起始地址是相同的(通过链接脚本u-boot.lds实现)。这是因为.rel.dyn段只用于把u-boot自己加载到内存,之后就没有了。在.bss段清零时,实际上也就把.rel.dyn段去掉了。这带来的一个问题是,u-boot在ROM运行时,.bss段是不为零的。.bss段存着未初始化的全局变量,因此此时使用变量时也不能假设变量初值为0。所幸大部分编程规范都要求全局变量初始化要显示初始化。下面是加载示意图。可以看到加载前只使用.rel.dyn段,加载后只使用.bss段。
本文乃fireaxe原创,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,并注明原作者及原链接。内容可任意使用,但对因使用该内容引起的后果不做任何保证。作者:fireaxe.hq@outlook.com博客:fireaxe.blog.chinaunix.net
打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮