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