一、ARM 汇编程序
1,ARM 汇编指令(ARM公司设计指定的)
一条 汇编指令 对应一条 机器指令
操作码 操作数
2,伪指令(由编译器厂商设计指定的,keil环境下有keil的伪指令,gnu环境下有gnu的伪指令)
keil伪指令详细请看《常用汇编伪指令.docx》文档
3,宏指令(由编译器厂商指定)
二、ARM指令类型
ARM常用指令主要包括如下4个类型
(1)数据处理指令: 完成寄存器中数据的算术运算和逻辑运算的操作
(2)程序状态寄存器处理指令: MRS 和 MSR
(3)跳转指令: B 和 BL
(4)Load/Store指令: 唯一用于寄存器和存储器(内存)之间进行数据传递的指令
三、ARM指令的格式
<opcode>{cond}{S} <Rd> ,<Rn> {,<operand2>}
注: <>必须要的 {}可选的
opcode:操作码,也叫指令助记符,如 MOV,LDR,STR,ADD等
cond:指令执行的条件码,如 EQ NE等等 ,如果不写条件码就是无条件执行
if(a == b)
{
R0 = 250; //MOV R0 ,250
}
==>
CMP a ,b ;实际上把a与b相减 ==> xPSR.Z 置一
MOVEQ R0,#250 ;如果上一次操作把Z置一了,然后就把250写入R0
条件码详情请看《指令条件码表》
S: Status 表示该指令执行结果是否影响xPSR寄存器的标志位
ADD R2 ,R0 ,R1 ;该指令的结果不影响xPSR
ADDS R0 ,R0 ,R1 ;该指令的结果影响xPSR
Rd: 目标寄存器(结果寄存器)
Rn: 第一个操作数寄存器
operand2: 第二个源操作数
注意: 汇编指令不区分大小写,在keil环境的行注释用 ';'
四、ARM指令寻址方式(如何获取到操作数)
(1)立即数寻址(操作数后面的地址码就是操作数本身)
MOV R5 ,#0xFF ;把寄存器R5的值设置为0xFF
立即数规则:
立即数是用12bits的空间来保存的。其中低8位存 基数x,
高4位存 右移次数y。而所表示的数为k
K = (x>>(y*2)) ;基于32位的循环右移(低位跑到高位)
判断以下数据是否为合法的立即数,如果是找出对应的x和y
0x1F00 0000 ==> x: 0x1F y:4
0x1FF ==> error
0x101 ==> error
0x104 ==> x: 0x41 y:15
0x102 ==> error
判断方法: 基数不能超过8位(小于256一定是合法的立即数),右移次数必须是2的倍数。
解决办法: MOV R0 ,#0x102 ;error
LDR R0 ,=0x102 ;right,这里是采用LDR伪指令的用法
(2) 寄存器寻址(操作数存放在寄存器中,执行的时候取出寄存器的数据,不涉及内存访问)
MOV R5 ,R7 ;把R7中的数据存放到R5
SUB R5 ,R5 ,R4 ;把R5中的数据减去R4中的数据,把结果保存到R5
(3)寄存器间接寻址(寄存器中存放的不是数据本身,而是数据的内存地址)
LDR{cond}{S}{B/H} Rd, [Rn]
STR{cond}{S}{B/H} Rd, [Rn]
B: Byte 一个字节, H: Half word 半字(两个字节)
{B/H}我们的寄存器Rd是32bits,那么Rd的高字节怎么办
S: Signed 把地址的那个变量,当作是有符号的数据。
有符号数据,高位补符号位
无符号数据,高位补零
char a = 255;
unsigned char b = 255;
int c = -1;
unsigned int d = -1;
printf("%u %d %d %d %d\n",a,a,b,c,d);//4294967295 -1 255 -1 -1
LDR R3 ,[R5] ;取出R5的值,把这个值作为地址,再到该地址处把数据取出,存放到R3
STR R3 ,[R5] ;取出R5的指,把这个值作为地址,把R3的值存放到该地址处
(4) 寄存器偏移寻址(第二个操作数是寄存器的位移方式,在执行命令之前先位移)
MOV R3 ,R2 ,LSL #3 ; R3 = R2<<3;
注: LSL逻辑左移 LSR逻辑右移 ASR算术右移 ROR循环右移
(5) 寄存器基址变址寻址(将寄存器的内容与给出的偏移量相加形成操作数的地址,
使可以访问基址附近的单位)
LDR R0 ,[R1,#4] ;R0 = [R1+4]
LDR R0 ,[R1,R2] ;R0 = [R1+R2]
LDR R0 ,[R1,#4]! ;先R0=[R1+4],然后 R1=R1+4
LDR R0 ,[R1],#4 ;先R0=[R1],然后再 R1=R1+4
(6) 多寄存器寻址(一次可以取出多个数据 Multi)
LDMIA R1! ,{R2-R7,R12} ;将R1指向的地址的数据,依次写到R2-R7,R12中,且R1的值会变
STMIA R0! ,{R2-R7,R12} ;将寄存器R2-R7,R12的值依次保存到R0指向的存储单元,且R0的值会变
注: 寄存器列表用','隔开,寄存器由小到大排列,连续的寄存器可以用'-'表示
IA 每次传送后地址加4 increase after
IB 每次传送前地址加4 increase before
DA 每次传送后地址减4 decrease after
DB 每次传送前地址减4 decrease before
(7) 堆栈寻址: 先进后出
入栈时地址向下增长为 减栈,入栈时地址向上增长为 增栈
栈顶空间保存数据为 满栈,栈顶空间不保存数据为 空栈
注: ARM 为 满减栈
F: full E: empty
D: decrease A: add
POP PUSH
满减栈 LDMFD STMFD
满增栈 LDMFA STMFA
空减栈 LDMED STMED
空增栈 LDMEA STMEA
STMFD SP! ,{R2-R7,LR} ;把R2-R7,LR的值压栈,栈顶是SP指向的地址。
LDMFD SP! ,{R2-R7,PC} ;把SP出栈,值保存到R2-R7,PC中。
★在调用子程序(函数)时,子程序间通过寄存器R0-R3来传递参数。在子程序中,用
R4-R12来保存局部变量,如果在子程序中使用寄存器R4-R12中的某些寄存器,子程序
进入时必须要保护这些寄存器的值(现场保护),并在返回前需要恢复(现场恢复)。
xxx_func
STMFD SP! ,{R4-R12,LR} ;现场保护
;函数体指令
;。。。
;返回值==>R0和R1,返回32位的数据用R0,如果返回64位的数据用R0和R1,
;不支持64位以上的数据返回
LDMFD SP! ,{R4-R12,PC} ;现场恢复,并返回
(8) 相对寻址
没有写出具体的地址,根据给定的条件,编译的时候自动计算出地址
__wait
B __wait
五、ARM指令
(1)跳转指令
B lable ;PC <- lable 不带返回的跳转
BL _func ;先LR <- PC-4(基于三级流水) ,然后 PC <- _func 带返回的跳转
★BL 可以实现函数调用功能
★B 往前跳可以实现循环结构,往后跳可以实现if结构和if~else结构
另外的方式实现跳转
MOV PC ,LR ;修改PC的值也能实现跳转
(2)数据处理指令(加减法、与或运算,赋值,比较)
注: 数据处理指令只能对寄存器R0~R15操作,不能对内存进行操作
a. 数据传送指令[MOVEQS]
MOV Rd ,operand ;数据传送 Rd = operand
MVN Rd ,operand ;数据非传送 Rd = ~(operand)
eg:
MOV R0 ,#0xFF
MOV R0 ,R1
MVN R0 ,#0xFF ;R0 = 0xFFFFFF00
MOV R0 ,R1 ,LSL #2
b. 比较指令(一定会影响标志位)
CMP Rn ,operand 比较指令(大小)
CMP R0 ,#100 ;将寄存器R0的值与100相减,结果影响标志位
CMN Rn ,operand 负数比较指令
CMN R0 ,#100 ;将寄存器R0的值与100相加,结果影响标志位
TST Rn ,operand 位测试指令
TST R0 ,#100 ;将寄存器R0的值与100进行位与,结果影响标志位
TEQ Rn ,operand 相等测试
TEQ R0 ,#100 ;将寄存器R0的值与100进行异或,结果影响标志位
c. 算术运算指令{cond}{S}
ADD Rd ,Rn ,operand2 加法指令
ADC Rd ,Rn ,operand2 带进位加法(计算结果会加上c标志位)
SUB Rd ,Rn ,operand2 减法指令
SBC Rd ,Rn ,operand2 带借位减法
RSB Rd ,Rn ,operand2 逆向减法
RSC Rd ,Rn ,operand2 带借位逆向减法
eg:
ADDNE R0 ,R1 ,R2 ;条件执行,R0 = R1+R2
SUBS R0 ,R1 ,R2 ;R0 = R1-R2 结果影响标志位
RSB R0 ,R2 ,#5 ;R0 = 5-R2
d. 位运算
AND Rd ,Rn ,operand2 位与
ORR Rd ,Rn ,operand2 位或
EOR Rd ,Rn ,operand2 位异或
BIC Rd ,Rn ,operand2 位清除,Rd = Rn & (~operand2)
BIC R0 ,R2 ,#0xFF ;把R2的低8位清零,然后将结果保存到R0
(3) 存储器访问指令
a. 单寄存器操作指令 LD: load ST: store R:register
LDR SP ,[R0] ;把RO存放的地址指向的数据写入到寄存器SP
STR PC ,[R1] ;把PC的值保存到R1指向的空间
b. 多寄存器操作指令
LDMIA R1! ,{R2-R7,R12}
STMIA R0! ,{R2-R7,R12}
c. 伪指令: 没有相对应的操作码,由编译器厂商实现的
LDR R0 ,=0x102 ;把R0写为 0x102
(4) SWP 指令(交换指令)
SWP{cond} Rd ,Rn1 ,[Rn2]
作用: [Rn2] => Rd
Rn1 => [Rn2]
SWP R0 ,R0 ,[R1] ;将R1所指向的存储中的数据与R0中的数据交换
(5) MRS/MSR 指令,唯一能操作 程序状态寄存器的指令
MRS Rd ,xPSR ;把程序状态寄存器的值,赋值给Rd
MSR xPSR ,Rd ; 把通用寄存器Rd的值,赋值给xPSR
MRS R0 ,xPSR
LDR R1 ,=0x01000000
LDR R1 ,[R0] ORR R0 ,R0 ,R1 ;把R0的[24]置一
MSR xPSR ,R0
(6) 伪指令: 编译器会把它变成一条或多条合适的机器指令
1) NOP 空操作指令,占用机器周期
NOP
==>
MOV R0 ,R0
2) LDR{cond} Rd ,=expression
LDR Rd ,=_addr_a
a. 如果 _addr_a 是一个合法的立即数
LDR Rd ,=_addr_a
==>
MOV Rd ,#_addr_a
b. 如果 _addr_a 不是一个合法的立即数
LDR Rd ,=_addr_a
==>
LDR Rd ,[PC,offset_to_litpool] ;Rd = [PC+offset_to_litpool]
...
litpool
DCD _addr_a
六、混合编程
(1) 汇编语言 调用 汇编语言
xxx_func
STMFD SP! ,{R4-R12,LR} ;现场保护
;函数体指令
;。。。
;返回值==>R0和R1,返回32位的数据用R0,如果返回64位的数据用R0和R1,
;不支持64位以上的数据返回
LDMFD SP! ,{R4-R12,PC} ;现场恢复,并返回
BL xxx_func
(2) c语言函数 调用 c语言函数
(3) 汇编语言 调用 c语言
在汇编文件中: 要导入符号
IMPORT main
设置R0~R3作为函数的参数
BL main
(4) c语言函数 调用 汇编函数
在汇编文件中: 要导出符号
EXPORT wanshu
在c语言中: 要进行申明
extern int wanshu(void);
extern int num;
...
版权声明:本文为weixin_44025419原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。