#2. ARM彙編語言
###2.1. ARM命名規則
ARM是Advanced RISC Machines的縮寫,是典型的RISC(Reduced Instruction Set Computer)架構的CPU。ARM的版本控制的命名規則分成兩類。 一類是基於ARM 架構的版本命名規則,它是一種架構設計的總和;另一類是基於某ARM架構版本的系列處理器的命名規則。
###2.1.1. ARM架構命名規則
| ARMv | n | variants | x(variants) |
ARM架構的命名由四部分組成:
常見的變種有:
例如,ARMv5TxP表示ARM指令集版本為5,支持Thumb指令集,不支持LDRD, MCRR, MRRC, PLD和STRD指令。
###表 1. ARM架構版本 版本 說明
版本 | 說明 |
---|---|
ARMv1[a] | 該版本的原型機是ARM1,沒有用於商業產品 |
ARMv2 | 對v1版進行了擴展,包含了對32位結果的乘法指令和協處理器指令的支持。 |
ARMv3 | ARM公司第一個微處理器ARM6核心是版本3的,它作為IP核、獨立的處理器、具有片上高速緩存、MMU和寫緩衝的集成CPU。 |
ARMv4 | 應用非常廣泛,但僅支持32位ARM指令集。 |
ARMv4T | 同時支持ARM指令集4和Thumb指令集1。 |
ARMv5T | 同時支持ARM指令集5和Thumb指令集2。添加計算開始0個數的clz指令和軟中斷bkpt指令 |
ARMv5TExP | 在ARMv5T基礎上添加增強型DSP指令,但不支持LDRD, MCRR, MRRC, PLD, 和STRD指令 |
ARMv5TE | 在ARMv5T基礎上添加增強型DSP指令。 |
ARMv5TEJ | 在ARMv5TE基礎上支持Jazelle指令集。 |
ARMv6 | 同時支持ARM指令集6和Thumb指令集3。增加和增強了相當多的單指令多數據處理指令。 |
ARMv6K | 添加支持多處理的ARM指令集,以及一些額外的內存模型的特性。 |
ARMv6T2 | 推出Thumb-2技術。為16位和32位指令碼的結合,免去了狀態切換之複雜度。 |
ARMv7A | 高性能運算,針對手機,PDA等手持便攜設備。增加視頻編解碼和3D處理的NEON多媒體技術。 |
ARMv7R | 減少輸入輸出延遲,提高指令預測精度,重視實時處理,針對網略設備,汽車儀表等應用。支持PMSA。 |
ARMv7M | 低成本微處理器應用,去除NEON指令集。 |
[a] ARMv1至ARMv3版本已經廢棄,不再被使用。 |
ARM處理器的命名規則如下:
ARM{x}{y}{z}{T}{D}{M}{I}{E}{J}{F}{-S}
各組成部分解釋如下:
ARM使用一種基於數字的命名法。在早期(1990s),還在數字後面添加字母后綴,用來進一步明細該處理器支持的特性。就拿ARM7TDMI來說,T代表Thumb指令集,D是說支持JTAG調試(Debugging),M意指快速乘法器,I則對應一個嵌入式ICE模塊。後來,這4項基本功能成了任何新產品的標配,於是就不再使用這4個後綴相當於默許了。但是新的後綴不斷加入,包括定義存儲器接口的,定義高速緩存的,以及定義"緊耦合存儲器(TCM)"的,於是形成了新一套命名法,這套命名法一直使用至今。比如ARM1176JZF-S,它實際上默認就支持TDMI功能,除此之外還支持JZF。
從ARMv6指令集才開始支持SMP,這是由於從該指令集開始提供ldrex和strex系列獨佔(Exclusive)訪問指令,該系列指令可以保證多CPU在同時訪問內存時是互斥的。這也可以從內核代碼中得到應徵:
arch/arm/include/asm/atomic.h
#if __LINUX_ARM_ARCH__ >= 6
......
#else /* ARM_ARCH_6 */
#include <asm/system.h>
#ifdef CONFIG_SMP
#error SMP not supported on pre-ARMv6 CPUs
#endif
......
基於ARMv7架構的ARM處理器不再沿用過去的數字命名方式,而是冠以Cortex前綴,基於ARMv7A的處理器成為Cortex-A系列,基於ARMv7R的處理器成為Cortex-R系列,基於ARMv7M的處理器成為Cortex-M系列,
##2.2. 程序狀態寄存器
當前程序狀態寄存器CPSR(Current Program Status Register)可以在任何工作模式下通過mrs指令訪問。它包含了條件碼(Condition code)標誌位,禁止中斷標誌位,處理器工作模式位以及其他狀態和控制信息。每一箇中斷模式還擁有一個單獨的保存的程序狀態寄存器SPSR(Saved Program Status Register),當發生該模式的中斷時,它被用來備份CPSR的值。用戶模式和系統這兩種模式不是中斷模式,所以沒有SPSR寄存器。在這兩種模式下嘗試訪問SPSR結果未知。
圖 1. CPSR和SPSR比特位
如上圖所示,程序狀態寄存器比特位按訪問權限分為四種:
關於J和T的詳細組合如下表所示:
表 2. Memory Hierarchy
J | T | 指令集 |
---|---|---|
0 | 0 | ARM |
0 | 1 | Thumb |
1 | 0 | Jazelle |
1 | 1 | 保留 |
2.3. ARM指令格式
ARM指令的基本格式如下:
<opcode>{<cond>}{S} <Rd>, <Rn/#Num>{, <operand2>}
其中<>號內的項是必須的,{}號內的項是可選的,/表示任選其中之一。各項的說明如下:
靈活的使用第2個操作數“operand2”能夠提高代碼效率。它有如下的形式:
比如:subnes r1, r1, #0xd; 如果Z標誌為0(不等),則執行sub r1, r1, #0xd並根據結果更新CPSR寄存器。 參考資料:ARM嵌入式系統基礎教程(第2版)PPT
指令中的執行條件由CPSR寄存器中的狀態位控制,參考狀態位。 表 3. 條件碼錶
條件碼(二進制) | 條件助記符 | 標誌位 | 含義 |
---|---|---|---|
0000 | eq | Z==1 | 相等 |
0001 | ne | Z==0 | 不等 |
0010 | cs/hs | C==1 | 無符號數大於或等於 |
0011 | cc/lo | C==0 | 無符號數小於 |
0100 | mi | N==1 | 負數 |
0101 | pl | N==0 | 正數或0 |
0110 | vs | V==1 | 溢出 |
0111 | vc | V==0 | 無溢出 |
1000 | hi | C==1 且 Z==0 | 無符號數大於 |
1001 | ls | C==0 且 Z==1 | 無符號數小於或等於 |
1010 | ge | N==V | 有符號數大於或等於 |
1011 | lt | N!=V | 有符號數小於 |
1100 | gt | Z==0 且 N==V | 有符號數大於 |
1101 | le | Z==1 或 N!=V | 有符號數小於或等於 |
1110 | al | 無條件 | 無條件執行(默認) |
##2.4. 測試用例
為了進行標誌位等的驗證,這裡使用一個通用的示例程序,它包含兩個文件:
main.S
.section .text
.align 2
.global main
main:
mov r0, #12
mov r1, #34
mov r2, #56
bl print
mov r0, #0
bl quit
注意到main.S中的子程序名為main,這是因為GCC編譯器必須將main作為入口,否則無法使用Glibc的庫函數。注意到mov命令,根據ATPS規則,這裡是給print函數準備參數,這裡可以更改r0和r1中的值,通過print來打印出它們。
print.c
#include <stdio.h>
#include <stdlib.h>
char regs[32][8] =
{
"M0", "M1", "M2", "M3", "M4", "T", "F", "I", /* 0-7 */
"A", "E", " ", " ", " ", "CV", " ", " ", /* 8-15 */
"|", "G", "E", "|", " ", " ", " ", " ", /* 16-23 */
"J", " ", " ", "Q", "V", "C", "Z", "N" /* 24-31 */
};
void format(char *regname, int reg)
{
int i = 0;
if(!reg)
return;
printf("%3s", regname);
for(i = 31; i >= 0; i--)
printf("%3x", reg & (1 << i) ? 1 : 0);
printf("\n");
}
void print(int r0, int r1, int r2)
{
int i = 0;
printf(" General out:\n");
printf(" Hex\tr0 0x%08x\tr1 0x%08x\tr2 0x%08x\n", r0, r1, r2);
printf(" Oct\tr0 %8d\tr1 %8x\tr2 %8d\n\n", r0, r1, r2);
if(r0 || r1 || r2)
{
printf(" Format out:\n ");
for(i = 31; i >= 0; i--)
printf("%3d", i);
printf("\n ");
for(i = 31; i >= 0; i--)
printf("%3s", regs[i]);
printf("\n");
}
format("r0", r0);
format("r1", r1);
format("r2", r2);
}
void quit(int err)
{
exit(err);
}
print.c中的print通過Glibc庫函數printf打印出結果,而quit則通過標準庫函數exit退出運行。另外編譯命令和測試結果如下:
arm-linux-gcc print.c main.S -o test
# ./test
General out:
r0 0x0000000c r1 0x00000022 r2 0x00000038
Format out:
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
N Z C V O CV I F T M4 M3 M2 M1 M0
r0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0
r1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0
r2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0
藉助c語言的格式化輸出,能夠很好的觀察狀態位的變化。 2.4.1. CPSR操作
修改main.S指令的如下,獲取cpsr和spsr寄存器標誌位:
main:
mrs r0, cpsr
mrs r1, spsr
mov r2, #0
......
這裡主要用到了mrs命令,它可以直接獲取當前程序狀態寄存器cpsr和保存的程序狀態寄存器spsr,用戶模式並沒有spsr,所以它的值就是cpsr。以上程序結果如下所示:
General out:
r0 0x20000010 r1 0x20000010 r2 0x00000000
Format out:
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
N Z C V O CV I F T M4 M3 M2 M1 M0
r0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0
r1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0
可以看到r0和r1的值相同,並且只設置了C標誌。而M模式位為0x10,為用戶模式。通常有些情況下需要設置cpsr,可以通過msr指令實現。
mov r0, #0
msr cpsr_f, r0
......
注意這裡的cpsr_f指明操作標誌位[31:24],這裡將用戶模式可以設置的位於[31:24]中的所有位清零。這裡的f指明瞭傳送的區域,它可以為以下幾種值:
##2.4.2. 含s置位的指令
修改main.S指令的以測試含s置位指令:
main:
mov r0, #0 /* without 's' */
mrs r1, cpsr
movs r0, #0 /* with s */
mrs r2, cpsr
bl print
mov r0, #0
bl quit
測試的結果如下所示:
General out:
r0 0x00000000 r1 0x20000010 r2 0x60000010
Format out:
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
N Z C V O CV I F T M4 M3 M2 M1 M0
r1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0
r2 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0
可以看到第一條指令並沒有影響標誌位,而movs指令則改變了Z標誌位,也即當前的運算結果為0。 ##2.4.3. Z標誌條件編碼
首先通過movs賦值r1為0,此時Z標誌位1,r1記錄此時的cpsr值。然後改變r2的值為0,以實現測試moveq和movne的測試。
.macro testz, cmd
movs r0, #0
mrs r1, cpsr
mov r2, #0
\cmd r2, #1
bl print
.endm
.global main
main:
testz moveq
testz movne
mov r0, #0
bl quit
moveq當Z==1時才被執行,顯然此時被執行,r2的值被賦值為1。接著測試movne,它只有在Z==0時才被執行,所以這裡r2的值並沒有被賦值為1,而依然是0。
General out:
r0 0x00000000 r1 0x60000010 r2 0x00000001
Format out:
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
N Z C V O CV I F T M4 M3 M2 M1 M0
r1 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0
r2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
General out:
r0 0x00000000 r1 0x60000010 r2 0x00000000
Format out:
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
N Z C V O CV I F T M4 M3 M2 M1 M0
r1 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0
其他標誌位可以使用相同方法進行測試。 ##2.5. 原碼和補碼
對於計算機而言,最根本的動作就是高低電位的跳變,從而表示0和1兩種狀態。在計算機中,所有的負數都用補碼錶示,而負數的原碼對於計算機來說沒有任何意義。對於人來說,通常使用十進制,十進制在表示負數時有符號-來表示。但是對於針對計算機而設計的二進制來說,並沒有所謂的符號-來表示負數,而是要拿出一位來表示數的正負——最高位。考慮以下的代碼:
int main()
{
int tmp = -0x1;
printf("tmp:%x\n", tmp);
return 0;
}
儘管編譯時沒有錯誤,但是通常人們並不使用針對計算機設計的二,八或者十六進制添加符號-來表示負數,而是使用十進制-1,或者直接指定符號位:0x80000001。但是注意通過二,八或者十六進製表示的沒有符號-的負數,永遠都被直接編譯進指令中作為補碼使用,否則編譯器將把它們轉換為補碼後再放入指令中。所以0x80000001並不表示-1,而是-2147483647這樣一個數字,這是因為0x80000001是它的補碼,而非-1的。看來補碼的引入並沒有符合人的使用習慣,那麼引入補碼的意義是什麼呢?
模的概念:把一個計量單位稱之為模或模數。例如,時鐘是以12進制進行計數循環的,即以12為模。在時鐘上,時針加上(正撥)12的整數位或減去(反撥)12的整數位,時針的位置不變。14點鐘在捨去模12後,成為(下午)2點鐘(14=14-12=2)。從0點出發逆時針撥10格即減去10小時,也可看成從0點出發順時針撥2格(加上2小時),即2點(0-10=-10=-10+12=2)。因此,在模12的前提下,-10可映射為+2。由此可見,對於一個模數為12的循環系統來說,加2和減10的效果是一樣的;因此,在以12為模的系統中,凡是減10的運算都可以用加2來代替,這就把減法問題轉化成加法問題了(注:計算機的硬件結構中只有加法器,所以大部分的運算都必須最終轉換為加法)。10和2對模12而言互為補數。
同理,計算機的運算部件與寄存器都有一定字長的限制(假設字長為8),因此它的運算也是一種模運算。當計數器計滿8位也就是256個數後會產生溢出,又從頭開始計數。產生溢出的量就是計數器的模,顯然,8位二進制數,它的模數為28=256。在計算中,兩個互補的數稱為"補碼"。 計算機引入補碼後:
##2.6. 條件碼標誌位
N(Negative)、Z(Zero)、C(Carry)及V(oVerflow)統稱為條件標誌位。大部分的ARM 指令可以根據CPSR 中的這些條件標誌位來選擇性地執行。它們在以下兩種情況下被改變:
##2.6.1. N負數標誌位測試
測試N標誌位的思想很簡單,就是根據指令結果的符號位為0和1來查看N標誌位。
main:
/* save orig cpsr->r0 */
mrs r0, cpsr
/* give a non-Neg value get cpsr->r1 */
movs r3, #1
mrs r1, cpsr
/* give a Neg value get cpsr->r2 */
movs r3, #-1
mrs r2, cpsr
......
輸出結果如下所示,顯然r1的N標誌位為0,而r2則變為了1。
General out:
Hex r0 0x20000010 r1 0x20000010 r2 0xa0000010
Oct r0 536870928 r1 20000010 r2 -1610612720
Format out:
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
N Z C V Q J | E G | CV E A I F T M4 M3 M2 M1 M0
r0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0
r1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0
r2 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0
##2.6.2. Z零標誌位測試
測試N標誌位的思想很簡單,就是根據指令結果的是否為0來查看Z標誌位。
main:
/* save orig cpsr->r0 */
mrs r0, cpsr
/* give a non-Zero let cpsr->r1 */
movs r3, #1
mrs r1, cpsr
/* give a Zero let cpsr->r2 */
movs r3, #0
mrs r2, cpsr
......
輸出結果如下所示,顯然r1的Z標誌位為0,而r2則變為了1。
General out:
Hex r0 0x20000010 r1 0x20000010 r2 0x60000010
Oct r0 536870928 r1 20000010 r2 1610612752
Format out:
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
N Z C V Q J | E G | CV E A I F T M4 M3 M2 M1 M0
r0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0
r1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0
r2 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0
##2.6.3. C進位標誌位測試
測試C標誌位相對複雜,包括四種情況。
###2.6.3.1. 加法指令進位
加法指令add將所有操作數當做無符號處理,CPU內部使用CarryFrom(Rn + shifter_operand)來計算是否進位,其中rn和shifter_operand分別為第一操作數和第二操作數。測試思想為給第一操作數賦值為0xfffffffe,這保證在第一次加1後,不會進位,再次加1後必定進位。另外注意到r0存數了0xfffffffe + 2的結果。CarryFrom的算法很簡單,在ARM指令集中它通過比較Rn + shifter_operand > 232-1的值決定是否上溢。對於8位和16位的語出操作記作CarryFrom8和CarryFrom16,它們通常被用在字節和半字操作中[1]。
main:
/* clean all condition flags */
msr cpsr_f, #0
/* make sure the add result won't carry and
let cpsr->r1 */
mov r3, #0xfffffffe
adds r3, r3, #1
mrs r1, cpsr
/* carried and let cpsr->r2 */
adds r3, r3, #1
mov r0, r3
......
輸出結果如下所示,顯然r1的C標誌位為0,而由於出現進位r2則變為了1。
Hex r0 0x00000000 r1 0x80000010 r2 0xbeb62ebc
Oct r0 0 r1 80000010 r2 -1095356740
Format out:
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
N Z C V Q J | E G | CV E A I F T M4 M3 M2 M1 M0
r1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0
r2 1 0 1 1 1 1 1 0 1 0 1 1 0 1 1 0 0 0 1 0 1 1 1 0 1 0 1 1 1 1 0 0
###2.6.3.2. 減法指令借位
減法指令add將所有操作數當做無符號處理,CPU內部使用NOT BorrowFrom(Rn - shifter_operand)來計算是否借位,其中rn和shifter_operand分別為第一操作數和第二操作數。BorrowFrom的算法很簡單,在ARM指令集中它通過比較Rn - shifter_operand < 0的值決定是否借位。如果發生借位,那麼C標誌為0,否則為1。
/* clean all condition flags */
msr cpsr_f, #0
/* make sure the sub result won't borrow and
let cpsr->r1 */
mov r3, #0x1
subs r3, r3, #1
mrs r1, cpsr
/* borrowed let cpsr->r2 */
subs r3, r3, #1
mrs r2, cpsr
mov r0, r3
......
測試原理:將作出的被減數設置為1,第一次減1時,不發生借位置C為1,此時r3內容為0,然後再減去1,顯然發生借位,所以置C為1。
General out:
Hex r0 0xffffffff r1 0x60000010 r2 0x80000010
Oct r0 -1 r1 60000010 r2 -2147483632
Format out:
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
N Z C V Q J | E G | CV E A I F T M4 M3 M2 M1 M0
r0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
r1 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0
r2 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0
注意r0中為1-2的值,也即為-1的補碼。
###2.6.3.3. 移位指令
###2.6.3.4. V溢出位
在加減運算中,通過OverflowFrom(Rn + shifter_operand)來判斷是否溢出。OverflowFrom分為兩種情況:加法運算中,如果操作數的符號位相同,但是結果的符號位發生改變;在減法運算中,如果兩操作數符號位不同,結果與第一個操作數的符號位也不同,則溢出,也即超出補碼錶示範圍。
main:
/* clean all condition flags */
msr cpsr_f, #0
/* make sure the adds result won't overflow */
mov r3, #0x6ffffffe
adds r3, r3, #1
mrs r1, cpsr
/* overflowed let cpsr->r2 */
adds r3, r3, #1
mrs r2, cpsr
mov r0, r3
......
這裡以加法為例,首先設置r3為0x7ffffffe,確保第一次加1時,不會溢出,而第二次則會發生結果符號位的改變。
General out:
Hex r0 0x80000000 r1 0x00000010 r2 0x90000010
Oct r0 -2147483648 r1 10 r2 -1879048176
Format out:
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
N Z C V Q J | E G | CV E A I F T M4 M3 M2 M1 M0
r0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
r1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0
r2 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0
顯然r1中的V為0,而r2中的V為1。 #2.7. 跳轉指令
##2.7.1. b和bl指令
b(Branch)和bl(Branch and Link)指令實現分枝跳轉,並且支持條件跳轉。bl指令與b指令的區別是它在跳轉前會將當前pc的下一條指令地址存入lr寄存器,指令編碼的區別就在於L位是否為1。所以bl適合子程序調用。bl的指令格式如下,signed_immed_24表示24位有符號立即數,顯然它可以表示的大小為+-8M,由於ARM的指令長度總是32bits,所以可以表示+/-32MB的地址空間。 31 28 27 26 25 24
+-----+--------+---+--------------------------------+
|cond | 1 0 1| L | signed_immed_24 |
+-----+--------+---+--------------------------------+
bl指令的偽代碼表示如下:
if ConditionPassed(cond) then
if L == 1 then
LR = address of the instruction after the branch instruction
PC = PC + (SignExtend_30(signed_immed_24) << 2)
bl指令格式如下,它的參數是子程序的標籤,並不接受立即數,所以這牽涉到signed_immed_24計算方法。顯然,它是在編譯器編譯過程中確定。
bl{cond} label
offset之所以要減去8,是因為PC + (SignExtend_30(signed_immed_24) << 2)中的pc是指當前正在執行的pc(excute),而pc(excute)= pc(fetch) + 8。指令中的label對應的地址相當於pc(fetch)。這裡用一個簡單的例子說明:
.section .text
.align 2
.global main
main:
mov r0, #12
mov r1, #34
mov r2, #56
bl over
.global over
over:
mov r0, #0
以上是一個簡單的跳轉測試,over標籤與bl指令地址相差1個指令,4個字節。反彙編代碼如下:
00008790 <main>:
8790: e3a0000c mov r0, #12 ; 0xc
8794: e3a01022 mov r1, #34 ; 0x22
8798: e3a02038 mov r2, #56 ; 0x38
879c: ebffffff bl 87a0 <over>
000087a0 <over>:
87a0: e3a00000 mov r0, #0 ; 0x0
ebffffff就是指令碼,eb無需解釋了,而0xffffff就是signed_immed_24,它通過(0x87a0 - 0x879c - 8) >> 2計算而得,這個值就是-1,由於指令中的操作數都是採用補碼錶示,所以0xffffff就是-1的補碼。修改例子如下,此時地址相差3個指令,這保證偏移的指令數為正整數1。
.section .text
.align 2
.global main
main:
mov r0, #12
bl over
mov r1, #34
mov r2, #56
.global over
over:
mov r0, #0
觀察反彙編的結果,顯然指令碼為eb000001,正數1的補碼和原碼相同。
00008790 <main>:
8790: e3a0000c mov r0, #12 ; 0xc
8794: eb000001 bl 87a0 <over>
8798: e3a01022 mov r1, #34 ; 0x22
879c: e3a02038 mov r2, #56 ; 0x38
000087a0 <over>:
87a0: e3a00000 mov r0, #0 ; 0x0
圖 3. bl跳轉示意圖
b指令的存在意義在於,有時候無需返回,或者lr寄存器另作它用,此時如需返回需要顯式保存下一 指令地址到其他寄存器。
##2.7.2. ldr和adr指令
ldr和adr是另兩個常用來實現跳轉的指令,嚴格來說ldr指令有兩種,根據操作數,它可以是指令,也可以是偽指令。 作為指令的ldr的語法格式如下,通常實現把一個32位的字從內存裝入一個寄存器。
ldr{<cond>} <Rd>,<addr_mode>
指令編碼格式如下:
31 28 27 26 25242322212019 1615 1211 0
+-----+------+-+-+-+-+-+-+----+----+----------------+
|cond | 0 1 |1|P|U|0|W|1| Rn | Rd | addr_mode |
+-----+------+-+-+-+-+-+-+----+----+----------------+
ldr指令根據addr_mode所確定的地址模式將一個32位字讀取到指令中的目標寄存器Rd。如果指令中的尋址方式確定的地址不是字對齊的,則讀出的數值要進行循環右移。所移位數為尋址方式確定的地址bits[1:0]的8倍,也就是說處理器將取到的數值作為字的最低位處理。
addr_mode它確定了指令編碼中的I、P、U、W、Rn和addr_mode位。所有的尋址模式中,都會 確定一個基址寄存器Rn,通常為pc。考慮如下的ldr測試程序:
......
.global main
main:
mov r0, pc
ldr r1, [r0]
ldr r2, main
ldr r0, =main
bl print
b quit
編譯後的反彙編程序如下,顯然對於第一個還有第二個指令來說,它們均是作為指令編譯的,並且是相對於pc(當前正在取指的指令地址,而非正在執行的當前ldr指令的地址)寄存器進行偏移。而第三天指令則是將main的絕對地址0x00008790裝載到r0。所以可以通過ldr指令進行相對偏移來跳轉到C函數
00008790 <main>:
8790: e1a0000f mov r0, pc
8794: e5901000 ldr r1, [r0]
8798: e51f2010 ldr r2, [pc, #-16] ; 8790 <main>
879c: e59f0004 ldr r0, [pc, #4] ; 87a8 <main+0x18>
87a0: ebffff9d bl 861c <print>
87a4: eafffff2 b 8774 <quit>
87a8: 00008790 .word 0x00008790
#2.8. 協處理器指令
#2.9. ARM彙編偽指令
##2.9.1. .macro
宏指令是由.macro和.endm來定義開始和結束的一組指令的集合。它類似於C語言中的宏函數,使用它將可以:
偽指令格式:
.macro macroname {$param1} {$param2} ...
@commands
.mend
macroname是定義的宏名,$paramx是宏指令的參數,當宏指令被展開式將被替換成相應的值,類似於函數中的形式參數。一個例子是Linux內核中用於打印信息的kputc宏命令。在使用宏參數時必須這樣“$param”表示參數。
.macro kphex,val,len
mov r0, \val
mov r1, #\len
bl phex
.endm
在C語言和彙編語言的交互調用中,使用宏可以簡化參數的傳遞。可以使用.if宏開關來定義宏指令,.exitm可以跳出宏。一個進行移位運算的例子如下:
.macro shiftleft reg, shift
.if \shift < 0
mov \reg, \reg, asr #-\shift
.exitm
.endif
mov \reg, \reg, lsl #\shift
.endm
##2.9.2. .rept
.rept 用於重複執行一組指令,它的格式如下:
.rept <repeat>
.endr
repeat指明重複執行的次數。一個例子是Linux內核中用於nop的操作:
.rept 8
mov r0, r0
.endr
#2.10. Sandbox
表 4. Memory Hierarchy
[1] 儘管ARM提供了豐富的相關指令,比如uadd8,uadd16等,但並不是所有編譯器都能夠支持這些指令。
上一頁 下一頁 Linux內核學習和研究及嵌入式(ARM)學習和研究的開放文檔 起始頁 3. ARM尋址方式