BackGroundKnowledge
一条汇编指令格式应该为:Operation{cond}{s} Rd,Rn, Operand2
操作码 - 条件指令 - 状态码 - 目标寄存器 - 源寄存器 - 后续附加数据
like:ADD EQ N R1 R2 #100
写出来就是 → ADDEQN R1,r2,#100
其中{}看情况用,不是必须
条件指令
一般是跟在 B,LDR 等指令后的:
EQ : 等于
如果一次比较之后设置了 Z 标志。
NE : 不等于
如果一次比较之后清除了 Z 标志。
VS : 溢出设置
如果在一次算术操作之后设置了 V 标志,计算的结果不适合放入一个 32bit 目标寄存器中。
VC : 溢出清除
如果清除了 V 标志,与 VS 相反。
HI : 高于(无符号)
如果一次比较之后设置了 C 标志并清除了 Z 标志。
LS : 低于或同于(无符号)
如果一次比较操作之后清除了 C 标志或设置了 Z 标志。
PL : 正号
如果一次算术操作之后清除了 N。出于定义‘正号’的目的,零是正数的原因是它不是负数…
MI : 负号
如果一次算术操作之后设置了 N 标志。
CS : 进位设置
如果一次算术操作或移位操作之后设置了 C 标志,操作的结果不能表示为 32bit。你可以把 C 标志当作结果的第 33 位。
CC : 进位清除
与 CS 相反。
GE : 大于或等于(有符号)
如果一次比较之后…
设置了 N 标志并设置了 V 标志
或者…
清除了 N 标志并清除了 V 标志。GT : 大于(有符号)
如果一次比较之后…
设置了 N 标志并设置了 V 标志
或者…
清除了 N 标志并清除了 V 标志
并且…
清除了 Z 标志。LE : 小于或等于(有符号)
如果一次比较之后…
设置了 N 标志并清除了 V 标志
或者…
清除了 N 标志并设置了 V 标志
并且…
设置了 Z 标志。LT : 小于(有符号)
如果一次比较之后…
设置了 N 标志并清除了 V 标志。
或者…
清除了 N 标志并设置了 V 标志。AL : 总是
缺省条件,所以不用明显声明。
状态码
状态码保存在 CPU 内部寄存器 CPSR 中,用于记录特定的信息
N(NEGATIVE)标志 31 位
CPSR 的第 31 位是 N,符号标志位。它记录相关指令执行后,其结果是否为负.如果为负 N = 1,如果是非负数 N = 0.
注意:在 ARM64 的指令集中,有的指令的执行时影响状态寄存器的,比如 add\sub\or 等,他们大都是运算指令(进行逻辑或算数运算);
Z(ZERO)标志 30 位
CPSR 的第 30 位是 Z,0 标志位。它记录相关指令执行后,其结果是否为 0.如果结果为 0.那么 Z = 1.如果结果不为 0,那么 Z = 0.
注意:对于 Z 的值,我们可以这样来看,Z 标记相关指令的计算结果是否为 0,如果为 0,则 N 要记录下是 0 这样的肯定信息.在计算机中 1 表示逻辑真,表示肯定.所以当结果为 0 的时候 Z = 1,表示结果是 0.如果结果不为 0,则 Z 要记录下不是 0 这样的否定信息.在计算机中 0 表示逻辑假,表示否定,所以当结果不为 0 的时候 Z = 0,表示结果不为 0。
当结果为 0 时 Z = 1 当结果不为 0 时 Z = 0
C(CARRY)标志 29 位
- CPSR 的第 29 位是 C,进位标志位。一般情况下,进行无符号数的运算。
- 加法运算:当运算结果产生了进位时(无符号数溢出),C=1,否则 C=0。
- 减法运算(包括 CMP):当运算时产生了借位时(无符号数溢出),C=0,否则 C=1。
对于位数为 N 的无符号数来说,其对应的二进制信息的最高位,即第 N - 1 位,就是它的最高有效位,而假想存在的第 N 位,就是相对于最高有效位的更高位。
进位
我们知道,当两个数据相加的时候,有可能产生从最高有效位想更高位的进位。比如两个 32 位数据:0xaaaaaaaa + 0xaaaaaaaa,将产生进位。由于这个进位值在 32 位中无法保存,我们就只是简单的说这个进位值丢失了。其实 CPU 在运算的时候,并不丢弃这个进位制,而是记录在一个特殊的寄存器的某一位上。ARM 下就用 C 位来记录这个进位值。比如,下面的指令
1 | mov w0,#0xaaaaaaaa;0xa 的二进制是 1010 |
借位
当两个数据做减法的时候,有可能向更高位借位。
两个 32 位数据:0x00000000 - 0x000000ff,将产生借位,借位后,相当于计算 0x100000000 - 0x000000ff。得到 0xffffff01 这个值。由于借了一位,所以 C 位 用来标记借位。C = 0.比如下面指令:
1
2
3
4mov w0,#0x0
subs w0,w0,#0xff ;
subs w0,w0,#0xff
subs w0,w0,#0xff
V(OVERFLOW)溢出标志 28 位
CPSR 的第 28 位是 V,溢出标志位。在进行有符号数运算的时候,如果超过了机器所能标识的范围,称为溢出。
正数 + 正数 为负数 溢出
负数 + 负数 为正数 溢出
正数 + 负数 不可能溢出
寻址方式
由于数据保存在:内存/寄存器中,因此 ARM 难点就在于如何从前两者容器中获取数据,转移数据
前三种比较常见,看看第四个寄存器移位地址
表示:R1 寄存器左移 2 位,加上 R2,再赋给 R3
- 相对寻址:
- BL NEXT; 跳转到 NEXT(自己写的函数)
- MOV PC,LR; LR 的值赋给程序计数器 PC
ARM 堆栈
- ◎ Full descending 满递减堆栈
堆栈首部是高地址,堆栈向低地址增长。栈指针总是指向堆栈最后一个元素(最后一个元素是最后压入的数据)。
ARM-Thumb 过程调用标准和 ARM、Thumb C/C++ 编译器总是使用 Full descending 类型堆栈。
- ◎ Full ascending 满递增堆栈
堆栈首部是低地址,堆栈向高地址增长。栈指针总是指向堆栈最后一个元素(最后一个元素是最后压入的数据)。
- ◎ Empty descending 空递减堆栈
堆栈首部是低地址,堆栈向高地址增长。栈指针总是指向下一个将要放入数据的空位置。
- ◎ Empty ascending 空递增堆栈
堆栈首部是高地址,堆栈向低地址增长。栈指针总是指向下一个将要放入数据的空位置。
操作指令 - ALU 操作
数据操作表 1:https://blog.csdn.net/dengjin20104042056/article/details/106794349
数据操作表 2:https://blog.csdn.net/dengjin20104042056/article/details/106794861
主要分:数据操作 + 逻辑操作 + 比较操作
- 就像 java 中 加减乘除 对应 数据操作;
- 或与非对应逻辑操作;
- = <对应比较操作;
- if else,whilefor 循环对应流程操作
比较操作通过运算,将结果不保存在寄存器而是 S 标识符中用于判断
内存操作 - 读取内存
重点在于研究清楚:LDR STR 存储顺序
内存操作,可以理解为将内存器内容读取到寄存器中,或将寄存器数据读取到内存里。即存储器和寄存器数据交流
主要分三类:
- 单寄存器读写指令:单寄存器与内存数据传输
- 多寄存器内存访问指令:连续的内存数据读到单寄存器;或是多个内存器数据读到单个内存块中
- 数据交换指令:内存寄存器字交换,字节交换
LDR 指令
LDR - 寄存器和内存数据通信
LDR 指令的格式: LDR{条件} 目的寄存器 <存储器地址>
作用:将 存储器地址 所指地址处连续的 4 个字节(1 个字)的数据传送到目的寄存器中。
LDR R0,[R1] ;将存储器地址为 R1 的字数据读入寄存器 R0。
LDR R0,[R1,R2] ;将存储器地址为 R1+R2 的字数据读入寄存器 R0。
LDR R0,[R1,#8] ;将存储器地址为 R1+8 的字数据读入寄存器 R0。
LDR R0,[R1],R2 ;将存储器地址为 R1 的字数据读入寄存器 R0,并将 R1+R2 的值存入 R1。
LDR R0,[R1],#8 ;将存储器地址为 R1 的字数据读入寄存器 R0,并将 R1+8 的值存入 R1。
LDR R0,[R1,R2]! ;将存储器地址为 R1+R2 的字数据读入寄存器 R0,并将 R1+R2 的值存入 R1。
LDR R0,[R1,LSL #3] ;将存储器地址为 R1*8 的字数据读入寄存器 R0。
LDR R0,[R1,R2,LSL #2] ;将存储器地址为 R1+R2*4 的字数据读入寄存器 R0。
LDR R0,[R1,,R2,LSL #2]! ;将存储器地址为 R1+R24 的字数据读入寄存器 R0,并将 R1+R24 的值存入 R1。
LDR R0,[R1],R2,LSL #2 ;将存储器地址为 R1 的字数据读入寄存器 R0,并将 R1+R2*4 的值存入 R1。
LDR R0,Label ;Label 为程序标号,Label 必须是当前指令的-4~4KB 范围内。
LDRB 指令(LDR + B)
LDRB 指令的格式为:LDR{条件}B 目的寄存器,<存储器地址>
LDRB 指令的作用:
LDRB
指令用于从存储器中将一个8
位的字节数据传送到目的寄存器中,同时将寄存器的高24
位清零。该指令通常用于从存储器中读取8
位的字节数据到通用寄存器,然后对数据进行处理。当程序计数器`PC``作为目的寄存器时,指令从存储器中读取的字数据被当作目的地址,从而可以实现程序流程的跳转。```
指令示例:
LDRB R0,[R1] ;将存储器地址为 R1 的字节数据读入寄存器 R0,并将 R0 的高 24 位清零。
LDRB R0,[R1,#8] ;将存储器地址为 R1 + 8 的字节数据读入寄存器 R0,并将 R0 的高 24 位清零。
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 位清零。
STR 指令
寄存器和内存之间数据通信
STR 指令的格式为:STR{条件} 源寄存器,<存储器地址>
作用:STR 指令用于从源寄存器中将一个 32 位的字数据传送到存储器中。(四个字节)
STR R0,[R1],#8 ;将 R0 中的字数据写入以 R1 为地址的存储器中,并将新地址 R1 + 8 写入 R1。
STR R0,[R1,#8] ;将 R0 中的字数据写入以 R1 + 8 为地址的存储器中。STR r1, [r0] ;将 r1 寄存器的值,传送到地址值为 r0 的(存储器)内存中
STRB(STR + B)
- 按字节,将源寄存器数据输入到寄存器地址的内存中
多寄存器内存访问指令
- LDM 和 STM
模式 | 说明 | 模式 | 说明 |
---|---|---|---|
IA - Increase After | 每次传送后地址加 4 | FD | 满递减堆栈 |
IB - Increase Before | 每次传送前地址加 4 | ED | 空递减堆栈 |
DA - Decrease After | 每次传送后地址减 4 | FA | 满递增堆栈 |
DB - Decrease Before | 每次传送前地址减 4 | EA | 空递增堆栈 |
数据块传送操作 | 堆栈操作 |
- LDMIA R1,{R2,R3,R4}:以 R1 寄存器为基址,将 R1→ R2 ,然后 R1+4 读一个新的数据,写到 R3,再加 4 读新值写到 R4;
下图是使用了小端地址,四个字节一组;
首先将 R1 寄存器地址首地址设为【0x0000000F】(从后往前读)
那么 R1 中的数据为:E800E800E8,该数据会给到 R2
R1+4,挪到第二行,此时的数据为:E7FF0010,给到 R3
再+4,数据位 E800E800,给到 R4
表面上看起来是从寄存器中给数据到存储器,但实质其实是从存储器读数据到寄存器!
- 首先调用 LDMIB R1,{R2-R9}进行写,然后 SIMIA R1,{R2-R9}:将 R2 地址所在的值读到 R1,R1 地址+4,读 R2….
数据交换指令
MOV R1,#0x0f
MOV R2,#0x12
SWP R0,R1,[R2]:
- 把 R2 对应的 0x12 中数据:FFE71011 写到 R0 里,观察到 R0 寄存器从 0x00000000 → 0XFFE71011;
- 把 R1 的内容 0x0f,写到 R2 对应的四个字节块中,从 10 00 FF E7 → FF 00 00 00
SUB 指令
SUB 指令的格式为: SUB{条件}{S} 目的寄存器,操作数 1,操作数 2
作用:SUB 指令用于把操作数 1 减去操作数 2,并将结果存放到目的寄存器中。
- 操作数 1 应是一个寄存器
- 操作数 2 可以是一个寄存器,被移位的寄存器,或一个立即数。
该指令可用于有符号数或无符号数的减法运算
1 | AREA example,CODE,READONLY |
跳转指令
跳转,相当于函数调用,goto,break 语句…
B 指令 - 理解成 goto
ARM 汇编伪指令
混合编程
C 语言 + ARM 汇编
主要分为三种:
- C/C++ 嵌入 汇编
- 汇编调用 C/C++
- 汇编,C/C++ 程序间相互调用
C - 嵌入汇编
格式:__asm[volatile]{instruction [; instruction]}
【myMain.c】
1 | void my_strcpy(char *src, char *dest) |
1 |
|
C - 调用汇编
- 调用汇编的步骤:
- 汇编 export
- c 语言定义
extern function
- c 语言使用
- 具体原理
C 语言的汇编之间的参数传递,以【R0-R3】作为媒介,like:R0 传递第一个参数,R1 传递第二个…and so on,如果多余【4】个,就需要借助【栈】来辅助完成,函数的返回值通过【R0】传递,这是规定。
编程这东西理论固然重要,还是要看实践:
以下两个文件放在同一个 Project 里面运行
- 【myArm.S】
1 | AREA myARM,CODE,READONLY |
- 【myMain.c】
1 | /* |
汇编 - 调用 C【核心】
编程步骤
- C 语言去实现函数
- 汇编文件通过【import】函数名
- BL 函数名
1 | AREA myARM,CODE,READONLY |
1 | int cFunc(int a, int b, int c) |
混合
考试相关
以下多
一维数组和二维数组按照某种规律求和
一维数组求和
1 | AREA t1,CODE,READONLY ;一维数组求和 |
二维数组求和
1 | AREA t11,CODE,READONLY ;二维数组求和 先求每行的和,再求总和 2*5 |
求两个数的最大公约数
辗转相减法(更相减损术):
第一步:任意给定两个正整数;判断它们是否都是偶数。若是,则用 2 约简;若不是则执行第二步。
第二步:以较大的数减较小的数,接着把所得的差与较小的数比较,并以大数减小数,继续这个操作,直到所得的减数和差相等为止。第一步中约掉的若干个 2 的积与第二步中等数的乘积就是所求的最大公约数。
1 | AREA Test,CODE,READONLY |
求两个数的最小公倍数
通过求出最大公约数,用两个数的乘积去除最大公约数就可以得到最小公倍数,由于 ARM 汇编不支持除法,因此用循环减法进行模拟除法操作
1 | area getlcm, code, readonly |
对一维数组进行排序
【选择排序】
1 | AREA Sort,CODE,READONLY |
【冒泡排序 - mfy】
1 | ; 冒泡排序 |
【hpg - 手写】
1 | AREA BUBBLE,CODE,READONLY |
求一维数组中的最大值
1 | AREA t5,CODE,READONLY |
求一维数组中的最小值
与上面的区别就在于:MOVLT → MOVGT
1 | AREA t5,CODE,READONLY |
字符串的复制
1 | AREA t7,CODE,READONLY |