《嵌入式 - 嵌入式大杂烩》ARM汇编入门

Posted Bruceoxl

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《嵌入式 - 嵌入式大杂烩》ARM汇编入门相关的知识,希望对你有一定的参考价值。

我们在学习ARM的时候,一般都不用看汇编启动代码,直接使用芯片厂商提供的汇编启动代码,但是要想深入了解ARM内部原理,就必须掌握一定的汇编知识。

我们在前面总结了处理器架构与指令集,那么汇编和处理器架构、指令集有什么关系呢?先看下图:

从上图可以看出,不同的处理器架构、不同指令集合对应不同汇编指令。可以说,一种指令集就对应一种汇编指令,汇编是开发者与计算机交互的接口,

总结一下,汇编语言是指令集构架的机器码一对一的人类可以理解的翻译,是用人类看得懂的语言来描述指令集。否则指令集的机器码都是一堆二进制数字,人类读起来非常麻烦,但汇编是用类似人类语言的方式描述指令集,从而控制不同的处理器按照人们的想法去工作。

对于CPU来说,它只能识别二进制码,那怎么能识别高级语言呢?于是人们开发了编译器,依照如下顺序,将高级语言翻译成二进制码:

可以说,汇编语言就是高级语言和二进制机器码的桥梁。

关于处理器架构、指令集、寄存器的介绍请看笔者以前的文章​。

处理器架构与指令集

深入理解ARM寄存器

本文针对ARM架构的芯片讲解其相关的指令集。

1 工具

工欲善其事,必先利其器,在学习ARM汇编之前,我们先准备好学习软件。这里推荐使用的是VisUAL。VisUAL是一款ARM汇编模拟器,支持Windows、Mac OS X和Linux系统。

下载地址

使用方法

【注】下载VisUAL需要VPN,如果没有VPN请自行参看后文提示获取。

VisUAL模拟的ARM板子如下图所示:

它没有模拟外设,仅仅模拟了CPU、ROM、RAM。红色区域是ROM,不能读不能写,只能运行其中的程序。ROM区域本来可以读的,这是VisUAL的局限。RAM区域可读可写。

VisUAL 支持一小部分 ARM UAL 指令。这些主要是算术、逻辑、加载/存储和分支指令。下面给出了指令语法的简短摘要。有关详细信息和示例,请移步VisUAL关官网。


1.1 VisUAL设置

VisUAL设置基本不需要什么设置,界面也很简单,常用的有设置背景、编码字体与颜色。设置方式如下:

值得注意的是,设置字体大小需要点击回车才能生效。

1.2 VisUAL使用

首选在编辑区写好你要模拟的汇编代码,点击运行按钮就可进行调试。

值得一提的是,VisUAL可以回退运行,在运行完一条指令后,还可以回退到上一条指令,非常的实用。

当代码运行错误,会提示错误信息,点击[Reset],根据提示修改错误的代码。

根据提示修改代码后,点击[Execute],运行结果如下:

运行成功后,不仅可以看到寄存器的情况,还可以快速查内存情况。以上指令的含义后文会详细讲解。
当然啦,VisUAL还提供了内存分析工具,使用功能方法如下。

好了,关于的VisUAL工具的介绍就这些了,后面会结合ARM的具体汇编指令进一步使用VisUAL工具。

2 ARM汇编编程简介

首先,我们先看一个简单的汇编程序:

area ff,code,readonly   ;声明代码段  
    code32  ;声明为32位ARM指令  
    entry   ;声明程序入口  
start  
    ;b指令  
    ;1.b 跳转范围+_ 32M b + 标号  
    ;b start  
    ;b stop  
    ;2.bl 子函数调用  
    ;会把预取指令的地址保存在lr(r14)  
    ;3.bx 子函数返回  
    mov r0,#9  
    mov r1,#15  
    mov r5,#9  
    bl func  
    ;int func(int a,int b)  
stop  
    b stop  
func  
    mov r5,#1   
loop  
    cmp r0,r1  
    beq stop1  
    subgt r0,r0,r1  
    sublt r1,r1,r0  
    b loop  
stop1  
    bx lr  
    end  

可以看出,ARM汇编程序用“;”号进行注释。

当然啦,看不懂上面的示例不要紧,后面会在详细介绍。。

2.1汇编语言程序格式

一个完整的ARM汇编由两部分组成:声明,实际代码段两部分组成。

1、声明
在一个程序之前先要进行声明:
A.声明代码段:
用AREA指令定义一个段,说明所定义段的相关属性。(说明段的名字,段的属性)

B.声明ARM指令:
用CODE32或CODE16来声明程序为32位ARM指令或是16位Thumb指令。

C.声明程序入口:
用ENTRY指令标识程序的入口点。

注:这3个声明缺一不可。在程序完成后要用END 指令声明程序结束。每一个汇编程序段都必须有一条END指令,指示代码段的结束。

2、段
A.在ARM汇编语言程序中,以程序段为单位组织代码。段是相对独立的指令或数据序列,具有特定的名称。

B.段的分类
代码段:代码段的内容为执行代码
数据段:数据段存放代码运行时需要用到的数据。

注:一个汇编程序至少有一个代码段。如果程序较长时,可以分割为多个代码段和数据段。多个段在程序编译连接时最终形成一个可执行的映像文件。

C.段具有以下的属性

  • READONLY
  • READWRITE

2.2汇编语言的语句格式

[LABEL] OPERATION [OPERAND] [;COMMENT]
标号域 操作助记符域 操作数域 注释域

1.标号域(LABLE)
A.标号域用来表示指令的地址、变量、过程名、数据的地址和常量。

B.标号是可以自己起名的标识符,语句标号可以是大小写字母混合,通常以字母开头,由字母、数字、下划线等组成。

C.语句标号不能与寄存器名、指令助记符、伪指令(操作)助记符、变量名同名。

D.语句标号必须在一行的开头书写,不能留空格。

2.操作助记符域(OPERATION)
A.操作助记符域可以为指令、伪操作、宏指令或伪指令的助记符。

B.ARM汇编器对大小写敏感,在汇编语言程序设计中,每一条指令的助记符可以全部用大写、或全部用小写,但不允许在一条指令中大、小写混用。

C.所有的指令都不能在行的开头书写,必须在指令的前面有空格,然后再书写指令。

D.指令助记符和后面的操作数或操作寄存器之间必须有空格,不可以在这之间使用逗号。

3.操作数域(OPERAND)
操作数域表示操作的对象,操作数可以是常量、变量、标号、寄存器名或表达式,不同对象之间必须用逗号“,”分开。

2.3 ARM指令集格式

基本格式是操作码,后跟可选条件码,可选S (set flags),如下所示

Operation{cond}{S} Rd, Rn, Operand2

1.其中<>中的项是必须的,{}中的项是可选的。

2.opcode 表示指令助记符。

  • cond:表示执行条件。
  • S:表示是否影响CPSR寄存器的值。
  • Rd:表示目标寄存器。
  • Rn:表示第一个操作数的寄存器。
  • operand2:表示第2个操作数。

3.“operand2”具有如下的形式:

A.#immed_8r:常数表达式
eg:

MOV  R0,#1
ADD   R0,R1,#0X0F 

B.Rm:寄存器形式。
即在寄存器方式下,操作数即为寄存器的数值。

eg:

MOV     PC,R0
ADD     R1,R1,R2

C.Rm,shift:寄存器移位方式。

将寄存器的移位结果作为操作数,当Rm值保持不变。

  • ASR #n:表示算术右移n位。
  • LSR #n:表示逻辑右移n位。
  • ROR #n:表示循环右移n位。
  • RRX #n:带扩展的循环右移n位。
  • LSL #n:逻辑左移n位。

4.使用条件码“cond”可以实现高效的逻辑操作,提高代码的效率。

  • 所有的ARM指令都可以条件执行。
  • Thumb指令只有B(跳转)指令具有条件执行功能。

注:如果执行中不表明条件码,默认为无条件(AL)执行。

2.4汇编程序中常用的符号

在汇编语言程序设计中,经常使用各种符号表示变量、常量和地址

A.符号由大小写字母、数字以及下划线组成。

B.符号区分大小写,同名的大、小写符号会被编译器认为是两个不同的符号。

C.符号在其作用范围内必须唯一,即在其作用范围内不可有同名的符号。

D.自定义的符号名不能与系统的保留字相同。

【注】符号名不应与指令或伪指令同名。

1.程序中的变量

  • ARM汇编程序所支持的变量有数字变量,逻辑变量和字符串变量

  • 在ARM汇编程序设计中,可使用GBLA,GBLL,GBLS伪定义声明全局变量,使用LCLA,LCLL,LCLS声明局部变量,并可使用SETA,SETL和SETS对其经行初始化。

2.程序中的常量
ARM汇编程序所支持常量有数字常量,逻辑常量和字符串常量。

3.程序中的变量代换
程序中的变量可通过代换操作取的一个常量。代换操作符为”$”。使用示例:

LCLS    S1  
LCLS    S2    ;定义局部字符串变量S1和S2  
S1      SETS        “Test!”  
S2      SETS        “This is a $ S1”;S2的值为“This is a Test  

3 ARM汇编指令详解

ARM公司针对ARM架构的芯片发布了三种指令集:

1.ARM指令集:每条指令是是 32位的,每条指令能承载更多的信息,因此使用最少的指令完成功能,所以在相同频率下运行速度也是最快的,但也因为每条指令是32位的而占用了最多的程序空间。

2.Thumb指令集:每条指令是 16位的,每条指令所能承载的信息少,因此它需要使用更多的指令才能完成功能,因此运行速度慢,但它也占用了最少的程序空间。

3.Thumb-2指令集:支持16位指令、32位指令混合编程, 当一个操作可以使用一条32位指令完成时就使用32位的指令, 加快运行速度, 而当一次操作只需要一条16位指令完成时就使用16位的指令,节约存储空间。

目前ARM架构主流是基于ARMv7架构的Cortex系列产品,由A、R、M三个系列组成,具体分类延续了一直以来ARM面向具体应用设计CPU的思路。其中A是应用处理器(Application Processor),基于虚拟存储的操作系统和应用程序而设计,支持ARM、Thumb、Thumb-2指令集,R是实时控制处理(Real Time Control)系列,支持支持ARM、Thumb、Thumb-2指令集,M微控制器(Micro Controller)系列,对价格相对敏感,仅支持Thumb 和Thumb-2指令集

ARM公司针对不同指令集,ARM公司推出了: Unified Assembly Language UAL,统一汇编语言,你不需要去区分这些指令集。在程序前面用CODE32/CODE16/THUMB表示指令集ARM /Thumb/ Thumb2。

在具体讲解指令之前,我们首先要清楚两个重要的概念:立即数和寻址方式。

3.1立即数

立即数通常是指在立即寻址方式指令中给出的数,直接包含在指令中,可以是8位、16位或32位,该数值紧跟在操作码之后。

先看一条指令:

MOV   R0, #VAL

其含义是把VAL这个值存入R0寄存器。

在ARM指令中,VAL的值必须是以下情况:

  • 一个常数,可以通过将一个8位的值旋转任意偶数位来产生。
  • 形式为0x00XY00XY的常数。
  • 形式为0xXY00XY00的常数。
  • 形式为0xXYXYXYXY的常数。

其中XY为十六进制数,取值范围为0x00 ~ 0xFF。

综上,VAL必须是立即数。

那么,立即数有何特别之处呢?在之前,需要了解数据编码的知识,对于开发者而言,开发者使用的是汇编指令,而汇编指令不是计算机能够理解的,计算机所能理解的只是 0 1 这两种数字。所以,汇编指令还要经过译码器译码成机器指令才能被计算机所理解。而汇编指令作为开发者和计算机交流的接口,那么常常会有一些限制。

对于ARM指令,汇编指令的对应的机器指令格式大致如下:

从上图可以看到,在一条指令中,只有后12位用于存放立即数,其中这 12 位又分为两部分:前7~0位是数值部分(immediate);后11~8 位是前7~0位要进行移位操作的移位数(rotate)。

其中 immediate有 8 位,也就是我们的立即数部分占 8 位,因此有如下结论:

  • 如果一个立即数小于 0xFF(255)那么直接用前 7~0 位表示即可,此时不用移位,11~8 位的 immediate_rotate等于 0。
  • 如果前八位 immediate的数值大于 255,那么就看这个数是否能有immediate中的某个数移位 2*immediate_rotate位形成的。如果能,那么就是合法立即数;否则非法。
    因此,立即数的判定方法如下:

一个 32 位数用 12 位编码表示,符合以下规则才是合法立即数。

立即数 = immediate循环右移 (2 * immediate_rotate)

如果存在一个 immediate_rotate能够让该立即数由 immediate循环右移 2*immediate_rotate位(偶数位)表示,那么这个立即数就是合法的。

举两个例子吧。

Eg1: 0x234
0x234 用二进制表示:

0000 | 0000 | 0000 | 0000 | 0000 | 0010 | 0011 | 0100

其中的 1000 | 1101 部分循环右移 30 位可以和这个二进制数相同。因此immediate_rotate的值是 15,而 immediate的值是 1000 | 1101 即 8D。

Eg2: 0x132
0x132 用二进制表示:

0000 | 0000 | 0000 | 0000 | 0000 | 0001 | 0011 | 0010

没有任何部分循环右移偶数次可以成为该数本身,因此它不是一个合法的立即数。

以上方式是从立即数的本质入手去判断一个数是不是立即数,对于很多朋友不太友好,下面笔者告诉你个更加容易点。

立即数的标准判断的步骤:
Step1.把数据转换成二进制形式,从低位到高位写成4位1组的形式,最高位一组不够四位的,在最高位前面补0。
Step2.数1的个数,如果大于8个肯定不是立即数,如果小于等于8进行下面步骤。
Step3.如果数据中间有连续的大于等于24个0,循环左移4的倍数,使高位全为0。
Step4.找到最高位的1,去掉前面最大偶数个0
Step5.找到最低位的1,去掉后面最大偶数个0
Step6.数剩下的位数,如果小于等于8位,那么这个数就是立即数,反之就不是立即数。

典型例子:
(1)0x4FF (2)0x122 (3)0x234 (4)0xF000000F

Eg1: 0x4FF
Step1: 0100 1111 1111
Step2:其中1的个数是9个,大于8个,判定不是立即数

Eg 2: 0x122
Step1: 0001 0010 0010
Step2: 其中1的个数4个,小于8,继续
Step3: 其中没有连续大于等于24个0,继续
Step4: xx01 0010 0010 (最高位前面有3个0,最大偶数2,去掉2个0)
Step5: xx10 0011 0010 (最低位后面只有1个0,最大偶数0)
Step6: 剩下10 0011 0010 共10位,大于8,判定0x122不是立即数

Eg 3: 0x234
Step1: 0010 0011 010
Step2:其中1的个数4个,小于8,继续
Step3:其中没有连续大于等于24个0,继续
Step4: xx10 0011 0100
Step5: xx10 0011 01xx
Step6:剩下10 0011 01 共8位,等于8,判定0x234是立即数

Eg 4: 0xF000000F
Step1: 1111 0000 0000 0000 0000 0000 0000 1111
Step2:其中1的个数8个,没有大于8,继续
Step3:其中有连续24个0,循环左移4位,使高位全为0
0000 0000 0000 0000 0000 0000 0000 1111 1111
Step4: xxxx xxxx xxxx xxxx xxxx xxxx xxxx 1111 1111
Step5: xxxx xxxx xxxx xxxx xxxx xxxx xxxx 1111 1111
Step6:剩下1111 1111共8位,等于8,判定0xF000000F是立即数

好了,总结下,立即数就是一个特殊的数,它必须满足一定的规则,就是通过循环移位(偶数位),去掉前后偶数个0得到一个小于等于8位的数。如果还是不理解的,请多看几遍,仔细琢磨吧。

3.2 ARM寻址方式

接下里谈谈ARM的寻址方式。所谓寻址方式,是指处理器根据指令中给出的地址信息来寻找物理地址的方式,目前ARM指令系统支持以下几种寻址方式。

3.2.1立即寻址

也称为立即数寻址,这种寻址方式指令中就已经给出了操作数。也就是在执行指令的过程中,处理器取得指令的同时也取得了操作数,因此称为立即数寻址。例如:

MOV	R0 ,#0X20000     ;将0X20000写入寄存器R0中
ADD	R0, R0, #0x3F    ;将R0中的值加上立即数0X37,再写入R0中

在上面两条指令中,源操作数就是立即数,要求以“#”开始,对于十六进制的立即数。

3.2.2寄存器寻址

操作数的值在寄存器中,指令中的地址码字段指出的是寄存器编号,指令执行时直接取出寄存器值来操作。寄存器寻址是各类微处理器常用的寻址方式,也是效率较高的寻址方式。寄存器寻址指令举例如下:

MOV  R1,R2	     ;将R2的值存入R1 
SUB   R0,R1,R2     ;将R1的值减去R2的值,结果保存到R0

3.2.3寄存器移位寻址

寄存器移位寻址是ARM指令集特有的寻址方式。当第2个操作数是寄存器移位方式时,第2个寄存器操作数在与第1个操作数结合之前,选择进行移位操作。寄存器移位寻址指令举例如下:

MOV	R0,R2,LSL #3	     ;R2的值左移3位,结果放入R0,	;即是R0=R2×8 
ANDS	R1,R1,R2,LSL R3  ;R2的值左移R3位,然后和R1相	;“与”操作,结果放入R1

移位操作:

LSL移位操作:

LSR移位操作:

ASR移位操作:

ROR移位操作:

RRX移位操作:

3.2.4寄存器间接寻址

寄存器间接寻址指令中的地址码给出的是一个通用寄存器的编号,所需的操作数保存在寄存器指定地址的存储单元中,即寄存器为操作数的地址指针。寄存器间接寻址指令举例如下:

LDR	    R1,[R2]	    ;将R2指向的存储单元的数据读出   ;保存在R1中 
SWP	R1,R1,[R2]	;将寄存器R1的值和R2指定的存储  ;单元的内容交换

3.2.5基址寻址(变址寻址)

基址寻址就是将基址寄存器的内容与指令中给出的偏移量相加/减,形成操作数的有效地址。基址寻址用于访问基址附近的存储单元,常用于查表、数组操作、功能部件寄存器访问等。寄存器间接寻址是偏移量为0的基址加偏移寻址。

基址寻址指令举例如下:

  • 前索引寻址
LDR	 R2,[R3,#0x0C]	     ;读取R3+0x0C地址上的存储单元的内容,放入R2 
  • 后索引基址寻址
LDR	 R0,[R1] ,#4          ;R0=[R1],R1=R1+4		      	          
  • 自动索引机制不消耗额外的时间
LDR  R0,[R1,R2]!     ;R0=[R1+R2]

3.2.6多寄存器寻址

多寄存器寻一次可传送几个寄存器值,允许一条指令传送16个寄存器的任何子集或所有寄存器。多寄存器寻址指令举例如下:

LDMIA	R1!,{R2-R7,R12}  ;将R1指向的单元中的数据读出到R2~R7、R12中(R1自动加4) 
STMIA	R0!,{R2-R7,R12}  ;将寄存器R2~R7、R12的值保存到R0指向的存储, 单元中(R0自动加4)

3.2.7堆栈寻址

堆栈是一个按特定顺序进行存取的存储区,操作顺序为“后进先出” 。堆栈寻址是隐含的,它使用一个专门的寄存器(堆栈指针)指向一块存储区域(堆栈),指针所指向的存储单元即是堆栈的栈顶。存储器堆栈可分为两种:

向上生长:向高地址方向生长,称为递增堆栈
向下生长:向低地址方向生长,称为递减堆栈

堆栈指针指向最后压入的堆栈的有效数据项,称为满堆栈。(压栈时先修改sp,后数据压栈;出栈时先数据出栈,后修改sp)

堆栈指针指向下一个待压入数据的空位置,称为空堆栈。(压栈时先数据压栈,后修改sp;出栈时先修改sp,后数据出栈)

四种类型的堆栈方式:
满递增:堆栈向上增长,堆栈指针指向内含有效数据项的最高地址。指令如LDMFA、STMFA等;
空递增:堆栈向上增长,堆栈指针指向堆栈上的第一个空位置。指令如LDMEA、STMEA等;
满递减:堆栈向下增长,堆栈指针指向内含有效数据项的最低地址。指令如LDMFD、STMFD等;
空递减:堆栈向下增长,堆栈指针向堆栈下的第一个空位置。指令如LDMED、STMED等。

3.2.8相对寻址

相对寻址是基址寻址的一种变通。由程序计数器PC提供基准地址,指令中的地址码字段作为偏移量,两者相加后得到的地址即为操作数的有效地址。相对寻址指令举例如下:

	BL	SUBR1		;调用到SUBR1子程序
	BEQ	 LOOP		;条件跳转到LOOP标号处
	...
LOOP	MOV	R6,#1
	...
SUBR1	...

3.2.9块拷贝寻址

用于将一块数据从存储器的某一位置拷贝到另一位置。

STMIA  R0!,{R1-R7}  ;将R1~R7的数据保存到存储器中,存储器指针在保存第一个值之后增加,增长方向为向上增长。
STMIB  R0!,{R1-R7}  ;将R1~R7的数据保存到存储器中,存储器指针在保存第一个值之前增加,增长方向为向上增长。
STMDA  R0!,{R1-R7}  ;将R1~R7的数据保存到存储器中,存储器指针在保存第一个值之后增加,增长方向为向下增长。
STMDB  R0!,{R1-R7}  ;将R1~R7的数据保存到存储器中,存储器指针在保存第一个值之前增加,增长方向为向下增长。

3.3数据处理指令

ARM内核只能在寄存器上执行数据处理,不能直接在内存上执行。所有ARM数据传送或算术逻辑运算指令均可选择使用S后缀,以使指令影响CPSR中的标志。数据处理指令(在大多数情况下)使用一个目标寄存器和两个源操作数。

数据处理指令大致可分为3类:

  • 数据传送指令;数据传送指令用于在寄存器和存储器之间进行数据的双向传输;

  • 算术逻辑运算指令;算术逻辑运算指令完成常用的算术与逻辑的运算,该类指令不但将运算结果保存在目的寄存器中,同时更新CPSR中的相应条件标志位;

  • 比较指令。比较指令不保存运算结果,只更新CPSR中相应的条件标志位。

下表总结了数据处理过程中的常用汇编指令,给出了它们的助记操作码、操作数和它们的功能的简要描述。

1.ADC指令(带进位相加)

ADC指令的格式为:

ADC{条件}{S} 目的寄存器,操作数1,操作数2

ADC指令用于把两个操作数相加,再加上CPSR中的C条件标志位的值,并将结果存放到目的寄存器中。它使用一个进位标志位,这样就可以做比32位大的数 的加法,注意不要忘记设置S后缀来更改进位标志。操作数1应是一个寄存器,操作数2可以是一 个寄存器,被移位的寄存器,或一个立即数。

以下指令序列完成两个128位数的加法,第一个数由高到低存放在寄存器R7~R4,第二个数由高到低存放在寄存器R11~R8,运算结果由高到低存放在寄 存器R3~R0。

指令示例:

ADDS    R0, R4, R8         ; 加低端的字
ADCS    R1, R5, R9          ;加第二个字,带进位
ADCS    R2, R6, R10        ;第三个字,带进位
ADC      R3, R7, R11        ;加第四个字,带进位

2.ADD指令(相加)

ADD指令的格式为:

ADD{条件}{S} 目的寄存器,操作数1,操作数2

ADD指令用于把两个操作数相加,并将结果存放到目的寄存器中。操作数1应是一个寄存器,操作数2可以是一个寄存器,被移位的寄存器,或一个立即数。

指令示例:

ADD     R0, R1, R2                  ; R0 = R1 + R2
ADD     R0, R1, #256                ; R0 = R1 + 256
ADD     R0, R2, R3, LSL#1       ; R0 = R2 + (R3 << 1)

3.MOV指令(传送)

MOV指令的格式为:

MOV{条件}{S} 目的寄存器,源操作数

MOV指令可完成从另一个寄存器、被移位的寄存器或将一个立即数加载到目的寄存器。其中S选项决定指令的操作是否影响CPSR中条件标志位的值,当没有S 时指令不更新CPSR中条件标志位的值。

指令示例:

MOV R1, R0                   ;将寄存器R0的值传送到寄存器R1
MOV PC, R14                ;将寄存器R14的值传送到 PC,常用于子程序返回
MOV R1, R0, LSL #3     ;将寄存器R0的值左移3位后传送到R1

4.MVN指令(求反)

MVN指令的格式为:

MVN{条件}{S} 目的寄存器,源操作数

MVN指令可完成从另一个寄存器、被移位的寄存器、或将一个立即数加载到目的寄存器。与MOV指令不同之处是在传送之前按位被取反了,即把一个被取反的值 传送到目的寄存器中。其中S决定指令的操作是否影响CPSR中条件标志位的值,当没有S时指令不更新CPSR中条件标志位的值。

指令示例:

MVN R0, #0           ;将 立即数0取反传送到寄存器R0中,完成后R0=-1

5.RSB指令(逆向减法)

RSB指令的格式为:

RSB{条件}{S} 目的寄存器,操作数1,操作数2

RSB指令称为逆向减法指令,用于把操作数2减去操作数1,并将结果存放到目的寄存器中。操作数1应是一个寄存器,操作数2可以是一个寄存器,被移位 的寄存器,或一个立即数。该指令可用于有符号数或无符号数的减法运算。

指令示例:

RSB     R0, R1, R2                         ; R0 = R2 - R1
RSB     R0, R1, #256                     ; R0 = 256 - R1
RSB     R0, R2, R3, LSL#1            ; R0 = (R3 << 1) - R2

6.RSC指令(反向带进位减)
RSC指令的格式为:

RSC{条件}{S} 目的寄存器,操作数1,操作数2

RSC指令用于把 操作数2减去操作数1,再减去CPSR中的C条件标志位的反码,并将结果存放到目的寄存器中。操作数1应是一个寄存器,操作数2可以是一个寄存器,被移位 的寄存器,或一个立即数。该指令使用进位标志来表示借位,这样就可以做大于32位的减法,注意不要忘记设置S后缀来更改进位标志。该指令可用于有符号数或 无符号数的减法运算。
指令示例:

RSC     R0, R1, R2          ; R0 = R2 - R1 - !C

7.SBC指令(带进位减)

SBC指令的格式为:

SBC{条件}{S} 目的寄存器,操作数1,操作数2

SBC指令用于把操作数1减去操作数2,再减去CPSR中的SBC条件标志位的反码,并将结果存放到目的寄存器中。操作数1应是一个寄存器,操作数2可以 是一个寄存器,被移位的寄存器,或一个立即数。该指令使用进位标志来表示借位,这样就可以做大于32位的减法,注意不要忘记设置S后缀来更改进位标志。该指令可用于有符号数或无符号数的减法运算。

指令示例:

SBC   R0, R1, R2        ;R0 = R1 - R2 - !C,并根据结果设置CPSR的进位标志位

8.SUB指令(相减)

SUB指令的格式为:

SUB{条件}{S} 目的寄存器,操作数1,操作数2

SUB指令用于把操作数1减去操作数2,并将结果存放到目的寄存器中。操作数1应是一个寄存器,操作数2可以是一个寄存器,被移位的寄存器,或一个立即数。该指令可用于有符号数或无符号数的减法运算。

指令示例:

SUB     R0, R1, R2                 ; R0 = R1 - R2
SUB     R0, R1, #256            ; R0 = R1 - 256
SUB     R0, R2, R3, LSL#1     ; R0 = R2 - (R3 << 1)

9.AND指令(逻辑位与)

AND指令的格式为:

AND{条件}{S} 目的寄存器,操作数1,操作数2

AND指令用于在两个操作数上进行逻辑与运算,并把结果放置到目的寄存器中。操作数1应是一个寄存器,操作数2可以是一个寄存器,被移位的寄存器,或一个立即数。该指令常用于屏蔽操作数1的某些位。

指令示例:

AND R0,R0, #3          ;该指令保持R0的0、1位,其余位清零。

10.BIC指令(位清零)

BIC指令的格式为:

BIC{条件}{S} 目的寄存器,操作数1,操作数2

BIC指令用于清除操作数1的某些位,并把结果放置到目的寄存器中。操作数1应是一个寄存器,操作数2可以是一个寄存器,被移位的寄存器,或一个立即数。 操作数2为32位的掩码,如果在掩码中设置了某一位,则清除这一位。未设置的掩码位保持不 变。

指令示例:

BIC R0, R0, #%1011    ;该指令清除R0中的位 0、1、和 3,其余的位保持不变

11.EOR指令(逻辑位 异或)

EOR指令的格式为:

EOR{条件}{S} 目的寄存器,操作数1,操作数2

EOR指令用于在两个操作数上进行逻辑异或运算,并把结果放置到目的寄存器中。操作数1应是一个寄存器,操作数2可以是一个寄存器,被移位的寄存器,或一 个立即数。该指令常用于反转操作数1的某些位。

指令示例:

EOR R0. R0, #3        ;该指令反转R0的0、1位,其余位保持不变

12.ORR指令(逻辑位 或)

ORR指令的格式为:

ORR{条件}{S} 目的寄存器,操作数1,操作数2

ORR指令用于在两个操作数上进行逻辑或运算,并把结果放置到目的寄存器中。操作数1应是一个寄存器,操作数2可以是一个寄存器,被移位的寄存器,或一个立即数。该指令常用于设置操作数1的某些位。

指令示例:

ORR R0, R0, #3        ;该指令设置R0的0、1位,其余位保持不变。

13.CMP指令(比较)

CMP指令的格式为:

CMP{条件} 操作数1,操作数2

CMP指令用于把一个寄存器的内容和另一个寄存器的内容或立即数进行比较,同时更新CPSR中条件标志位的值。该指令进行一次减法运算,但不存储结果,只更改条件标志位。 标志位表示的是操作数1与操作数2的关系(大、小、相等),例如,当操作数1大于操作操作数2,则此后的有GT后缀的指令将可以执行。

指令示例:

CMP   R1, R0       ;将寄存器R1的值与寄存器R0的值相减,并根据 结果设置CPSR的标志位
CMP R1, #100   ;将寄存器R1的值与立即数100相减,并根 据结果设置CPSR的标志位

14.CMN指令(负数比较)

CMN指令的格式为:

CMN{条件} 操作数1,操作数2

CMN指令用于把一个寄存器的内容和另一个寄存器的内容或立即数取反后进行比较,同时更新CPSR中条件标志位的值。该指令实际完成操作数1和操作数2相 加,并根据结果更改条件标志位。

指令示例:

CMN   R1, R0     ;将寄存器R1的值与寄存器R0的值相加,并根据 结果设置CPSR 的标志位
CMN R1, #100  ;将寄存器R1的值与立即数100相加,并根据 结果设置CPSR的标志位

15.TEQ指令(测试相等)
TEQ指令的格式为:

TEQ{条件} 操作数1,操作数2

TEQ指令用于把一个寄存器的内容和另一个寄存器的内容或立即数进行按位的异或运算,并根据运算结果更新CPSR中条件标志位的值。该指令通常用于比较操作数1和操作数2是否相等。

指令示例:

TEQ   R1, R2      ;将寄存器R1的值与寄存器R2的值按位异或,并根据结果 设置CPSR的标志位

16.TST指令(测试)

TST指令的格式为:

TST{条件} 操作数1,操作数2

TST指令用于把一个寄存器的内容和另一个寄存器的内容或立即数进行按位的与运算,并根据运算结果更新CPSR中条件标志位的值。操作数1是要测试的数 据,而操作数2是一个位掩码,该指令一般用来检测是否设置了特定的位。

指令示例:

TST   R1, #%1           ;用于测试在寄存器R1中是否设置了最低位(%表 示二进制数)
TST R1, #0xffe        ;将寄存器R1的值与立即数0xffe按位与,并根据 结果设置CPSR的标志位

3.4内存操作指令

ARM内核只在寄存器上执行算术逻辑单元(ALU)操作。ARM使用加载(Load,将数据从内存读入寄存器)/存储(Store,将数据从寄存器写入内存)指令来读写内存,这意味着你只能使用LDR和STR指令访问内存。

在ARM上数据必须从内存中加载到寄存器之后才能进行其他操作,而在x86上大部分指令都可以直接访问内存中的数据。如前所述,在ARM上增加内存里的一个32-bit数据值,需要三个指令(load,increment,store)。

LDR 用于将内存中的值加载到寄存器中,STR 用于将寄存器内的值存储到内存地址。常用的加载存储指令如下:

1.LDR指令

LDR指令的格式为:

LDR{条件} 目的寄存器,<存储器地址>

LDR指令用于从存储器中将一个32位的字数据传送到目的寄存器中。该指令通常用于从存储器中读取32位的字数据到通用寄存器,然后对数据进行处理。当程序计数器PC作为目的寄存器时,指令从存储器中读取的字数据被当作目的地址,从而可以实现程序流程的跳转。该指令在程序设计中比较常用,且寻址方式灵活多样,请读者认真掌握。

指令示例:

LDR R0, [R1]            ;将存储器地址为R1的字数据读入寄存器R0
LDR R0, [R1, R2]     ;将存储器地址为R1+R2的字数据读入寄存器R0
LDR R0, [R1, #8]    ;将存储器地址为R1+8的字数据读入寄存器R0
LDR R0, [R1, R2] !  ;将存储器地址为R1+R2的字数据读入寄存器R0,并将新地址R1+R2写入R1
LDR R0, [R1, #8] !  ;将存储器地址为R1+8的字数据读入寄存器R0,并将新地址 R1+8写入R1
LDR R0, [R1], R2    ;将存储器地址为R1的字数据读入寄存器R0,并将新地址 R1+R2写入R1。
LDR R0, [R1, R2, LSL#2]! ;将存储器地址为R1+R2×4的字数据读入寄存器R0,并将新地址R1+R2*4写入R1
LDR R0, [R1], R2, LSL #2    ;将存储器地址为R1的字数据读入 寄存器R0,并将新地址R1+R2*4写入R1

2.LDRB指令

LDRB指令的格式为:

LDR{条件}B 目的寄存器,<存储器地址>

LDRB指令用于从存储器中将一个8位的字节数据传送到目的寄存器中,同时将寄存器的高24位清零。 该指令通常用于从存储器中读取8位的字节数据到通用寄存器,然后对数据进行处理。当程序计数器PC作为目的寄存器时,指令从存储器中读取的字数据被当作目 的地址,从而可以实现程序流程的跳转。

指令示例:

LDRB R0, [R1]       ;将存储器地址为R1的字节数据读入寄存器R0,并将R0的高24位清零
LDRB R0, [R1, #8]    ;将存储器地址为R1+8的字节数据读入寄存器R0,并将R0的高24位清零

3.LDRH指令

LDRH指令的格式为:

LDR{条件}H 目的寄存器,<存储器地址>

LDRH指令用于从存储器中将一个16位的半字数据传送到目的寄存器中,同时将寄存器的高16位清零。 该指令通常用于从存储器中读取16位的半字数据到通用寄存器,然后对数据进行处理。当程序计数器PC作为目的寄存器时,指令从存储器中读取的字数据被当作 目的地址,从而可以实现程序流程的跳转。

指令示例:

LDRH   R0, [R1]      ;将存储器地址为R1的半字数据读入寄存器 R0,并将R0的高16位清零
LDRH R0, [R1, #8]  ; 将存储器地址为R1+8的半字数据读入寄存器R0,并将R0 的高16位清零
LDRH R0, [R1, R2]  ;将存储器地址为R1+R2的半字数据读入寄存器R0,并将 R0的高16位清零

4.STR指令

STR指令的格式为:

STR{条件} 源寄存器,<存储器地址>

STR指令用于从源寄存器中将一个32位的字数据传送到存储器中。 该指令在程序设计中比较常用,且寻址方式灵活多样,使用方式可参考指令LDR。

指令示例:

STR R0, [R1], #8    ;将R0中的字数据写入以R1为地址的存储器中,并将新地址R1+8写入R1
STR R0, [R1, #8]   ;将R0中的字数据写入以R1+8为地址的存储器中

5.STRB指令

STRB指令的格式为:

STR{条件}B 源寄存器,<存储器地址>

STRB指令用于从源寄存器中将一个8位的字节数据传送到存储器中。该字节数据为源寄存器中的低8位。

指令示例:

STRB    R0, [R1]      ;将寄存器R0中的字节数据写入以R1为地 址的存储器中
STRB    R0, [R1, #8]   ;将寄存器R0中的字节数据写入以R1+8为地址的存储器中

6.STRH指令

STRH指令的格式为:

STR{条件}H 源寄存器,<存储器地址>

STRH指令用于从源寄存器中将一个16位的半字数据传送到存储器中。该半字数据为源寄存器中的低16位。

指令示例:

STRH   R0, [R1]            ;将寄存器R0中的半字数据写入以R1为地址的 存储器中
STRH   R0, [R1, #8]    ;将寄存器R0中的半字数据写入以R1+8 为地址的存储器中

值得注意的是,LDR指令加等号就是伪指令,LDR伪指令没有立即数范围的限制,可以直接赋值。
LDR伪指令的形式是“LDR Rn,=expr”。作用是装在一个32bit常数和一个地址到寄存器。

下面举一个例子来说明它的用法。

COUNT EQU   0x20000 ;  COUNT是我们定义的一个变量,地址为0x20000
LDR   R1, =COUNT;将COUNT这个变量的地址,也就是0x20000放到R1中。
MOV  R0, #4 ; 4是将立即数,将4放到R0中
STR   R0, [R1] ;将R0中的值放到以R1中的值为地址的存储单元去。实际就是将4放到地址为0x20000的存储单元中去。

这三条指令是为了完成对变量COUNT赋值。用三条指令来完成对一个变量的赋值。

从运行结果来看,最终内存地址0X20000的值是4,以上分析和运行结果是相符的。

为了进一步区分LDR指令和LDR伪指令,再看一个例子:

LDR		R1,=0x20000 ; 将0x20000放到R1中。
MOV		R0,#4 ; 4是将立即数,将4放到R0中
STR		R0,[R1] ;将R0中的值放到以R1中的值为地址的存储单元去。
LDR		R2, =0X20010;将0x20010放到R2中。
STR		R0,[R2]; 将R0中的值放到以R2中的值为地址的存储单元去。
LDR		R1,[R2]; 将R2的地址的值,存入R0的寄存器中

最终,R1寄存的值是4。内存地址0x20000和0x20010分别是4.

关于内存操作寻址请参看手册ARM® Cortex™ Series Programmer’s Guide。

3.5跳转指令

跳转指令用于实现程序流程的跳转,在ARM程序中有以下两种方法可以实现程序流程的跳转。
Ⅰ.使用专门的跳转指令;

Ⅱ.直接向程序计数器PC写入跳转地址值,通过向程序计数器PC写入跳转地址值,可以实现在4GB的地址空间中的任意跳转,在跳转之前结合使用MOV LR,PC等类似指令,可以保存将来的返回地址值,从而实现在4GB连续的线性地址空间的子程序调用。

ARM指令集中的跳转指令可以完成从当前指令向前或向后的32MB的地址空间的跳转,包括以下4条指令:

1.B指令

B指令的格式为:

B{条件} 目标地址

B指令是最简单的跳转指令。一旦遇到一个B指令,ARM处理器将立即跳转到给定的目标地址,从那里继续执行。注意存储在跳转指令中的实际值是相对当前PC 值的一个偏移量,而不是一个绝对地址,它的值由汇编器来计算(参考寻址方式中的相对寻址)。它是24位有符号数,左移两位后有符号扩展为32 位,表示的有效偏移为26 位(前后32MB的地址空间)。

指令示例:

B    Label        ;程序无条件跳转到标号Label处执行
CMP R1,#0      ;当CPSR寄存器中的Z条件码置位时,程序跳转到标号Label处执行BEQ Label       

2.BL指令

BL指令的格式为:

BL{条件} 目标地址

BL是另一个跳转指令,但跳转之前,会在寄存器R14中保存PC的当前内容,因此,可以通过将R14的内容重新加载到PC中,来返回到跳转指令之后的那个 指令处执行。该指令是实现子程序调用的一个基本但常用的手段。

指令示例:

BL   Label     ;当程序无条件跳转到标号Label处执行时,同时将当前的 PC值保存到R14(LR)中

3.BLX指令

BLX指令的格式为:

BLX 目标地址

BLX指令从ARM指令集跳转到指令中所指定的目标地址,并将处理器的工作状态有ARM状态切换到Thumb状态,该指令同时将PC的当前内容保存到寄存器R14中。因此,当子程序使用Thumb指令集,而调用者使用ARM指令集时,可以通过BLX指令实现子程序的调用和处理器工作状态的切换。同时,子程序的返回可以通过将寄存器R14值复制到PC中来完成。

4.BX指令

BX指令的格式为:

BX{条件} 目标地址

BX指令跳转到指令中所指定的目标地址,目标地址处的指令既可以是ARM指令,也可以是Thumb指令。

3.6乘法指令

ARM具有三种乘法指令,分别为:

  • 32*32位乘法指令;
  • 32*32位乘加指令;
  • 32*32位结果为64位的乘/乘加指令。

ARM 微处理器支持的乘法指令与乘加指令共有6条,可分为运算结果为32位和运算结果为64位两类,与前面的数据处理指令不同,指令中的所有操作数、目的寄存器 必须为通用寄存器,不能对操作数使用立即数或被移位的寄存器,同时,目的寄存器和操作数1必须是不同的寄存器。

乘法指令与乘加指令共有以下6条:

1.MUL指令(相乘)

MUL指令的格式为:

MUL{条件}{S} 目的寄存器,操作数1,操作数2

MUL指令完成将操作数1与操作数2的乘法运算,并把结果放置到目的寄存器中,同时可以根据运算结果设置CPSR中相应的条件标志位。其中,操作数1和操 作数2均为32位的有符号数或无符号数。

指令示例:

MUL R0, R1, R2          ;R0 = R1 * R2
MULS R0, R1, R2         ;R0 = R1 * R2,同时设置CPSR中的相关条件标志位

2.MLA指令(带累加的相乘)

MLA指令的格式为:

MLA{条件}{S} 目的寄存器,操作数1,操作数2,操作数3

MLA指令完成将操作数1与操作数2的乘法运算,再将乘积加上操作数3,并把结果放置到目的寄存器中,同时可以根据运算结果设置CPSR中相应的条件标志 位。其中,操作数1和操作数2均为32位的有符号数或无符号数。

指令示例:

MLA   R0, R1, R2, R3            ; R0 = R1 * R2 + R3
MLAS R0, R1, R2, R3            ; R0 = R1 * R2 + R3,同时设置CPSR中的相关条件标志位

3.SMULL指令

SMULL指令的格式为:

SMULL{条件}{S} 目的寄存器Low,目的寄存器High,操作数1,操作数2

SMULL指令完成将操作数1与操作数2的乘法运算,并把结果的低32位放置到目的寄存器Low中,结果的高32位放置到目的寄存器High中,同时可以 根据运算结果设置CPSR中相应的条件标志位。其中,操作数1和操作数2均为32位的有符号数。

指令示例:

SMULL   R0, R1, R2, R3        ;R0 = (R2 * R3)的低32位
                                                   ; R1 = (R2 * R3)的高32位

4.SMLAL指令

SMLAL指令的格式为:

SMLAL{条件}{S} 目的寄存器Low,目的寄存器High,操作数1,操作数2

SMLAL指令完成将操作数1与操作数2的乘法运算,并把结果的 低32位同目的寄存器Low中的值相加后又放置到目的寄存器Low中,结果的高32位同目的寄存器High中的值相加后又放置到目的寄存器High中,同 时可以根据运算结果设置CPSR中相应的条件标志位。其中,操作数1和操作数2均为32位的有符号数。

对于目的寄存器Low,在指令执行前存放64位加数的低32位,指令执行后存放结果的低32位;对于目的寄存器High,在指令执行前存放64位加数的高32位,指令执行后存放结果的高32位。

指令示例:

SMLAL   R0, R1, R2, R3      ;R0 = (R2 * R3)的低32位 + R0
                                              ; R1 = (R2 * R3)的高32位 + R1

5.UMULL指令

UMULL指令的格式为:

UMULL{条件}{S} 目的寄存器Low,目的寄存器High,操作数1,操作数2

UMULL指令完成将操作数1与操作数2的乘法运算,并把结果的低32位放置到目的寄存器Low中,结果的高32位放置到目的寄存器High中,同时可以 根据运算结果设置CPSR中相应的条件标志位。其中,操作数1和操作数2均为32位的无符号数。

指令示例:

UMULL   R0, R1, R2, R3       ; R0 = (R2 * R3)的低32位
                                                 ; R1 = (R2 * R3)的高32位

6.UMLAL指令

UMLAL指令的格式为:

UMLAL{条件}{S} 目的寄存器Low,目的寄存器High,操作数1,操作数2

UMLAL指令完成将操作数1与操作数2的乘法运算,并把结果的 低32位同目的寄存器Low中的值相加后又放置到目的寄存器Low中,结果的高32位同目的寄存器High中的值相加后又放置到目的寄存器High 中,同 时可以根据运算结果设置CPSR中相应的条件标志位。其中,操作数1和操作数2均为32位的无符号数。

对于目的寄存器Low,在指令执行前存放64位加数的低32位,指令执行后存放结果的低32位;对于目的寄存器High,在指令执行前存放64位加数的高32位,指令执行后存放结果的高32位。

指令示例:

UMLAL   R0, R1, R2, R3          ;R0 = (R2 * R3)的低32位+ R0
                                                    ; R1 = (R2 * R3)的高32位 + R1

3.7状态操作指令

1.MRS指令

MRS指令的格式为:

MRS{条件} 通用寄存器 程序状态寄存器(CPSR或SPSR)

MRS指令用于将程序状态寄存器的内容传送到通用寄存器中。该指令一般用在以下两种情况:

Ⅰ.当需要改变程序状态寄存器的内容时,可用MRS将程序状态寄存器的内容读入通用寄存器,修改后再写回程序状态寄存器。

Ⅱ.当在异常处理或进程切换时,需要保存程序状态寄存器的值,可先用该指令读出程序状态寄存器的值,然后保存。

指令示例:

MRS R0,CPSR                        ;传送CPSR的内容到R0
MRS R0,SPSR                         ;传送 SPSR的内容到R0

2.MSR指令

MSR指令的格式为:

MSR{条件} 程序状态寄存器(CPSR或SPSR)_<域>,操作数

MSR指令用于将操作数的内容传送到程序状态寄存器的特定域中。其中,操作数可以为通用寄存器或立即数。<域>用于设置程序状态寄存器中需要 操作的位,32位的程序状态寄存器可分为4个域:

位[31:24]为条件位域,用f表示;
位[23:16]为状态位域,用s表示;
位[15:8] 为扩展位域,用x表示;
位[7:0] 为控制位域,用c表示;

该指令通常用于恢复或改变程序状态寄存器的内容,在使用时,一般要在MSR指令中指明将要操作的域。

指令示例:

MSR CPSR, R0       ;传送R0的内容到CPSR
MSR SPSR, R0        ;传送R0的内容到SPSR
MSR CPSR_c; R0     ;传送R0的内容到SPSR,但仅仅修改CPSR中的控制位域

3.8协处理器指令

1.CDP指令

CDP指令的格式为:

CDP{条件} 协处理器编码,协处理器操作码1,目的寄存器,源寄存器1,源寄存器2,协处理器操作码2。

CDP指令用于ARM处理器通知ARM协处理器执行特定的操作,若协处理器不能成功完成特定的操作,则产生未定义指令异常。其中协处理器操作码1和协处理 器操作码2为协处理器将要执行的操作,目的寄存器和源寄存器均为协处理器的寄存器,指令不涉及ARM处理器的寄存器和存储器。

指令示例:

CDP   P3, 2, C12, C10, C3, 4   ;该指令完成协处理器P3的初始化 

2.LDC指令

LDC指令的格式为:

LDC{条件}{L} 协处理器编码,目的寄存器,[源寄存器]

LDC指令用于将源寄存器所指向的存储器中的字数据传送到目的寄存器中,若协处理器不能成功完成传送操作,则产生未定义指令异常。其中,{L}选项表示指 令为长读取操作,如用于双精度数据的传输。

指令示例࿱

以上是关于《嵌入式 - 嵌入式大杂烩》ARM汇编入门的主要内容,如果未能解决你的问题,请参考以下文章

《嵌入式 - 嵌入式大杂烩》Keil反编译入门

《嵌入式 - 嵌入式大杂烩》Keil反编译入门

《嵌入式 - 嵌入式大杂烩》Keil反编译入门

《嵌入式 - 嵌入式大杂烩》Keil反编译入门

《嵌入式 - 嵌入式大杂烩》深入理解ARM寄存器

《嵌入式 - 嵌入式大杂烩》深入理解ARM寄存器