对于嵌入式开发者来说,了解汇编语言和内核寄存器是对内核深入理解的基础
..增加 2.2 汇编伪指令 章节 2021/12/12
..完善 2.3 ARM汇编指令集 2021/12/12
..增加 3.1 不同编译器的反汇编 2021/12/14
..增加 3.2 C和汇编 比较分析 2021/12/15
从开始写起也没想到内容有这么多,其中有很多干货的东西,希望自己能够说明到了,
其中有很多推荐的博文和网站,在此要特别感谢韦东山老师的视频,绝对干货满满
- 一、ARM内核寄存器
- 1.1 M3/M4内核寄存器
- 1.2 A7内核寄存器
- 1.3 ARM中的PC指针的值
- 二、ARM汇编语言
- 2.1 ARM汇编基础
- 2.2 汇编伪指令
- 2.3 ARM汇编指令集
- 三、代码反汇编简析
- 3.1 不同编译器的反汇编
- 3.2 C 和 汇编 比较分析
开头直接来看几个简单的汇编指令:
MOV R0,R1``MOV PC,R14
上面的指令中使用了汇编 MOV
指令,但是其中的 R0,R1,R14,PC分别是什么?哪来的?怎么用?
要讲 ARM 汇编语言,必须得先了解ARM的内核寄存器,内核处理所有的指令计算,都需要用到内核寄存器,所以ARM汇编里面指令大都是基于寄存器的操作。
文章前推荐韦东山老师的单片机核心视频,视频可以在韦东山老师官网里面找到:百问网
ARM版本简单介绍:
内核(架构)版本 | 处理器版本 |
---|---|
ARMv1 | ARM1 |
ARMv2 | ARM2、ARM3 |
ARMv3 | ARM6、 |
ARMv4 | ARM7、StrongARM |
ARMv5 | ARM9、ARM10E |
ARMv6 | ARM11 |
ARMv7 | ARM Cortex-A、ARM Cortex-M、ARM Cortex-R |
ARMv8 | ARM Cortex-A30、ARM Cortex-A50、ARM Cortex-A70 |
一、ARM内核寄存器
内核寄存器与外设寄存器:
内核寄存器与外设寄存器是完全不同的概念。内核寄存器是指 CPU 内部的寄存器,CPU处理所有指令数据需要用到这些寄存器保存处理数据;外设寄存器是指的 串口,SPI,GPIO口这些设备有关的寄存器。
在我的另一篇博文:FreeRTOS记录(三、FreeRTOS任务调度原理解析_Systick、PendSV、SVC)内核中断管理 章节讲到过Cortex-M的寄存器
的相关内容,这里我们再简单说明一下:
1.1 M3/M4内核寄存器
对于M3/M4而言:R13,栈指针(Stack Pointer)
- R13寄存器中存放的是栈顶指针,M3/M4 的栈是向下生长的,入栈的时候地址是往下减少的。
- 裸机程序不会用到PSP,只用到MSP,需要运行RTOS的时候才会用到PSP。
- 堆栈主要是通过POP,PUSH指令来进行操作。在执行 PUSH 和 POP 操作时, SP 的地址寄存器,会自动调整。
R14 ,连接寄存器(Link Register)
- LR 用于在调用子程序时存储返回地址。例如,在使用 BL(分支并连接, Branch and Link)指令时,就自动填充 LR 的值(执行函数调用的下一指令),进而在函数退出时,正确返回并执行下一指令。如果函数中又调用了其他函数,那么LR将会被覆盖,所以需要先将LR寄存器入栈。
- 保存子程序返回地址。使用BL或BLX时,跳转指令自动把返回地址放入r14中;子程序通过把r14复制到PC来实现返回
- 当异常发生时,异常模式的r14用来保存异常返回地址,将r14如栈可以处理嵌套中断
R15,程序计数器(Program Count)
- 在Cortex-M3中指令是3级流水线,出于对Thumb代码的兼容的考虑,读取pc时,会返回当前指令地址+4的值。
- 读 PC 时返回的值是当前指令的地址+4,关于M3、M4 和 A7的 PC值的问题需要单独来解释一下
其中程序状态寄存器 XPSR:
程序状态寄存器,该寄存器由三个程序状态寄存器组成 应用PSR(APSR) :包含前一条指令执行后的条件标志,比较结果:大于等于,小于,进位等等; 中断PSR(IPSR ) :包含当前ISR的异常编号 执行PSR(EPSR) :包含Thumb状态位
1.2 A7内核寄存器
对于 A7 而言:(上图取自原子教材,此图在官方文档《ARM Cortex-A(armV7)编程手册V4.0》中第3章.ARM Processor Modes and Registers 部分有英文原版,这里用中文版本更容易理解)
A7的 R13、R14、R15 的作用和 M3/4类似。
需要注意的一点就是,对于A7而言 R15,程序计数器(Program Count) :
- 读 PC 时返回的值是当前指令的地址+8, PC 指向当前指令的下两条指令地址。
- 由于ARM指令总是以字对齐的,故PC寄存器 bit[1:0] 总是00。
A7内核的程序状态寄存器 CPSR:
1.3 ARM中的PC指针的值
因为ARM指令采用三级流水线机制,所以PC指针的值并不是当前执行的指令的地址值:
- 当前执行地址A的指令,
- 同时已经在对下一条指令进行译码,
- 同时已经在读取下下一条指令:PC = A +4 (Thumb/Thumb2指令集)、PC = A + 8 (ARM指令集)
在文档《ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition》中对于 PC 的值有明确的说明:
M3/M4/M0:
PC的值 = 当前地址 + 4;
下面是一个 STM32F103 反汇编程序,找了一段有[pc,#0]的代码,方便判断:
A7:
PC的值 = 当前地址 + 8;
二、ARM汇编语言
ARM芯片属于精简指令集计算机(RISC:Reduced Instruction Set Computing),具体说明在下面这篇博文5.4小结有过说明:
STM32的内存管理相关(内存架构,内存管理,map文件分析)
2.1 ARM汇编基础
2.1.1 ARM指令集说明
最初,ARM公司发布了两类指令集:
- ARM指令集,32位的ARM指令,每条指令占据32位,高效,但是太占空间;
- Thumb指令集,16位的Thumb指令,每条指令占据16位,节省空间;
比如:MOV R0,R1
这条指令,可能是16位的,也可能是32位的
那么在汇编中是如何在 ARM 指令 和 Thumb 指令之间切换呢:
/*ARM指令 与 Thumb 指令 的切换*/
CODE16 ;(表示下面是 Thumb 指令)
...
...
;(调用下面的B函数)
bx B_addr;(B的地址B_addr的bit0 = 0,表示跳转过去执行 ARM 指令)
;A 函数
...
CODE32 ;(表示下面是 ARM 指令)
...
...
;B 函数
;(回到上面的A函数)
bx A_addr + 1 ;(A的地址A_addr的bit0 = 1,表示跳转过去执行 Thumb 指令)
...
/**********************/
对于A7、ARM7、ARM9 内核而言它们支持 16位的Thumb 指令集 和 32位的 ARM 指令集
对于M3、M4 内核而言它们支持的是 Thumb2 指令集,它支持16位、32位指令混合编程
对于内核来说使用的是 ARM指令集 还是 Thumb指令集,就是在 XPSR 和 CPSR
在M3/M4中, XPSR 寄存器的 T(bit24):1表示 Thumb指令集根据上面所述,M3是使用的 Thumb2 指令集,所以会有 T 总是 1.
在A7中 CPSR中的:T(bit5) :控制指令执行状态,表明本指令是 ARM 指令还是 Thumb 指令,通常和 J(bit24)一起表明指令类型
J(bit24) | T(bit5) | 指令集 |
---|---|---|
0 | 0 | ARM |
0 | 1 | Thumb |
1 | 1 | ThumbEE -- 提供从Thumb-2而来的一些扩充性,在所处的运行环境下,使得指令集能特别适用于运行阶段的编码产生(例如实时编译)。Thumb-2EE是专为一些语言如Limbo、Java、C#、Perl和Python,并能让实时编译器能够输出更小的编译码却不会影响到性能。 |
1 | 0 | Jazelle |
回到开始的指令 MOV R0,R1
code 16 ;(表示下面指令是16位的 Thumb 指令)
MOV R0,R1
code 32 ;(表示下面指令是32位的 ARM 指令)
MOV R0,R1
Thumb ;(编译器会根据指令自动识别是32位还是16位的 Thumb2)
MOV R0,R1
2.1.2 ARM汇编格式
编码格式:
不同指令集的编码格式(以 LDR 为例),摘自《ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition》:以“数据处理”(其他的还有内存访问,分支跳转等)指令为例,UAL汇编格式为:Operation
表示各类汇编指令,比如 ADD、MOV;cond表示conditon,即该指令执行的条件,如 EQ,NE 等;S表示该指令执行后,是否会影响CPSR寄存器的值, 是否影响CPSR 寄存器的值,书写时影响CPSR,否则不影响;Rd
为目的寄存器,用来存储运算的结果;Rn 第一个操作数的寄存器Operand2第二个操作数 ,其可以有3种操作源:1-- 立即数 2-- 寄存器 3-- 寄存器移位
其指令编码格式如下(32位):|bit 31-28 |27-25 |24-21 |20 |19-16 | 15-12 |11-0 | |--|--|--|--|--|--|--|--|--| |cond | 001 |Operation |S |Rn |Rd | Operand2 |
举个例子:
...
CMP R0,R2 ;比较R0和R2的值
MOV EQ R0,R1 ;加上EQ,如果上面R0的值和R2的值相等的话,才执行此语句
...
对于“数据处理”处理指令中的Operation ,指令集如下:对于其中的条件cond ,如下:
2.1.3 立即数
在一条ARM数据处理指令中,除了要包含处理的数据值外,还要标识ARM命令名称,控制位,寄存器等其他信息。这样在一条ARM数据处理指令中,能用于表示要处理的数据值的位数只能小于32位;
在上面的ARM汇编格式中我们介绍过,ARM在指令格式中设定,只能用指令机器码32位中的低12位来表示要操作的常数。
那么对于指令MOV R0, #value
(把value的值存入R0寄存器)而言,value 的值也不能是任意的值,其值只能是符合某些规定的数,在官方文档中 value 的值需要满足如下条件:什么是立即数?
满足上图中条件的数我们称之为 立即数,立即数就是符合一定规矩的数。
立即数表示方式:每个立即数由一个8位的常数循环右移偶数位得到。其中循环右移的位数由一个4位二进制的两倍表示。
立即数 = 一个8位的常数 循环位移 偶数位
一个8bit常数循环右移(Y*2 = {0,2,4,6,8, ...,26, 28, 30})就得到一个立即数了;(为什么是0到30的偶数下面解释)
如果需要深入理解立即数,推荐一篇博文:深刻认识 -->> 立即数
ARM处理器是按32位来处理数据的,ARM处理器处理的数据是32位,为了扩展到32位,因此使用了构造的方法,在12位中用8位表示基本数据值,用4位表示位移值,通过用8位基本数据值往右循环移动4位位移值*2次,来表示要操作的常数。
这里要强调最终的循环次数是4位位移值乘以2得到的,所以得到的最终循环次数肯定是一个偶数,为什么要乘以2呢,实质还是因为范围不够,4位表示位移次数,最大才15次(移位0,等于没有循环),加上8位数据还是不够32位,这样只能通过ALU的内部结构设计将4位位移次数乘以2,这样就能用12位表示32位常数了。
所以 12bit 数据存放格式如下:|bit 11-8 |7-0 | |--|--|--|--|--|--|--|--|--| |移位 1111b (0~15) | 8bit常数 |
但是我们去判断一个数是否立即数,实在是太麻烦了,但是我们想把任意数值赋给 R0 寄存器,怎么办? 这就需要用到伪指令了,下面说一说什么是伪指令。
-
ARM
+关注
关注
134文章
9040浏览量
366726 -
寄存器
+关注
关注
31文章
5308浏览量
119978 -
PC
+关注
关注
9文章
2065浏览量
153998 -
汇编语言
+关注
关注
14文章
409浏览量
35730
发布评论请先 登录
相关推荐
评论