1、背景
計(jì)算機(jī)語言具有高級(jí)語言和低級(jí)語言之分。而高級(jí)語言又主要是相對(duì)于匯編語言而言的,它是較接近自然語言和數(shù)學(xué)公式的編程,基本脫離了機(jī)器的硬件系統(tǒng),用人們更易理解的方式編寫程序。編寫的程序稱之為源程序。低級(jí)語言分機(jī)器語言(二進(jìn)制語言)和匯編語言(符號(hào)語言),這兩種語言都是面向機(jī)器的語言,和具體機(jī)器的指令系統(tǒng)密切相關(guān)。機(jī)器語言用指令代碼編寫程序,而符號(hào)語言用指令助記符來編寫程序。高級(jí)語言、匯編語言和機(jī)器語言都是用于編寫計(jì)算機(jī)程序的語言。
1.1定義
機(jī)器語言將指令編碼為 0 和 1 的序列;這種二進(jìn)制編碼是計(jì)算機(jī)的處理器被構(gòu)建來執(zhí)行的。但是,使用這種編碼編寫程序?qū)τ谌祟惓绦騿T來說是笨拙的。因此,當(dāng)程序員想要指示計(jì)算機(jī)要執(zhí)行的精確指令時(shí),他們會(huì)使用匯編語言,該語言允許以文本形式編寫指令。匯編器將包含匯編語言代碼的文件翻譯成相應(yīng)的機(jī)器語言。
讓我們看一個(gè) ARM 設(shè)計(jì)的簡(jiǎn)單示例。這是機(jī)器語言指令:
1110 0001 1010 0000 0011 0000 0000 1001
當(dāng)處理器被告知執(zhí)行該二進(jìn)制序列時(shí),它會(huì)將值從“register 9”復(fù)制到“register 9”。但作為程序員,您幾乎不想閱讀長(zhǎng)二進(jìn)制序列并理解它。相反,程序員更喜歡用匯編語言進(jìn)行編程,我們將使用以下行來表達(dá)這一點(diǎn)。
MOV R3, R9
然后程序員將使用匯編程序?qū)⑵滢D(zhuǎn)換為計(jì)算機(jī)實(shí)際執(zhí)行的二進(jìn)制編碼。
但不只是只有一種機(jī)器語言:為每條處理器設(shè)計(jì)了不同的機(jī)器語言,旨在提供一組強(qiáng)大的快速指令,同時(shí)允許構(gòu)建相對(duì)簡(jiǎn)單的電路。處理器通常被設(shè)計(jì)為與以前的處理器兼容,因此它遵循相同的機(jī)器語言設(shè)計(jì)。例如,英特爾的處理器系列(包括 80386、奔騰和酷睿 i7)支持類似的機(jī)器語言。但是 ARM 處理器支持完全不同的機(jī)器語言。機(jī)器語言編碼的設(shè)計(jì)稱為指令集架構(gòu)(ISA,instruction set architecture)。
并且對(duì)于每種機(jī)器語言,必須有不同的匯編語言,因?yàn)閰R編語言必須對(duì)應(yīng)于一組完全不同的機(jī)器語言指令。
1.2不同的指令集架構(gòu)(ISA)
在眾多 ISA(指令集架構(gòu))中,x86 是最廣為人知的。它最初由 Intel 于 1974 年設(shè)計(jì)用于 8 位處理器(Intel 8080),多年來它擴(kuò)展到 16 位形式(1978,Intel 8086),然后擴(kuò)展到 32 位形式(1985,Intel 80386),然后是 64 位形式(2003,AMD Opteron)。今天,支持 IA32 的處理器現(xiàn)在由 Intel、AMD 和 VIA 制造,并且可以在大多數(shù)個(gè)人計(jì)算機(jī)中找到。
今天另一個(gè)著名的 ISA 是 PowerPC。Apple 的 Macintosh 計(jì)算機(jī)一直使用這些處理器,直到 2006 年 Apple 將其計(jì)算機(jī)切換到 x86 系列處理器。但 PowerPC 仍然普遍用于汽車和游戲機(jī)(包括 Wii、Playstation 3 和 XBox 360)等應(yīng)用程序。
但我們將研究的 ISA 來自一家名為 ARM 的公司。(與其他成功的 ISA 一樣,ARM 的 ISA 多年來一直在發(fā)展。我們將研究 4T 版本。)支持 ARM ISA 的處理器分布相當(dāng)廣泛,通常用于手機(jī)、數(shù)字音樂播放器和手持游戲系統(tǒng)等低功耗設(shè)備。iPhone、Kindle 和 Nintendo DS 都是采用 ARM 處理器的設(shè)備的突出例子。
我們研究 ARM 的 ISA 而不是 IA32 有幾個(gè)原因。
匯編語言編程很少用于功能更強(qiáng)大的計(jì)算系統(tǒng),因?yàn)橛酶呒?jí)編程語言進(jìn)行編程要容易得多。但是對(duì)于小型設(shè)備,匯編語言編程仍然很重要:由于功率和價(jià)格的限制,設(shè)備的資源非常少,開發(fā)人員可以使用匯編語言盡可能高效地使用這些資源。
IA32 架構(gòu)的多個(gè)擴(kuò)展導(dǎo)致它過于復(fù)雜,以至于我們無法真正徹底理解。
IA32 可以追溯到 1970 年代,那是一個(gè)完全不同的計(jì)算時(shí)代。ARM 更能代表更現(xiàn)代的 ISA 設(shè)計(jì)。
2ARM匯編語言基礎(chǔ)
2.1一個(gè)簡(jiǎn)單的程序:數(shù)字求和
讓我們從一個(gè)簡(jiǎn)單的例子開始我們的介紹。想象一下,我們想要將 1 到 10 的數(shù)字相加。我們可以在 C 語言中這樣做,如下所示。
下面將其翻譯成 ARM 的 ISA 支持的指令。
您會(huì)注意到在匯編語言程序中提到了 R0 和 R1。這些是對(duì)寄存器的引用,它們位于處理器中,用于在計(jì)算期間存儲(chǔ)數(shù)據(jù)。ARM 處理器包括 16 個(gè)易于訪問的寄存器,編號(hào)為 R0 到 R15。每個(gè)都存儲(chǔ)一個(gè) 32 位數(shù)字。請(qǐng)注意,盡管寄存器存儲(chǔ)數(shù)據(jù),但它們與內(nèi)存的概念非常不同:內(nèi)存通常更大(千字節(jié)或通常千兆字節(jié)),因此它通常存在于處理器之外。由于內(nèi)存的大小,訪問內(nèi)存比訪問寄存器需要更多的時(shí)間——通常大約是 10 倍。因此,匯編語言編程傾向于盡可能關(guān)注使用寄存器。
因?yàn)閰R編語言程序的每一行都直接對(duì)應(yīng)于機(jī)器語言,所以這些行的格式受到嚴(yán)格限制。可以看到每一行由兩部分組成:首先是操作碼,例如 MOV,它是表示操作類型的縮寫;之后是諸如“R0,#0”之類的參數(shù)。每個(gè)操作碼對(duì)允許的參數(shù)都有嚴(yán)格的要求。例如,一條 MOV 指令必須正好有兩個(gè)參數(shù):第一個(gè)必須標(biāo)識(shí)一個(gè)寄存器,第二個(gè)必須提供一個(gè)寄存器或一個(gè)常量(以“#”為前綴)。直接放在指令中的常量稱為立即數(shù),因?yàn)樘幚砥髟谧x取指令時(shí)可以立即使用它。
在上述匯編語言程序中,我們首先使用 MOV 指令將 R0 初始化為 0,將 R1 初始化為 10。ADD 指令計(jì)算 R0 和 R1 的和(第二個(gè)和第三個(gè)參數(shù))并將結(jié)果放入 R0(第一個(gè)參數(shù)) );這對(duì)應(yīng)于總數(shù) += i;等效 C 程序的行。隨后的 SUBS 指令將 R1 減 1。
要理解下一條指令,我們需要了解除了寄存器 R0 到 R15 之外,ARM 處理器還包含一組四個(gè)“標(biāo)志”,分別標(biāo)記為零標(biāo)志 (Z)、負(fù)標(biāo)志 (N)、進(jìn)位標(biāo)志 (C) 和溢出標(biāo)志 (V)。每當(dāng)算術(shù)指令的末尾有一個(gè) S 時(shí),就像 SUBS 一樣,這些標(biāo)志將根據(jù)計(jì)算結(jié)果進(jìn)行更新。在這種情況下,如果 R1 減 1 的結(jié)果為 0,則 Z 標(biāo)志將變?yōu)?1;N、C 和 V 標(biāo)志也更新了,但它們與我們對(duì)此代碼的討論無關(guān)。
下面的指令 BNE 將檢查 Z 標(biāo)志。如果未設(shè)置 Z 標(biāo)志(即,先前的減法給出非零結(jié)果),則 BNE 安排處理器,以便執(zhí)行的下一條指令是 ADD 指令,再次標(biāo)記;這導(dǎo)致以較小的 R1 值重復(fù)循環(huán)。如果設(shè)置了 Z 標(biāo)志,處理器將繼續(xù)執(zhí)行下一條指令。(BNE 代表 Branch if Not Equal。這個(gè)名字來源于想象我們想要檢查兩個(gè)數(shù)字是否相等。使用 ARM 的 ISA 執(zhí)行此操作的一種方法是首先告訴處理器減去兩個(gè)數(shù)字;如果差是零,那么這兩個(gè)數(shù)字必須相等,零標(biāo)志將為 1。它們導(dǎo)致零,這將設(shè)置零標(biāo)志。)
最后一條指令 B 總是分支回到指定的指令。在這個(gè)程序中,指令為自己命名,通過使計(jì)算機(jī)進(jìn)入一個(gè)緊密的無限循環(huán)來有效地停止程序。
2.2另一個(gè)例子:冰雹序列
輸入任何一個(gè)大于1的正整數(shù)N,如果是偶數(shù)的話就除以2,如果是奇數(shù)的話就乘以3再加上1,最后這個(gè)數(shù)都會(huì)變?yōu)?。特殊地,當(dāng)輸入為1時(shí),序列為1。這就是冰雹序列。其公式如下:
現(xiàn)在,讓我們考慮一下冰雹序列。給定一個(gè)整數(shù) n,我們反復(fù)想應(yīng)用以下過程:
迭代次數(shù)←0
當(dāng) n ≠ 1 時(shí):迭代器←迭代器+1
如果 n 是奇數(shù):n ← 3 ⋅ n + 1
別的:n ← n / 2
例如,如果我們從 3 開始,那么因?yàn)檫@是奇數(shù),所以我們的下一個(gè)數(shù)字是 3 ⋅ 3 + 1 = 10。這是偶數(shù),所以我們的下一個(gè)數(shù)字是 10 / 2 = 5。這是奇數(shù),所以我們的下一個(gè)數(shù)字是3 ⋅ 5 + 1 = 16。這是偶數(shù),所以我們轉(zhuǎn)到 8,它仍然是偶數(shù),所以我們轉(zhuǎn)到 4,然后是 2 和 1。
在將其翻譯成 ARM 的匯編語言時(shí),我們必須面對(duì)一個(gè)事實(shí),即 ARM 缺少任何與除法相關(guān)的指令。(設(shè)計(jì)人員認(rèn)為除法很少需要在它所需的復(fù)雜電路上浪費(fèi)晶體管。)幸運(yùn)的是,該算法中的除法相對(duì)簡(jiǎn)單:我們只需將 n 除以 2,這可以通過右移來完成。
ARM 有一種不同尋常的移位方法:我們已經(jīng)看到,每條基本算術(shù)指令,最后的參數(shù)都可以是常量(如 SUBS R1、R1、#1)或寄存器(如 ADD R0、R0、R1)。但是當(dāng)最后一個(gè)參數(shù)是一個(gè)寄存器時(shí),我們可以選擇添加一個(gè)移位距離:例如,指令“ADD R0, R0, R1, LSL #1”。表示在將 R1 添加到 R0 之前添加左移版本的 R1(而 R1 本身保持不變)。ARM 指令集支持四種類型的移位:
LSL |
logical shift left(邏輯左移); |
LSR |
logical shift right(邏輯右移); |
ASR |
arithmetic shift right(算術(shù)右移); |
ROR |
rotate right(向右旋轉(zhuǎn)); |
移位距離可以是 1 到 32 之間的立即數(shù),也可以基于寄存器值:“MOV R0, R1, ASR R2”等價(jià)于“R0 = R1 >> R2”。
在將我們的偽代碼翻譯成匯編語言時(shí),我們會(huì)發(fā)現(xiàn)移位操作對(duì)于將 n 乘以 3(計(jì)算為 n + (n « 1))和除以 n (計(jì)算為 n » 1)都很有用。我們還需要處理測(cè)試 n 是否為奇數(shù)。我們可以通過測(cè)試 n 的 1 位是否設(shè)置來做到這一點(diǎn),我們可以使用 ANDS 指令與 1 進(jìn)行按位與來完成。ANDS 指令根據(jù)結(jié)果是否為 0 設(shè)置 Z 標(biāo)志。如果結(jié)果為 0,那么這意味著n的1位是0,所以n是偶數(shù)。
2.3另一個(gè)例子:添加數(shù)字
讓我們看另一個(gè)例子。在這里,假設(shè)我們要添加一個(gè)正數(shù)的數(shù)字;例如,給定數(shù)字 1024,我們想要計(jì)算 1 + 0 + 2 + 4,即 7。用 C 語言表達(dá)這一點(diǎn)的明顯方法如下。
但是,很難將其轉(zhuǎn)換為 ARM 的 ISA,因?yàn)?ARM 沒有任何除法指令。但是,我們可以使用一個(gè)巧妙的技巧來使用乘法來執(zhí)行這種除法:如果我們將一個(gè)數(shù)字乘以 232 / 10,則乘積的高 32 位告訴我們將原始數(shù)字除以 10 的結(jié)果。這種解法導(dǎo)致以下替代方法對(duì)數(shù)字中的數(shù)字求和。
在將其翻譯成匯編代碼時(shí),我們必須面對(duì)兩個(gè)問題。更明顯的是確定使用哪個(gè)指令來執(zhí)行乘法。在這里,我們要使用 UMULL 指令(Unsigned MULtiply Long),它將兩個(gè)寄存器解釋為無符號(hào)的 32 位數(shù)字,并將寄存器值的 64 位乘積放入兩個(gè)不同的寄存器中。下面的例子說明了。
我們必須面對(duì)的不太明顯的問題是將 0x1999999A 放入寄存器中。一開始您可能會(huì)想使用 MOV,但這條指令有一個(gè)主要限制:任何立即數(shù)都必須循環(huán)偶數(shù)位才能達(dá)到 8 位值。對(duì)于 0 到 255 之間的數(shù)字,這不是問題;對(duì)于 1,024 也不是問題,因?yàn)?0x400 可以通過將 1 向左旋轉(zhuǎn) 12 位來實(shí)現(xiàn)。但是對(duì)于 0x1999999A 沒有辦法做到這一點(diǎn)。我們將使用的解決方案是分別加載每個(gè)字節(jié),使用 ORR 指令將它們連接起來,該指令計(jì)算兩個(gè)值的按位或。
順便說一句,您有時(shí)可能希望將一個(gè)小的負(fù)數(shù)(如 -10)放入寄存器中。您不能使用 MOV 來完成此操作,因?yàn)樗亩M(jìn)制補(bǔ)碼表示為 0xFFFFFFF6,無法旋轉(zhuǎn)為 8 位數(shù)字。如果碰巧知道某個(gè)寄存器包含數(shù)字 0,那么您可以使用 SUB。但如果不是,則 MVN(MoVe Not)指令很有用:它將其參數(shù)的按位 NOT 放入目標(biāo)寄存器。因此,要將 -10 放入 R0,我們可以使用“MVN R0,#0x9”。
2.4計(jì)算的指令摘要
ARM 包括 16 條“基本”算術(shù)指令,編號(hào)從 0 到 15。下面列出了所有 16 條指令,其功能由相關(guān)的 C 運(yùn)算符總結(jié)。(每行開頭的數(shù)字用于將指令翻譯成機(jī)器語言。程序員沒有理由記住這種對(duì)應(yīng)關(guān)系:畢竟,這就是我們有匯編程序的原因。)
除 TST、TEQ、CMP 和 CMN 外,所有指令都可以在操作碼后綴 S 以表示操作應(yīng)設(shè)置標(biāo)志。對(duì)于 TST、TEQ、CMP 和 CMN,S 是隱含的:指令不會(huì)更改任何通用寄存器,因此執(zhí)行指令的唯一要點(diǎn)是設(shè)置標(biāo)志。
我們還看到了上述基本算術(shù)指令中沒有的其他三個(gè)操作碼:UMULL 是“非基本”算術(shù)指令,B 和 BNE 不是算術(shù)指令。
2.5條件代碼
每條 ARM 指令都可以包含一個(gè)條件代碼,指定該操作僅在某些標(biāo)志組合成立時(shí)才發(fā)生。您可以通過將條件代碼包含在操作碼中來指定條件代碼。它通常出現(xiàn)在操作碼的末尾,但它在基本算術(shù)指令上的可選 S 之前。條件代碼的名稱是基于假設(shè)標(biāo)志是基于 CMP 或 SUBS 指令設(shè)置的。
到目前為止,我們看到的這個(gè)條件代碼的唯一實(shí)例是 BNE 指令:在這種情況下,我們有一個(gè)用于分支的 B 指令,但只有在 Z 標(biāo)志為 0 時(shí)才會(huì)發(fā)生分支。
但是 ARM 的 ISA 也允許我們將條件代碼應(yīng)用于其他操作碼。例如,ADDEQ 表示如果 Z 標(biāo)志為 1,則執(zhí)行加法。在非分支指令上使用條件代碼的一種常見情況是使用 Euclid 的 GCD 算法計(jì)算兩個(gè)數(shù)字的最大公約數(shù)。
傳統(tǒng)的匯編語言翻譯只會(huì)在分支指令上使用條件代碼。
但是,以下是更短且更有效的翻譯。
由于兩個(gè)原因,這更有效。更明顯的是,每次迭代執(zhí)行的指令數(shù)量更少(四個(gè)對(duì)五個(gè))。但另一個(gè)原因來自現(xiàn)代處理器在執(zhí)行當(dāng)前指令時(shí)“預(yù)取”下一條指令的事實(shí)。但是,由于無法確定下一條指令的位置,因此分支會(huì)中斷此過程。第二次翻譯涉及更少的分支指令,因此預(yù)取指令的問題更少。
3存儲(chǔ)(Memory)
我們已經(jīng)了解了如何構(gòu)建執(zhí)行基本數(shù)值計(jì)算的匯編程序。我們現(xiàn)在將轉(zhuǎn)向檢查匯編程序如何訪問內(nèi)存。
3.1基本內(nèi)存指令
ARM 支持通過兩條指令 LDR 和 STR 訪問內(nèi)存。LDR 指令從內(nèi)存中加載數(shù)據(jù),STR 將數(shù)據(jù)存儲(chǔ)到內(nèi)存中。每個(gè)都有兩個(gè)參數(shù)。第一個(gè)參數(shù)是數(shù)據(jù)寄存器:對(duì)于一條 LDR 指令,加載的數(shù)據(jù)放在這個(gè)寄存器中;對(duì)于 STR 指令,在該寄存器中找到的數(shù)據(jù)存儲(chǔ)到內(nèi)存中。第二個(gè)參數(shù)表示包含正在訪問的內(nèi)存地址的寄存器;它將使用括號(hào)中的寄存器名稱寫入。
有關(guān)這些指令如何工作的示例,假設(shè)我們需要一個(gè)匯編程序片段,它將整數(shù)添加到數(shù)組中。我們假設(shè) R0 保存數(shù)組的第一個(gè)整數(shù)的地址,R1 保存數(shù)組中整數(shù)的個(gè)數(shù)。
在這個(gè)片段中,我們使用 R4 來保存到目前為止的整數(shù)之和。在 LDR 指令中,我們?cè)?R0 中查找內(nèi)存地址并將在該地址找到的數(shù)據(jù)加載到 R2 中。然后我們將此值添加到 R4 中。然后,我們移動(dòng) R0 使其包含數(shù)組中下一個(gè)整數(shù)的內(nèi)存地址;我們將 R0 增加四,因?yàn)槊總€(gè)整數(shù)消耗四個(gè)字節(jié)的內(nèi)存。最后,我們遞減 R1,這是要從數(shù)組中讀取的整數(shù)個(gè)數(shù),如果還有整數(shù),我們重復(fù)這個(gè)過程。
LDR 和 STR 都加載和存儲(chǔ) 32 位值。還有使用 8 位值、LDRB 和 STRB 的說明;這些主要用于處理字符串。下面是 C 的 strcpy 函數(shù)的實(shí)現(xiàn);我們假設(shè) R0 保存目標(biāo)數(shù)組的第一個(gè)字符的地址,而 R1 保存源字符串的第一個(gè)字符的地址。我們希望繼續(xù)復(fù)制,直到我們復(fù)制終止 NUL 字符(ASCII 0)。
3.2尋址模式
在上一節(jié)的示例中,我們通過將寄存器名稱括在括號(hào)中來提供地址。但是 ARM 也允許使用其他幾種方式來指示內(nèi)存地址。每一種這樣的技術(shù)都稱為尋址模式。簡(jiǎn)單地命名保存內(nèi)存地址的寄存器的技術(shù)就是一種這樣的尋址模式,稱為寄存器尋址,但還有其他的。
其中之一是縮放的寄存器偏移量,我們?cè)诶ㄌ?hào)中包括一個(gè)寄存器、另一個(gè)寄存器和一個(gè)移位值。為了計(jì)算要訪問的內(nèi)存地址,處理器獲取第一個(gè)寄存器,并將根據(jù)移位值移位的第二個(gè)寄存器添加到它。(括號(hào)中提到的寄存器都不會(huì)改變值。)當(dāng)訪問知道數(shù)組索引的數(shù)組時(shí),這種尋址模式很有用。我們可以修改之前的例程,將整數(shù)添加到數(shù)組中,以利用這種尋址模式。
對(duì)于循環(huán)的每次迭代,我們首先減少循環(huán)索引 R1。然后我們使用縮放的寄存器偏移量檢索數(shù)組條目處的元素:我們使用 R0 作為我們的基數(shù),并將 R1 添加到它左移兩個(gè)位置。我們將 R1 左移兩位,使 R1 乘以 4;畢竟,數(shù)組中的每個(gè)整數(shù)都是四個(gè)字節(jié)長(zhǎng)。將加載的值添加到 R4 中,累加總數(shù)后,如果 R1 尚未達(dá)到 0,我們重復(fù)循環(huán)。
除了使用不同的尋址模式之外,這個(gè)版本的代碼在三個(gè)方面與我們的原始實(shí)現(xiàn)略有不同。首先,它以相反的順序加載數(shù)組中的數(shù)字——也就是說,它首先加載數(shù)組中的最后一個(gè)數(shù)字。其次,R0 在片段的過程中保持不變。最后,它會(huì)更快一些,因?yàn)樗诿看窝h(huán)迭代中減少了一條指令。
立即后索引尋址是另一種尋址模式。為了在匯編語言中表示這種模式,我們?cè)诶ㄌ?hào)后面加上逗號(hào)和正或負(fù)立即數(shù)。在執(zhí)行指令時(shí),處理器仍然會(huì)訪問在寄存器中找到的內(nèi)存地址,但在訪問內(nèi)存后,地址寄存器會(huì)根據(jù)立即數(shù)增加或減少。
我們的 strcpy 實(shí)現(xiàn)是一個(gè)有用的示例,其中立即后索引尋址很有用:在我們存儲(chǔ)到 R0 之后,我們希望 R0 在下一次迭代中增加 1;同樣,在我們從 R1 加載之后,我們希望 R1 增加 1。我們可以使用立即后索引尋址來避免我們?cè)缙诎姹镜膬蓚€(gè) ADD 指令。
目前ARM處理器支持9種尋址方式,分別是立即數(shù)尋址、寄存器尋址、寄存器偏移尋址、寄存器間接尋址、基址變址尋址、多寄存器尋址、相對(duì)尋址、堆棧尋址和塊拷貝尋址。
尋址方式介紹:https://www.cnblogs.com/laojie4321/archive/2012/04/05/2432957.html
對(duì)于那些涉及移位的尋址模式,移位技術(shù)與算術(shù)指令(LSL、LSR、ASR、ROR、RRX)一樣。但移位距離不能根據(jù)寄存器:距離必須是立即數(shù)。
3.3. 初始化內(nèi)存
我們經(jīng)常希望保留內(nèi)存來保存程序中的數(shù)據(jù)。為此,我們使用指令:指令匯編器做一些事情,而不是簡(jiǎn)單地將匯編語言指令翻譯成相應(yīng)的機(jī)器代碼。一種有用的指令是 DCD,它將一個(gè)或多個(gè) 32 位數(shù)值插入機(jī)器代碼輸出。(DCD 神秘地代表定義常量雙字。)
在這個(gè)例子中,我們創(chuàng)建了標(biāo)簽 primes,它將對(duì)應(yīng)于 2 放入內(nèi)存的地址。在接下來的四個(gè)字節(jié)中放置整數(shù) 3,然后是 5,依此類推。
在我們的程序中,我們希望將數(shù)組的地址加載到寄存器中;為此,我們將素?cái)?shù)添加到程序計(jì)數(shù)器 PC(與 R15 同義)中。下面的片段將第五個(gè)素?cái)?shù) (11) 加載到 R1 中。
另一個(gè)值得一提的指令是 DCB,用于將字節(jié)加載到內(nèi)存中。因此,我們可以編寫以下內(nèi)容。
但是,我們只為每個(gè)數(shù)字使用一個(gè)字節(jié),因此我們只能包含介于 -128 和 127 之間的數(shù)字。我們還可以在列表中包含一個(gè)字符串;字符串的每個(gè)字符將占用一個(gè)字節(jié)的內(nèi)存。
請(qǐng)注意我們?nèi)绾卧谧址蟀?0。沒有這個(gè),字符串不會(huì)被 NUL 字符終止。
這里還有一個(gè)值得注意的指令是百分號(hào) %。當(dāng)您希望保留一塊內(nèi)存但您不關(guān)心內(nèi)存的初始值時(shí),這很有用。
3.4多寄存器內(nèi)存指令
ARM ISA 還包括允許在同一指令中加載或存儲(chǔ)多個(gè)值的指令。LDMIA 指令就是這樣一條指令:它允許從另一個(gè)寄存器中指定的地址開始加載到多個(gè)寄存器中。在下面的使用示例中,我們將代碼用于添加數(shù)組的整數(shù),并使用 LDMIA 對(duì)其進(jìn)行修改,以便在循環(huán)的每次迭代中處理四個(gè)整數(shù)。這種策略允許程序使用更少的指令運(yùn)行,但代價(jià)是更高的復(fù)雜性。
在執(zhí)行上面的 LDMIA 指令時(shí),ARM 處理器在 R0 寄存器中查找地址。它將從該地址開始的四個(gè)字節(jié)加載到 R5,接下來的四個(gè)字節(jié)加載到 R6,接下來的四個(gè)字節(jié)加載到 R7,接下來的四個(gè)字節(jié)加載到 R8。同時(shí),R0 前移 16 個(gè)字節(jié),因此在下一次迭代中,LDMIA 指令會(huì)將接下來的四個(gè)字加載到寄存器中。
大括號(hào)內(nèi)可以是任何寄存器列表,使用破折號(hào)表示寄存器范圍,并使用逗號(hào)分隔范圍。因此,指令 LDMIA R0!, { R1-R4, R8, R11-R12 } 將從內(nèi)存中加載 7 個(gè)字。寄存器列出的順序并不重要;即使我們寫 LDMIA R0!, { R11-R12, R8, R1-R4 },R1 也會(huì)收到從內(nèi)存中加載的第一個(gè)字。
在我們的例子中,R0 后面的感嘆號(hào)可以省略;如果省略,則地址寄存器不會(huì)被指令更改。也就是說,R0 將繼續(xù)指向數(shù)組中的第一個(gè)整數(shù)。在上面的示例中,我們希望 R0 發(fā)生變化,使其指向下一個(gè)由四個(gè)整數(shù)組成的塊以進(jìn)行下一次迭代,因此我們包含了感嘆號(hào)。
另一條指令是 STMIA,它將幾個(gè)寄存器存儲(chǔ)到內(nèi)存中。在下面的示例中,我們將數(shù)組中的每個(gè)數(shù)字移動(dòng)到下一個(gè)位置;因此,數(shù)組<2,3,5,7>變?yōu)?lt;0,2,3,5>。
請(qǐng)注意 LDMIA 指令如何省略感嘆號(hào),以便不修改 R0。這樣 STMIA 將存儲(chǔ)到剛剛加載到寄存器中的相同地址范圍內(nèi)。STMIA 指令帶有感嘆號(hào),因?yàn)楸仨毿薷?R0 以準(zhǔn)備循環(huán)的下一次迭代。
ARM 處理器包括多重加載和多重存儲(chǔ)指令的四種變體;LDM 和 STM 縮寫必須始終表示這四種變體之一。
LDMIA, STMIA 之后遞增:我們從命名地址開始加載,并不斷增加地址。
LDMIB, STMIB 之前的增量:我們開始從比命名地址多四個(gè)的地址開始加載,并不斷增加地址。LDMDA, STMDA 減量后:我們從命名地址開始加載并進(jìn)入遞減地址。
LDMDB、STMDB 遞減前:我們從比指定地址少四個(gè)開始加載到遞減地址。
在所有四種模式中,編號(hào)最高的寄存器始終對(duì)應(yīng)于內(nèi)存中的最高地址。因此,指令 LDMDA R0, { R1-R4 } 會(huì)將 R4 放入由 R0 命名的地址,將 R3 放入 R0 - 4,依此類推。
正如我們將在研究子程序時(shí)看到的那樣,當(dāng)我們想將一塊未使用的內(nèi)存用作堆棧時(shí),不同的變體特別有用。
部分參考文獻(xiàn)
https://www.cnblogs.com/laojie4321/archive/2012/04/05/2432957.html
http://aratxa.ii.uam.es/~gdrivera/sed/docs/ARMBook.pdf
https://students.mimu.edu.pl/~zbyszek/asm/arm/asm_guide.pdf