在移植 uboot 時,接觸到一個概念叫做 位置無關碼,那麼與它對應的就是位置有關碼。提到這兩個概念就還得提一提連結地址、載入地址
連結地址,連結指令碼里指定的,理論上程式執行時所處的地址。在編譯時,編譯器會根據連結地址來翻譯位置有關碼。
載入地址,程式執行時,實際所處的地址。
位置無關碼,位置有關碼,是相對於一條指令的正常目的來說的。比如 ldr r0 ,=標號,它的正常目的是取得標號處的地址,對於這個目的,它是位置有關碼,執行的地址不對就獲取不到正確的標號地址,其實它無論在哪都是獲取的程式載入地址等於連結地址時,標號的地址,如果你就是想要這個值,那麼用這條指令是非常正確的,就不用理會什麼位置無關碼,位置有關碼的概念了,這一點非常重要。
因此,當載入地址不等於連結地址時,並不是不可以用位置無關碼,而是要看你用位置無關碼是否達到了你想要的目的。
位置無關碼,依賴於程式當前執行的PC值,進行相對的跳轉,導致的結果就是,無論程式碼在哪,總能達到指令的正常目的,因此是位置無關的。
位置有關碼,不依賴當前PC值,是絕對跳轉,只有程式執行在連結地址處時,才能達到指令的正常目的,因此是位置有關係的。
下面,我們來看常用的彙編指令以及C語言中哪些操作是位置有關碼,哪些是位置無關碼。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
SECTIONS { . = 0x33f80000; .text : { *(.text) } . = ALIGN(4); .rodata : {*(.rodata*)} . = ALIGN(4); .data : { *(.data) } . = ALIGN(4); __bss_start = .; .bss : { *(.bss) *(COMMON) } __bss_end = .; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
.text .global _start _start: bl close_watch_dog /* 相對跳轉,位置無關 */ bl _start adr r0, close_watch_dog /* 獲取標號地址,位置無關 */ ldr r0, SMRDATA /* 獲取標號處的值,位置無關 */ ldr r0, =0x12345678 ldr r0, =SMRDATA /* 獲取標號地址,位置有關 */ ldr r0, =main /* 獲取函式名的地址,位置有關 */ ldr r0 ,=__bss_start /* 獲取連結指令碼里標號的地址,位置有關 */ close_watch_dog: mov r1, #0 str r1, [r0] mov pc, lr SMRDATA: .word 0x22111120 |
1 2 3 4 5 6 7 8 9 10 11 |
int a; void abc(){ a = 2; } int main(){ int b; a =1 ; b =1 ; abc(); return 0; } |
如果載入地址為 0 ,那麼程式碼將按照下面的順序排放
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
00000000 <_start>: 00000000: eb000006 bl 33f80020 <close_watch_dog> 00000004: ebfffffd bl 33f80000 <_start> 00000008: e28f0010 add r0, pc, #16 0000000c: e59f0018 ldr r0, [pc, #24] ; 00000010: e59f0018 ldr r0, [pc, #24] ; 00000014: e59f0018 ldr r0, [pc, #24] ; 00000018: e59f0018 ldr r0, [pc, #24] ; 0000001c: e59f0018 ldr r0, [pc, #24] ; 00000020 <close_watch_dog>: 00000020: e3a01000 mov r1, #0 00000024: e5801000 str r1, [r0] 00000028: e1a0f00e mov pc, lr 0000002c <SMRDATA>: 0000002c: 22111120 andscs r1, r1, #8 00000030: 12345678 eorsne r5, r4, #125829120 ; 0x7800000 00000034: 33f8002c mvnscc r0, #44 ; 0x2c 00000038: 33f80064 mvnscc r0, #100 ; 0x64 0000003c: 33f800a0 mvnscc r0, #160 ; 0xa0 00000040 <abc>: 00000040: e52db004 push {fp} ; (str fp, [sp, #-4]!) 00000044: e28db000 add fp, sp, #0 00000048: e59f3010 ldr r3, [pc, #16] ; 33f80060 <abc+0x20> 0000004c: e3a02002 mov r2, #2 00000050: e5832000 str r2, [r3] 00000054: e28bd000 add sp, fp, #0 00000058: e8bd0800 pop {fp} 0000005c: e12fff1e bx lr 00000060: 33f800a0 mvnscc r0, #160 ; 0xa0 00000064 <main>: 00000064: e92d4800 push {fp, lr} 00000068: e28db004 add fp, sp, #4 0000006c: e24dd008 sub sp, sp, #8 00000070: e59f3024 ldr r3, [pc, #36] ; 33f8009c <main+0x38> 00000074: e3a02001 mov r2, #1 00000078: e5832000 str r2, [r3] 0000007c: e3a03001 mov r3, #1 00000080: e50b3008 str r3, [fp, #-8] 00000084: ebffffed bl 33f80040 <abc> 00000088: e3a03000 mov r3, #0 0000008c: e1a00003 mov r0, r3 00000090: e24bd004 sub sp, fp, #4 00000094: e8bd4800 pop {fp, lr} 00000098: e12fff1e bx lr 0000009c: 33f800a0 mvnscc r0, #160 ; 0xa0 |
如果載入地址為0x33f80000 則按照下邊的順序排放
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
33f80000 <_start>: 33f80000: eb000006 bl 33f80020 <close_watch_dog> 33f80004: ebfffffd bl 33f80000 <_start> 33f80008: e28f0010 add r0, pc, #16 33f8000c: e59f0018 ldr r0, [pc, #24] ; 33f8002c <SMRDATA> 33f80010: e59f0018 ldr r0, [pc, #24] ; 33f80030 <SMRDATA+0x4> 33f80014: e59f0018 ldr r0, [pc, #24] ; 33f80034 <SMRDATA+0x8> 33f80018: e59f0018 ldr r0, [pc, #24] ; 33f80038 <SMRDATA+0xc> 33f8001c: e59f0018 ldr r0, [pc, #24] ; 33f8003c <SMRDATA+0x10> 33f80020 <close_watch_dog>: 33f80020: e3a01000 mov r1, #0 33f80024: e5801000 str r1, [r0] 33f80028: e1a0f00e mov pc, lr 33f8002c <SMRDATA>: 33f8002c: 22111120 andscs r1, r1, #8 33f80030: 12345678 eorsne r5, r4, #125829120 ; 0x7800000 33f80034: 33f8002c mvnscc r0, #44 ; 0x2c 33f80038: 33f80064 mvnscc r0, #100 ; 0x64 33f8003c: 33f800a0 mvnscc r0, #160 ; 0xa0 33f80040 <abc>: 33f80040: e52db004 push {fp} ; (str fp, [sp, #-4]!) 33f80044: e28db000 add fp, sp, #0 33f80048: e59f3010 ldr r3, [pc, #16] ; 33f80060 <abc+0x20> 33f8004c: e3a02002 mov r2, #2 33f80050: e5832000 str r2, [r3] 33f80054: e28bd000 add sp, fp, #0 33f80058: e8bd0800 pop {fp} 33f8005c: e12fff1e bx lr 33f80060: 33f800a0 mvnscc r0, #160 ; 0xa0 33f80064 <main>: 33f80064: e92d4800 push {fp, lr} 33f80068: e28db004 add fp, sp, #4 33f8006c: e24dd008 sub sp, sp, #8 33f80070: e59f3024 ldr r3, [pc, #36] ; 33f8009c <main+0x38> 33f80074: e3a02001 mov r2, #1 33f80078: e5832000 str r2, [r3] 33f8007c: e3a03001 mov r3, #1 33f80080: e50b3008 str r3, [fp, #-8] 33f80084: ebffffed bl 33f80040 <abc> 33f80088: e3a03000 mov r3, #0 33f8008c: e1a00003 mov r0, r3 33f80090: e24bd004 sub sp, fp, #4 33f80094: e8bd4800 pop {fp, lr} 33f80098: e12fff1e bx lr 33f8009c: 33f800a0 mvnscc r0, #160 ; 0xa0 Disassembly of section .bss: 33f800a0 <a>: 33f800a0: 00000000 andeq r0, r0, r0 |
一、B BL指令
- bl close_watch_dog
33f80000:eb000006bl33f80020
b 是相對跳轉:PC + 偏移值 (PC值等於當前地址+8)
偏移值:機器碼 0xeb000006 低 24位 0x000006 按符號為擴充套件為 32 位 0x00000006 正數,向後跳轉 0x6 個 4位元組 也就是 0x1c
- 載入地址0:0 + 8 + 0x1c = 0x20 正確跳轉
- 載入地址0x3ff80000:0x3ff80000 + 8 + 0x1c = 0x33f80020 正確跳轉
- bl _start
33f80004:ebfffffdbl33f80000
偏移值:機器碼 0xebfffffd 低 24位 fffffd 按符號位擴充套件為 32 位 0xfffffffd 負數(-3),向前跳轉 0x3 個 4位元組 也就是 0xc
- 載入地址0:4 + 8 – 0xc = 0 正確跳轉
- 載入地址0x3ff80000:0x3ff80004 + 8 + 0xc = 0x33f80000 正確跳轉
通過以上分析,我們知道B是相對跳轉,位置無關碼,也可以知道為什麼32為arm指令集,B的範圍為正負32M了,24位去掉1位符號位,恰好等於32M。
二、ADR
- adr r0, close_watch_dog /* 獲取標號處的地址,位置無關 */
33f80008: e28f0010addr0, pc, #16
- 載入地址0:0 + 8 + 16 = 0x20 正確
- 載入地址0x3ff80000:0x3ff80008 + 8 + 16 = 0x33f80020 正確
adr 獲取的是標號處的“實際”地址,標號在哪就是哪個地址,跟位置無關,總能獲得想要的值。
三、LDR
- ldr r0, SMRDATA /* 獲取標號處的值,位置無關 */
33f8000c:e59f0018ldrr0, [pc, #24]; 33f8002c
- 載入地址0:r0 = c + 8 + 24 = 0x2c 處的值 0x22111120 正確
- 載入地址0x3ff80000:r0 = 0x3ff8000c + 8 + 24 = 0x33f8002c處的值 0x22111120 正確
- ldr r0, =0x12345678 /* 常數賦值,位置無關 */
33f80010: e59f0018ldrr0, [pc, #24]; 33f80030
- 載入地址0:r0 = 0x10 + 8 + 24 = 0x30 處的值 0x12345678 正確
- 載入地址0x3ff80000:r0 = 0x3ff80010 + 8 + 24 = 0x33f80030處的值 0x12345678 正確
- ldr r0, =SMRDATA /* 獲取標號地址,位置有關 */
33f80014: e59f0018ldrr0, [pc, #24]; 33f80034
- 載入地址0:r0 = 0x14 + 8 + 24 = 0x34 處的值 33f8002c 與標號實際地址(2c)不符合,不正確
- 載入地址0x3ff80000:r0 = 0x3ff80014 + 8 + 24 = 0x33f80034 處的值 33f8002c 正確
- ldr r0, =main/* 獲取函式名的地址,位置有關 */
- ldr r0 ,=__bss_start /* 獲取連結指令碼里標號的地址,位置有關 */
這倆和 ldr r0, =SMRDATA 一致,位置有關,在0地址處執行不正確。
四、C函式
1、全域性變數
1 2 3 4 5 6 7 8 9 10 |
00000040 <abc>: 00000040: e52db004 push {fp} ; (str fp, [sp, #-4]!) 00000044: e28db000 add fp, sp, #0 00000048: e59f3010 ldr r3, [pc, #16] ; 33f80060 <abc+0x20> 0000004c: e3a02002 mov r2, #2 00000050: e5832000 str r2, [r3] 00000054: e28bd000 add sp, fp, #0 00000058: e8bd0800 pop {fp} 0000005c: e12fff1e bx lr 00000060: 33f800a0 mvnscc r0, #160 ; 0xa0 |
1 2 |
000000a0 <a>: 000000a0: 00000000 andeq r0, r0, r0 |
1 2 3 4 5 6 7 8 9 10 |
33f80040 <abc>: 33f80040: e52db004 push {fp} ; (str fp, [sp, #-4]!) 33f80044: e28db000 add fp, sp, #0 33f80048: e59f3010 ldr r3, [pc, #16] ; 33f80060 <abc+0x20> 33f8004c: e3a02002 mov r2, #2 33f80050: e5832000 str r2, [r3] 33f80054: e28bd000 add sp, fp, #0 33f80058: e8bd0800 pop {fp} 33f8005c: e12fff1e bx lr 33f80060: 33f800a0 mvnscc r0, #160 ; 0xa0 |
1 2 |
33f800a0 <a>: 33f800a0: 00000000 andeq r0, r0, r0 |
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]
位置無關。