ARM的跳轉及指令集切換


B BL BX BLX Thumb與ARM的切換

條件分支就是典型的跳轉指令,這在編程中必不可少,arm有2種方式支持指令跳轉

  • 使用B系列指令(B有很多帶后綴的其他指令)
  • 直接修改pc的值

跳轉指令 B

  • B,就是最直接最基礎的跳轉,沒有副作用
  • BL,將BL的下一條指令保存在lr寄存器中,然后跳轉,這種跳轉方式通常需要在執行跳轉任務后需要回到出發處的

除了這2個最基本的通用跳轉外,還有與狀態寄存器想配合的條件跳轉;因為arm架構指令集本身就是帶執行條件的架構, 與狀態寄存器搭配使用的跳轉有比如 beq,bge,blcc等。需要注意的是跳轉目的地並非直接編碼入指令中的,而是目的地的偏移地址,也就是the_jump_target_addr = current_addr+offset_addr中的那個offset_addr;這樣的意義可以大幅的減少編碼位置的占用,因此thumbarm指令集占用的偏移長度位數是不一樣的,arm能訪問到的內容空間是上下32M,而thumb訪問的空間是上下16M

image.png

arm與thumb的跳轉空間

因為指令編碼的原因,B系指令跳轉空間受限,而直接將一個地址值立即數寫入pc跳轉是可行的,這樣就不受限地址空間。不過這里比較棘手的問題在於arm流水線問題:

image.png
單周期最佳流水線

大部分時候跳轉后都需要調回來,這時候需要知道當前指令的地址值,那么當前指令的地址值就是此時pc寄存器中的值嗎?答案是否定的,因為armfetchdecode excute三級流水的結構,導致本條指令執行時,pc的值已經是下下條指令的地址值了,這對於arm指令集而言是current_code_addr = pc - 8,而對於thumb指令集而言current_code_addr = pc - 4;可能有的cpu設計了不止3級流水線,或者有5級流水線(取指、譯碼、執行、訪存、回寫5級流水線),但是前三條結構是一致的,因此無論如何pc寄存器與當前指令的地址值關系是確定的。

因此,這里的麻煩之處在於需要計算准確的pc值,而這又取決於不同的指令集,當然,這是出現在讀pc寄存器值時候的問題。

pc 指針並不會出現讀 pc 指針的問題,在 thumb 指令集中,add、 mov、pop等指令可以寫 pc 執行跳轉,寫入 PC 的值將會被強制對齊,對齊的字節數根據對應的指令集而定,thumb下是半字(2字節),arm下是字(4字節)。除了通用指令寫 pc,還有一些專門用於跳轉的指令默認操作的就是 pc 指針,比如 B、BL、BX、BLX 等,這些是一些復合指令,也就是說這些指令包含的操作可能不僅僅是對 pc 的操作,可能還隱含其它操作(比如修改lr寄存器和切換指令集)

指令集切換

armv7支持thumbarm兩種指令集,分別用16bits32bits指令長度,這兩種長度各有優劣,比如thumb指令集的指令密度是要大於arm指令集的 ,而arm指令集的性能則更為強大(因為更長的指令編碼可以將多個步驟合一以及更多資源的訪問能力),因此將兩者結合起來使用做到指令切換是有其現實積極意義的(不過這給程序員造成了一些麻煩)

  • BX

    BX Rmrm4bits,因此支持r0~r15,在arm指令集下bx的指令編碼格式是:

    image.png

bx指令的arm編碼格式

顯然,在arm指令集下是支持cond條件執行的,因此有bxz、bxne等指令;使用bx切換指令集的依據是根據跳轉目標地址的最后1bits決定,arm指令集是定長4字節,因此其指令地址值不可能為奇數,這就決定了其最后1bit不可能為1,因此利用這個特性,bx指令當發現跳轉目的地址最后一位為0時,則切換或者保持在arm指令集,否則,將切換或保持在thumb指令集。

  • BLX

    blx是帶返回的跳轉,它的指令集切換分為2種情況:

    • blx register
    • blx imm

    當為blx register時,情況與bx相同。

    當為blx imm時,則為無條件指令切換,也就是若當前為arm指令集,則切換到thumb指令集,若當前為thumb指令集,則切換到arm指令集。

當直接采用修改pc寄存器的值來做跳轉時,也涉及到指令集切換的問題,這一情況與bx一致,也是根據跳轉目標地址的最后1bit來做決定。

main:
    adr r0,back       #獲取 back 標號的地址
    push {r0}          #將 back 地址保存在棧上
    adr r0,foo        #獲取 foo 標號的地址,當前處於 arm 指令集
    add r0,r0,#1     #將 foo 地址最后一位加1,表示切換到 thumb 指令集
    mov pc,r0         #跳轉到 foo 地址
back:
    blx _exit          #退出
.thumb                 #指定代碼編譯為 thumb 指令集
foo:
    pop {pc}           #將棧上保存的 back 標號地址賦值給 pc,即實現跳轉,同時指令集切換為 arm

上面有幾處需要注意:

  • adr偽指令的使用,arm的跳轉都是基於偏移而非絕對地址,因此使用pc進行跳轉時需要將偏移值賦值給pc寄存器,而這又是一件比較麻煩的事,我們需要跳轉到back地址,不能直接將標簽back處的地址值賦值給pc寄存器,應該是當前跳轉執行指令地址值 - back得到一個偏移值,然后賦值給pc,那么,當前跳轉執行指令地址值是多少呢,顯然就是當前這條指令pc值,但是,因為流水線的存在,實際上此時的pc已經位於下下條指令處,因此此刻的真實pc值應該是pc-4或者pc-8,具體是減多少,這取決於此刻是thumb指令集還是arm指令集,這就是棘手的地方!也就是我們必須首先判斷此刻是什么指令集,然后再用pc減去對應的大小,最終算得偏移賦值給pc寄存器,一個簡單的跳轉,實在太難了...好在!adr的出現幫我們解決了這個問題,我們完全不用操心此刻是什么指令集模式,也不用手動去做標簽的減法,adr偽指令編譯器會幫我們轉換為合適的真實硬件指令,得到最終的跳轉偏移值,簡直大救星!!!
  • add r0,r0,#1,將地址值加一,是的最后1bit為1,這樣目標地址就被切換為thumb指令集(因為我們的跳轉目標foothumb指令集模式)
  • pop {pc},將棧中保持的立即數(之前push的返回地址)pop賦值給pc寄存器,因為此時處於thumb指令集模式下,因此直接切換成arm指令集

需要注意的是,切換指令集是匯編指令運行時的事,而某指令是用何種指令集是在匯編編期就決定(通過偽指令.arm.thumb決定),因此使用匯編指令跳轉到某目標地址,當該地址處的指令集為arm時,而你跳轉時卻切換為了thumb,那么會運行失敗,反之亦然。(因此在編寫跳轉指令時,需要明確跳轉的目標地址處是什么指令集,該切換指令集時切換,不該切換時別瞎切~)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM