上一篇关于动态加载讲述的是M3下面的ropi的实现细节,这一篇则讲述RW段的实现细节以及系统加载RW段的思路,我在M3上根据这个思路可以实现elf的动态加载,当然进一步的可以优化很多东西,还可以研究将bin加载起来,这个需要一些辅助的东西实现。
言归正文,使用/acps/rwpi编译代码,解决RW段即全局变量的加载。
首先编译的时候会为每一个全局变量生成一个相对于r9寄存器的偏移量,这个偏移量会在.text段中。
如下例子:
1 static int elf_test_num = 1; 2 int elf_test_num2 = 12; 3 int main(void) 4 { 5 elf_test_num = 2; 6 elf_test_num2 = 4; 7 for(;;); 8 }
编译:
armcc -c --cpu Cortex-M3 -O0 --apcs=interwork --apcs /ropi/rwpi -o main.o main.c
使用fromelf查看汇编代码
fromelf.exe -s -c main.o
生成的汇编代码如下(Cortex-M3):
1 $t 2 .text 3 SystemInit 4 0x00000000: 4770 BX lr 5 main 6 0x00000002: 2002 MOVS r0,#2 7 0x00000004: 4904 LDR r1,[pc,#16] ; [0x18] = 0 8 0x00000006: 4449 ADD r1,r1,r9 9 0x00000008: 6008 STR r0,[r1,#0] 10 0x0000000a: 2004 MOVS r0,#4 11 0x0000000c: 4903 LDR r1,[pc,#12] ; [0x1c] = 0 12 0x0000000e: 4449 ADD r1,r1,r9 13 0x00000010: 6008 STR r0,[r1,#0] 14 0x00000012: bf00 NOP 15 0x00000014: e7fe B {pc} ; 0x14 16 $d 17 0x00000016: 0000 .. DCW 0 18 0x00000018: 00000000 .... DCD 0 19 0x0000001c: 00000000 .... DCD 0
在编译阶段相对r9偏移量还都是零,要到链接阶段才确定相对r9偏移量的大小,链接之后如下:
armlink.exe --cpu Cortex-M3 --ropi --ro_base 0 --rwpi --rw_base 0x0 --entry=main --no_startup main.o -o main.elf
使用fromelf查看汇编代码
fromelf.exe -s -c main.elf
查看最终的elf文件汇编如下:
1 $t 2 .text 3 SystemInit 4 0x00000000: 4770 BX lr 5 main 6 0x00000002: 2002 MOVS r0,#2 7 0x00000004: 4904 LDR r1,[pc,#16] ; [0x18] = 0x4 8 0x00000006: 4449 ADD r1,r1,r9 9 0x00000008: 6008 STR r0,[r1,#0] 10 0x0000000a: 2004 MOVS r0,#4 11 0x0000000c: 4903 LDR r1,[pc,#12] ; [0x1c] = 0x8 12 0x0000000e: 4449 ADD r1,r1,r9 13 0x00000010: 6008 STR r0,[r1,#0] 14 0x00000012: bf00 NOP 15 0x00000014: e7fe B 0x14 ; main + 18 16 $d 17 0x00000016: 0000 .. DCW 0 18 0x00000018: 00000004 .... DCD 4 19 0x0000001c: 00000008 .... DCD 8
此时$d对应的偏移量均已确定大小。
取出对应一句C的汇编代码如下:
1 elf_test_num = 2; 2 3 0x00000002: 2002 MOVS r0,#2 4 0x00000004: 4904 LDR r1,[pc,#16] ; [0x18] = 0 5 0x00000006: 4449 ADD r1,r1,r9 6 0x00000008: 6008 STR r0,[r1,#0]
详细解释如下:
1、MOVS r0,#2
即r0 = 2。
2、LDR r1,[pc,#16] ; [0x18] = 0
即r1 = *(pc + 16)。这里实现了RW无关性,相对当前PC值取出偏移量所在的地址即pc,#16 = 0x18,再从0x18地址出取出偏移量的大小即[pc,#16] = 0x04,从上面加黑的位置查看0x00000018地址的值即为0x00000004,存放到r1寄存器。(这里的pc值应该是下一指令的pc值,并且应该是对齐32位的,具体赢查看arm指令手册。)
3、ADD r1,r1,r9
即r1 = r1+r9,所以指定了在r9偏移0x00000004的地址处给到r1。
4、STR r0,[r1,#0]
即*(r1 + 0) = r0,即将r0赋给r1指向的地址处,此时r1即是偏移r9基址4的地方。
综上所述,在加载elf阶段,将RW段加载到RAM当中之后,需要将r9寄存器指向此片内存的基地址,然后接下来就可以跳转到加载的elf的代码中去执行,就可以实现全局变量的加载了。具体实现思路可以如下:
1 __global_reg(6) char *sb; //在C中使用r9寄存器(static base register)的方法 2 3 char rw_buf[100]; //rw段的加载地址,也可以让系统动态分配一段内存地址 4 5 char *saved_sb; //保存r9 6 7 void load_fun(void) 8 9 { 10 11 saved_sb = sb; //先保存r9的值 12 13 sb = rw_buf; //将r9指向rw段的加载地址 14 15 entry(); //跳转执行到具体的一个elf的入口执行 16 17 sb = saved_sb; //从elf程序跳转回来赋回原来r9的值 18 19 }