說說ARM彙編的LDR偽指令

2021-05-26 20:33:57 字數 3182 閱讀 4324

說說arm彙編的ldr偽指令

我們知道arm cpu中有一條被廣泛使用的指令ldr,它主要是用來從儲存器(確切地說是位址空間)中裝載資料到通用暫存器。但不論是armasm還是gnu arm as,都提供了一條與之同名的偽指令ldr,而在實際中使用該偽指令的情況也較多,那他們有什麼不同呢?下面我談談我的理解。

由於我使用gnu工具鏈,所以以下的內容都以gnu as的arm語法為準。

ldr偽指令的語法形式如下:

ldr , =

這個常量表示式中可以包含label(在arm彙編中label會在連線時解釋為乙個常數),且其中的常數前不加#符號。

範例demo.s: .equ    stack_base, 0x0c002000

.equ    stack_size, 0x00001000

.text

ldr    sp, = stack_base

ldr    sl, = stack_base - stack_size

ldr    pc, = entry

這是乙個合法的彙編檔案,它把堆疊基址設為0x0c002000,棧限設為0x0c001000,然後跳到entry所標識的c程式中執行。

下面我們假設符號「entry」的位址為0x0c000000。

我們如果把上面**寫成: .text

mov    sp, #0x0c002000

mov    sl, #0x0c001000

mov    pc, #0x0c000000

彙編器會報錯:

demo.s: assembler messages:

demo.s:2: error: invalid constant -- `mov sp,#0x0c002000'

demo.s:3: error: invalid constant -- `mov sl,#0x0c001000'

說起這個錯誤的原因可就話長了,簡而言之是因為risc有乙個重要的概念就是所有指令等長。在arm指令集中,所有指令長度為4位元組(thumb指令是2位元組)。那問題就來了,4位元組是不可能同時存的下指令控制碼和32位立即數的,那麼我要把乙個32位立即數(比如乙個32位位址值)傳送給暫存器該怎麼辦?

risc cpu提供乙個通用的方法就是把位址值作為資料而不是**,從儲存器中相應的位置讀入到暫存器中,待會我們會看到這樣的例子。

此外arm還提供另一種方案。由於傳送類指令的指令控制碼部分(cond, opcode, s, rd, rn域)占去了20個位元組,那能提供給立即數的就只剩12個位了。

arm並未使用這12個位來直接存乙個12位立即數,而是使用了類似有效數字一樣的概念,只存8個位元組的有效位和乙個4位的位偏移量(偏移單位為2)。這個東西在arm被叫做術語immed_8,有興趣的人可以找資料了解一下,到處都有介紹。

可以看出arm的這個方法能直接使用的立即數是相當有限的,像0xfffffff0這樣的數顯然無法支援。別著急,arm的傳送類指令中還有乙個mvn指令可以解決該問題。顯然0x0000000f是乙個有效立即數,mvn會先將其取反再傳送,這樣有效立即數的範圍又擴充了一倍。

可就算如此仍有大量的32位立即數是無效的,比如上面那個例子中的0x0c002000和0x0c001000。面對這種問題一是使用risc的通用方法,二是分次載入。

比如可以這樣載入0x0c002000: .text

mov    sp, #0x0c000000

add    sp, sp, #0x00002000    或者: .text

mov    sp, #0x0c000000

orr    sp, sp, #0x00002000

感覺很狡猾是吧,呵呵。但是要注意它和方法一的一大區別:需要多條指令。那麼在一些對指令數目有限制的場合就無法使用它,比如異常向量表處要做長跳轉(超過±32mb)的話就只能用方法一;還有就是在同步事務中該操作不是原子的,因此可能需要加鎖。

扯了這麼多再回到ldr偽指令上來。顯然上面的內容是複雜繁瑣的,如果然程式設計師在寫程式的時候還要考慮某個數是不是immed_8一定超級麻煩,因此為了減輕程式設計師的負擔才引入了ldr偽指令。

你一定很好奇第一段**demo.s被gnu as變成了什麼,好,讓我們在linux環境下執行下面的命令:

arm-elf-as -o demo.o demo.s

arm-elf-objdump -d demo.o

結果: demo.o:     file format elf32-littlearm

disassembly of section .text:

00000000 <.text>:

0:   e59fd004        ldr     sp, [pc, #4]    ; c <.text+0xc>

4:   e59fa004        ldr     sl, [pc, #4]    ; 10 <.text+0x10>

8:   e59ff004        ldr     pc, [pc, #4]    ; 14 <.text+0x14>

c:   0c002000        stceq   0, cr2, [r0]

10:   0c001000        stceq   0, cr1, [r0]

14:   00000000        andeq   r0, r0, r0

disassembly of section .data:

由於entry還沒連上目標位址,objdump反彙編會認為是0,我們先不管它。另外兩條ldr偽指令變成了實際的ldr指令!但目標很奇怪,都是[pc, #4]。那好我們看看[pc, #4]是什麼。

我們知道pc中存放的是當前指令的下下條指令的位置,也就是. + 8。那麼上面的第一條指令ldr sp, [pc, #4]中的pc就是0x8,pc + 4就是0xc,而[0xc]的內容正是0x0c002000;同理,第二條ldr指令也是如此。顯然這裡ldr偽指令採用的是risc通用的方法。

另外要說的是,如果ldr的是乙個immed_8或者immed_8的反碼數,則會直接被解釋成mov或mvn指令。如ldr pc, = 0x0c000000會被解釋成mov pc, 0x0c000000。

最後一點補充,我發現arm-elf-gcc通常都用累加法。如c語句中的i = 0x100ffc04;會變成類似於以下的語句:

mov   r0, #0x10000004

add   r0, r0, #0x000ff000

add   r0, r0, #0x00000c00

...原因不詳。

ARM彙編中LDR偽指令和LDR指令

arm是risc結構,資料從記憶體到cpu之間的移動只能通過l s指令來完成,也就是ldr str指令。比如想把資料從記憶體中某處讀取到暫存器中,只能使用ldr比如 ldr r0,0x12345678 就是把0x12345678這個位址中的值存放到r0中。而mov不能實現這個功能,mov只能在暫存器...

ARM彙編中LDR偽指令和LDR指令

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!arm是risc結構,資料從記憶體到cpu之間的移動只能通過l s指令來完成,也就是ldr str指令。比如想把資料從記憶體中某處讀取到暫存器中,只能使用ldr比如 ldr r0,0x12345678就是把0x12345678這個位址中的值存放到r...

ARM彙編偽指令

global,local,set,equ global 使得符號對聯結器可見,變為對整個工程可用的全域性變數,通俗講就是定義全域性變數 eg global symbol local set 給乙個全域性變數或區域性變數賦值,和.equ的功能一樣 eg set symbol expr set star...