ARM 汇编程序

一、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版权协议,转载请附上原文出处链接和本声明。