?
10.5??ARM匯編程序設計舉例
在本節(jié)中通過一些例子來說明ARM中偽操作及指令的基本用法。
10.5.1??條件跳轉(zhuǎn)及循環(huán)
1.ALU狀態(tài)標志
所有ARM指令都可以條件執(zhí)行。大部分ARM指令集和Thumb-2指令集的數(shù)據(jù)處理指令都可以選擇是否根據(jù)指令的執(zhí)行結(jié)果設置ALU的狀態(tài)標志位。
注意 |
較早的ARM體系結(jié)構(gòu)中使用的Thumb指令不能選擇是否更新ALU的標志位。當數(shù)據(jù)處理指令執(zhí)行完后,處理器自動根據(jù)指令的執(zhí)行結(jié)果更新狀態(tài)標志。 |
較早的Thumb-2指令只有跳轉(zhuǎn)指令可以條件執(zhí)行。新的體系結(jié)構(gòu)中的Thumb-2指令可以IT(if-then)標識使程序條件執(zhí)行。
更詳細的介紹請參加本書的指令集部分。
2.ARM狀態(tài)下的條件執(zhí)行
在程序狀態(tài)寄存器CPSR中保存著以下4個ALU狀態(tài)標志。
·??N:當指令的執(zhí)行結(jié)果為負時,該位置1。
·??Z:當指令的執(zhí)行結(jié)果為零時,該位置1。
·??C:當指令的執(zhí)行結(jié)果有進位時,該位置1。
·??V:當指令的執(zhí)行結(jié)果溢出時,該位置1。
當加法操作的結(jié)果大于等于232或加法操作的結(jié)果為負時,進位標志C置位。
當加法、減法、比較操作結(jié)果大于等于231或小于-232時,溢出標志V置位。
在ARM指令后增加條件域可以使指令條件執(zhí)行,各條件碼的含義和助記符如表10.11所示。可條件執(zhí)行的指令可以在其助記符的擴展域加上條件碼助記符,從而在特定條件下執(zhí)行。
表10.11 指令的條件碼
條??件??碼 |
助記符后綴 |
標????志 |
含????義 |
0000 |
EQ |
Z置位 |
相等 |
0001 |
NE |
Z清零 |
不相等 |
0010 |
CS |
C置位 |
無符號數(shù)大于或等于 |
0011 |
CC |
C清零 |
無符號數(shù)小于 |
0100 |
MI |
N置位 |
負數(shù) |
0101 |
PL |
N清零 |
正數(shù)或零 |
0110 |
VS |
V置位 |
溢出 |
0111 |
VC |
V清零 |
未溢出 |
1000 |
HI |
C置位Z清零 |
無符號數(shù)大于 |
1001 |
LS |
C清零Z置位 |
無符號數(shù)小于或等于 |
1010 |
GE |
N等于V |
帶符號數(shù)大于或等于 |
1011 |
LT |
N不等于V |
帶符號數(shù)小于 |
1100 |
GT |
Z清零且(N等于V) |
帶符號數(shù)大于 |
1101 |
LE |
Z置位或(N不等于V) |
帶符號數(shù)小于或等于 |
1110 |
AL |
忽略 |
無條件執(zhí)行 |
默認情況下,ARM指令并不會更新ARM寄存器cpsr中的N、Z、C、V標志。對大多數(shù)指令,若要更新這些標志需要對指令助記符加后綴S。但CMP指令不需要S后綴就更新這些標志位。
下面的一段程序,說明了指令的S后綴和條件執(zhí)行的過程。
???ADD?????r0,?r1,?r2 ;r0?=?r1?+?r2,不更新標志位
???ADDS????r0,?r1,?r2 ;r0?=?r1?+?r2,并且更新標志位
???ADDSCS??r0,?r1,?r2 ;如果進位標志位C=1,r0?=?r1?+?r2,并且更新標志位
???CMP?????r0,?r1 ;根據(jù)?r0-r1的結(jié)果更新標志位
從上面的例子可以看出,無論更新標志位標志“S”是否設置,CMP指令自動更新標志位。
3.條件執(zhí)行的例子
通過組合使用條件執(zhí)行和條件標志設置,可是簡單地實現(xiàn)分支語句,不需要任何分支指令。這樣可以改善性能,因為分支指令會占用較多的周期數(shù);同時這樣做也可以減小代碼尺寸,提高代碼密度。
下面是一段C語言程序,該程序?qū)崿F(xiàn)了著名的Euclid最大公約數(shù)算法。
int?gcd(int?a,?int?b)
{
??????while?(a?!=?b)
????????{
???????????if?(a?>?b)
???????????????????a?=?a?-?b;
???????????else
???????????????????b?=?b?-?a;
????????}
??????return?a;
}
用ARM匯編語言重寫來重寫這個例子,如下所示。
【程序1】
gcd?????CMP??????r0,?r1
?????????BEQ??????end
?????????BLT??????less
?????????SUB??????r0,?r0,?r1
?????????B????????gcd
less
?????????SUB??????r1,?r1,?r0
?????????B????????gcd
End
充分地利用條件執(zhí)行修改上面的例子,得到【程序2】。
【程序2】
gcd
????????CMP??????r0,?r1
????????SUBGT????r0,?r0,?r1
????????SUBLT????r1,?r1,?r0
????????BNE??????gcd
【程序1】僅使用了分支指令,【程序2】充分利用了ARM指令條件執(zhí)行的特點,僅使用了4條指令就完成了全部算法。這對提供程序的代碼密度和執(zhí)行速度十分有幫助。
事實上,分支指令十分影響處理器的速度。每次執(zhí)行分支指令,處理器都會排空流水線,重新裝載指令。
注意 |
新的ARM處理器如ARM10和StrongARM都有分支預測硬件。只有當分支預測硬件失敗時,處理器才排空流水線,重新裝載指令。 |
?
表10.12和10.13分別總結(jié)了【程序1】和【程序2】在ARM7上的執(zhí)行過程(假設r0=1、r1=2)。
表10.12 程序1執(zhí)行過程
r0:a |
R1:b |
指????令 |
執(zhí)行周期(ARM7) |
1 |
2 |
CMP?r0,?r1 |
1 |
1 |
2 |
BEQ?end |
1?(未執(zhí)行) |
1 |
2 |
BLT?less |
3 |
1 |
2 |
SUB?r1,?r1,?r0 |
1 |
1 |
2 |
B?gcd |
3 |
1 |
1 |
CMP?r0,?r1 |
1 |
1 |
1 |
BEQ?end |
3 |
Total?=?13 |
表10.13 程序2執(zhí)行過程
r0:a |
R1:b |
指????令 |
執(zhí)行周期(ARM7) |
1 |
2 |
CMP?r0,?r1 |
1 |
1 |
2 |
SUBGT?r0,r0,r1 |
1?(未執(zhí)行) |
1 |
1 |
SUBLT?r1,r1,r0 |
1 |
1 |
1 |
BNE?gcd |
3 |
1 |
1 |
CMP?r0,r1 |
1 |
1 |
1 |
SUBGT?r0,r0,r1 |
1?(未執(zhí)行) |
1 |
1 |
SUBLT?r1,r1,r0 |
1?(未執(zhí)行) |
1 |
1 |
BNE?gcd |
1?(未執(zhí)行) |
Total?=?10 |
從上面的例子可以看出,利用條件執(zhí)行執(zhí)行可以實現(xiàn)大部分條件語句,比使用條件分支指令效率高很多。
10.5.2??傳送指令程序設計
1.加載立即數(shù)
通常,使用MOV和MVN指令向寄存器加載常量,但由于單條MOV或MVN指令只能包含8位立即數(shù),向寄存器加載立即數(shù)需要一些特殊的操作。
注意 |
在ARMv6T2體系結(jié)構(gòu)及以上版本中,可以使用MOV32偽操作將一個32位立即數(shù)加載到寄存器。 |
下面分別介紹加載立即數(shù)的不同方法。
(1)使用MOV和MVN指令
在ARM或Thumb-2狀態(tài)下,可以使用MOV和MVN指令將符合一定規(guī)則的常數(shù)加載到寄存器。在16位的Thumb指令下,可以將0~255的任意常數(shù)加載到寄存器。表10.14列出了在ARM狀態(tài)下可直接加載到寄存器的立即數(shù)。
表10.14 ARM狀態(tài)合法立即數(shù)
二??進??制 |
十??進??制 |
步驟 |
十六進制 |
MVN指令值 |
注釋 |
000000000000000000000000abcdefgh |
0~255 |
1 |
0~0xFF |
-1~-256 |
|
0000000000000000000000abcdefgh00 |
0~1020 |
4 |
0~0x3FC |
-4~-1024 |
|
00000000000000000000abcdefgh0000 |
0~4080 |
16 |
0~0xFF0 |
-16~-4096 |
|
000000000000000000abcdefgh000000 |
0~16320 |
64 |
0~0x3FC0 |
-64~-16384 |
|
... |
... |
... |
... |
||
abcdefgh000000000000000000000000 |
0~255×224 |
224 |
0~0xFF000000 |
1~256ד-224” |
|
cdefgh000000000000000000000000ab |
(bit?pattern) |
— |
— |
(bit?pattern) |
② |
efgh000000000000000000000000abcd |
(bit?pattern) |
— |
— |
(bit?pattern) |
② |
gh000000000000000000000000abcdef |
(bit?pattern) |
— |
— |
(bit?pattern) |
② |
00000000000000000000abcdefghijkl |
0~4095 |
1 |
0~0xFFF |
— |
③ |
注釋 |
①?在ARM數(shù)據(jù)操作指令中,除MVN外,其他指令不能直接操作MVN的值,即MVN列所列出的立即數(shù)在其他數(shù)據(jù)操作指令中可能為非法操作數(shù)。 ②?表中所列數(shù)據(jù)為ARM狀態(tài)合法數(shù)據(jù),對Thumb-2狀態(tài)不一定適用。 ③?這些值只能在ARMv6T2體系結(jié)構(gòu)及其以上版本中適用。 |
ARM狀態(tài)下立即數(shù)的使用要符合以下規(guī)則。
①?每個立即數(shù)由一個8位的常數(shù)循環(huán)右移偶數(shù)位得到。
注意 |
這樣得到的立即數(shù)對任何數(shù)據(jù)處理指令都是合法的。 |
②?MVN指令向寄存器加載一個合法立即數(shù)的“按位反”,即-(n+1)。其中n為MOV指令可以加載的合法立即數(shù)。
③?ARMv6T2體系結(jié)構(gòu)及其以上版本,可以加載12位的立即數(shù),但MVN指令不能加載此12位立即數(shù)的“按位反”。
?
在ARMv6T2體系結(jié)構(gòu)及其以上版本的Thumb狀態(tài)下,可以加載的合法立即數(shù)有以下規(guī)則。
①?32位的MOV指令可以加載的立即數(shù)符合以下規(guī)則。
·??任意8位立即數(shù)(范圍0x0~0xff)。
·??任意8位立即數(shù)向左移任意位。
·??任意8位立即數(shù)重復4次填充32位寄存器。
·??使用任意8位立即數(shù)填充一個32位寄存器的第0和第2字節(jié),其余兩個字節(jié)填充0。
·??使用任意8位立即數(shù)填充一個32位寄存器的第1和第3字節(jié),其余兩個字節(jié)填充0。
注意 |
通過上述方法得到的立即數(shù),在其他數(shù)據(jù)操作指令中同樣合法。 |
②?MVN指令向寄存器加載一個合法立即數(shù)的“按位反”,即-(n+1)。其中n為MOV指令可以的加載的合法立即數(shù)。
③?可以加載任意12位的立即數(shù),但MVN指令不能加載此12位立即數(shù)的“按位反”。
表10.15列出了在Thumb-2狀態(tài)下可直接加載到寄存器的立即數(shù)。
表10.15 Thumb狀態(tài)合法立即數(shù)
二??進??制 |
十進制 |
步驟 |
十?六?進?制 |
MVN指令值 |
注釋 |
000000000000000000000000abcdefgh |
0~255 |
1 |
0~0xFF |
-1?to?-256 |
|
00000000000000000000000abcdefgh0 |
0~510 |
2 |
0~0x1FE |
-2?to?-512 |
|
0000000000000000000000abcdefgh00 |
0~1020 |
4 |
0~0x3FC |
-4?to?-1024 |
|
... |
... |
... |
... |
||
0abcdefgh00000000000000000000000 |
0~0x7F800000 |
||||
abcdefgh000000000000000000000000 |
0~0xFF000000 |
||||
abcdefghabcdefghabcdefghabcdefgh |
(bit?pattern) |
- |
0xXYXYXYXY |
- |
|
00000000abcdefgh00000000abcdefgh |
(bit?pattern) |
- |
0x00XY00XY |
0xFFXYFFXY |
|
abcdefgh00000000abcdefgh00000000 |
(bit?pattern) |
- |
0xXY00XY00 |
0xXYFFXYFF |
|
00000000000000000000abcdefghijkl |
0~4095 |
1 |
0~0xFFF |
- |
① |
(2)使用MOV32偽操作
ARMv6T2體系結(jié)構(gòu)中,ARM和Thumb-2指令集包括下面兩條數(shù)據(jù)傳送指令。
·??MOV指令:可以加載任意8位立即數(shù)到32位寄存器。
·??MOVT指令:可以加載任意16位立即數(shù)(0x0~0xffff)到32寄存器的高16位或低16位,而不改變余下的位。
可以使用組合的MOV和MOVT指令加載任意32位立即數(shù)到寄存器,也可以使用偽操作MOV32實現(xiàn)上述兩條指令的組合功能。匯編器在掃描源代碼時,自動將偽操作MOV32編譯成MOV加MOVT指令的組合。
(3)使用LDR偽操作
LDR?Rd,=const偽操作可以將任意32位立即數(shù)加載到寄存器??梢允褂么藗尾僮鲗⒊鯩OV和MVN指令操作范圍的立即數(shù)加載到寄存器。LDR的效率很高,如果立即數(shù)可以由指令中的<shifter_operand>表示,則匯編器生成相應的MOV或MVN指令;如果立即數(shù)不能由<shifter_operand>直接表示,則匯編器將該立即數(shù)放到一個緩沖池(literal?pool),并生成一條將該緩沖池內(nèi)容加載到目標寄存器的LDR指令。例如,
LDR??????rn,?[pc,?#offset?to?literal?pool]
該指令從地址[pc,?#offset?to?literal?pool]向Rn寄存器加載一個字。因此,必須確保在該LDR指令的訪問范圍內(nèi),存在一個可用的緩沖池。匯編器會在每個段后面添加一個緩沖池。對于ARM指令集,計數(shù)器PC的偏移量必須小于4KB,對于Thumb指令,PC的偏移量必須小于1KB。通常,匯編器會在LDR偽操作后尋找可用的緩沖池,但是,如果LDR指令與默認緩沖池距離太遠,則匯編器將會報錯。此時必須在LDR指令上下4KB(Thumb為1KB)之間用LTORG偽操作顯式地在代碼段中添加緩沖池,而且由于緩沖池在代碼段中,必須確保它不會被處理器作為指令而加以執(zhí)行。通常將其緊跟在無條件跳轉(zhuǎn)指令后面。
下面是使用LDR偽操作加載立即數(shù)的例子。
AREA?????Loadcon,?CODE,?READONLY
ENTRY ;函數(shù)入口
start BL???????func1 ;跳轉(zhuǎn)到func1
BL???????func2 ;跳轉(zhuǎn)到func2
stop MOV??????r0,?#0x18 ;angel_SWIreason_ReportException為SWI調(diào)用準備參數(shù)
LDR??????r1,?=0x20026 ;調(diào)用SWI的ADP_Stopped_ApplicationExit功能
SWI??????0x123456 ;調(diào)用ARM?semihosting?SWI
func1
LDR??????r0,?=42 ;將立即數(shù)42存入
LDR??????r1,?=0x55555555
;將立即數(shù)0x55555555存入r1,該指令被編譯為LDR?R1,[PC,#offset]
LDR??????r2,?=0xFFFFFFFF ;將0xFFFFFFFF存入r2
BX???????lr
LTORG ;緩存池?1?contains
;該緩存池中包含立即數(shù)Ox55555555
func2
LDR??????r3,?=0x55555555
;將立即數(shù)存入r3,該偽操作被編譯為LDR?R3,[PC,#offset]
;?LDR?r4,?=0x66666666 ;將立即數(shù)0x66666666存入r4
BX???????lr
LargeTable
SPACE????4200 ;申請4200字節(jié)的內(nèi)存空間并初始化為零
END ;Literal?Pool?2?is?empty
(4)加載浮點常數(shù)
ARM體系結(jié)構(gòu)中,允許加載單精度和雙精度的浮點數(shù)。詳細信息請參見FLD偽操作。
?
2.加載程序地址
通常在編寫程序時需要加載程序地址。這些地址包括變量地址、字符串地址或程序跳轉(zhuǎn)表的入口地址。這些地址通常是基于程序計數(shù)器PC或某個基址寄存器的偏移量。
下面分別介紹加載程序地址的不同方法。
(1)使用ADR和ADRL偽操作
使用ADR和ADRL偽操作可以直接向寄存器加載程序地址。ADR和ADRL偽操作的操作數(shù)可以是程序相關表達式,匯編器在編譯時,將此表達式轉(zhuǎn)換成相對PC或寄存器的偏移量加載到目標寄存器。
注意 |
ADR和ADRL偽操作所加載的地址是有一定限制的。加載的標號地址必須和當前指令在同一代碼段內(nèi)。如果加載的程序標號和當前指令不在同一代碼段,匯編器將報錯并中止匯編。另外在Thumb狀態(tài)下,ADR偽操作只能產(chǎn)生字對齊的地址加載指令。 |
ADRL偽操作只能在ARM狀態(tài)下使用。
ADR和ADRL偽操作(ADR?rn,label或ADRL?rn,label)所能加載的地址范圍依賴使用的指令集。
下面列出了在不同指令集下,ADR偽操作所能加載的地址范圍。
·??ARM:在字節(jié)或半字節(jié)對齊的內(nèi)存使用模式下,范圍為±255字節(jié)。在字對齊的內(nèi)存使用模式下,范圍為±1020字節(jié)。
·??16位Thumb指令集:0~1020字節(jié)。Label必須是字對齊地址標號,可以和偽操作ALIGN配合使用。
·??32位Thumb-2指令集:±4095字節(jié)(無論標號label是字、半字還是字節(jié)對齊)。
下面列出了在不同指令集下,ADRL偽操作所能加載的地址范圍。
·??ARM:在字節(jié)或半字節(jié)對齊的內(nèi)存使用模式下,范圍為±64KB。在字對齊的內(nèi)存使用模式下,范圍為:±256KB。
·??16位Thumb指令集:ADRL偽指令不可用。
·??32位Thumb-2指令集:±1M字節(jié)(無論標號label是字、半字還是字節(jié)對齊)。
匯編過程中,匯編器將偽指令ADR編譯成一條ADD或SUB指令,如果一條指令不能完成偽操作的功能,編譯器將報錯。ADRL偽操作被編譯器編譯成兩條數(shù)據(jù)處理指令,詳細信息參見ARM偽操作一節(jié)。
下面的程序使用ADR偽操作加載了跳轉(zhuǎn)表的入口地址,成功地實現(xiàn)了程序跳轉(zhuǎn)。
AREA????Jump,?CODE,?READONLY ;將該子程序命名為Name
CODE32 ;下面的代碼為ARM代碼
num?????EQU?????2 ;要查找的跳轉(zhuǎn)表入口號
ENTRY ;程序入口
start ;程序指令開始
MOV????r0,?#0 ;為程序跳轉(zhuǎn)加載3個參數(shù)
MOV?????r1,?#3
MOV?????r2,?#2
BL??????arithfunc ;調(diào)用函數(shù)arithfunc
stop????MOV?????r0,?#0x18 ;為SWI調(diào)用準備參數(shù)
LDR?????r1,?=0x20026 ;調(diào)用ADP_Stopped_ApplicationExit功能
SWI?????0x123456 ;ARM?semihosting?SWI
arithfunc ;arithfunc函數(shù)入口
CMP?????r0,?#num ;r0中數(shù)值和num做比較
integer
BXHS????lr ;如果r0≥num,函數(shù)返回
ADR?????r3,?JumpTable ;加載跳轉(zhuǎn)表地址
LDR?????pc,?[r3,r0,LSL#2] ;跳轉(zhuǎn)到相應的函數(shù)入口
JumpTable
DCD?????DoAdd
DCD?????DoSub
DoAdd???ADD?????r0,?r1,?r2 ;r1和r2相加,結(jié)果放入r0
BX??????lr ;子函數(shù)返回
DoSub???SUB?????r0,?r1,?r2 ;r1和r2相減,結(jié)果放入r0
BX??????lr ;子函數(shù)返回
END ;程序結(jié)束
上面的程序段中,函數(shù)arithfunc帶有3個參數(shù)(使用r0、r1和r2傳參),函數(shù)的返回值通過r0返回。參數(shù)1決定該函數(shù)的功能。
·??參數(shù)1=0,結(jié)果=參數(shù)1+參數(shù)2;
·??參數(shù)1=1,結(jié)果=參數(shù)1-參數(shù)2。
程序中偽操作LDR?pc,[r3,r0,LSL#2]向PC寄存器加載了跳轉(zhuǎn)表中的正確的子函數(shù)入口地址。
(2)使用LDR?Rd,?=?label偽指令
使用LDR?Rd,?=?label可以將32位的常數(shù)加載到寄存器。詳見ARM偽指令一節(jié)。
ARM匯編器首先將label地址存入數(shù)據(jù)緩沖池,在使用LDR?rn?[pc,?#offset?to?literal?pool]指令將該label地址加載到寄存器。
使用LDR?Rd,?=?label可以加載本段以外的標號label地址值(與ADR不同)。如果LDR?Rd,?=?label加載的地址標號label和指令不在同一段,匯編器將在目標碼中放置重定位偽操作指示連接器在連接時替換成合適地址值。
下面的例子使用LDR?Rd,?=?label加載了本段之外的標號。
AREA????LDRlabel,?CODE,READONLY
ENTRY ;程序入口
start
BL??????func1 ;跳轉(zhuǎn)到func1
BL??????func2 ;跳轉(zhuǎn)到func2
stop????MOV?????r0,?#0x18 ;為SWI調(diào)用準備參數(shù)
LDR?????r1,?=0x20026 ;準備調(diào)用SWI的ADP_Stopped_ApplicationExit功能
SWI?????0x123456 ;semihosting軟中斷調(diào)用
func1
LDR?????r0,?=start ;加載標號地址
LDR?????r1,?=Darea?+?12 ;該偽操作被編譯為指令LDR?R1,[PC,#offset?into
;Literal?Pool?1]
LDR?????r2,?=Darea?+?6000 ;該偽操作被編譯為指令LDR?R2,?[PC,?#offset?into
;Literal?Pool?1]
MOV?????pc,lr ;程序返回
LTORG ;內(nèi)存池1入口
func2
LDR?????r3,?=Darea?+?6000 ;編譯為指令LDR?r3,?[PC,?#offset?into
;Literal?Pool?1]
;(共享內(nèi)存池)
;?LDR???r4,?=Darea?+?6004 ;從內(nèi)存池2中加載新的數(shù)據(jù),因為內(nèi)存池2超出范圍,
;所以指令失敗
BX??????lr ;返回
Darea???SPACE???8000 ;申請8000字節(jié)的內(nèi)存空間并初始化為0
END ;程序結(jié)束,內(nèi)存池2超出了LDR指令的數(shù)據(jù)加載范圍
?
3.使用LDM和STM指令實現(xiàn)堆棧操作
無論ARM、Thumb-2還是16位Thumb指令集,都有相同的加載多個寄存器的指令(LDM和STM指令)。多寄存器數(shù)據(jù)傳輸指令為寄存器和內(nèi)存的多數(shù)據(jù)交換提供了有效方法。這些指令通常用于塊拷貝或堆棧操作。使用多寄存器數(shù)據(jù)傳輸指令代替多條單寄存器傳輸指令的組合,有下面幾點優(yōu)勢。
①?產(chǎn)生的代碼量小,代碼密度高。
②?在沒有Cache的ARM處理器上使用多寄存器傳輸指令傳輸數(shù)據(jù)時,除第一個被傳送的數(shù)據(jù)外,其余數(shù)據(jù)均是在連續(xù)的指令周期完成的(第一個被傳送的數(shù)據(jù)使用非連續(xù)的內(nèi)存周期),提高了指令的執(zhí)行速度。
多寄存器的LDM和STM指令會增加中斷的延時,因為ARM通常不會打斷正在執(zhí)行的指令去相應中斷,而必須等到指令執(zhí)行完。編譯器會提供一個開關來控制Load/Store指令可以傳送的最大寄存器數(shù)目以限制最大的中斷延遲。
更多的關于LDM和STM指令的信息請參加ARM指令一節(jié)。
使用ARM匯編語言實現(xiàn)堆棧操作時,下面4條指令常被用到。
·??LDM:加載多寄存器指令;
·??STM:存儲多寄存器指令;
·??PUSH:將多個寄存器的值存儲到堆棧并更新堆棧指針;
·??POS:從堆棧中裝載多個寄存器的值并更新堆棧指針。
下面分別介紹操作這些指令時的一些限制。
對于LDM/STM指令,可操作的寄存器要滿足以下要求。
①?ARM狀態(tài)下,r0~r15中的任意寄存器或寄存器的組合。
②?在32位的Thumb-2指令集中,可以是r0~r12中的任意寄存器或寄存器的組合或有選擇性的使用r14或r15。
③?在16位的Thumb指令集中,可以使用r0~r7中的任意寄存器或寄存器的組合。
對于LDM/STM指令,內(nèi)存地址要滿足以下要求。
①?數(shù)據(jù)傳送后增長。
②?數(shù)據(jù)傳送前增長(僅在ARM指令集中)。
③?數(shù)據(jù)傳送后減少(僅在ARM指令集中)。
④?數(shù)據(jù)傳送前減少(僅在ARM和32位Thumb指令集中)。
任何當前寄存器組的子集都可以使用多寄存器LDM/STM指令與寄存器進行數(shù)據(jù)交換?;芳拇嫫鱎n決定目標或源地址,可以通過選擇使用Rn后綴字符“!”來確定Rn的值是否隨著傳送而改變,就像使用回寫前變址尋址的單寄存器傳送指令一樣。
對于PUSH和POS,要滿足以下要求。
①?使用堆棧指針寄存器r13作為基址寄存器,并在操作后更新該寄存器的值。
②?在每一個POP指令后,基址寄存器地址自動增加;在每一個PUSH指令后,基址寄存器地址自動減少。
③?對指令操作的寄存器的限制同LDM/STM指令。
在ARM的37個寄存器中,R13通常用作堆棧指針。堆棧尋址是隱含的,堆棧指針所指定的存儲單元就是堆棧的棧頂,堆棧尋址通常有兩種方式:向上生長(ascending)和向下生長(descending)。ARM處理器有ARM和Thumb兩種指令集。每種指令集都有豐富的指令可以對堆棧進行操作。堆棧指針指向最后壓入堆棧的有效數(shù)據(jù),稱為滿堆棧(full?stack);堆棧指針指向下一個數(shù)據(jù)項放入的空位置,稱為空堆棧(empty?stack)。根據(jù)堆棧的生長方向不同,可以生成4種類型的堆棧,即滿遞增、空遞增、滿遞減和空遞減。
注意 |
這種遞增和遞減的多寄存器傳送指令不僅可以對堆棧進行操作,而且也可以用于正向或反向訪問數(shù)組。 |
為方便堆棧的操作,ARM指令增加了專門對堆棧操作的指令后綴。表10.16列出了對堆棧操作的數(shù)據(jù)傳輸指令。
表10.16 對堆棧操作的數(shù)據(jù)傳送指令
堆?棧?類?型 |
Push |
Pop |
滿遞減 |
STMFD(STMDB,Decrement?Before) |
LDMFD?(LDM?,?Icrement?Ater)? |
滿遞增 |
STMFA?(STMIB,?Increment?Before)? |
LDMFA?(LDMDA?,?Decrement?After)? |
空遞減 |
STMED?(STMDA,?Decrement?After)? |
LDMED?(LDMIB,?Increment?Before)? |
空遞增 |
STMEA?(STM?,?increment?after)? |
LDMEA?(LDMDB,?Decrement?Before)? |
下面的例子顯示了數(shù)據(jù)傳送指令對堆棧的操作。
STMFD?r13!,?{r0-r5}???;r0~r5入棧,該堆棧為滿遞減堆棧
LDMFD?r13!,?{r0-r5}???;數(shù)據(jù)出棧,放入寄存器r0~r5,該堆棧為滿遞減堆棧
注意 |
ARM過程調(diào)用標準AAPCS定義使用滿遞減堆棧。使用PUSH和POP指令操作堆棧,堆棧類型默認為滿遞減堆棧,自動使用地址回寫。 |
堆棧操作經(jīng)常用于子程序調(diào)用的現(xiàn)場保護和子程序返回的現(xiàn)場恢復。另外,子程序的返回地址也常常被壓棧保護。
下面的例子顯示如何在子程序中進行現(xiàn)場保護和現(xiàn)場恢復。
subroutine?PUSH?{r5-r7,lr} ;將工作寄存器和返回地址lr壓棧
;?code
BL?somewhere_else
;?code
POP?{r5-r7,pc} ;保存的寄存器數(shù)據(jù)和返回地址出棧
如果系統(tǒng)中存在ARM和Thumb的交互工作,以上代碼只能用于ARMv5架構(gòu)及其以上版本。在ARMv4架構(gòu)中,直接將返回地址送PC,不能引起處理器狀態(tài)的切換。
下面的例子顯示在符合AAPCS標準的滿遞減堆棧上,使用STMFD指令完成的PUSH操作(見圖10.1)。STMFD指令把寄存器內(nèi)容壓棧,SP指針指向棧頂。
圖10.1??滿遞減堆棧的STMFD操作
????????MOV??r1,0x01
????????MOV??r4,0x04
????????STMFD??SP!,{r1,r4}
若要檢查一個堆棧,必須關注堆棧的3個屬性:堆?;贰⒍褩V羔樇岸褩O拗?。堆?;肥嵌褩T?a class="article-link" target="_blank" href="/tag/%E5%AD%98%E5%82%A8%E5%99%A8/">存儲器中的起始地址;堆棧指針初始時指向堆棧基址單元,隨著數(shù)據(jù)壓棧,堆棧指針連續(xù)移動并始終指向棧頂;如果堆棧指針超過了堆棧限制,就會發(fā)生堆棧溢出錯誤。下面代碼用于檢測遞減式堆棧的溢出錯誤。
SUB??SP,SP,#size
CPM??SP,r10
BLLO??_stack_overflow??????;條件
AAPCS把寄存器r10定義為堆棧限制或sl(stack?limit)寄存器。這是一個可選的操作,因為,堆棧檢查只有在堆棧檢查使能的時候才可以使用。BLL0指令是一個附加了條件助記符L0的帶鏈接的分支指令。如果在執(zhí)行一個push操作后,SP的值小于r10的值,就發(fā)生了堆棧溢出錯誤。如果堆棧指針在執(zhí)行pop操作后,超出了堆?;?,那么就產(chǎn)生了堆棧下溢(stack?underflow)錯誤。
?
4.使用LDM和STM指令實現(xiàn)塊復制
當程序中有大量數(shù)據(jù)需要復制或搬移時,常用到LDM和STM指令。雖然使用單寄存器數(shù)據(jù)傳送指令LDR和STR也能實現(xiàn)同樣的功能,但代碼密度和執(zhí)行效率要低于使用多寄存器傳送指令。
下面通過兩個例子說明了使用單寄存器數(shù)據(jù)傳送指令和使用多寄存器數(shù)據(jù)傳送指令的區(qū)別。
AREA Word,?CODE,?READONLY ;給代碼段起名為Word
num EQU 20 ;num=20將要拷貝的字的個數(shù)
ENTRY ;程序入口
call
start
LDR r0,?=src ;r0?=?源操作塊起始地址
LDR r1,?=dst ;r1?=?目的操作塊起始地址
MOV r2,?#num ;r2?=?將要拷貝的字的個數(shù)
wordcopy LDR r3,?[r0],?#4 ;從源操作塊裝載一個字
STR r3,?[r1],?#4 ;存儲到目的操作塊
SUBS r2,?r2,?#1 ;指針移到下一個要拷貝的字單元
BNE wordcopy ;循環(huán)拷貝
stop MOV r0,?#0x18 ;angel_SWIreason_ReportException為軟中斷調(diào)用準備參數(shù)
LDR r1,?=0x20026 ;調(diào)用Semihosting的ADP_Stopped_ApplicationExit功能
SWI 0x123456 ;ARM?semihosting?SWI調(diào)用
AREA BlockData,?DATA,?READWRITE
src DCD 1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8,1,2,3,4
dst DCD 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
END
下面是使用多寄存器數(shù)據(jù)拷貝命令LDM和STM重寫的代碼。
AREA Block,?CODE,?READONLY ;給代碼段起名為Block
num EQU 20 ;num=20將要拷貝的字的個數(shù)
ENTRY ;程序入口?
call
start
LDR r0,?=src ;r0?=?源操作塊起始地址
LDR r1,?=dst ;r1?=?目的操作塊起始地址
MOV r2,?#num ;r2?=?將要拷貝的字的個數(shù)
MOV sp,?#0x400 ;設置堆棧指針?(r13)
blockcopy MOVS r3,r2,?LSR?#3 ;判斷要移動的字的個數(shù)是8的幾倍
BEQ copywords ;如果移動的字的個數(shù)小于8則跳轉(zhuǎn)到copywords子函數(shù)
PUSH {r4-r11} ;將用到的工作寄存器壓棧保存
octcopy LDM r0!,?{r4-r11} ;從源地址拷貝8個字
STM r1!,?{r4-r11} ;存到目的地址
SUBS r3,?r3,?#1 ;計數(shù)器減1
BNE octcopy ;如果計數(shù)器不等于零,繼續(xù)拷貝
POP {r4-r11} ;如果計數(shù)器等于零,恢復工作寄存器
copywords ANDS r2,?r2,?#7 ;判斷要拷貝的剩余字個數(shù)
BEQ stop ;判斷剩余字數(shù)是否為零
wordcopy LDR r3,?[r0],?#4 ;從源地址加載一個字
STR r3,?[r1],?#4 ;到目的地址
SUBS r2,?r2,?#1 ;計算器減1
BNE wordcopy ;如果計數(shù)器不等于零,繼續(xù)拷貝
stop MOV r0,?#0x18 ;為軟中斷調(diào)用準備參數(shù)
LDR r1,?=0x20026 ;調(diào)用Semihosting的ADP_Stopped_ApplicationExit功能
SWI 0x123456 ;調(diào)用Semihosting軟中斷
AREA BlockData,?DATA,?READWRITE
src DCD 1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8,1,2,3,4
dst DCD 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
END
為了增加數(shù)據(jù)拷貝的效率,程序中使用了“MOVS????r3,r2,?LSR?#3”指令。該指令將為下面以8個字節(jié)為單位進行數(shù)據(jù)拷貝做準備。將數(shù)據(jù)以8個字為單位拷貝,是因為ARM體系結(jié)構(gòu)中所能使用的寄存器為8個,即:r4~r11(根據(jù)AAPCS標準,r0~r3在子程序調(diào)用過程中將被使用)。
10.5.3??宏的使用
使用偽操作MACRO和MEND可以將一段代碼定義為宏。程序運行時,使用宏名調(diào)用宏。程序中使用宏的優(yōu)勢在于。
①?提高代碼的可讀性,使用更能代表程序功能的名稱代替特定的代碼段。
②?避免同一代碼段在程序中重復多次。
1.宏在分支程序中的應用
在ARMv6T2之前的ARM體系結(jié)構(gòu)中,一個“測試-分支”操作需要至少兩條ARM指令完成??梢允褂煤甓x這種“分支-測試”結(jié)構(gòu)。
?????????MACRO
$label??TestAndBranch??$dest,?$reg,?$cc
$label??CMP??????$reg,?#0
??????????B$cc????$dest
??????????MEND
上面的程序段定義了一個叫做TestAndBranch的宏,并且為該宏定義了4個參數(shù)($label、$dest、$reg和$cc),在程序中使用該宏時,必須為參數(shù)賦值。下面的程序段顯示了如何調(diào)用TestAndBranch宏。
test????TestAndBranch????NonZero,?r0,?NE
???????????...
???????????...
NonZero
After?substitution?this?becomes:
test????CMP?????r0,?#0
??????????BNE?????NonZero
???????????...
???????????...
NonZero
2.宏在除法中的應用
下面的例子程序定義的宏實現(xiàn)了無符號整數(shù)的除法,調(diào)用該宏需要為以下4個參數(shù)賦值。
·??$Bot:保存除法除數(shù)的寄存器。
·??$Top:指令執(zhí)行前保存被除數(shù),指令執(zhí)行后保存除數(shù)。
·??$Div:保存除法運算的商,如果除法只要求余數(shù),該寄存器的值為0。
·??$Temp:臨時寄存器。
MACRO
$Lab DivMod??$Div,$Top,$Bot,$Temp
ASSERT??$Top?<>?$Bot ;如果使用的寄存器不全相等,產(chǎn)生錯誤信息
ASSERT??$Top?<>?$Temp
ASSERT??$Bot?<>?$Temp
IF??????"$Div"?<>?""
????????????ASSERT??$Div?<>?$Top ;如果$Div不為空,產(chǎn)生三個提示信息
????????????ASSERT??$Div?<>?$Bot
????????????ASSERT??$Div?<>?$Temp
ENDIF
$Lab
MOV?????$Temp,?$Bot ;將除數(shù)放入$Temp
CMP?????$Temp,?$Top,?LSR?#1 ;$Temp乘以2,直到2*$Temp>$Top
90 MOVLS???$Temp,?$Temp,?LSL?#1
CMP?????$Temp,?$Top,?LSR?#1
BLS?????%b90 ;b表示向后搜索
IF??????"$Div"?<>?"" ;如果$Div為空,忽略下一條指令
????????????MOV?????$Div,?#0 ;將$Div初始化
ENDIF
91 CMP?????$Top,?$Temp ;Can?we?subtract?$Temp?$Top大于$Temp?
SUBCS???$Top,?$Top,$Temp ;如果大于,$Top減去$Temp,結(jié)果放入$Top
IF??????"$Div"?<>?"" ;如果$Div為空,忽略下一條指令
????????????ADC?????$Div,?$Div,?$Div ;將$Div乘以2
ENDIF
MOV?????$Temp,?$Temp,?LSR?#1 ;將$Temp除2
CMP?????$Temp,?$Bot ;循環(huán),直到$Temp小于除數(shù)
BHS?????%b91
MEND
下面是該除法宏被引用時的結(jié)果。
ASSERT r5?<>?r4 ;如果寄存器全不相等,顯示提示信息
ASSERT r5?<>?r2
ASSERT r4?<>?r2
ASSERT r0?<>?r5 ;如果$Div不為空,產(chǎn)生三個提示信息
ASSERT r0?<>?r4
ASSERT r0?<>?r2
ratio
MOV r2,?r4 ;將除數(shù)放在r2中
CMP r2,?r5,?LSR?#1 ;r2乘以2,直到2*r2>r5
90 MOVLS r2,?r2,?LSL?#1
CMP r2,?r5,?LSR?#1
BLS %b90 ;b表示向后搜索
MOV r0,?#0 ;初始化r0
91 CMP r5,?r2 ;判斷r5是否大于r2
SUBCS r5,?r5,?r2 ;如果大于,r5減去r2,結(jié)果放入r5
ADC r0,?r0,?r0 ;r0乘以2
MOV r2,?r2,?LSR?#1 ;r2除以2
CMP r2,?r4 ;循環(huán),直到r2<r4
BHS %b91
?
10.5.4??使用MAP和FIELD命令描述數(shù)據(jù)結(jié)構(gòu)
可以使用MAP和FIELD偽操作來描述數(shù)據(jù)結(jié)構(gòu)。這兩個偽操作一般是一起使用的。
使用MAP和FIELD偽操作定義數(shù)據(jù)結(jié)構(gòu)有以下好處。
·??容易維護。
·??可以方便地實現(xiàn)相同數(shù)據(jù)結(jié)構(gòu)的重復定義。
·??可以更高效地存取數(shù)據(jù)。
MAP偽操作指定數(shù)據(jù)結(jié)構(gòu)的基址。
FIELD偽操作指定一個數(shù)據(jù)項所需的存儲器數(shù)量,并為該數(shù)據(jù)項指定一個標號。對結(jié)構(gòu)中的每個數(shù)據(jù)項重復該命令。
注意 |
使用MAP和FIELD偽操作當定義一個數(shù)據(jù)結(jié)構(gòu)時,不分配存儲器空間。使用定義常數(shù)的命令(如?DCD)來分配存儲器空間。 |
?
1.相對映射
MAP/FIELD偽操作和使用寄存器相對尋址的LOAD/STORE指令配合使用,訪問事先定義好的結(jié)構(gòu)體是MAP/FIELD偽操作的基本用法。
下面是一個使用寄存器相對尋址的LOAD指令訪問結(jié)構(gòu)體的例子。
MAP?0
consta?FIELD?4 ;consta占用4字節(jié),偏移量為0
constb?FIELD?4 ;constb占用4字節(jié),偏移量為4
x?FIELD?8 ;x占用8字節(jié),偏移量為8
y?FIELD?8 ;y占用8字節(jié),偏移量為16
string?FIELD?256 ;string占用256,偏移量為24
上面定義的數(shù)據(jù)結(jié)構(gòu),可以使用下列指令來訪問:
MOV?r9,#4096
LDR?r4,[r9,#constb]
標號是相對于數(shù)據(jù)結(jié)構(gòu)的開始位置的。用于存放映射的起始地址的寄存器(此例中為?r9)稱為基址寄存器。
數(shù)據(jù)結(jié)構(gòu)的位置是由運行時裝載到基址寄存器的值確定的。MAP/FIELD偽操作不實際分配內(nèi)存地址。
同一映射可以用于描述數(shù)據(jù)結(jié)構(gòu)的多個實例。它們可以位于存儲器中的任何位置。
備注 |
r9是“ARM-Thumb?程序調(diào)用標準”中的靜態(tài)基址寄存器?(sb)。詳細信息請參閱本書附錄。 |
2.基于寄存器的映射
一般情況下,每次訪問一個數(shù)據(jù)結(jié)構(gòu)時可以使用相同的寄存器作為基址寄存器??梢栽谑褂肕AP定義映射的基址時指定該寄存器的名稱。
下面的例子顯示了一個基于寄存器的映射。
MAP?0,r9
consta?FIELD?4 ;consta變量占用4個字節(jié),地址偏移量為0(相對于r9中的地址)
constb?FIELD?4 ;constb變量占用4個字節(jié),地址偏移量為4
x?FIELD?8 ;x占用8個字節(jié),地址偏移量為8
y?FIELD?8 ;y占用8個字節(jié),地址偏移量為16
string?FIELD?256 ;字符串占用256字節(jié),起始地址偏移量為24
利用示例中的映射,可以訪問數(shù)據(jù)結(jié)構(gòu)(不管它在內(nèi)存中什么位置):
ADR?r9,datastart
LDR?r4,constb?;?=>?LDR?r4,[r9,#4]????????;該偽操作被編譯為LDR?r4,[r9,#4]
constb包含從數(shù)據(jù)結(jié)構(gòu)開始位置算起的數(shù)據(jù)項的偏移量,也包含基址寄存器。在此例中,基址寄存器是在MAP命令中定義的r9。
3.相對于程序的映射
可以使用程序計數(shù)器?(r15)?作為一個映射的基址寄存器。當使用r15做為基址寄存器時,被使用LOAD/STORE指令尋址的數(shù)據(jù)結(jié)構(gòu)偏移量不能超出4KB范圍。這是因為使用PC寄存器為基址的LOAD/STORE指令,最大尋址能力為4KB。
下面的例子顯示了使用r15作為基址寄存器的程序段。其中包含一個為數(shù)據(jù)結(jié)構(gòu)分配存儲器空間的SPACE偽操作。
datastruc?SPACE?280??????;保留280個字節(jié)來定義數(shù)據(jù)結(jié)構(gòu)
MAP?datastruc
consta?FIELD?4
constb?FIELD?4
x?FIELD?8
y?FIELD?8
string?FIELD?256
使用下面的指令讀取constb域所包含的內(nèi)容。
LDR?r2,constb?????;?=>?LDR?r2,[pc,offset]?????該偽操作被編譯為LDR?r2,[pc,offset]
在此例中,不需要在裝載數(shù)據(jù)之前裝載基址寄存器,此時使用程序計數(shù)器PC作為默認寄存器。
注意 |
由于處理器里面的流水線,r15寄存器值和?LDR?指令的實際地址并不相同。但是,匯編器將修正此問題。 |
?
4.定義結(jié)構(gòu)體的結(jié)束
可以使用帶有0操作數(shù)的FIELD偽操作來標記結(jié)構(gòu)內(nèi)的一個位置。標記位置后位置計數(shù)器并未增加(即偏移量offset不增加)。
下面的例子使用事先定義好的常量MaxStrLen和ArrayLen定義了字符串和數(shù)組的大小。為了防止MaxStrLen和ArrayLen值過大,使結(jié)構(gòu)體超出所能使用的內(nèi)存范圍,使用了“FIELD?0”偽操作來標識結(jié)構(gòu)體的結(jié)束。
StartOfData?EQU?0x1000
EndOfData?EQU?0x2000
MAP?StartOfData
Integer?FIELD?4
Integer2?FIELD?4
String?FIELD?MaxStrLen
Array?FIELD?ArrayLen*8
BitMask?FIELD?4
EndOfUsedData?FIELD?0
ASSERT?EndOfUsedData?<=?EndOfData
該例中使用了:
·??一個EQU命令定義可使用的最大內(nèi)存空間;
·??一個帶有0操作數(shù)的FIELD偽操作來標記數(shù)據(jù)結(jié)構(gòu)的結(jié)束位置;
·??一個ASSERT偽操作確認數(shù)據(jù)結(jié)構(gòu)的結(jié)束位置并未超出可用內(nèi)存。
5.強制內(nèi)存對齊
如果在定義數(shù)據(jù)結(jié)構(gòu)時,使用了一些字符變量(或其他非4字節(jié)的變量),就可能造成內(nèi)存訪問的對齊問題。
下面例子中顯示了一個數(shù)據(jù)結(jié)構(gòu)的定義。該定義方式使整數(shù)訪問不能保證在字邊界對齊。
StartOfData?EQU?0x1000
EndOfData?EQU?0x2000
MAP?StartOfData
Char?FIELD?1
Char2?FIELD?1
Char3?FIELD?1
Integer?FIELD?4?????;?非字邊界對齊
Integer2?FIELD?4
String?FIELD?MaxStrLen
Array?FIELD?ArrayLen*8
BitMask?FIELD?4
EndOfUsedData?FIELD?0
ASSERT?EndOfUsedData?<=?EndOfData
這里,不能使用ALIGN偽操作來修正該邊界對齊問題,因為ALIGN偽操作只對齊存儲器內(nèi)的當前位置。而MAP和FIELD偽操作不為其定義的結(jié)構(gòu)分配任何存儲空間。
解決的方法是,在Char3?FIELD?1后插入一個虛擬的FIELD?1。但是,如果改變了字符變量的數(shù)目,就會造成維護困難。每次必須重新計算正確的填充數(shù)。
下面的例子說明了一個更好的調(diào)整填充的方法。該示例使用一個帶有0操作數(shù)的FIELD偽操作來標記字符數(shù)據(jù)的結(jié)束位置。第二個FIELD命令根據(jù)標號的值插入正確的填充數(shù)。使用“:AND:”運算符來計算正確的值。
StartOfData?EQU?0x1000
EndOfData?EQU?0x2000
MAP?StartOfData
Char?FIELD?1
Char2?FIELD?1
Char3?FIELD?1
EndOfChars?FIELD?0
Padding?FIELD?(-EndOfChars):AND:3
Integer?FIELD?4
Integer2?FIELD?4
String?FIELD?MaxStrLen
Array?FIELD?ArrayLen*8
BitMask?FIELD?4
EndOfUsedData?FIELD?0
ASSERT?EndOfUsedData?<=?EndOfData
“(-EndOfChars):AND:3”表達式計算正確的填充數(shù):
·??如果EndOfChars是0?mod?4,則填充數(shù)是0;
·??如果EndOfChars是1?mod?4,則填充數(shù)是3;
·??如果EndOfChars是2?mod?4,則填充數(shù)是2;
·??如果EndOfChars是3?mod?4,則填充數(shù)是1。
無論何時添加或刪除字符變量,它會自動調(diào)整填充數(shù)。