数据处理指令:
-
数据处理指令只可用于寄存器之间或寄存器与立即数之间
移位指令
- Rotate left(ROL) 可以通过 rotate right (ROR)(32-number)来实现, e.g. ROL 10 相当于 ROR 22.
- 立即数:只要该数,可以通过0x00-0xFF中某个数,循环右移偶数位而产生,就是合法的mov的操作数,否则就是非法的mov的操作数。
mov r0, #256 ; mov r0, #0x100 mov r1, #0x40, 30 ; mov r1, #0x100
伪指令LDR可以用来MOV任意32位的常数到寄存器中
- 例如:
SUB r0,r1,#5 ;r1-5->r0 (立即寻址)
ADD r2,r3,r3,LSL #2 ;R3x4+r3->r2
ANDS r4,r4,#0x20 ;r4+0x20->r4,更新条件码标志位
ADDEQ r5,r5,r6 ;r5+r6->r5(条件-相等)(寄存器寻址)
存储器存取指令:
-
存储器存取指令用于通用寄存器与内存单元之间
- 例如
LDR r0,[r1],#4 ;r1+4->r0(基址变址寻址)
STRNEB r2,[r3,r4] ;NE符合-将r2低8位数写到[r3+r4]内存单元(寄存器间接寻址)
LDRSH r5,[r6,#8]! ;[r6+8]->r5(半字节),r5中高16位设置成该字节的符号位
STMFD sp!,{r0,r2-r7,r10} ;出栈(多寄存器寻址)
- STR :从寄存器中将32bits字数据存储到内存中
- LDM/STM(出栈/入栈) 用于基址寄存器所指的一片连续内存到寄存器列表中的多个寄存器之间的数据传送。
PSR 读写指令
- MRS 将CPSR/SPSR的内容读取到通用寄存器中
- MSR 将通用寄存器的值写到CPSR/SPSR的特定位域(f,c)中
- 例如
MRS R0, CPSR ;R0 = CPSR
MSR CPSR_c, R0 ;CPSR [c] = R0
ARM 跳转分支指令
- B
- BL <子程序>
保存返回地址到 LR
返回时从 LR 恢复 PC
对于 non-leaf 函数(func1), LR 必须压栈保存
ARM条件执行与标志位
- ARM指令可以通过添加合适的条件码后缀的方式使其条件执行
- 通常情况下,数据处理指令不影响条件码标志位,但可以添加"S"后缀来改变标志位状态。
常用条件码如下:
- 例如:
- if (a==0) func(1);
CMP r0,#0�
MOVEQ r0,#1
�BLEQ func
- if (a==0) x=0;�
if (a>0) x=1;
CMP r0,#0
�MOVEQ r1,#0�
MOVGT r1,#1�
- if (a==4 || a==10) x=0;
CMP r0,#4
�CMPNE r0,#10�
MOVEQ r1,#0
软件中断指令SWI
- SWI 指令用于产生软件中断,以便用户程序能调用操作系统的系统例
程。 - 操作系统在SWI 的异常处理程序中提供相应的系统服务,指令中
24 位的立即数指定用户程序调用系统例程的类型,相关参数通过通用
寄存器传递。 - 当指令中24 位的立即数被忽略时,用户程序调用系统例
程的类型由通用寄存器R0 的内容决定。
ARM 伪指令
1. 符号定义伪指令
符号定义伪指令用于定义ARM 汇编程序中的变量、对变量赋值以及定
义寄存器的别名等操作
- GBLA、GBLL 和GBLS 伪指令用于定义一个ARM 程序中的全局变量,
并将其初始化。GBLA Number ;定义一个全局的数字变量并初始化为0,变量名为Number Number SETA 0xaa ;将该变量赋值为0xaa GBLL Logical ;定义一个全局的逻辑变量并初始化为F(False),变量名为Logical Logical SETL {TRUE} ;将该变量赋值为真 GBLS Str ;定义一个全局的字符串变量并初始化为空,变量名为Str Str SETS “Testing” ;将该变量赋值为“Testing”
- LCLA、LCLL 和LCLS 伪指令用于定义一个ARM 程序中的局部变量,
并将其初始化。 - SETA、SETL、SETS 伪指令用于给一个已经定义的全局变量或局部变
量赋值。 - RLIST 伪指令可用于对一个通用寄存器列表定义名称,使用该伪指令定
义的名称可在ARM 指令LDM/STM 中使用。LDM/STM 指令中,列
表中的寄存器访问次序为根据寄存器的编号由低到高,而与列表中的寄
存器排列次序无关。RegList RLIST {R0-R5,R8,R10} ;将寄存器列表名称定义为RegList,可在ARM 指令LDM/STM 中通过该名称访问寄存器列表。
2. 数据定义伪指令
数据定义伪指令一般用于为特定的数据分配存储单元,同时可完成已分
配存储单元的初始化。
- DCB 伪指令用于分配一片连续的字节存储单元并用伪指令中指定的表
达式初始化。Str DCB “This is a test!” ;为Str分配一片连续的字节存储单元并初始化。 Str = “This is a test!”
- SPACE 伪指令用于分配一片连续的存储区域并初始化为0。
DataSpace SPACE 100;分配连续100 字节的存储单元并初始化为0。 DataSpace % 100
- MAP 伪指令用于定义一个结构化的内存表的首地址。MAP 也可用“^”
代替 - FIELD 伪指令用于定义一个结构化内存表中的数据域。FILED 也可用
“#”代替。MAP 0x100, R0 ;定义结构化内存表首地址的值为0x100+R0。 A FIELD 16 ;定义A 的长度为16 字节,位置为0x100+R0 B FIELD 32 ;定义B 的长度为32 字节,位置为0x110+R0 S FIELD 256 ;定义S 的长度为256 字节,位置为0x130+R0
3. 汇编控制伪指令
汇编控制伪指令用于控制汇编程序的执行流程。
- IF、ELSE、ENDIF 伪指令能根据条件的成立与否决定是否执行某个指
令序列。GBLL Test ;声明一个全局的逻辑变量,变量名为Test IF Test = TRUE 指令序列1 ELSE 指令序列2 ENDIF
- WHILE、WEND 伪指令能根据条件的成立与否决定是否循环执行某个
指令序列。GBLA Counter ;声明一个全局的数学变量,变量名为Counter Counter SETA 3 ;由变量Counter 控制循环次数 …… WHILE Counter < 10 指令序列 WEND
- MACRO、MEND 伪指令可以将一段代码定义为一个整体,称为宏指令,然后就可以在程序中通过宏指令多次调用该段代码。MEXIT 用于从宏定义中跳转出去。
MACRO $HandlerLabel HANDLER $Handlerpara;宏的名称为HANDLER,有1 个参数$Handlerpara, $HandlerLabel为标号,在宏指令被展开时会被替换为用户定义的符号。 sub sp,sp,#4 ;decrement sp(to store jump address) stmfd sp!,{r0} ;PUSH the work register to stack(lr does not push because it return to original address) ldr r0,=$Handlerpara;load the address of HandleXXX to r0 ldr r0,[r0] ;load the contents(service routine start address) of HandleXXX str r0,[sp,#4] ;store the contents(ISR) of HandleXXX to stack ldmfd sp!,{r0,pc} ;POP the work register and pc(jump to ISR) MEND
4. 其他常用伪指令
- AREA 伪指令用于定义一个代码段或数据段。
- ALIGN 伪指令可通过添加填充字节的方式,使当前位置满足一定的对
其方式。 - CODE16 伪指令通知编译器其后的指令序列为16 位的Thumb 指令,
- CODE32 伪指令通知编译器其后的指令序列为32 位的ARM 指令。
- ENTRY 伪指令用于指定汇编程序的入口点
- END 伪指令用于通知编译器已经到了源程序的结尾。
- EQU 伪指令用于为程序中的常量、标号等定义一个等效的字符名称,
类似于C 语言中的#define。其中EQU 可用“*”代替。 - EXPORT 伪指令用于在程序中声明一个全局的标号,该标号可在其他的
文件中引用。EXPORT 可用GLOBAL 代替。 - IMPORT 伪指令用于通知编译器要使用的标号在其他的源文件中定义,
但要在当前源文件中引用,而且无论当前源文件是否引用该标号,该标
号均会被加入到当前源文件的符号表中。 - EXTERN 伪指令用于通知编译器要使用的标号在其他的源文件中定义,
但要在当前源文件中引用,如果当前源文件实际并未引用该标号,该标
号就不会被加入到当前源文件的符号表中。 - GET 伪指令用于将一个源文件包含到当前的源文件中,并将被包含的
源文件在当前位置进行汇编处理。可以使用INCLUDE 代替GET。 - INCBIN 伪指令用于将一个目标文件或数据文件包含到当前的源文件
中。 - RN 伪指令用于给一个寄存器定义一个别名
AREA Init,CODE,READONLY,ALIGN=3 ;该伪指令定义了一个代码段,段名为Init,属性为只读, 并指定后面的指令为8 字节对齐。
ENTRY ;指定应用程序的入口点
Temp RN R0 ;将R0 定义一个别名Temp
GET a1.s ;通知编译器当前源文件包含源文件a1.s
GET C:\a2.s ;通知编译器当前源文件包含源文件C:\ a2.s
INCBIN a1.bin;通知编译器当前源文件包含文件a1.bin
EXPORT Stest ;声明一个可全局引用的标号Stest
IMPORT Main ;通知编译器当前文件要引用标号Main,但Main 在其他源文件中定义
Test EQU 50 ;定义标号Test 的值为50
CODE32 ;通知编译器其后的指令为32 位的ARM 指令
LDR R0,=NEXT+1 ;将跳转地址放入寄存器R0
BX R0 ;程序跳转到新的位置执行,并将处理器切换到Thumb 工作状态
……
CODE16 ;通知编译器其后的指令为16 位的Thumb 指令
NEXT LDR R3,=0x3FF
……
END ;指定应用程序的结尾
C与ARM汇编混合编程
1. C/C++中嵌入汇编程序
void enable_IRQ(void) //使能中断程序
{
int tmp; //定义临时变量,后面使用
__asm //内嵌汇编程序的关键词
{
MRS tmp, CPSR //把状态寄存器加载给tmp
BIC tmp, tmp, #80 //将IRQ控制位清0
MSR CPSR_c, tmp //加载程序状态寄存器
}
}
void disable_IRQ(void) //禁止中断程序
{
int tmp; //定义临时变量,后面使用
__asm //内嵌汇编程序的关键词
{
MRS tmp, CPSR //把状态寄存器加载给tmp
ORR tmp, tmp, #80 //将IRQ控制位置1
MSR CPSR_c, tmp //加载程序状态寄存器
}
}
2. 汇编与C/C++程序的变量相互访问
- 在C/C++程序中声明的全局变量可以被汇编程序通过地址间接访问。
AREA Example, CODE, READONLY EXPORT AsmAdd IMPORT g_cVal @声明外部变量g_cVal,在C中定义的全局变量 Add LDR R1, =g_cVal @装载变量地址 LDR R0, [R1] @从地址中读取数据到R0 ADD R0, R0, #1 @加1操作 STR R0, [R1] @保存变量值 MOV PC, LR @程序返回 END
- 在汇编程序中声明的数据可以被C/C++程序所访问。在汇编程序中用EXPORT/GLOBAL伪指令声明该符号为全局标号,C/C++程序中定义相应数据类型的指针变量
汇编:
C/C++:EXPORT Message @声明全局标号 Message DCB "HELLO$" @定义了5个有效字符,$为结束符
extern char* Message; int MessageLength() { int Length = 0; char *pMessage; //定义字符指针变量 pMessage = Message; //指针指向Message 内存块的首地址 /*while循环,统计字符串的长度*/ while(*pMessage != '$') //$为字符串的结束符 { Length++; pMessage++; } return(Length); //返回字符串的长度 }
3. 汇编与C/C++程序的函数相互调用(APTCS规则)
汇编程序设置要遵循APTCS规则,保证程序调用时参数的正确传递。
-
C程序调用汇编程序。
汇编程序中使用EXPORT伪指令声明本子程序可外部使用,使其他程序可调用该子程序;在C语言程序中使用extern关键字声明外部函数(声明要调用的汇编子程序),才可调用此汇编的子程序。
strcopy实现汇编代码:AREA Example, CODE, READONLY @声明代码段Example EXPORT strcopy @声明strcopy,以便外部函数调用 strcopy @ R0为目标字符串的地址, R1为源字符串的地址 LDRB R2, [R1], #1 @读取字节数据,源地址加1 STRB R2, [R0], #1 @保存读取的1字节数据,目标地址加1 CMP R2, #0 @判断字符是否复制完毕 BNE strcopy @没有复制完,继续循环复制 MOV PC, LR END
C/C++:
#include
extern void strcopy(char *d, const char *s); //声明外部函数,即要调用的汇编子程序 int main(void) { const char *srcstr = "First ource"; //定义字符串常量 char dststr[] = "Second string-destination"; //定义字符串变量 printf("Before copying: \n"); printf("src=%s, dst=%s\n", srcstr, dststr); //显示源字符串和目标字符串的内容 strcopy(dststr, srcstr); //调用汇编子程序R0=dststr, R1=srcstr printf("After copying: \n"); printf("src=%s, dst=%s\n", srcstr, dststr); //显示复制后的结果 return(0); } -
汇编程序调用C程序
在汇编程序中使用IMPORT伪指令声明将要调用的C程序函数。在调用C程序时,要正确设置入口参数,然后使用BL指令调用。
C/C++ sum函数:int sum(int a, int b, int c, int d, int e) { return(a+b+c+d+e); //返回5个变量的和 }
汇编调用sum函数:
AREA Example, CODE, READONLY IMPORT sum @ 声明外部标号sum,即C函数sum() EXPORT CALLSUM UM STMFD SP!, {LR} @LR寄存器入栈 MOV R0, #1 @设置sum函数入口参数,R0为参数a MOV R1, #2 @R1为参数b MOV R2, #3 @R2为参数c MOV R3, #5 @参数 e=5,保存到堆栈中 STR R3, {SP, #-4}! MOV R3, #4 @R3为参数d, d=4 BL sum @调用C程序中的sum函数,结果放在R0中 ADD SP, SP, #4 @调整堆栈指针 LDMFD SP, {PC} @程序返回 END
寄存器最多传递4个参数(R0-R3),超出四个的需要用到堆栈。
以上程序使用了5个参数,分别使用寄存器R0存储第1个参数,R1存储第2个参数,R2存储第3个参数,R3存储第4个参数,第5个参数利用堆栈传送。
由于利用了堆栈传递参数,在程序调用结束后要调整堆栈指针。汇编程序中调用了C程序的sum子函数,实现了1+2+3+4+5,最后相加结果保存在R0寄存器中。