ARM平臺下ldr和bl跳轉的區別

主要就是:

bl一般是地址無關的跳轉,最簡單理解就是跳轉函數也是在4K片內RAM裏的。這個不管你編譯時候它存放的位置,函數間的相互的位置關係是不會變的,函數間是一個確定的相對位置關係。跳轉的實現是通過PC指針加減一個相對偏移量來完成。顯然,這個相對偏移量是有限制的。

ldr是地址相關的,用來跳轉片外函數的,也就是這個代碼運行時是在片外RAM的。在這種情況下,如果用bl跳轉,PC可以用的最大偏移量仍然 無法滿足跳轉的需要,就需要用ldr指令實現PC指針的改變。也就是,函數間跳轉使用的是絕對地址

這算是我個人簡單的助記的理解。如果有朋友看到,覺得這個解釋容易出錯或者不合理,希望指出,讓我有更好的理解。下面就貼出我參考學習的文章。

轉自:http://www.cnblogs.com/shenlian/archive/2011/11/30/2269341.html


一,按lds文件連接的不同模塊,不能用bl實現跳轉
一個錯誤的例子:
1.crt0.s
@******************************************************************************
@ File:crt0.s
@ 功能:通過它轉入C程序
@****************************************************************************** 
.extern main
.text
.global _start
_start:
ldr sp, =1024*4 @設置堆棧,注意:不能大於4k ,
@這兒堆棧可以設置爲0x34000000,根據內存地址空間分配確定

bl main @調用C程序中的main函數
halt_loop: 
b halt_loop
2.leds.c
@******************************************************************************
@ file leds.c
@ main函數
@****************************************************************************** 
int main()
{
__asm__

" ldrb r0, [r1], #1\n"
" strb r0, [r2], #1\n"
);
return 0;
}
3.Makefile
CFLAGS := -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -ffreestanding -c
leds : crt0.s leds.c
arm-linux-gcc $(CFLAGS) -o crt0.o crt0.s
arm-linux-gcc $(CFLAGS) -o leds.o leds.c
arm-linux-ld -Tleds.lds crt0.o leds.o -o leds_tmp.o
arm-linux-objcopy -O binary -S leds_tmp.o leds
arm-linux-objdump -D -b binary -m arm leds >ttt.s
clean:
rm -f leds
rm -f leds.o
rm -f leds_tmp.o
rm -f crt0.o
4.leds.lds 文件
SECTIONS { 
firtst 0x00000000 : { crt0.o }
second 0x00000000 : AT(0x0100) { leds.o }

5.反彙編代碼ttt.s
00000000 :
0: e3a0da01 mov sp, #40Array6 ; 0x1000
4: ebfffffd bl 0x0 //這兒不能調轉到main 因爲bl跳轉有限制
8: eafffffe b 0x8
...
100: e24dd040 sub sp, sp, #64 ; 0x40
104: e3a00000 mov r0, #0 ; 0x0
108: e28dd040 add sp, sp, #64 ; 0x40
10c: e1a0f00e mov pc, lr
110: 43434700 cmpmi r3, #0 ; 0x0
114: 4728203a undefined
118: 202Array554e eorcs r5, rArray, lr, asr #10
11c: 2e332e33 mrccs 14, 1, r2, cr3, cr3, {1}
120: 00000032 andeq r0, r0, r2, lsr r0
通過上面的例子可以看到crt0中的bl main出錯
"4: ebfffffd bl 0x0 "
bl沒有成功。 
6.改正方法1:
原lds文件把倆個目標文件分開排列,這裏把倆個目標文件指定到一起,這樣不能重定位

修改後的lds文件
SECTIONS { 
firtst 0x00000000 : { crt0.o leds.o }


改正後的效果
0: e3a0da01 mov sp, #40Array6 ; 0x1000
4: eb000000 bl 0xc //這裏bl跳轉到正確的地址
8: eafffffe b 0x8
c: e24dd040 sub sp, sp, #64 ; 0x40
10: e3a00000 mov r0, #0 ; 0x0
14: e28dd040 add sp, sp, #64 ; 0x40
18: e1a0f00e mov pc, lr
1c: 43434700 cmpmi r3, #0 ; 0x0
20: 4728203a undefined
24: 202Array554e eorcs r5, rArray, lr, asr #10
28: 2e332e33 mrccs 14, 1, r2, cr3, cr3, {1}
2c: 00000032 andeq r0, r0, r2, lsr r0
二,使用ldr命令來實現長跳轉(改正方法2)
1.
ldr pc, =main @調用C程序中的main函數
通過ldr 對pc賦值來實現跳轉
@******************************************************************************
@ File:crt0.s
@ 功能:通過它轉入C程序
@****************************************************************************** 
.extern main
.text
.global _start
_start:
ldr sp, =1024*4 @設置堆棧,注意:不能大於4k,nand flash中的代碼在復位後會移到內部ram中,此ram只有4k
ldr pc, =main @調用C程序中的main函數
halt_loop:
b halt_loop
2.leds.lds文件
SECTIONS { 
firtst 0x00000000 : { crt0.o }
second 0x30000000 : AT(0x1000) { leds.o }


3.反彙編結果
00000000 :
0: e3a0da01 mov sp, #40Array6 ; 0x1000
4: e5Arrayff000 ldr pc, [pc, #0] ; 0xc
8: eafffffe b 0x8
c: 30000000 andcc r0, r0, r0
...
1000: e4d10001 ldrb r0, [r1], #1



地址無關: 編譯地址不等於運行地址.
地址相關: 編譯地址等於運行地址.

常見的一些Boot(如, U-Boot, VIVI)和Linux Kernel代碼開始的一段是位置無關的, 意思就是說運行地址與編譯地址無關. 如, Kernel編譯地址是0xc0008000, 而運行地址是0x30008000.
爲什麼?
爲什麼代碼的編譯地址和運行地址會不相等呢? 原因主要有以下幾種: 1) 對於Boot, 用於存放Boot代碼的存儲器容量小於代碼量. 如, Boot片有4K, 而代碼通常有50-60K. 這樣, 通常會在前4K代碼裏, 讓Boot把自己複製到RAM, 再接着運行.這裏我們需要作出一個選擇, 是讓前面的代碼與地址相關, 還是讓後面的代碼與地址相關呢? 顯然我們會選擇前面一段代碼量小的與地址無關. 2) 對於Linux Kernel, 它是運行在虛擬地址空間的, 如0xc0008000, 但在MMU打開之前, 通常這個地址是
不存在的, 也就是說在MMU打開之前, Kernel的代碼必須是地址無關的.
怎麼辦?
對於位置無關的代碼, 尋址是基於pc值的, 在pc值上+/-一個偏移值, 得到運行地址.以ARM爲例, 用adr來尋址, adr的實際上是一個宏指令, 在代碼編譯時, 會被編譯器替換成對pc的+/-運算
這裏要注意, 對pc的+/-運行顯然是有一個地址範圍的, 所以我們在上面選擇代碼量小的地址無關, 是很明智的.

而訪問地址相關的代碼, 只需要使用其它的尋址指令就行了. 但在這之前, 必須保證代碼被放在正確的地址上, 所以通常都會有一個複製代碼的過程, 然後就是跳轉到一個標號, 地址相關代碼就開始運行

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章