首先看兩行匯編代碼:
1: adr r0, _start
2: ldr r1, =_start
同樣是加載一個標號的地址值,adr和ldr有什么區別呢?注意這里的ldr不是命令ldr,而是偽指令ldr,若想區分它們請參看我的一篇博文《adr adrl ldr mov總結整理》。
要區分它們,就需要引入4個概念:
1、運行時地址起始位置:它芯片公司指定的一開始運行代碼的位置。這個位置和芯片本身有關,不可改動。對於2440來說一般就是片內SRAM的首地址0x0;對於210來說就是片內SRAM中的地址0xD0020010。
2、鏈接地址起始位置:它是由程序員指定的,或者說是有鏈接腳本設定。是可以變動的。但是這個位置在程序鏈接之后,就會確定下來。
3、運行時地址:就在從運行時地址起始位置(包括起始位置)往后排都是運行時地址。
4、鏈接地址:就是從鏈接地址起始位置(包括起始位置)往后排都是鏈接地址。
說明了以上4點內容之后,我需要鋪墊一些前提內容,adr r0, _start ; ldr r1, =_start
這兩句代碼是從朱老師的一個實驗程序里直接截取出來,這實驗的目的是演示重定位。之后這段代碼我會貼到文章的最后。因為開發板是210的板子所以運行時地址是從0xd0020010開始的,鏈接地址設置為0xd0024000開始的。
整個程序編譯之后,在進行反編譯,我們查找adr r0, _start ; ldr r1, =_start 對應的反匯編內容:
1、adr r0, _start 對應的是:
d002401c: e24f0024 sub r0, pc, #36 ; 0x24
2、ldr r1, =_start對應的是:
d0024020: e59f1048 ldr r1, [pc, #72] ; d0024070 <run_on_dram+0x10>
同樣是加載_start的地址,反匯編之后卻是截然不同的命令。首先我們需要會看反匯編,最左邊的是鏈接地址,第二個是機器碼,第三個是反匯編得到的內容,最右邊分號之后的是反匯編編譯器額外幫我們注釋了一些內容方便我們閱讀。
我們發現反匯編之后,有一個地方很不同,就是pc指針。ldr r1, =_start對於的反匯編pc指針被放到的了[]里面,而另一條反匯編沒有。我們知道對於匯編而言,放到[]里面代表是取得寄存器的值並且將寄存器的值當作地址,來訪問地址中存儲的值。
而對於pc而言,當你直接讀取pc的值時訪問的是運行時地址,而當你讀取[pc]的值時訪問的是鏈接地址。
反觀adr r0, _start 和ldr r1, =_start它們都是偽指令,意思也分別是讀取運行時地址和讀取鏈接地址。和反匯編意義吻合。
我們現在來驗證,我們前面分析的是否正確。首先_start作為程序的最開始,所以_start如果對應運行時地址,那么讀取的_start的值應該是運行時地址起始位置及0xd0020010。
觀察反匯編及對應的匯編
1、adr r0, _start
d002401c: e24f0024 sub r0, pc, #36 ; 0x24
由於此時該句代碼的鏈接地址是d002401c鏈接地址的起始位置設定的是0xd0024000,偏移量是0x1c,根據這個便宜量可以算出該句代碼的運行時地址為0xd0020010 +0x1c = d002002C,前面提到pc的值對應的就是運行時地址所以pc = d002002C。
d002002C - (36 十進制)+ 8 (流水線)= D002 0010 ;正好得到了_start的運行時地址完全沒錯。
再看鏈接地址是否算錯,首先_start作為程序的最開始,所以_start如果對應鏈接地址,那么讀取的_start的值應該是鏈接地址起始位置及之前設定的0xd0024000。
2、ldr r1, =_start
d0024020: e59f1048 ldr r1, [pc, #72] ; d0024070 <run_on_dram+0x10>
根據偏移量,這句的運行時地址是d0020030,如果說是運行時地址 + 偏移量(72十進制),得到的是D002 0078,再加8(流水線)等於D002 0080,顯然不對。
明顯這里的[pc]的值得到的是當前語句對應的鏈接地址,d0024020 + 偏移量(72十進制)+ 8 才等於D002 4070(這個值也正好是注釋里的值)大家是不是奇怪,為啥值不是0xd0024000?是不是算錯了?其實不是,你到D002 4070這個鏈接地址看看就會發現這里存放的值正好就是D002 4070。
代碼如下:d0024070: d0024000 andle r4, r2, r0
這里符合ldr r1, [pc, #72]這句指令的本意,他訪問的就是這個值代表的地址中的值。(這種跳轉的方法其實就是為了應對非法立即數,導致在一個機器碼里放不下命令和數據的情況)
代碼如下:
.text .global _start _start: bl disable_watch_dog @ 關閉WATCHDOG,否則CPU會不斷重啟 bl memsetup @ 設置存儲控制器 bl copy_steppingstone_to_sdram @ 復制代碼到SDRAM中 ldr pc, =on_sdram @ 跳到SDRAM中繼續執行 on_sdram: ldr sp, =0x34000000 @ 設置堆棧 bl main halt_loop: b halt_loop
增改:2016-06-07
總結:
首先說明,之前的提出的結論:
1、運行時地址:就在從運行時地址起始位置(包括起始位置)往后排都是運行時地址。
2、鏈接地址:就是從鏈接地址起始位置(包括起始位置)往后排都是鏈接地址。
3、對於pc而言,當你直接讀取pc的值時訪問的是運行時地址,而當你讀取[pc]的值時訪問的是鏈接地址。
4、adr r0, _start ;加載的是運行時地址; ldr r1, =_start加載的是鏈接地址。
整個過程是這樣的:
1、程序在運行之前先編譯鏈接,鏈接完了之后,每句程序都對應一個鏈接地址(就像你反匯編看到的那樣)。而鏈接地址的起始地址其實位置往往是DDR的起始位置。
2、但是一開始程序是在SRAM上運行的,運行地址的起始位置往往就是SRAM的起始位置。那么一開始運行的代碼它的
鏈接地址和運行地址是不同的,但是代碼本身不知道這件事,而程序員明白,所以這段代碼只能是位置無關碼,主要負責一些必要的初始化和搬運(重定位)。直到
搬運完成,pc的值還是運行時地址的值。也就是說pc不等於[pc].
3、通過絕對跳轉修改PC的值為當前鏈接地址的值:
ldr pc, =on_sdram @ 跳到SDRAM中繼續執行,及讓pc = [pc]
如果用相對跳轉就是當前運行時地址加上一個偏移量,而在那個地方可能並沒有內存和程序。
如: bl on_sdram @通過PC + 偏移量完成,此時PC運行是運行時地址。這樣就無法到DDR上運行程序了。
4、此后,可以認為運行時地址和鏈接地址相等(pc = [pc]),或者說不需要運行時地址和鏈接地址這兩個概念了。除非你還想重定位程序。