天天看點

說說ARM彙編的LDR僞指令

    我們知道ARM CPU中有一條被廣泛使用的指令LDR,它主要是用來從存儲器(确切地說是位址空間)中裝載資料到通用寄存器。但不論是ARMASM還是GNU ARM AS,都提供了一條與之同名的僞指令LDR,而在實際中使用該僞指令的情況也較多,那他們有什麼不同呢?下面我談談我的了解。

    由于我使用GNU工具鍊,是以以下的内容都以GNU AS的ARM文法為準。

    LDR僞指令的文法形式如下:

       LDR <reg>, = <constant-expression>

    這個常量表達式<constant-expression>中可以包含Label(在ARM彙編中Label會在連接配接時解釋為一個常數),且其中的常數前不加#符号。

    範例demo.s:

說說ARM彙編的LDR僞指令

.equ    STACK_BASE, 0x0c002000

說說ARM彙編的LDR僞指令

.equ    STACK_SIZE, 0x00001000

說說ARM彙編的LDR僞指令
說說ARM彙編的LDR僞指令

.text

說說ARM彙編的LDR僞指令

    ldr    sp, = STACK_BASE

說說ARM彙編的LDR僞指令

    ldr    sl, = STACK_BASE - STACK_SIZE

說說ARM彙編的LDR僞指令

    ldr    pc, = entry

    這是一個合法的彙編檔案,它把堆棧基址設為0x0c002000,棧限設為0x0c001000,然後跳到entry所辨別的C程式中執行。

    下面我們假設符号“entry”的位址為0x0c000000。

    我們如果把上面代碼寫成:

說說ARM彙編的LDR僞指令

.text

說說ARM彙編的LDR僞指令

    mov    sp, #0x0c002000

說說ARM彙編的LDR僞指令

    mov    sl, #0x0c001000

說說ARM彙編的LDR僞指令

    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:

說說ARM彙編的LDR僞指令

.text

說說ARM彙編的LDR僞指令

    mov    sp, #0x0c000000

說說ARM彙編的LDR僞指令

    add    sp, sp, #0x00002000     或者:

說說ARM彙編的LDR僞指令

.text

說說ARM彙編的LDR僞指令

    mov    sp, #0x0c000000

說說ARM彙編的LDR僞指令

    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

   結果:

說說ARM彙編的LDR僞指令

demo.o:     file format elf32-littlearm

說說ARM彙編的LDR僞指令
說說ARM彙編的LDR僞指令

Disassembly of section .text:

說說ARM彙編的LDR僞指令
說說ARM彙編的LDR僞指令

00000000 <.text>:

說說ARM彙編的LDR僞指令

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

說說ARM彙編的LDR僞指令

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

說說ARM彙編的LDR僞指令

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

說說ARM彙編的LDR僞指令

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

說說ARM彙編的LDR僞指令

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

說說ARM彙編的LDR僞指令

  14:   00000000        andeq   r0, r0, r0

說說ARM彙編的LDR僞指令

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

       ...

    原因不詳。

繼續閱讀