這是一個很簡單的例子,
.globl _start
_start:
adr r0,_start
ldr r1,_TEXT_BASE
...
_TEXT_BASE: .word _start
在組裝時,鏈接,然後拆卸:
00008000 <_start>:
8000: e24f0008 sub r0, pc, #8
8004: e59f101c ldr r1, [pc, #28] ; 8028 <_TEXT_BASE>
...
00008028 <_TEXT_BASE>:
8028: 00008000 andeq r8, r0, r0
還有就是你的答案。 adr指令基於假設您的pc在執行時包含0x8008。無論你身在何處,ldr都會提供一個相同的鏈接時間值。
例如,如果這個代碼實際上位於地址0x20000000處,那麼當第一條指令(adr是僞指令,在反彙編中它是8的一個子集)時,adr被執行,現在你得到一個0x20000008- 8 = 0x20000000,您將它與不匹配的0x8000進行比較。如果你在0x8000處運行代碼,那麼0x8008-8 = 0x8000和兩個匹配。
只要閱讀代碼並查找adr指令(或者做我所做的,只是嘗試它並檢查編譯器/工具的輸出,並/或在硬件上運行它,如果不顯示答案的話)。
編輯:
使用具有不同前綴的GNU工具,但是這個代碼是很簡單的幾乎不關心。 arm-none-eabi-或arm-none-linux-gnueabi-。
假設大會文件名是foo.s
arm-none-eabi-as foo.s -o foo.o
arm-none-eabi-ld -T memmap foo.o -o foo.elf
arm-none-eabi-objdump -D foo.elf
我所謂MEMMAP是鏈接描述文件,你可以使用命令行-Ttext =爲0x8000在這種情況下。我喜歡將.elf擴展名用於我的交叉編譯二進制文件,而不是每個人都這樣做。
00008000 <_start>:
8000: e24f0008 sub r0, pc, #8
8004: e59f101c ldr r1, [pc, #28] ; 8028 <_TEXT_BASE>
...
00008028 <_TEXT_BASE>:
8028: 00008000 andeq r8, r0, r0
_start是GNU工具的事情鏈接器需要/想這個標籤知道代碼的入口點。所以它不是一個真正的uboot的東西,雖然uboot可能也很在意,但它絕對是一個gnu鏈接器的東西。你可以看到linux作爲一個內核在類似的地址上啓動,但它確實是任意的,你可以設置你的系統和二進制文件。
對於這裏發生的事情,沒有什麼是ARM特有的或魔法。在任何平臺上您都可以提出正確的說明。
程序計數器在前面是兩個指令,這是一個32位的arm指令,所以程序計數器在執行指令時被假定爲這個指令的地址加上8個。由於這個adr的東西是第一個指令在_start表示這條指令在地址0x8000處被鏈接/編譯,並且從0x8008到0x8000得到的指令編碼爲r0 = pc-8;其他信息是鏈接器在標籤_TEXT_BASE之後提供_start的地址。所以另一個步驟是將該值加載到r1中。
這隻適用於假設代碼實際存在於閃存中以使處理器在地址0x8000處看到閃存中的指令的情況。 0x8000和0x8000的比較是相等的,所以程序在ram中生成它自己的副本,然後它會跳到這個副本的起始位置,這次它通過代碼的副本時,一個寄存器包含一些地址除了0x8000以外,所以比較失敗,如果你從閃存運行原始版本或者從RAM運行拷貝,這只是一個檢測器。目的是複製並運行基於RAM的副本。需要其他預防措施以確保代碼可以在兩個地址上運行(與位置無關的代碼)。
如果你碰巧知道flash地址爲0x00008000,碰巧知道的例子,RAM地址是說0x20000000你可以有,而不是比較的PC用說的0x10000或可能相與PC的價值與0xFF000000
and r0,pc,#0xFF000000
beq stack_setup
但我認爲這是更通用的代碼,所以使用額外的說明和準確的比較。
這種類型的技巧還是比較常見的,檢測閃存或RAM複製等。使用的確切指令以及如何讓鏈接器爲您填充事物是特定於目標的。
在這種情況下,閃存可能位於某個非零地址,而0x8000是RAM副本。我不知道。
只是讀取代碼。它是比較你是從閃存中運行這些代碼還是在內存中運行它的一個副本。如果從閃存運行,然後再次嘗試,如果從內存然後繼續到下一個東西(堆棧設置) –
@dwelch thaks爲響應,所以r0有FLASH位置和r1有RAM位置? –
我認爲差異將會是adr vs ldr。一個人將把PC作爲數據的一部分(以及地址),另一個不是。這是關鍵的區別。 –