ARM下的位置無關和相關碼
為什么需要位置無關碼?
見 :
U-BOOT詳解(什么是《編譯地址》?什么是《運行地址》?) http://bbs.21ic.com/forum.php?mod=viewthread&tid=857037&typeid=114
ARM位置無關代碼設計規范 http://wenku.baidu.com/view/5ef25b890b4c2e3f562763a8.html
位置無關可執行文件PIE包括位置無關代碼PIC和位置無關數據PID兩部分。
通常情況下,將bootloader程序下載到ROM的0x0地址進行啟動(比如固化到NorFlash中)。然而在很多的設計中,比如將bootloader固化在NAND中,在系統復位后S3C2440A中NAND控制器自動讀取NAND中存儲的前4K的代碼到s3c2440a中稱之為
steppingstone的RAM中,steppingstone中的代碼用進行一些非核心的硬件初始化,再將NAND中剩下的bootloader代碼拷貝到RAM中運行。一般境況下兩者的地址並不相同,程序在SDRAM中的地址重定位過程必須由程序員來完成。這樣就有了位置無關代碼的概念,指代碼不在連接時制定的運行地址空間,也可以執行,它一段加載到任意地址空間都能執行的特殊代碼。這樣在steppingstone設計的代碼要用位置無關設計。
位置無關代碼可以用於以下場合:
1. 程序在運行期間動態加載到內存;
2. 程序在不同場合與不同程序組合后加載到內存(共享的動態鏈接庫); 3. 在運行期間不同地址相互之間的映射(如bootloader)
怎么實現位置無關碼?
{
1. 位置無關的函數跳轉
2. 位置無關的常量訪問
}
位置無關代碼,即該段代碼無論放在內存的哪個地址,都能正確運行。究其原因,是因為代碼里沒有使用絕對地址,都是相對地址。
而位置相關碼,即它的地址與代碼處於的位置相關,是絕對地址,如:mov PC ,#0xff;ldr pc,=0xffff等。
如果你的這段代碼需要實現位置無關,那么你就不能使用絕對尋址指令,否則的話就是位置有關了。
一、位置無關的寫法:
(1) B指令
B指令接受一個相對地址,因此在匯編里用B跳轉到一個標號時,實際編譯的結果是一個相對跳轉。相對地址有個范圍限制,即目標不能太遠,一般目標放在同一個文件里是肯定可以的。 Offset must IN 32Mbit
_start:
b _reset
_reset:
...
(2) BL
BL用於調用函數,也是一個相對跳轉,same as B instrction
(3) ADR
獲取標號的地址,在編譯時會使用PC+偏移的方式得到該位置的地址。例如,當TEXT_BASE是0時
SMRDATA可能被放在0x100的位置,當TEXT_BASE為0x30000000時放在0x30000100的位置。使用ADR
總能獲取正確的位置,與程序的加載地址無關。
ADR R0, SMRDATA
SMRDATA:
.word 0x22111120
.word 0x00002F50
.word 0x00000700
(相應的, LDR Rn, =LABEL是位置相關的)
(4) LDR
當加標號時,LDR可以用於偽指令,也可以真指令。
真指令: (標號前不加=號,表示取標號處的值)
LDR R0, SDRDATA
實際被編譯為LDR R0, [PC, #NN],其中NN是目標的相對距離
偽指令: (標號前加=號,取標號的地址)
LDR R0, = SDRDATA
實際編譯的時候的時候,會在某位置存處SDRDATA的值,然后用一個LDR取出來。
顯然,用LDR時,加不加=號有很大區別。
無=號:取該標號處的值,位置無關
有=號:取該標號的地址,位置相關
(5) bl/b調用的c語言函數里面也不要使用全局變量, 因為c里面的全局變量的地址是根據鏈接地址生成的.
見例7.
==================================================================================================================
舉例分析
例1:中斷向量跳轉
_start:
b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
_undefined_instruction: .word undefined_instruction
_software_interrupt: .word software_interrupt
_prefetch_abort: .word prefetch_abort
_data_abort: .word data_abort
_not_used: .word not_used
_irq: .word irq
_fiq: .word fiq
其中,
ldr pc, _irq,由於沒加=號,表示取值_irq處的值放在pc里 (位置無關)
_irq: .word irq ,表示_irq存放的值是irq的絕對地址(位置有關)
例2:
bl main ; 位置無關
ldr pc, =main; 把main的地址放在pc,位置相關
例3: 靜態變量
_MAGIC_NUM:
.word 0x12345678
取值
LDR R0, _MAGIC_NUM ; 位置無關
例4: 存放標號絕對地址(絕對地址是編譯的時候已經固定)
_OS_Running_p:
.word OS_Runing
則_OS_Running_p存放的是標號OS_Running的絕對地址
例5: 顯式LDR和隱式LDR
以給某C中的變量的g_num賦值為例
(1) 使用偽指令LDR,即為隱式
LDR R0, =g_num @取g_num的地址到R0
MOV R1, #10
STR R1, R0
(2) 顯式賦值
先定義一個變量p_g_num,用於保存g_num的地址
p_g_num:
.word g_num @ g_num的絕對地址
然后賦值
LDR R0, p_g_num
MOV R1, #10
STR R1, R0
顯然,兩者其實一樣,偽指令被展開后其實就是(2)的樣子。
不同點在於:在多次引用的時候,如果使用偽指令,則會有多個臨時定義。所以,
在多次引用的時候應該使用顯式定義。
例6: 使用LinkScript中的變量
這種情形和例5相同
1) LinkScript中定義了兩個位置
{
__bss_start = .;
.bss : { *(.bss) }
_end = .;
}
2) 定義兩個變量,用於存處這兩個位置
.globl _bss_start
_bss_start:
.word __bss_start
.globl _bss_end
_bss_end:
.word _end
3) 使用這兩個位置
ldr r0, _bss_start /* find start of bss segment */
ldr r1, _bss_end /* stop here */
例7. bl/b調用的c語言函數里面也不要使用全局變量
in head.s and *.c :
Reset: ldr sp, =4096 @ 設置棧指針,以下都是C函數,調用前需要設好棧 bl disable_watch_dog @ 關閉WATCHDOG,否則CPU會不斷重啟 bl clock_init @ 設置MPLL,改變FCLK、HCLK、PCLK bl memsetup @ 設置存儲控制器以使用SDRAM bl nand_init @ 初始化NAND Flash
#define WTCON (*(volatile unsigned long *)0x53000000) void disable_watch_dog(void) { WTCON = 0; // 關閉WATCHDOG很簡單,往這個寄存器寫0即可 } #define CLKDIVN (*(volatile unsigned long *)0x4c000014) void clock_init(void) { CLKDIVN = 0x03; // FCLK:HCLK:PCLK=1:2:4, HDIVN=1,PDIVN=1 .. .. } void nand_init(void) { S3C2410_NAND * s3c2410nand = (S3C2410_NAND *)0x4e000000; /* 設置時序 */ s3c2440nand->NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4); .... } 以上函數都沒有使用到全局變量, 一旦使用了全局變量,就不能作為位置無關碼的一部分了. 比如bootloader啟動的第一階段, copy2ram的前面部分.必須是位置無關碼