0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

深度剖析ARM跳轉(zhuǎn)指令

汽車電子技術(shù) ? 來源:宅學(xué)部落 ? 作者:王利濤 ? 2023-02-17 09:37 ? 次閱讀

跳轉(zhuǎn)指令

順序、選擇、循環(huán)是構(gòu)建程序的基本結(jié)構(gòu),任何一個邏輯復(fù)雜的程序基本上都可以由這三種程序結(jié)構(gòu)組合而成。而跳轉(zhuǎn)指令,則在子程序調(diào)用、選擇、循環(huán)程序結(jié)構(gòu)中被大量使用。程序的跳轉(zhuǎn)是如何實現(xiàn)的呢?在了解這個機制之前,我們需要先了解一下程序計數(shù)器PC。

程序計數(shù)器PC,是CPU寄存器列表中最重要的一個寄存器。它就像一桿槍,指哪打哪:你給PC指針賦值哪個地址,CPU就會到PC指針指向的這個地址去取指令、翻譯指令、執(zhí)行指令。一般情況下,當(dāng)你沒有給PC指針賦新地址時,CPU在PC指針指向的地址取完指令后,PC計數(shù)器會自動加一,指向下一條指令,程序可以自動執(zhí)行下去。當(dāng)我們需要跳轉(zhuǎn)時,可以直接給PC指針賦一個新地址,于是CPU就會跳轉(zhuǎn)到新地址去執(zhí)行了。

ARM中,常見的跳轉(zhuǎn)指令有B、BL、MOV、LDR等。不同的指令,它們的使用條件、使用場合是不同的,今天就給大家總結(jié)一下它們的區(qū)別及各自使用的場合。

1B跳轉(zhuǎn)指令

B指令是ARM中最基本的跳轉(zhuǎn)指令,它的使用方法如下:

B label

上面語句表示跳轉(zhuǎn)到label的標(biāo)號處去執(zhí)行。B跳轉(zhuǎn)指令是ARM中最簡單的指令,只是單純的跳轉(zhuǎn),而且是相對跳轉(zhuǎn)。它可以跳到以當(dāng)前位置PC為基址,前后32MB的地址空間范圍,所以B指令只是在臨近的代碼塊、標(biāo)號之間跳轉(zhuǎn)。

B指令跳轉(zhuǎn),大多數(shù)時候是單向的,跳過去就不再返回來了。但是我們可以通過添加一些標(biāo)號來實現(xiàn)一些控制邏輯:比如循環(huán)、選擇程序結(jié)構(gòu):

;循環(huán)結(jié)構(gòu)示例
LOOP
    SUB R0,R0,#1
    ...
    CMP R0,#0
    BNE LOOP
;選擇結(jié)構(gòu)示例
    MOV R1,#10
    MOV R2,#20
    CMP R1,R2
    BEQ HERE
    ...
    B END 
HERE
    ...
END
    ...

在上面的程序中,我們使用B跳轉(zhuǎn)指令實現(xiàn)了選擇、循環(huán)這兩種基本的程序結(jié)構(gòu)。B指令像ARM的其它指令一樣,可以根據(jù)CPSR狀態(tài)寄存器的標(biāo)志位,有條件的執(zhí)行。這樣,可以減少指令數(shù)目、提高代碼密度和運行效率。如BNE、BEQ就是當(dāng)結(jié)果相等、不相等時的條件跳轉(zhuǎn)。

當(dāng)前程序狀態(tài)寄存器:

圖片

各種各樣的條件碼:

圖片

2BL指令

BL指令跟B不同:在跳轉(zhuǎn)之前,會先將當(dāng)前指令的下一條指令地址保存到LR寄存器中,然后才跳轉(zhuǎn)到標(biāo)號執(zhí)行。這樣做的好處是:當(dāng)我們想從標(biāo)號地方返回時,可以直接將LR寄存器中的返回地址賦值給PC,程序就可以返回到原來的程序中繼續(xù)執(zhí)行了。

BL跳轉(zhuǎn)指令一般用在子程序的調(diào)用中。無論是匯編語言子程序,還是C語言子程序,在跳轉(zhuǎn)到子程序之前,都要將返回地址保存起來。當(dāng)子程序執(zhí)行完畢,將LR寄存器保存的返回地址,重新賦值給PC,處理器就可以返回到主程序繼續(xù)執(zhí)行了。

BEGIN
    MOV R0,#SRC
    MOV R1,#DST
    MOV R2,#100
    BL COPY
    NOP
    ...
 COPY
    SUB R2,R2,#1
    LDR R3,[R0],#1
    STR R3,[R1],#1
    CMP R2,#0
    BNE COPY
    MOV PC,LR

上面的匯編代碼段,我們定義了一個匯編子程序COPY,實現(xiàn)了數(shù)據(jù)拷貝的功能。當(dāng)我們使用BL指令調(diào)用這個子程序COPY時,CPU會首先將當(dāng)前指令的下一條指令:NOP 的地址保存到LR寄存器中,然后才跳轉(zhuǎn)到COPY子程序去執(zhí)行。在COPY子程序中,處理完數(shù)據(jù)搬運后,通過

MOV PC,LR

這條語句,將保存在LR寄存器中的返回地址,重新賦值給PC,這樣我們就可以返回到原來的程序中繼續(xù)執(zhí)行了。

在上面的匯編代碼中,LR,即R14,連接寄存器,常用來存放程序的返回地址;PC,即R15,程序計數(shù)器,表示當(dāng)前指令地址。LR和PC都是ARM匯編器為了方便程序員編程,預(yù)定義的一些宏。你在程序中使用這些助記符其實就是相當(dāng)于操作R14和R15寄存器。除此之外,ARM中常用的助記符有:

  • FP:棧幀基址寄存器,即R12
  • SP:棧指針寄存器,即R13
  • LR:鏈接寄存器,即R14
  • PC:程序計數(shù)器,即R15

同樣,在C語言調(diào)用子函數(shù)的過程中,在跳轉(zhuǎn)子函數(shù)執(zhí)行之前,CPU也會將當(dāng)前指令的下一條指令地址保存到LR寄存器中,然后再跳轉(zhuǎn)到子函數(shù)中執(zhí)行。因為在子函數(shù)運行過程中,也有可能會用到ARM的一些寄存器,也有可能會調(diào)用其它的子函數(shù),會覆蓋掉保存在LR寄存器中的返回地址,所以,我們一般在運行子函數(shù)之前,會首先將LR寄存器壓入子函數(shù)的棧幀,相當(dāng)于將返回地址保存到了棧上。當(dāng)子函數(shù)運行結(jié)束時,再通過出棧操作,將保存在棧中的返回地址彈出到PC指針中,這樣程序就成功從子程序中返回了,直接返回到原來的函數(shù)中繼續(xù)執(zhí)行。

int main(void)
{
    func();
    printf("Hello!\\n");
    return 0;
} 
;對應(yīng)的匯編代碼
main
    BL func
    BL printf
func
    PUSH LR
    ...
    pop pc 
;func子函數(shù)返回

3MOV指令

通過上面的學(xué)習(xí),我們可以看到,無論是B指令、還是BL指令,都是相對尋址。其本質(zhì)都是以當(dāng)前指令地址PC為基址,然后加上一個[0,32M]的偏移,達(dá)到修改PC的目的。

除此之外,我們也可以直接給PC指針賦值,達(dá)到跳轉(zhuǎn)的目的。如上面的 func 子程序返回,就是直接通過

MOV PC,LR

這條指令,將LR寄存器中的返回地址,直接賦值給PC,直返回到原來的主函數(shù)去執(zhí)行。

MOV指令主要用來在寄存器之間傳輸數(shù)據(jù),或者將一個立即數(shù)傳送到寄存器。但是MOV指令有一個硬傷,就是傳遞的立即數(shù)只能是8位數(shù),有大小的限制。這是為什么呢?很簡單,ARM是RISC架構(gòu),在一個32位的ARM中,指令通常都是32位的。而一個指令中,通常要包括操作碼+操作數(shù),如下圖:

圖片

一條指令,總共有32個bit空間,MOV這個操作碼要占幾位吧,Rd寄存器編碼要占據(jù)幾位吧,剩下的留給立即數(shù)的空間就不多了,所以這也就限定了MOV指令能傳遞的立即數(shù)的大小了。而一般的32位程序中,無論是變量還是函數(shù),它們的地址一般都是32位的,如果使用MOV指令,將他們的地址傳送到PC,使用下面的形式:

MOV PC,#0x30008000

你會發(fā)現(xiàn),立即數(shù)#0X30008000這個地址就已經(jīng)32位了,在加上MOV指令這個操作碼,已經(jīng)超過32位了,編譯器是無法翻譯這個指令的,所以說,當(dāng)一個變量或函數(shù)地址為32位時,使用MOV指令給PC直接賦值,行不通,那怎么辦呢?

4LDR偽指令

辦法總是有的,比如,我們就可以通過偽指令LDR,直接將一個32位的立即數(shù)地址,傳送到PC:

LDR PC,=0x30008000

LDR偽指令的功能和MOV一樣,都可以將一個立即數(shù)傳送到寄存器。唯一區(qū)別的就是,MOV指令只能傳送8位的,而LDR可以傳送一個32位的立即數(shù)或地址。

這里需要注意一下,立即數(shù) 0X30008000 的前面有一個等于號“=”,這表示前面的LDR指令是一個偽指令。除此之外,在ARM中,LDR還有另外一個意思,用來將內(nèi)存中的數(shù)據(jù)加載到寄存器。我們知道,ARM是RISC架構(gòu),使用LDR/STR架構(gòu),不能直接修改內(nèi)存中的數(shù)據(jù)。如果我們要修改內(nèi)存中的一個變量,要首先使用LDR指令將內(nèi)存中的變量加載到寄存器中,接著對寄存器進(jìn)行操作,最后再使用STR指令將寄存器中的變量回寫到內(nèi)存中。所以,LDR可以看作是一個偽指令,也可以看做是普通的一個LDR指令,判定他們的區(qū)別就是看前面的等于號。

普通的LDR指令主要使用寄存器間接尋址,常用的使用方式如下:

LDR R0,[R1]
LDR R0,0x30008000

這里注意后面一句,是將地址0x30008000地址上的內(nèi)容傳動到寄存器R0,而不是直接將這個地址傳送到R0,這里一定要注意其跟LDR偽指令的區(qū)別,這一點沒有注意到,你在分析程序時就可能誤入歧途了。

在《C語言嵌入式Linux高級編程》第二期中,我們已經(jīng)探討了CPU、指令集、偽指令的基本概念,這里就不贅述了。簡單來說,偽指令并不是真正的ARM指令,并不屬于ARM指令集中的標(biāo)準(zhǔn)指令。它只是編譯器為了方便我們程序員開發(fā)程序,定義的一些助記符。在編譯時,這些偽指令還是會使用指令集中的標(biāo)準(zhǔn)指令來實現(xiàn)。

比如上面的LDR偽指令,程序在編譯時,看到這個偽指令,會使用ARM指令集中標(biāo)準(zhǔn)的指令實現(xiàn)。如果LDR偽指令中的立即數(shù)小于8位,它就會轉(zhuǎn)換為MOV指令來實現(xiàn):

LDR R0,=200
MOV R0,#200

如果LDR偽指令中,立即數(shù)大于8bit表示的數(shù)據(jù)范圍,比如說是一個32位的立即數(shù)或地址,那就不能使用MOV指令來實現(xiàn)了,可以采用文字池的形式,先將這個地址常量單獨存放在存儲單元中,然后使用相對尋址,曲線救國,完成這個32位地址或立即數(shù)與寄存器之間的傳輸,這些細(xì)節(jié)在教程視頻中都有講到,就不再贅述了。

5小結(jié)

通過上面的學(xué)習(xí),我們基本上理清了ARM系統(tǒng)中常見的幾種跳轉(zhuǎn)指令,以及它們的區(qū)別。只有徹底理解他們的底層機制及實現(xiàn)細(xì)節(jié),才有可能在使用反匯編分析程序時,達(dá)到事半功倍的效果,從而大大提高我們的工作效率。否則,這些基本的細(xì)節(jié)和概念搞不清,將會永遠(yuǎn)成為你學(xué)習(xí)和工作上的障礙。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 計數(shù)器
    +關(guān)注

    關(guān)注

    32

    文章

    2248

    瀏覽量

    94178
  • 程序結(jié)構(gòu)
    +關(guān)注

    關(guān)注

    1

    文章

    7

    瀏覽量

    6912
  • 程序調(diào)用
    +關(guān)注

    關(guān)注

    0

    文章

    3

    瀏覽量

    814
收藏 人收藏

    評論

    相關(guān)推薦

    c語言深度剖析

    c語言深度剖析
    發(fā)表于 04-02 09:12

    C語言深度剖析

    C語言深度剖析
    發(fā)表于 08-25 09:08

    C語言深度剖析

    C語言深度剖析[完整版].pdfC語言深度剖析[完整版].pdf (919.58 KB )
    發(fā)表于 03-19 05:11

    arm匯編跳轉(zhuǎn)指令總結(jié)

    目前所知道的跳轉(zhuǎn)指令有 b,bl,bep,bne.他們共同點是都是以b開頭,首先從字面上分析:b:是Branch,表示分支。bl:是Branch Link表示帶連接的分支。bep:Branch
    發(fā)表于 04-26 02:39

    請問一下ARM跳轉(zhuǎn)指令的范圍是多少

    , ?]構(gòu)造跳轉(zhuǎn)指令。)B,BL指令保存的是偏移地址,這個地址的計算方法是:假設(shè)跳轉(zhuǎn)指令處的地址是A,
    發(fā)表于 04-14 09:30

    請問一下ARM匯編中的B跳轉(zhuǎn)指令和LDR跳轉(zhuǎn)的區(qū)別在哪

    請問一下ARM匯編中的B跳轉(zhuǎn)指令和LDR跳轉(zhuǎn)的區(qū)別有哪些不同之處呢?
    發(fā)表于 07-21 15:57

    ARM匯編語言跳轉(zhuǎn)指令的特殊用法還有嗎

    關(guān)于 ARM匯編語言跳轉(zhuǎn)指令的特殊用法。有如下兩條跳轉(zhuǎn)指令: beq lablefbeq lableb其中 lable 為某段程序的標(biāo)號,b
    發(fā)表于 10-31 15:30

    arm匯編語言跳轉(zhuǎn)指令有何特殊用法呢?

    關(guān)于 ARM 匯編語言跳轉(zhuǎn)指令的特殊用法。有如下兩條跳轉(zhuǎn)指令: beq lablefbeq lableb其中 lable 為某段程序的標(biāo)號,
    發(fā)表于 02-24 15:28

    arm指令集(1)

    arm指令集(1)  ARM跳轉(zhuǎn)指令可以從當(dāng)前指令向前或向后的32MB地址空間
    發(fā)表于 03-02 15:46 ?79次下載

    ARM指令集(2)

    ARM指令集(2)  1.跳轉(zhuǎn)指令   在ARM中有兩種方式可以實現(xiàn)程序的跳轉(zhuǎn):一種是刀
    發(fā)表于 03-02 15:49 ?68次下載

    Thumb指令集之Thumb跳轉(zhuǎn)指令

    Thumb指令集中的跳轉(zhuǎn)指令分以下6種類型。 ① 無條件跳轉(zhuǎn),其跳轉(zhuǎn)空間為2KB。 ② 條件跳轉(zhuǎn)
    發(fā)表于 10-19 10:04 ?1次下載
    Thumb<b class='flag-5'>指令</b>集之Thumb<b class='flag-5'>跳轉(zhuǎn)</b><b class='flag-5'>指令</b>

    跳轉(zhuǎn)指令B及帶連接的跳轉(zhuǎn)指令BLX上 

    跳轉(zhuǎn)(B)和跳轉(zhuǎn)連接(BL)指令是改變指令執(zhí)行順序的標(biāo)準(zhǔn)方式。ARM一般按照字地址順序執(zhí)行指令,
    發(fā)表于 10-19 10:26 ?2次下載
    <b class='flag-5'>跳轉(zhuǎn)</b><b class='flag-5'>指令</b>B及帶連接的<b class='flag-5'>跳轉(zhuǎn)</b><b class='flag-5'>指令</b>BLX上 

    ARM嵌入式系統(tǒng)的中斷服務(wù)例程跳轉(zhuǎn)

    在32位 ARM 系統(tǒng)中,一般都是在中斷向量表中放置一條分支指令或PC寄存器加載指令,實現(xiàn) 程序跳轉(zhuǎn) 到 中斷服務(wù) 例程的功能。例如: IRQEntry B HandleIRQ ;
    發(fā)表于 04-10 10:11 ?2793次閱讀

    PLC跳轉(zhuǎn)/標(biāo)號指令的工作原理及應(yīng)用舉例

    跳轉(zhuǎn)/標(biāo)號執(zhí)行是用來跳過部分程序使其不執(zhí)行必須用在同一程序塊內(nèi)部實現(xiàn)跳轉(zhuǎn)。跳轉(zhuǎn)/標(biāo)號指令有兩條,分別為跳轉(zhuǎn)
    的頭像 發(fā)表于 10-08 09:59 ?5361次閱讀

    西門子博途SCL的GOTO跳轉(zhuǎn)指令

    跳轉(zhuǎn)標(biāo)簽和“跳轉(zhuǎn)指令必須在同一個塊中。在一個塊中,跳轉(zhuǎn)標(biāo)簽的名稱只能指定一次。每個跳轉(zhuǎn)標(biāo)簽可以是多個
    發(fā)表于 07-03 14:53 ?7629次閱讀