天天看點

位置無關碼、位置有關碼、連結位址、加載位址

    在移植 uboot 時,接觸到一個概念叫做 位置無關碼,那麼與它對應的就是位置有關碼。提到這兩個概念就還得提一提連結位址、加載位址。

    連結位址,連結腳本裡指定的,理論上程式運作時所處的位址。在編譯時,編譯器會根據連結位址來翻譯位置有關碼。

    加載位址,程式運作時,實際所處的位址。

    位置無關碼,位置有關碼,是相對于一條指令的正常目的來說的。比如 ldr r0 ,=标号,它的正常目的是取得标号處的位址,對于這個目的,它是位置有關碼,運作的位址不對就擷取不到正确的标号位址,其實它無論在哪都是擷取的程式加載位址等于連結位址時,标号的位址,如果你就是想要這個值,那麼用這條指令是非常正确的,就不用理會什麼位置無關碼,位置有關碼的概念了,這一點非常重要。

    是以,當加載位址不等于連結位址時,并不是不可以用位置無關碼,而是要看你用位置無關碼是否達到了你想要的目的。

    位置無關碼,依賴于程式目前運作的PC值,進行相對的跳轉,導緻的結果就是,無論代碼在哪,總能達到指令的正常目的,是以是位置無關的。

    位置有關碼,不依賴目前PC值,是絕對跳轉,隻有程式運作在連結位址處時,才能達到指令的正常目的,是以是位置有關系的。

    下面,我們來看常用的彙編指令以及C語言中哪些操作是位置有關碼,哪些是位置無關碼。

SECTIONS {
    . = 0x33f80000;
    .text : { *(.text) }
    
    . = ALIGN(4);
    .rodata : {*(.rodata*)} 
    
    . = ALIGN(4);
    .data : { *(.data) }
    
    . = ALIGN(4);
    __bss_start = .;
    .bss : { *(.bss)  *(COMMON) }
    __bss_end = .;
}
           
.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 
           
int a;
void abc(){
	a = 2;
}
int main(){
	int b;
	a =1 ;
	b =1 ;
	abc();
	return 0;
}
           

如果加載位址為 0 ,那麼代碼将按照下面的順序排放

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 則按照下邊的順序排放

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

    1、加載位址0:0 + 8 + 0x1c = 0x20 正确跳轉

    2、加載位址0x3ff80000:0x3ff80000 + 8 + 0x1c = 0x33f80020 正确跳轉

    bl _start

    33f80004:ebfffffdbl33f80000 <_start>

    偏移值:機器碼 0xebfffffd 低 24位 fffffd 按符号位擴充為 32 位 0xfffffffd 負數(-3),向前跳轉 0x3 個 4位元組 也就是 0xc 

    1、加載位址0:4 + 8 - 0xc = 0 正确跳轉

    2、加載位址0x3ff80000:0x3ff80004 + 8 + 0xc = 0x33f80000 正确跳轉

    通過以上分析,我們知道B是相對跳轉,位置無關碼,也可以知道為什麼32為arm指令集,B的範圍為正負32M了,24位去掉1位符号位,恰好等于32M。

二、ADR

    adr r0, close_watch_dog     

    33f80008: e28f0010addr0,  pc,  #16  

    1、加載位址0:0 + 8 + 16 = 0x20 正确

    2、加載位址0x3ff80000:0x3ff80008 + 8 + 16 = 0x33f80020 正确

    adr 擷取的是标号處的“實際”位址,标号在哪就是哪個位址,跟位置無關,總能獲得想要的值。

三、LDR

    ldr r0, SMRDATA       

    33f8000c:e59f0018ldrr0, [pc, #24]; 33f8002c <SMRDATA>

    1、加載位址0:r0 = c + 8 + 24 = 0x2c 處的值 0x22111120 正确

    2、加載位址0x3ff80000:r0 = 0x3ff8000c + 8 + 24 = 0x33f8002c處的值 0x22111120 正确

    ldr r0, =0x12345678   

    33f80010: e59f0018ldrr0, [pc, #24];  33f80030 <SMRDATA+0x4>

    1、加載位址0:r0 = 0x10 + 8 + 24 = 0x30 處的值 0x12345678 正确

    2、加載位址0x3ff80000:r0 = 0x3ff80010 + 8 + 24 = 0x33f80030處的值 0x12345678 正确

    ldr r0, =SMRDATA            

    33f80014: e59f0018ldrr0,  [pc, #24];  33f80034 <SMRDATA+0x8>

    1、加載位址0:r0 = 0x14 + 8 + 24 = 0x34 處的值 33f8002c 與标号實際位址(2c)不符合,不正确

    2、加載位址0x3ff80000:r0 = 0x3ff80014 + 8 + 24 = 0x33f80034 處的值 33f8002c 正确

    ldr r0, =main

    ldr r0 ,=__bss_start

    這倆和 ldr r0, =SMRDATA 一緻,位置有關,在0位址處運作不正确。

四、C函數

    1、全局變量

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
           
000000a0 <a>:
000000a0:	00000000 	andeq	r0, r0, r0
           
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
           
33f800a0 <a>:
33f800a0:	00000000 	andeq	r0, r0, r0
           

    r3 為全局變量 a 的位址,a 是存放在 0起始的位址還是0x33f80000起始的位址,它都認為 a 的位址是 0x33f800a0 。是以,C函數中調用全局變量是位置有關碼。

    2、函數調用

    33f80084:ebffffedbl33f80040 <abc>

    由于 main 函數和 abc 函數挨得比較近,在32M範圍之内,是以被翻譯成了一條 bl 指令,那麼與位置無關。

    如果,調用的函數比較遠,大于32M的話,我認為是與位置有關系的,這個不再驗證了。

    3、局部變量

    局部變量在函數剛開始的地方被壓入棧,指派語句被翻譯成:

    33f8007c: e3a03001movr3, #1

    33f80080: e50b3008 str r3, [fp, #-8]

    位置無關。

繼續閱讀