CPU
内部的寄存器中,有一种特殊的寄存器(对于不同的处理器,个数和结构都可能不同)。这种寄存器在ARM
中,被称为状态寄存器就是CPSR
(current program status register
)寄存器。
CPSR
和其他寄存器不一样,其他寄存器是用来存放数据的,都是整个寄存器具有一个含义。而CPSR
寄存器是按位起作用的,也就是说,它的每一位都有专门的含义,记录特定的信息。
CPSR
寄存器是32位
的
CPSR
的低8位
(包括I
、F
、T
和M[4:0]
)称为控制位,程序无法修改。除非CPU
运行于特权模式下,程序才能修改控制位N
、Z
、C
、V
均为条件码标志位。它们的内容可被算术或逻辑运算的结果所改变,并且可以决定某条指令是否被执行。意义重大
案例:
改变
cpsr
寄存器的值,代码执行流程也会跟随改变打开
ViewController.m
文件,写入以下代码:#import "ViewController.h" @implementation ViewController void funcB(int a, int b){ if(a == b){ printf("a == b"); } else{ printf("error"); } } - (void)viewDidLoad { funcB(10, 20); } @end
真机运行项目,使用断点单步调试,来到
funcB
函数
cmp
指令和b.ne
指令搭配使用cmp w8, w9
:比较w8
、w9
的值b.ne
指令,有条件的跳转原本代码流程:
funcB
函数的a
、b
两个参数,分别传入10
和20
。此时w8 ≠ w9
,执行b.ne
指令,应该跳转到标号处
b.ne
指令的跳转,和cpsr
寄存器有关
cpsr
寄存器的值为0x80000000
,将0x8
转为二进制1000
。将首位1
右移一位,变为0100
,十六进制位0x4
使用
register write
命令,改变cpsr
寄存器的值为0x40000000
向下执行
1
步,没有跳转到b.ne
指令的标号处,而是向下继续执行了代码
原本传入的
a
、b
参数,它们的值完全不一样。但由于cpsr
寄存器被改变,导致代码执行流程跟随改变
N(Negative)标志
CPSR
的第31
位是N
,符号标志位。它记录相关指令执行后,其结果是否为负。如果为负N = 1
,如果是非负数N = 0
在
ARM64
的指令集中,有的指令在执行时影响状态寄存器,例如:add\sub\or
等,它们大都是运算指令(进行逻辑或算数运算)
案例:
改变
N
标志位打开
ViewController.m
文件,写入以下代码:#import "ViewController.h" @implementation ViewController void funcB(int a, int b){ asm( "mov w0,#0x0\n" "adds w0,w0,#-0xff\n" ); } - (void)viewDidLoad { funcB(10, 20); } @end
向下执行
1
步,将#0x0
写入w0
cpst
为0x40000000
,此时N
标志位为0
向下执行
1
步,w0
加等#-0xff
cpst
为0x80000000
,此时N
标志位为1
在
ARM64
中,add
加法不带进位,而adds
带进位的。sub
和subs
是做减法,用法类似。如果指定了s
,则这些指令将会根据结果来更新N
、Z
、C
和V
标记
Z(Zero)标志
CPSR
的第30
位是Z
,0
标志位。它记录相关指令执行后,其结果是否为0
。如果结果为0
,那么Z = 1
。如果结果不为0
,那么Z = 0
对于
Z
的值,我们可以这样来看,Z
标记相关指令的计算结果是否为0
。如果为0
,则Z
要记录下是0
这样的肯定信息。在计算机中1
表示逻辑真,表示肯定。所以当结果为0
的时候Z = 1
,表示结果是0
。如果结果不为0
,则Z
要记录下不是0
这样的否定信息。在计算机中0
表示逻辑假,表示否定。所以当结果不为0
的时候Z = 0
,表示结果不为0
案例:
改变
Z
标志位打开
ViewController.m
文件,写入以下代码:#import "ViewController.h" @implementation ViewController void funcB(int a, int b){ asm( "mov w0,#0x0\n" "adds w0,w0,#0x1\n" ); } - (void)viewDidLoad { funcB(10, 20); } @end
向下执行
1
步,将#0x0
写入w0
cpst
为0x40000000
,此时N
标志位为0
,Z
标志位为1
向下执行
1
步,w0
加等#0x1
cpst
为0x00000000
,此时N
标志位为0
,Z
标志位为0
计算结果为
1
,结果为非负数,所以N
标志位为0
。结果不为零,所以Z
标志位为0
C(Carry)标志
CPSR
的第29
位是C
,进位标志位。一般情况下,进行无符号数的运算
- 加法运算:当运算结果产生了进位时(无符号数溢出),
C=1
,否则C=0
- 减法运算(包括
CMP
):当运算时产生了借位时(无符号数溢出),C=0
,否则C=1
对于位数为
N
的无符号数来说,其对应的二进制信息的最高位,即第N - 1
位,就是它的最高有效位,而假想存在的第N
位,就是相对于最高有效位的更高位
进位
我们知道,当两个数据相加的时候,有可能产生从最高有效位想更高位的进位。例如:两个
32位
数据,0xaaaaaaaa + 0xaaaaaaaa
,将产生进位。由于这个进位值在32位
中无法保存,我们就只是简单的说这个进位值丢失了。其实CPU
在运算的时候,并不丢弃这个进位制,而是记录在一个特殊的寄存器的某一位上。ARM
下就用C
位来记录这个进位值
案例:
打开
ViewController.m
文件,写入以下代码:#import "ViewController.h" @implementation ViewController void funcB(int a, int b){ asm( "mov w0,#0xaaaaaaaa\n" "adds w0,w0,w0\n" "adds w0,w0,w0\n" "adds w0,w0,w0\n" "adds w0,w0,w0\n" ); } - (void)viewDidLoad { funcB(10, 20); } @end
向下执行
1
步,将#0xaaaaaaaa
写入w0
cpst
为0x40000000
,此时N
标志位为0
,Z
标志位为1
,C
标志位为0
向下执行
1
步,w0
加等#0xaaaaaaaa
w0
为0x55555554
,因为0xaaaaaaaa + 0xaaaaaaaa
结果溢出cpst
为0x30000000
,此时N
标志位为0
,Z
标志位为0
,C
标志位为1
向下执行
1
步,w0
加等#0x55555554
w0
为0xaaaaaaa8
cpst
为0x90000000
,此时N
标志位为1
,Z
标志位为0
,C
标志位为0
向下执行
1
步,w0
加等#0xaaaaaaa8
w0
为0x55555550
,因为0xaaaaaaa8 + 0xaaaaaaa8
结果溢出cpst
为0x30000000
,此时N
标志位为0
,Z
标志位为0
,C
标志位为1
向下执行
1
步,w0
加等#0x55555550
w0
为0xaaaaaaa0
cpst
为0x90000000
,此时N
标志位为1
,Z
标志位为0
,C
标志位为0
借位
当两个数据做减法的时候,有可能向更高位借位。例如:两个
32位
数据:0x00000000 - 0x000000ff
,将产生借位。借位后,相当于计算0x100000000 - 0x000000ff
。得到0xffffff01
这个值。由于借了一位,所以C
位用来标记借位
案例:
打开
ViewController.m
文件,写入以下代码:#import "ViewController.h" @implementation ViewController void funcB(){ asm( "mov w0,#0x0\n" "subs w0,w0,#0xff\n" "subs w0,w0,#0xff\n" "subs w0,w0,#0xff\n" ); } - (void)viewDidLoad { funcB(); } @end
向下执行
1
步,将#0x0
写入w0
cpst
为0x40000000
,此时N
标志位为0
,Z
标志位为1
,C
标志位为0
向下执行
1
步,w0
减等#0xff
w0
为0xffffff01
,因为#0x0 - #0xff
,结果溢出cpst
为0x80000000
,此时N
标志位为1
,Z
标志位为0
,C
标志位为0
向下执行
1
步,w0
减等#0xff
w0
为0xfffffe02
cpst
为0xa0000000
,此时N
标志位为1
,Z
标志位为0
,C
标志位为1
向下执行
1
步,w0
减等#0xff
w0
为0xfffffd03
cpst
为0xa0000000
,此时N
标志位为1
,Z
标志位为0
,C
标志位为1
V(Overflow)溢出标志
CPSR
的第28
位是V
,溢出标志位。在进行有符号数运算的时候,如果超过了机器所能标识的范围,称为溢出
- 正数 + 正数 = 负数,溢出
V = 1
。否则V = 0
- 负数 + 负数 = 正数,溢出
V = 1
。否则V = 0
- 正数 + 负数,在同等宽度下,不可能溢出
案例1:
正数 + 正数 = 负数
打开
ViewController.m
文件,写入以下代码:#import "ViewController.h" @implementation ViewController void funcB(){ asm( "mov w0,#0xaaaaaaaa\n" "adds w0,w0,w0\n" "adds w0,w0,w0\n" ); } - (void)viewDidLoad { funcB(); } @end
向下执行
1
步,将#0xaaaaaaaa
写入w0
cpst
为0x40000000
,此时N
标志位为0
,Z
标志位为1
,C
标志位为0
,V
标志位为0
向下执行
1
步,w0
加等#0xaaaaaaaa
w0
为0x55555554
,因为0xaaaaaaaa + 0xaaaaaaaa
结果溢出cpst
为0x30000000
,此时N
标志位为0
,Z
标志位为0
,C
标志位为1
,V
标志位为1
向下执行
1
步,w0
加等#0x55555554
w0
为0xaaaaaaa8
,因为0x55555554 + 0x55555554
对于有符号数,结果溢出cpst
为0x90000000
,此时N
标志位为1
,Z
标志位为0
,C
标志位为0
,V
标志位为1
在计算过程中,底层无法得知当前是
无符号数
还是有符号数
。运算时,C
标志位按无符号数
运算,而V
标志位按有符号数
运算
案例2:
负数 + 负数 = 正数
打开
ViewController.m
文件,写入以下代码:#import "ViewController.h" @implementation ViewController void funcB(){ asm( "mov w0,#-0x7fffffff\n" "adds w0,w0,w0\n" ); } - (void)viewDidLoad { funcB(); } @end
向下执行
1
步,将#-0x7fffffff
写入w0
cpst
为0x40000000
,此时N
标志位为0
,Z
标志位为1
,C
标志位为0
,V
标志位为0
向下执行
1
步,w0
加等#-0x7fffffff
w0
为0x00000002
,因为#-0x7fffffff + #-0x7fffffff
结果溢出cpst
为0x30000000
,此时N
标志位为0
,Z
标志位为0
,C
标志位为1
,V
标志位为1
总结
状态寄存器
- 状态寄存器就是
CPSR
,也称之为标志寄存器
- 在
ARM64
中,状态寄存器(cpsr
)为32位
- 最高
4位
(28
、29
、30
、31
)为标志位
N
标志
- 负标记位
- 执行结果为负数,
N = 1
。非负数,N = 0
Z
标志
0
标记位- 结果为
0
,Z = 1
。结果非0
,Z = 0
C
标志
- 无符号数溢出
- 加法:进位
C = 1
,否则C = 0
- 减法:借位
C = 0
,否则C = 1
V
标志
- 有符号数溢出
- 正数 + 正数 = 负数,溢出
V = 1
。否则V = 0
- 负数 + 负数 = 正数,溢出
V = 1
。否则V = 0
- 正数 + 负数,在同等宽度下,不可能溢出
汇编指令
subs
指令:和sub
指令相似,做减法。影响目标寄存器,同时影响状态寄存器
adds
指令:和add
指令相似,做加法。影响目标寄存器,同时影响状态寄存器