說(shuō)說(shuō)ARM匯編的LDR偽指令
由于我使用GNU工具鏈,所以以下的內容都以GNU AS的ARM語(yǔ)法為準。
LDR偽指令的語(yǔ)法形式如下:
LDR
這個(gè)常量表達式
范例demo.s:本文引用地址:http://dyxdggzs.com/article/201611/319128.htm







這是一個(gè)合法的匯編文件,它把堆?;吩O為0x0c002000,棧限設為0x0c001000,然后跳到entry所標識的C程序中執行。
下面我們假設符號“entry”的地址為0x0c000000。
我們如果把上面代碼寫(xiě)成:




匯編器會(huì )報錯:
demo.s: Assembler messages:
demo.s:2: Error: invalid constant -- `mov sp,#0x0c002000
demo.s:3: Error: invalid constant -- `mov sl,#0x0c001000
說(shuō)起這個(gè)錯誤的原因可就話(huà)長(cháng)了,簡(jiǎn)而言之是因為RISC有一個(gè)重要的概念就是所有指令等長(cháng)。在A(yíng)RM指令集中,所有指令長(cháng)度為4字節(Thumb指令是2字節)。那問(wèn)題就來(lái)了,4字節是不可能同時(shí)存的下指令控制碼和32位立即數的,那么我要把一個(gè)32位立即數(比如一個(gè)32位地址值)傳送給寄存器該怎么辦?
RISC CPU提供一個(gè)通用的方法就是把地址值作為數據而不是代碼,從存儲器中相應的位置讀入到寄存器中,待會(huì )我們會(huì )看到這樣的例子。
此外ARM還提供另一種方案。由于傳送類(lèi)指令的指令控制碼部分(cond, opcode, S, Rd, Rn域)占去了20個(gè)字節,那能提供給立即數的就只剩12個(gè)位了。
ARM并未使用這12個(gè)位來(lái)直接存一個(gè)12位立即數,而是使用了類(lèi)似有效數字一樣的概念,只存8個(gè)字節的有效位和一個(gè)4位的位偏移量(偏移單位為2)。這個(gè)東西在A(yíng)RM被叫做術(shù)語(yǔ)immed_8,有興趣的人可以找資料了解一下,到處都有介紹。
可以看出ARM的這個(gè)方法能直接使用的立即數是相當有限的,像0xfffffff0這樣的數顯然無(wú)法支持。別著(zhù)急,ARM的傳送類(lèi)指令中還有一個(gè)MVN指令可以解決該問(wèn)題。顯然0x0000000f是一個(gè)有效立即數,MVN會(huì )先將其取反再傳送,這樣有效立即數的范圍又擴充了一倍。
可就算如此仍有大量的32位立即數是無(wú)效的,比如上面那個(gè)例子中的0x0c002000和0x0c001000。面對這種問(wèn)題一是使用RISC的通用方法,二是分次載入。
比如可以這樣載入0x0c002000:






感覺(jué)很狡猾是吧,呵呵。但是要注意它和方法一的一大區別:需要多條指令。那么在一些對指令數目有限制的場(chǎng)合就無(wú)法使用它,比如異常向量表處要做長(cháng)跳轉(超過(guò)±32MB)的話(huà)就只能用方法一;還有就是在同步事務(wù)中該操作不是原子的,因此可能需要加鎖。
扯了這么多再回到LDR偽指令上來(lái)。顯然上面的內容是復雜繁瑣的,如果然程序員在寫(xiě)程序的時(shí)候還要考慮某個(gè)數是不是immed_8一定超級麻煩,因此為了減輕程序員的負擔才引入了LDR偽指令。
你一定很好奇第一段代碼demo.s被GNU AS變成了什么,好,讓我們在Linux環(huán)境下執行下面的命令:
arm-elf-as -o demo.o demo.s
arm-elf-objdump -D demo.o
結果:












由于entry還沒(méi)連上目標地址,objdump反匯編會(huì )認為是0,我們先不管它。另外兩條LDR偽指令變成了實(shí)際的LDR指令!但目標很奇怪,都是[pc, #4]。那好我們看看[pc, #4]是什么。
我們知道pc中存放的是當前指令的下下條指令的位置,也就是. + 8。那么上面的第一條指令ldr sp, [pc, #4]中的pc就是0x8,pc + 4就是0xc,而[0xc]的內容正是0x0c002000;同理,第二條ldr指令也是如此。顯然這里L(fēng)DR偽指令采用的是RISC通用的方法。
另外要說(shuō)的是,如果LDR的是一個(gè)immed_8或者immed_8的反碼數,則會(huì )直接被解釋成mov或mvn指令。如ldr pc, = 0x0c000000會(huì )被解釋成mov pc, 0x0c000000。
最后一點(diǎn)補充,我發(fā)現arm-elf-gcc通常都用累加法。如C語(yǔ)句中的i = 0x100ffc04;會(huì )變成類(lèi)似于以下的語(yǔ)句:
mov r0, #0x10000004
add r0, r0, #0x000ff000
add r0, r0, #0x00000c00
...
原因不詳。
添加的內容:
1 指令LDR
應用舉例:
u LDR R0, [R1, #4] ;將內存單元R1+4中的字讀取到R0寄存器
其中,R1為基址,#4為偏移地址,R0為目標地址。注意,此時(shí)不更新R1。
u LDR R0, [R1, #-4] ;將內存單元R1-4中的字讀取到R0寄存器
u LDR R0, [R1, #4]! ;將內存單元R1+4中的字讀取到R0寄存器。同時(shí)更新R1,R1=R1+4。
u LDR R0, [R1], #4 ;將地址為R1的內存單元數據讀取到R0寄存器,然后R1=R1+4。
2 偽指令LDR
ARM中的偽指令不是真正的ARM指令或者Thumb指令,這些偽指令在匯編編譯器對源程序進(jìn)行匯編處理時(shí),被替換為相應的ARM或者Thumb指令(序列)。
LDR偽指令將一個(gè)32位的常數或者一個(gè)地址值讀取到寄存器中。
語(yǔ)法格式:
LDR{cond} register, =[expr | label-expr]
其中,register為目標寄存器
expr為32位的常量。編譯器將根據expr的取值情況,如下處理LDR偽指令:
u 當expr所表示的地址值沒(méi)有超過(guò)MOV或MVN指令中的地址取值范圍時(shí),編譯器用合適的MOV或者M(jìn)VN指令代替LDR偽指令。
應用舉例:
將0xFF0讀取到R1中
LDR R1, =0xFF0
匯編后得到:
MOV R1, 0xFF0
u 當expr表示的地址值超過(guò)了MOV或者M(jìn)VN指令中的地址的取值范圍(第二操作數的取值范圍)時(shí),編譯器將該常數放在數據緩沖區中,同時(shí)用一條基于PC的LDR指令讀取該常數。
LDR R1,=0xFFF
匯編后得到:
LDR R1, [PC, OFFSET_TO_LPOOL]
…
LPOOL DCD 0xFFF
關(guān)于label-expr的介紹我不是很理解。不理解其中關(guān)于“連接重定位偽操作”。(P144)
聲明:本文為我在學(xué)習杜春雷編著(zhù)的《ARM體系結構與編程》時(shí)做的總結筆記,文中摘錄了書(shū)中的很多內容。
補充2:
原文地址:http://hi.baidu.com/andylgh/blog/item/17dbdc1f7d102a62f624e4dc
說(shuō)說(shuō)這個(gè).word的作用。 word expression就是在當前位置放一個(gè)word型的值,這個(gè)值就是expression 例如: ldr r1, _rWTCON |
評論