從兩句彙編認識執行時位址與鏈結位址

2021-07-24 14:27:17 字數 3433 閱讀 8652

首先看兩行彙編**:

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、鏈結位址起始位置:它是由程式設計師指定的,或者說是有鏈結指令碼設定。是可以變動的。但是這個位置在程式鏈結之後,就會確定下來。

說明了以上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

同樣是載入_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

根據偏移量,這句的執行時位址是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 

總結:首先說明,之前的提出的結論:

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]),或者說不需要執行時位址和鏈結位址這兩個概念了。除非你還想重定位程式。

**:

從Runnable中的執行時異常說起

前段時間,夜晚突然收到報警,緊急上線排查。由於dba操作不當,大片資料回滾,發生鎖表的情況,請求返回時間過長,使得系統列印出大量的rejectedexecutionexception的異常。定位到 片段類似 threadpoolexecutor workers new threadpoolexecu...

兩種排序演算法的執行時間

問題及 2015級,煙台大學計算機與控制工程學院 作 者 陳朋 完成日期 2016年9月8日 輸入描述 大資料檔案 程式輸出 資料量和排序時間 include include include define maxnum 100000 void selectsort int a,int n if k ...

iOS 執行時使用 交換兩個方法

舉例 在建立了如下 nsstring str nil nsurl url nsurl urlwithstring str nslog url 但是想使用自己的方法並對傳入的字串做為空判斷 舉例 自己寫了乙個方法 instancetype sjurlwithstr nsstring str 替代系統方...