在移植 uboot 時,接觸到一個概念叫做 位置無關碼,那么與它對應的就是位置有關碼。提到這兩個概念就還得提一提鏈接地址、加載地址
鏈接地址,鏈接腳本里指定的,理論上程序運行時所處的地址。在編譯時,編譯器會根據鏈接地址來翻譯位置有關碼。
加載地址,程序運行時,實際所處的地址。
位置無關碼,位置有關碼,是相對于一條指令的正常目的來說的。比如 ldr r0 ,=標號,它的正常目的是取得標號處的地址,對于這個目的,它是位置有關碼,運行的地址不對就獲取不到正確的標號地址,其實它無論在哪都是獲取的程序加載地址等于鏈接地址時,標號的地址,如果你就是想要這個值,那么用這條指令是非常正確的,就不用理會什么位置無關碼,位置有關碼的概念了,這一點非常重要。
因此,當加載地址不等于鏈接地址時,并不是不可以用位置無關碼,而是要看你用位置無關碼是否達到了你想要的目的。
位置無關碼,依賴于程序當前運行的PC值,進行相對的跳轉,導致的結果就是,無論代碼在哪,總能達到指令的正常目的,因此是位置無關的。
位置有關碼,不依賴當前PC值,是絕對跳轉,只有程序運行在鏈接地址處時,才能達到指令的正常目的,因此是位置有關系的。
下面,我們來看常用的匯編指令以及C語言中哪些操作是位置有關碼,哪些是位置無關碼。
如果加載地址為 0 ,那么代碼將按照下面的順序排放
如果加載地址為0x33f80000 則按照下邊的順序排放
?
一、B BL指令
- bl close_watch_dog
33f80000:eb000006bl33f80020
b 是相對跳轉:PC + 偏移值 (PC值等于當前地址+8)
偏移值:機器碼 0xeb000006 低 24位 0x000006 按符號為擴展為 32 位 0x00000006 正數,向后跳轉 0x6 個 4字節 也就是 0x1c
- bl _start
33f80004:ebfffffdbl33f80000
偏移值:機器碼 0xebfffffd 低 24位 fffffd 按符號位擴展為 32 位 0xfffffffd 負數(-3),向前跳轉 0x3 個 4字節 也就是 0xc
通過以上分析,我們知道B是相對跳轉,位置無關碼,也可以知道為什么32為arm指令集,B的范圍為正負32M了,24位去掉1位符號位,恰好等于32M。
二、ADR
- adr r0, close_watch_dog /* 獲取標號處的地址,位置無關 */
33f80008:e28f0010addr0, pc, #16
adr 獲取的是標號處的“實際”地址,標號在哪就是哪個地址,跟位置無關,總能獲得想要的值。
三、LDR
- ldr r0, SMRDATA /* 獲取標號處的值,位置無關 */
33f8000c:e59f0018ldrr0, [pc, #24]; 33f8002c
- ldr r0, =0x12345678 /* 常數賦值,位置無關*/
33f80010:e59f0018ldrr0, [pc, #24]; 33f80030
- ldr r0, =SMRDATA /* 獲取標號地址,位置有關 */
33f80014:e59f0018ldrr0, [pc, #24]; 33f80034
- ldr r0, =main/* 獲取函數名的地址,位置有關 */
- ldr r0 ,=__bss_start /* 獲取鏈接腳本里標號的地址,位置有關 */
這倆和ldr r0, =SMRDATA 一致,位置有關,在0地址處運行不正確。
四、C函數
1、全局變量
?
?
?
?
r3 為全局變量 a 的地址,a 是存放在 0起始的地址還是0x33f80000起始的地址,它都認為 a 的地址是 0x33f800a0 。因此,C函數中調用全局變量是位置有關碼。
2、函數調用
33f80084:ebffffedbl33f80040
由于 main 函數和 abc 函數挨得比較近,在32M范圍之內,因此被翻譯成了一條 bl 指令,那么與位置無關。
如果,調用的函數比較遠,大于32M的話,我認為是與位置有關系的,這個不再驗證了。
3、局部變量
局部變量在函數剛開始的地方被壓入棧,賦值語句被翻譯成:
33f8007c:e3a03001movr3, #1
33f80080: e50b3008 str r3, [fp, #-8]
位置無關。
評論
查看更多