电子说
汇编语言分成两块:标准指令集和非标准指令集。伪指令属于非标准指令集。
什么是伪指令?
类似于宏的东西,把复杂的有好几天指令进行跳转的完成的小功能级进行新的标签设定,这就是伪指令。
类似于学c语言的时候的预处理,在预处理的时候把它定义于一堆的宏转化为真正的c语言的代码。同样,伪指令是在定义好之后的汇编,汇编的时候会把它翻译成标准指令,也许一条简单的伪指令可以翻译成很多条标准的汇编指令集,所以这就是伪指令最重要的作用。
我们前面说的 CODE16
CODE32
也是伪指令,用来指定其后的代码格式。
伪指令的作用?
基本的指令可以做各类操作了,但操作起来太麻烦了。伪指令定义了一些类似于带参数的宏,能够更好的实现汇编程序逻辑。(比如我现在要设置一个值给寄存器R0,但下次我修改了寄存器R0之后又需要读出来刚才的值,那我们就要先临时保存值到SPSR,CPSR,然后不断切换。)
伪指令只是在汇编器之前作用,汇编以后翻译为标准的汇编令集。
伪指令的类别伪指令可分为ARM汇编伪指令和GNU汇编伪指令
ARM汇编伪指令是ARM公司的,GNU汇编伪指令是GNU平台的。他们有自己的汇编器,不同的汇编器的解释语法可以设成不同。
在这里插入图片描述
这里列出部分伪指令说明,具体的伪指令可以结合 ARM汇编伪指令分析:
bit 11-8 | 7-0 |
---|---|
.word | 分配一个4字节空间 |
.byte | 定义单字节数据 |
.short | 定义双字节数据 |
.long | 定义一个4字节数据 |
.equ | 赋值语句:.equ a, 0x11 |
.align | 数据字节对齐:.align 4 (4字节对齐) |
.global | 定义全局符号:.global Default_Handler |
.end | 源文件结束 |
在我的另一篇博文:STM32的启动过程(startup_xxxx.s文件解析)
里面有过一些对伪指令意思的的说明,下面也列出部分说明:
AREA:
用于定义一个代码段或数据段。属性字段表示该代码段(或数据段)的相关属性,多个属性用逗号分隔。其中,段名若以数字开头,则该段名需用 “ | ” 括起来:
ALIGN:
ALIGN 伪指令可通过添加填充字节的方式,使当前位置满足一定的对其方式。其中,表达式的值用于指定对齐方式,可能的取值为2的幂,如 1 、2 、4 、8 、16 等。若未指定表达式,则将当前位置对齐到下一个字的位置。
CODE16和CODE32:
指定其后面的指令为 ARM 指令还是 Thumb 指令,前面介绍过。
ENTRY:
用于指定汇编程序的入口点。在一个完整的汇编程序中至少要有一个 ENTRY (也可以有多个,当有多个 ENTRY 时,程序的真正入口点由链接器指定),但在一个源文件里最多只能有一个 ENTRY。
在startup_stm32f103xg.s
里面就没有。
END:
用于通知编译器已经到了源程序的结尾。IMPORT 和 EXPORT:
IMPORT 定义表示这是一个外部变量的标号,不是在本程序定义的 EXPORT 表示本程序里面用到的变量提供给其他模块调用的
LDR
和 ADR
LDR
伪指令:
简单介绍了伪指令基础,回到上一小结留下的问题,想要把任意值复制给 R0,怎么处理,我们使用伪指令:LDR R0, =value
编译器会把“伪指令”替换成真实的指令:
LDR R0, =0x12
0x12是立即数,那么替换为:MOV R0, #0x12
LDR R0, =0x12345678
0x12345678不是立即数,那么替换为:LDR R0, [PC, #offset]
// 2. 使用Load Register读内存指令读出值,offset是链接程序时确定的 ……Label DCD 0x12345678
// 1. 编译器在程序某个地方保存有这个值
ADR
伪指令:
ADR的意思是:address,用来读某个标号的地址:ADR{cond} Rd, labe1
ADR R0, Loop
...
Loop
ADD R0, R0, #1
;(它是“伪指令”,会被转换成某条真实的指令,比如:)
ADD R0, PC, #val ; loop的地址等于PC值加上或者减去val的值,val的值在链接时确定,
...
Loop
ADD R0, R0, #1
在《ARM Cortex-M3与Cortex-M4权威指南》一文中第5章节有详细的指令集说明:汇编指令可以分为几大类:数据处理、内存访问、跳转、饱和运算、其他指令。
MOV指令,用于将数据从一个寄存器拷贝到另外一个寄存器,或者将一个立即数传递到寄存器。
MOV指令的格式为:MOV{条件}{S} 目的寄存器,源操作数
MOV R0,R1 ;@将寄存器R1中的数据传递给R0,即R0=R1
MOV R0, #0X12 ;@将立即数0X12传递给R0寄存器,即R0=0X12
MRS指令,用于将特殊寄存器(如CPSR和SPSR)中的数据传递给通用寄存器。
MSR指令,和MRS相反,用来将普通寄存器的数据传递给特殊寄存器。
;M3/M4
MRS R0, APSR ;单独读APSR
MRS R0, PSR ; 读组合程序状态
;A7
MRS R0, CPSR ; 读组合程序状态
...
MSR CPSR,R0 ;传送R0的内容到CPSR
LDR:
LDR 指令用于从存储器中将一个32位的字数据传送到目的寄存器中。该指令通常用于从存储器中读取32位的字数据到通用寄存器,然后对数据进行处理。
指令的格式为:LDR{条件} 目的寄存器,<存储器地址>
当程序计数器PC作为目的寄存器时,指令从存储器中读取的字数据被当作目的地址,从而可以实现程序流程的跳转。
LDRB: 字节操作
LDRH: 半字操作
LDR Rd, [Rn , #offset] ;从存储器Rn+offset的位置读取数据存放到Rd中。
...
LDR R0, =0X02077004 ;伪指令,将寄存器地址 0X02077004 加载到 R0 中,即 R0=0X02077004
LDR R1, [R0] ;读取地址 0X02077004 中的数据到 R1 寄存器中
...
LDR R0,[R1,R2] ;将存储器地址为R1+R2的字数据读入寄存器R0。
LDR R0,[R1,#8] ;将存储器地址为R1+8的字数据读入寄存器R0。
...
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。
...
LDRH R0,[R1] ;将存储器地址为R1的半字数据读入寄存器R0,并将R0的高16位清零。
STR:
STR 指令用于从源寄存器中将一个32位的字数据传送到存储器中。该指令在程序设计中比较常用,且寻址方式灵活多样,使用方式可参考指令LDR。
指令的格式为:STR{条件} 源寄存器,<存储器地址>
STRB: 字节操作,从源寄存器中将一个8位的字节数据传送到存储器中。该字节数据为源寄存器中的低8位。
STRH: 半字操作,从源寄存器中将一个16位的半字数据传送到存储器中。该半字数据为源寄存器中的低16位。
STR Rd, [Rn, #offset] ;将Rd中的数据写入到存储器中的Rn+offset位置。
LDR R0, =0X02077004 ;将寄存器地址 0X02077004 加载到 R0 中,即 R0=0X02077004
LDR R1, =0X2000060c ;R1 保存要写入到寄存器的值,即 R1=0X2000060c
STR R1, [R0] ;将 R1 中的值写入到 R0 中所保存的地址中
STR R0,[R1],#8 ;将R0中的字数据写入以R1为地址的存储器中,并将新地址R1+8写入R1。
STR R0,[R1,#8] ;将R0中的字数据写入以R1+8为地址的存储器中。
PUSH :
压栈,将寄存器中的内容,保存到堆栈指针指向的内存上面,将寄存器列表存入栈中。
PUSH < reg list >
POP :
出栈,从栈中恢复寄存器列表
POP < reg list >
push {R0, R1} ;保存R0,R1
push {R0~R3,R12} ;保存 R0~R3 和 R12,入栈
pop {R0~R3} ;恢复R0 到 R3 ,出栈
以M3内核来举个例子:
假设当前 MSP 值为 0x2000 2480;寄存器 R0 的值为 0x3434 3434 寄存器 R1 的值为 0x0000 1212 寄存器 R2 的值为 0x0000 0000
执行push {R0, R1,R2}
之后,
内存地址的数据为:0x2000 2474的值为: 0x3434 3434 (R0的值) 0x2000 2478的值为: 0x0000 1212 (R1的值) 0x2000 247C的值为: 0x0000 0000 (R2的值) MSP 的值变成 0x2000 2474
高位寄存器保存到高地址,先入栈,如果是POP,数据先出到低位寄存器
B :
ARM 处理器将立即跳转到指定的目标地址,不再返回原地址。
B指令的格式为:B{条件} 目标地址
注意存储在跳转指令中的实际值是相对当前PC值的一个偏移量,而不是一个绝对地址,它的值由汇编器来计算。
//设置栈顶指针后跳转到C语言
_start:
ldr sp,=0X80200000 ;设置栈指针
b main ;跳到 main 函数
BL :
BL 跳转指令,在跳转之前会在寄存器LR(R14)中保存当前PC寄存器值,所以可以通过将LR 寄存器中的值重新加载到PC中来继续从跳转之前的代码处运行,是子程序调用的常用的方法。
BL loop ;跳转到标号loop处执行时,同时将当前的PC值保存到R14中
BLX:
该跳转指令是当子程序使用Thumb指令集,而调用者使用ARM指令集时使用。
BLX指令从ARM指令集跳转到指令中所指定的目标地址,并将处理器的工作状态有ARM状态切换到Thumb状态,该指令同时将PC的当前内容保存到寄存器R14中。
BX:
BX指令跳转到指令中所指定的目标地址,目标地址处的指令既可以是ARM指令,也可以是Thumb指令。
算数运算指令和下面的逻辑运算指令表格摘自《【正点原子】I.MX6U嵌入式Linux驱动开发指南》
全部0条评论
快来发表一下你的评论吧 !