上一篇關於動態加載講述的是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 }