@[TOC](IOS 逆向开发(二十一) 汇编-基础)
1. 汇编简介
- 为啥要需要学习汇编?
- 大学都学习过汇编语言,那时候觉得汇编晦涩难懂,而且工作后实际开发中也一般都用高级语言,很少直接接触到汇编。但是理解汇编相关知识后,你会更加深入的理解高级语言的函数为啥要那样写,怎么写才能效率性能最高,有时候你调式代码可以通过下符号断点直接查看跟踪汇编代码。如果能读懂汇编代码,一切都不难题。
- 在逆向开发中,非常重要的一个环节就是静态分析.首先我们是逆向iOS系统上面的APP.那么我们知道,一个APP安装在手机上面的可执行文件本质上是二进制文件.因为iPhone手机本质上执行的指令是二进制.是由手机上的CPU执行的.所以静态分析是建立在分析二进制上面。理解汇编,我们就能自己通过汇编来调用相关代码。
- 越底层越单纯!真正的程序员都需要了解的一门非常重要的语言,汇编!
- 汇编是什么?
汇编执行指令是机器指令的符号化表示,其操作码用记忆符表示,地址码直接用标号、变量名字、常数等表示。汇编执行指令经汇编程序翻译为机器指令,二者之间基本上保持一一对应的关系。汇编伪指令又称作汇编指示,用于向汇编程序提供用户自定义的符号、数据的类型、数据空间的长度,以及目标程序的格式、存放位置等提示性信息,其作用是指示汇编程序如何进行汇编。使用汇编语言编写的源代码,需要通过使用相应的汇编程序将它们转换成可执行的机器代码。这一过程被称为汇编过程。
1.1 汇编语言发展过程
- 汇编的发展过程?
汇编程序的雏型是在电子离散时序自动计算机 EDSAC上研制成功的。这种系统的特征是用户程序中的指令由单字母指令码、十进制地址和终结字母组成。第一个汇编程序是符号优化汇编程序(SOAP)系统,它是50年代中期为IBM650计算机研制的。这种计算机用磁鼓作存储器,每条指令指出后继指令在磁鼓中的位置。当初研制SOAP系统的动机不是引入汇编语言的符号化特色,而是为了集中解决指令在磁鼓中合理分布的问题,以提高程序的运行效率。IBM704计算机的符号汇编程序(SAP)是汇编程序发展中的一个重要里程碑。此后的汇编程序大都以这一系统为模型,其主要特征未发生本质的变化。随着计算机软件的高速发展和广泛应用,汇编程序又吸收了宏加工程序、高级语言翻译程序等系统的一些优点,相继研制出宏汇编程序、高级汇编程序 。
- 机器语言
由0和1组成的机器指令.
加:0100 0000
减:0100 1000
乘:1111 0111 1110 0000
除:1111 0111 1111 0000
- 汇编语言(assembly language)
使用助记符代替机器语言
如:
加:INC EAX 通过编译器 0100 0000
减:DEC EAX 通过编译器 0100 1000
乘:MUL EAX 通过编译器 1111 0111 1110 0000
除:DIV EAX 通过编译器 1111 0111 1111 0000
- 高级语言(High-level programming language)
C\C++\Java\OC\Swift,更加接近人类的自然语言
比如C语言:
加:A+B 通过编译器 0100 0000
减:A-B 通过编译器 0100 1000
乘:A*B 通过编译器 1111 0111 1110 0000
除:A/B 通过编译器 1111 0111 1111 0000
-
我们的代码在终端设备上是这样的过程:
- 汇编语言与机器语言一一对应,每一条机器指令都有与之对应的汇编指令.
- 汇编语言可以通过编译得到机器语言,机器语言可以通过反汇编得到汇编语言.
- 高级语言可以通过编译得到汇编语言 \ 机器语言,但汇编语言\机器语言几乎不可能还原成高级语言.
1.2 汇编特点
可以直接访问、控制各种硬件设备,比如存储器、CPU等,能最大限度地发挥硬件的功能。
能够不受编译器的限制,对生成的二进制代码进行完全的控制。
目标代码简短,占用内存少,执行速度快。
汇编指令是机器指令的助记符,同机器指令一一对应。每一种CPU都有自己的机器指令集\汇编指令集,所以汇编语言不具备可移植性。
知识点过多,开发者需要对CPU等硬件结构有所了解,不易于编写、调试、维护。
不区分大小写,比如mov和MOV是一样的。
1.3 汇编语言的种类
- 目前讨论比较多的汇编语言有:
- 8086汇编(8086处理器是16bit的CPU)
- Win32汇编
- Win64汇编
- ARM汇编(嵌入式、Mac、iOS)
- 我们iPhone里面用到的是ARM汇编,但是不同的设备也有差异.因CPU的架构不同.
架构 | 设备 |
---|---|
armv6 | iPhone, iPhone2, iPhone3G, 第一代、第二代 iPod Touch |
armv7 | iPhone3GS, iPhone4, iPhone4S,iPad, iPad2, iPad3(The New iPad), iPad mini, iPod Touch 3G, iPod Touch4 |
armv7s | iPhone5, iPhone5C, iPad4(iPad with Retina Display) |
arm64 | iPhone5S 以后 iPhone13 , iPad Air, iPad mini2以后 |
1.4 汇编程序的种类
- 简单汇编程序
简单汇编程序 又称“装入并执行”式汇编程序。由于简便而得到广泛使用。这种汇编程序的特点是汇编后的机器语言程序直接放在内存之中准备执行。目标程序所占据的存储位置是在汇编时固定的,并且以后不能改变,所以这种工作方式不能将多个独立汇编的子程序合并为一个完整的程序,而且只能调用位置与目标程序不冲突的程序库中的子程序。
- 模块汇编程序
模块汇编程序 为适应模块程序设计方法而研制的。它除了克服简单汇编程序的缺点之外,还提供并行设计、编码和调试不同程序模块的能力,而且更改程序时只更改有关的模块即可。每个汇编后的程序模块称为目标模块,多个目标模块经连接装配程序组合成一个完整的可执行的程序。
- 条件汇编程序
条件汇编程序 主要特点是具有选择汇编某些程序段的能力。它适用于编写选择性较大的程序或程序包,以便根据用户的需要和设备的配置情况剪裁、编制适当的软件。这种汇编语言通常要引入“条件转移”、“转移”等汇编指示,以便根据用户指定的汇编条件有选择地汇编某些程序段或控制汇编程序的加工路径。
- 宏汇编程序
宏汇编程序 主要特点是在汇编程序中增加宏加工功能。它允许用户方便地定义和使用宏指令,适用于程序中多处出现、具有一定格式、可以通过少数参数调节改变的程序段落的场合。采用这种方法不仅减少程序的长度,增加可读性,而且程序段落的格式需要改变时,只须改动定义处,而不必改动每一使用处。
- 高级汇编程序
高级汇编程序 采用高级程序设计语言的控制语句结构的汇编程序。它不仅保持汇编语言表达能力强、程序运行效率高的优点,而且能充分吸收高级语言书写简单和易读的长处。这是由于高级汇编程序允许用户使用高级程序设计语言的控制语句(如条件语句、循环语句、函数和过程)编写程序中的控制部分,而且还允许用户直接利用汇编语言直接控制存储分配、存取寄存器硬件,描述高级语言难于表达的算法。第一个高级汇编程序是N.沃思为IBM360系统研制的PL/360语言汇编程序,其特点是程序的控制部分采用高级语言的控制语句编写,而数据加工部分采用IBM360汇编指令编写。自此以后,又相继出现了类似ALGOL的汇编程序,类似FORTRAN的汇编程序FAT 。
2. 汇编学习必要知识
- 要想学好汇编,首先需要了解CPU等硬件结构,需要理解APP/程序的执行过程;其中硬件相关最为重要是CPU/内存。在汇编中,大部分指令都是和CPU与内存相关的。
2.1 app程序执行过程
-
APP/程序的执行过程?
2.2 总线
- 每一个CPU芯片都有许多管脚,这些管脚和总线相连,CPU通过总线跟外部器件进行交互
- 总线:一根根导线的集合
- 总线的分类
- 地址总线
- 数据总线
控制总线
-
实例1:CPU从内存的3号单元读取数据
- 地址总线
- 它的宽度决定了CPU的寻址能力
8086的地址总线宽度是20,所以寻址能力是1M( 220 )
- 数据总线
- 它的宽度决定了CPU的单次数据传送量,也就是数据传送速度
- 8086的数据总线宽度是16,所以单次最大传递2个字节的数据
- 控制总线
它的宽度决定了CPU对其他器件的控制能力、能有多少种控制。
- 实例2:
- 一个CPU 的寻址能力为8KB,那么它的地址总线的宽度为13__
- 8080,8088,80286,80386 的地址总线宽度分别为16根,20根,24根,32根.那么他们的寻址能力分别为多少__64__KB, __1__MB,___16_MB,__4__GB?
- 8080,8088,8086,80286,80386 的数据总线宽度分别为8根,8根,16根,16根,32根.那么它们一次可以传输的数据为:___1_B,__1__B,__2__B,__2__B,__4__B,
- 从内存中读取1024字节的数据,8086至少要读512次,80386至少要读取256次.
2.3 内存
- 内存地址空间的大小受CPU地址总线宽度的限制。8086的地址总线宽度为20,可以定位220个不同的内存单元(内存地址范围0x00000~0xFFFFF),所以8086的内存空间大小为1MB
- 0x00000~0x9FFFF:主存储器。可读可写
- 0xA0000~0xBFFFF:向显存中写入数据,这些数据会被显卡输出到显示器。可读可写
- 0xC0000~0xFFFFF:存储各种硬件\系统信息。只读
2.4 CPU
CPU除了有控制器、运算器还有寄存器。其中寄存器的作用就是进行数据的临时存储。
CPU的运算速度是非常快的,为了性能CPU在内部开辟一小块临时存储区域,并在进行运算时先将数据从内存复制到这一小块临时存储区域中,运算时就在这一小快临时存储区域内进行。我们称这一小块临时存储区域为寄存器。
对于arm64系的CPU来说, 如果寄存器以x开头则表明的是一个64位的寄存器,如果以w开头则表明是一个32位的寄存器,在系统中没有提供16位和8位的寄存器供访问和使用。其中32位的寄存器是64位寄存器的低32位部分并不是独立存在的。
2.4.1 高速缓存
- iPhoneX上搭载的ARM处理器A11它的1级缓存的容量是64KB,2级缓存的容量8M.
- CPU每执行一条指令前都需要从内存中将指令读取到CPU内并执行。而寄存器的运行速度相比内存读写要快很多,为了性能,CPU还集成了一个高速缓存存储区域.当程序在运行时,先将要执行的指令代码以及数据复制到高速缓存中去(由操作系统完成).CPU直接从高速缓存依次读取指令来执行.
2.5 进制
很多人学不好进制,原因是总以十进制为依托去考虑其他进制,需要运算的时候也总是先转换成十进制,这种学习方法是错误的.
我们为什么一定要转换十进制呢?仅仅是因为我们对十进制最熟悉,所以才转换.
每一种进制都是完美的,想学好进制首先要忘掉十进制,也要忘掉进制间的转换!
2.5.1 进制的定义
- 八进制由8个符号组成:0 1 2 3 4 5 6 7 逢八进一
- 十进制由10个符号组成:0 1 2 3 4 5 6 7 8 9逢十进一
- N进制就是由N个符号组成:逢N进一
- 1 + 1 在什么情况下等于 3 ?
- 十进制由10个符号组成: 0 1 3 2 8 A B E S 7 逢十进一
- 如果这样定义十进制: 1 + 1 = 3!就对了!
- 十进制由十个符号组成,逢十进一,符号是可以自定义的!!
传统我们定义的十进制和自定义的十进制不一样.那么这10个符号如果我们不告诉别人这个符号表,别人是没办法拿到我们的具体数据的!这样我们可以将自定义的十进制符号用于加密!
2.5.2 进制的乘法表
- 如果现在让你做一个8进制的运算题,如果只给你一支笔,你多久可以计算出答案呢?
计算下面两个8进制数据的加减乘除运算:
2 + 3 = __ ,
2 * 3 = __ ,
4 + 5 = __ ,
4 * 5 = __.
277 + 333 = __ ,
276 * 54 = __ ,
237 - 54 = __ ,
234 / 4 = __ .
- 如果把上面的8题目改成10进制,相信很多童鞋都是可以很快计算出来的,这是因为我们小学的时候就背了99乘法表。如果没有借助乘法表,我们也是很难计算10进制的乘除法的。
- 所以我们计算其他进制的运算也是需要通过乘法表来计算的。
2.5.3 进制的运算
- 八进制加法表
0 1 2 3 4 5 6 7
10 11 12 13 14 15 16 17
20 21 22 23 24 25 26 27
...
1+1 = 2
1+2 = 3 2+2 = 4
1+3 = 4 2+3 = 5 3+3 = 6
1+4 = 5 2+4 = 6 3+4 = 7 4+4 = 10
1+5 = 6 2+5 = 7 3+5 = 10 4+5 = 11 5+5 = 12
1+6 = 7 2+6 = 10 3+6 = 11 4+6 = 12 5+6 = 13 6+6 = 14
1+7 = 10 2+7 = 11 3+7 = 12 4+7 = 13 5+7 = 14 6+7 = 15 7+7 = 16
- 八进制乘法表
0 1 2 3 4 5 6 7 10 11 12 13 14 15 16 17 20 21 22 23 24 25 26 27...
11 = 1
12 = 2 22 = 4
13 = 3 23 = 6 33 = 11
14 = 4 24 = 10 34 = 14 44 = 20
15 = 5 25 = 12 35 = 17 45 = 24 55 = 31
16 = 6 26 = 14 36 = 22 46 = 30 56 = 36 66 = 44
17 = 7 27 = 16 37 = 25 47 = 34 57 = 43 67 = 52 77 = 61
- 实例3: 四则运算
277 236 276 234
+ 333 - 54 * 54 / 4
-------- -------- -------- --------
2.5.4 二进制
- 二进制的简写形式
二进制: 1 0 1 1 1 0 1 1 1 1 0 0
三个二进制一组: 101 110 111 100
八进制: 5 6 7 4
四个二进制一组: 1011 1011 1100
十六进制: b b c
二进制:从0 写到 1111
0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111
这种二进制使用起来太麻烦,改成更简单一点的符号:
0 1 2 3 4 5 6 7 8 9 A B C D E F 这就是十六进制了
2.5.6 数据的宽度
- 数学上的数字,是没有大小限制的,可以无限的大。但在计算机中,由于受硬件的制约,数据都是有长度限制的(我们称为数据宽度),超过最多宽度的数据会被丢弃。
#import
#import "AppDelegate.h"
int test(){
int cTemp = 0x1FFFFFFFF;
return cTemp;
}
int main(int argc, char * argv[]) {
printf("%x\n",test());
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
2.5.7 计算机中常见的数据宽度
- 位(Bit): 1个位就是1个二进制位.0或者1
- 字节(Byte): 1个字节由8个Bit组成(8位).内存中的最小单元Byte.
- 字(Word): 1个字由2个字节组成(16位),这2个字节分别称为高字节和低字节.
- 双字(Doubleword): 1个双字由两个字组成(32位)
无符号数,直接换算!
有符号数:
正数: 0 1 2 3 4 5 6 7
负数: F E D B C A 9 8
-1 -2 -3 -4 -5 -6 -7 -8
2.5.8 自定义进制符号
- 实例4:现在有10进制数 10个符号分别是:2,9,1,7,6,5,4, 8,3 , A 逢10进1 那么: 123 + 234 = ____
十进制: 0 1 2 3 4 5 6 7 8 9
自定义: 2 9 1 7 6 5 4 8 3 A
92 99 91 97 96 95 94 98 93 9A
12 19 11 17 16 15 14 18 13 1A
72 79 71 77 76 75 74 78 73 7A
62 69 61 67 66 65 64 68 63 6A
52 59 51 57 56 55 54 58 53 5A
42 49 41 47 46 45 44 48 43 4A
82 89 81 87 86 85 84 88 83 8A
32 39 31 37 36 35 34 38 33 3A
922
那么刚才通过10进制运算可以转化10进制然后查表!但是如果是其他进制.我们就不能转换,要直接学会查表
- 实例5: 现在有9进制数 9个符号分别是:2,9,1,7,6,5,4, 8,3 逢9进1 那么: 123 + 234 = ____
十进制: 0 1 2 3 4 5 6 7 8
自定义: 2 9 1 7 6 5 4 8 3
92 99 91 97 96 95 94 98 93
12 19 11 17 16 15 14 18 13
72 79 71 77 76 75 74 78 73
62 69 61 67 66 65 64 68 63
52 59 51 57 56 55 54 58 53
42 49 41 47 46 45 44 48 43
82 89 81 87 86 85 84 88 83
32 39 31 37 36 35 34 38 33
922
2.6 寄存器
2.6.1 寄存器简介
- 什么是寄存器
- 寄存器是CPU内部用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果。其实寄存器就是一种常用的时序逻辑电路,但这种时序逻辑电路只包含存储电路。寄存器的存储电路是由锁存器或触发器构成的,因为一个锁存器或触发器能存储1位二进制数,所以由N个锁存器或触发器可以构成N位寄存器。寄存器是中央处理器内的组成部分。寄存器是有限存储容量的高速存储部件,它们可用来暂存指令、数据和位址。
- 寄存器的功能是存储二进制代码,它是由具有存储功能的触发器组合起来构成的。一个触发器可以存储1位二进制代码,故存放n位二进制代码的寄存器,需用n个触发器来构成。
- 按照功能的不同,可将寄存器分为基本寄存器和移位寄存器两大类。基本寄存器只能并行送入数据,也只能并行输出。移位寄存器中的数据可以在移位脉冲作用下依次逐位右移或左移,数据既可以并行输入、并行输出,也可以串行输入、串行输出,还可以并行输入、串行输出,或串行输入、并行输出,十分灵活,用途也很广。
- 在计算机领域,寄存器是CPU内部的元件,包括通用寄存器、专用寄存器和控制寄存器。寄存器拥有非常高的读写速度,所以在寄存器之间的数据传送非常快。
-
内部部件之间由总线连接
- 对程序员来说,CPU中最主要部件是寄存器,可以通过改变寄存器的内容来实现对CPU的控制
- 不同的CPU,寄存器的个数、结构是不相同的
- 寄存器的特点
- 寄存器最起码具备以下4种功能:
①清除数码:将寄存器里的原有数码清除。
②接收数码:在接收脉冲作用下,将外输人数码存人寄存器中。
③存储数码:在没有新的写入脉冲来之前,寄存器能保存原有数码不变。
④输出数码:在输出脉冲作用下,才通过电路输出数码。- 仅具有以上功能的寄存器称为数码寄存器;有的寄存器还具有移位功能,称为移位寄存器
寄存器有串行和并行两种数码存取方式。将凡位二进制数一次存人寄存器或从寄存器中读出的方式称为并行方式。将n位二进制数以每次l位,分成n次存人寄存器并从寄存器读出,这种方式称为串行方式。并行方式只需一个时钟脉冲就可以完成数据操作,工作速度快,但需要n根输入和输出数据线。串行方式要使用几个时钟脉冲完成输入或输出操作,工作速度慢,但只需要一根输入或输出数据线,传输线少,适用于远距离传输。
- 寄存器的结构
- 在数字电路中,用来存放二进制数据或代码的电路称为寄存器。寄存器是由具有存储功能的触发器组合起来构成的。一个触发器可以存储1位二进制代码,存放门位二进制代码的寄存器需用竹个触发器来构成
- 对寄存器中的触发器只要求它们具有置1,置0的功能即可,因而无论是用电平触发的触发器,还是用脉冲触发或边沿触发的触发器,都可以组成寄存器。
- 74HC175则是用CMOS边沿触发器组成的4位寄存器,根据边沿触发的动作特点可知,触发器输出端的状态仅仅取决于CLK上升沿到达时刻D端的状态。可见,虽然74LS75和74HC175都是4位寄存器,但由于采用了不同结构类型的触发器,所以动作特点是不同的
- 为了增加使用的灵活性,在有些寄存器电路中还附加了一些控制电路,使寄存器又增添了异步置零、输出三态控制和保持等功能。这里所说的保持,是指CLK信号到达时触发器不随D端的输入信号而改变状态,保持原来的状态不变。
上面介绍的两个寄存器电路中,接收数据时所有各位代码都是同时输入的,而且触发器中的数据是并行地出现在输出端的,因此将这种输入、输出方式称为并行输入、并行输出方式。
2.6.2 寄存器类型
2.6.2.1 通用寄存器
通用寄存器组包括AX、BX、CX、DX4个16位寄存器,用以存放16位数据或地址。也可用作8位寄存器。用作8位寄存器时分别记为AH、AL、BH、BL、CH、CL、DH、DL。只能存放8位数据,不能存放地址。它们分别是AX、BX、CX、DX的高八位和低八位。若AX=1234H,则AH=12H,AL=34H。通用寄存器通用性强,对任何指令,它们具有相同的功能。为了缩短指令代码的长度,在8086中,某些通用寄存器用作专门用途。例如,串指令中必须用CX寄存器作为计数寄存器,存放串的长度,这样在串操作指令中不必给定CX的寄存器号,缩短了串操作指令代码的长度。
- AX(AH、AL):累加器。有些指令约定以AX(或AL)为源或目的寄存器。输入/输出指令必须通过AX或AL实现,例如:端口地址为43H的内容读入CPU的指令为INAL,43H或INAX,43H。目的操作数只能是AL/AX,而不能是其他的寄存器。
- BX(BH、BL):基址寄存器。BX可用作间接寻址的地址寄存器和基地址寄存器,BH、BL可用作8位通用数据寄存器。
- CX(CH、CL):计数寄存器。CX在循环和串操作中充当计数器,指令执行后CX内容自动修改,因此称为计数寄存器。
- DX(DH、DL):数据寄存器。除用作通用寄存器外,在1/O指令中可用作端口地址寄存器,乘除指令中用作辅助累加器。
- ARM64拥有有31个64位的通用寄存器 x0 到 x30,这些寄存器通常用来存放一般性的数据,称为通用寄存器(有时也有特定用途)
那么w0 到 w28 这些是32位的. 因为64位CPU可以兼容32位.所以可以只使用64位寄存器的低32位.
比如 w0 就是 x0的低32位!
通常,CPU会先将内存中的数据存储到通用寄存器中,然后再对通用寄存器中的数据进行运算
-
假设内存中有块红色内存空间的值是3,现在想把它的值加1,并将结果存储到蓝色内存空间
CPU首先会将红色内存空间的值放到X0寄存器中:mov X0,红色内存空间
然后让X0寄存器与1相加:add X0,1
最后将值赋值给内存空间:mov 蓝色内存空间,X0
2.6.2.2 段寄存器
- 8086/8088CPU可直接寻址1MB的存储器空间,直接寻址需要20位地址码,而所有内部寄存器都是16位的,只能直接寻址6KB,因此采用分段技术来解决。将1MB的存储空间分成若干逻辑段,每段最长64KB,这些逻辑段在整个存储空间中可浮动。
- 8086/8088CPU内部设置了4个16位段寄存器,它们分别是代码段寄存器CS、数据段寄存器DS、堆栈段寄存器SS、附加段寄存器ES、由它们给出相应逻辑段的首地址,称为“段基址”。段基址与段内偏移地址组合形成20位物理地址,段内偏移地址可以存放在寄存器中,也可以存放在存储器中。
- 例如:代码段寄存器CS存放当前代码段基地址,IP指令指针寄存器存放了下一条要执行指令的段内偏移地址,其中CS=2000H,IP=001AH。通过组合,形成20位存储单元的寻址地址为2001AH。
- 代码段内存放可执行的指令代码,数据段和附加段内存放操作的数据,通常操作数在现行数据段中,而在串指令中,目的操作数指明必须在现行附加段中。堆栈段开辟为程序执行中所要用的堆栈区,采用先进后出的方式访问它。各个段寄存器指明了一个规定的现行段,各段寄存器不可互换使用。程序较小时,代码段、数据段、堆栈段可放在一个段内,即包含在64KB之内,而当程序或数据量较大时,超过了64KB,那么可以定义多个代码段或数据段、堆栈段、附加段。现行段由段寄存器指明段地址,使用中可以修改段寄存器内容,指向其他段。有时为了明确起见,可在指令前加上段超越的前缀,以指定操作数所在段。
2.6.2.3 指令指针寄存器
pc寄存器(program counter)
- 为指令指针寄存器,它指示了CPU当前要读取指令的地址
- 在内存或者磁盘上,指令和数据没有任何区别,都是二进制信息
- CPU在工作的时候把有的信息看做指令,有的信息看做数据,为同样的信息赋予了不同的意义
比如 1110 0000 0000 0011 0000 1000 1010 1010
可以当做数据 0xE003008AA
也可以当做指令 mov x0, x8
- CPU根据什么将内存中的信息看做指令?
CPU将pc指向的内存单元的内容看做指令
如果内存中的某段内容曾被CPU执行过,那么它所在的内存单元必然被pc指向过
- 8086/8088CPU中设置了一个16位指令指针寄存器IP,用来存放将要执行的下一条指令在现行代码段中的偏移地址。程序运行中,它由BIU自动修改,使IP始终指向下一条将要执行的指令的地址,因此它是用来控制指令序列的执行流程的,是一个重要的寄存器。8086程序不能直接访问IP,但可以通过某些指令修改IP的内容。例如,当遇到中断指令或调用子程序指令时,8086自动调整IP的内容,将IP中下一条将要执行的指令地址偏移量人栈保护,待中断程序执行完毕或子程序返回时,可将保护的内容从堆栈中弹出到IP,使主程序继续运行。在跳转指令时,则将新的跳转目标地址送入IP,改变它的内容,实现了程序的转移。
2.6.2.4 指针和变址寄存器
BP( Base Pointer regilter):基址指针寄存器。
SP( Stack Pointer Register):堆栈指针寄存器。
SI( Source Index register):源变址寄存器。
DI( Destination Index Register):目的变址寄存器。
这组寄存器存放的内容是某一段内地址偏移量,用来形成操作数地址,主要在堆栈操作和变址运算中使用。BP和SP寄存器称为指针寄存器,与SS联用,为访问现行堆栈段提供方便。通常BP寄存器在间接寻址中使用,操作数在堆栈段中,由SS段寄存器与BP组合形成操作数地址即BP中存放现行堆栈段中一个数据区的“基址”的偏移量,所以称BP寄存器为基址指针。
SP寄存器在堆栈操作中使用,PUSH和POP指令是从SP寄存器得到现行堆栈段的段内地址偏移量,所以称SP寄存器为堆栈指针,SP始终指向栈顶。
寄存器SI和DI称为变址寄存器,通常与DS一起使用,为访问现行数据段提供段内地址偏移量。在串指令中,其中源操作数的偏移量存放在SⅠ中,目的操作数的偏移量存放在DI中,SI和DI的作用不能互换,否则传送地址相反。在串指令中,SI、DI均为隐含寻址,此时,SI和DS联用,D和ES联用。
2.6.2.5 标志寄存器FR寄存器
- 标志寄存器FR也称程序状态字寄存器。
- FR是16位寄存器,其中有9位有效位用来存放状态标志和控制标志。状态标志共6位,CF、PF、AF、ZF、SF和OF,用于寄存程序运行的状态信息,这些标志往往用作后续指令判断的依据。控制标志有3位,IF、DF和TF,用于控制CPU的操作,是人为设置的。
2.6.2.6 数据地址寄存器
- 数据地址寄存器通常用来做数据计算的临时存储、做累加、计数、地址保存等功能。定义这些寄存器的作用主要是用于在CPU指令中保存操作数,在CPU中当做一些常规变量来使用。
ARM64中
64位: X0-X30, XZR(零寄存器)
32位: W0-W30, WZR(零寄存器)
注意:
之前讲解8086汇编中有一种特殊的寄存器段寄存器:CS,DS,SS,ES四个寄存器来保存这些段的基地址,这个属于Intel架构CPU中.在ARM中并没有
2.6.2.7 浮点和向量寄存器
因为浮点数的存储以及其运算的特殊性,CPU中专门提供浮点数寄存器来处理浮点数
- 浮点寄存器 64位: D0 - D31 32位: S0 - S31
现在的CPU支持向量运算.(向量运算在图形处理相关的领域用得非常的多)为了支持向量计算系统了也提供了众多的向量寄存器.
- 向量寄存器 128位:V0-V31
2.6.2.8 状态寄存器
- CPU内部的寄存器中,有一种特殊的寄存器(对于不同的处理器,个数和结构都可能不同).这种寄存器在ARM中,被称为状态寄存器就是CPSR(current program status register)寄存器
- CPSR和其他寄存器不一样,其他寄存器是用来存放数据的,都是整个寄存器具有一个含义.而CPSR寄存器是按位起作用的,也就是说,它的每一位都有专门的含义,记录特定的信息.
注:CPSR寄存器是32位的
- CPSR的低8位(包括I、F、T和M[4:0])称为控制位,程序无法修改,除非CPU运行于特权模式下,程序才能修改控制位!
- N、Z、C、V均为条件码标志位。它们的内容可被算术或逻辑运算的结果所改变,并且可以决定某条指令是否被执行!意义重大!
N(Negative)标志
- CPSR的第31位是 N,符号标志位。它记录相关指令执行后,其结果是否为负.如果为负 N = 1,如果是非负数 N = 0.
注意,在ARM64的指令集中,有的指令的执行时影响状态寄存器的,比如add\sub\or等,他们大都是运算指令(进行逻辑或算数运算);
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。
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位来记录这个进位值。比如,下面的指令
mov w0,#0xaaaaaaaa;0xa 的二进制是 1010
adds w0,w0,w0; 执行后 相当于 1010 << 1 进位1(无符号溢出) 所以C标记 为 1
adds w0,w0,w0; 执行后 相当于 0101 << 1 进位0(无符号没溢出) 所以C标记 为 0
adds w0,w0,w0; 重复上面操作
adds w0,w0,w0
借位
- 当两个数据做减法的时候,有可能向更高位借位。再比如,两个32位数据:0x00000000 - 0x000000ff,将产生借位,借位后,相当于计算0x100000000 - 0x000000ff。得到0xffffff01 这个值。由于借了一位,所以C位 用来标记借位。C = 0.比如下面指令:
mov w0,#0x0
subs w0,w0,#0xff ;
subs w0,w0,#0xff
subs w0,w0,#0xff
V(Overflow)溢出标志
- CPSR的第28位是V,溢出标志位。在进行有符号数运算的时候,如果超过了机器所能标识的范围,称为溢出。
正数 + 正数 为负数 溢出
负数 + 负数 为正数 溢出
正数 + 负数 不可能溢出
2.6.3 寄存器工作原理
- 在计算机及其他计算系统中,寄存器是一种非常重要的、必不可少的数字电路苛件,它通常由触发器(D触发器)组成,主要作用是用来暂时存放数码或指令。一个触发器司以存放一位二进制代码,若要存放N位二进制数码,则需用N个触发器。
- 寄存器应具有接收数据、存放数据和输出数据的功能,它由触发器和门电路组成。只有得到“存人脉冲”(又称“存入指令”、“写入指令”)时,寄存器才能接收数据;在得到“读出”指令时,寄存器才将数据输出。
- 寄存器存放数码的方式有并行和串行两种。并行方式是数码从各对应位输入端同时输入到寄存器中;串行方式是数码从一个输入端逐位输入到寄存器中。
- 寄存器读出数码的方式也有并行和串行两种。在并行方式中,被读出的数码同时出现在各位的输出端上;在串行方式中,被读出的数码在一个输出端逐位出现
2.6.4 寄存器寻址
- 寄存器寻址就是利用寄存器中的数值作为操作数,这种寻址方式是各类微处理器经常采用的一种方式,也是一种执行效率较高的寻址方式。
- 寄存器寻址是指操作数存放在CPU内部的寄存器中,指令中给出操作数所在的寄存器名。寄存器操作数可以是8位寄存器AH、AL、BH、BL、CH、CL、DH、DL,也可以是16位寄存器AX、BX、CX、DX、SP、BP、SI、DI等。因为寄存器寻址不需要通过总线操作访问存储器,所以指令执行速度比较快。
- 寄存器寻址( Register Addressing)是以通用寄存器的内容作为操作数的寻址方式,在该寻址方式下,操作数存放在寄存器中。寄存器寻址方式的寻址对象为:A,B,DPTR,RO~R7。其中,B仅在乘除法指令中为寄存器寻址,在其他指令中为直接寻址。A可以按寄存器寻址又可以直接寻址,直接寻址时写成ACC。
2.6.5 ARM处理器的寄存器
- ARM微处理器共有37个32位寄存器,其中31个为通用寄存器,6个为状态寄存器。但是这些寄存器不能被同时访问,具体哪些寄存器是可编程访问的,取决于微处理器的工作状态及具体的运行模式。但在任何时候,通用寄存器R14~R0、程序计数器PC、一个或两个状态寄存器都是可访问的。
- ARM9处理器共有37个32位长的寄存器,这些寄存器包括:
(1) RO~R12:均为32位通用寄存器,用于数据操作。但是注意:绝大多数16位Thumb指令只能访问R0~R7,而32位Thumb -2指令可以访问所有寄存器。
(2)堆栈指针:堆栈指针的最低两位永远是O,这意味着堆栈总是4字节对齐的。
(3)链接寄存器:当呼叫一个子程序时,由R14存储返回地址。
(4)程序计数器:指向当前的程序地址,如果修改它的值,就能改变程序的执行流。
(5)6个状态寄存器(1个CPSR、5个SPSR),用以标识CPU的丁作状态及程序的运行状态,均为32位,目前只使用了其巾的一部分。
3. 常用汇编指令
3.1 bl指令
CPU从何处执行指令是由pc中的内容决定的,我们可以通过改变pc的内容来控制CPU执行目标指令
ARM64提供了一个mov指令(传送指令),可以用来修改大部分寄存器的值,比如
mov x0,#10、mov x1,#20但是,mov指令不能用于设置pc的值,ARM64没有提供这样的功能
ARM64提供了另外的指令来修改PC的值,这些指令统称为转移指令,最简单的是bl指令
bl指令实例:
现在有两段代码!假设程序先执行A,请写出指令执行顺序.最终寄存器x0的值是多少?
_A:
mov x0,#0xa0
mov x1,#0x00
add x1, x0, #0x14
mov x0,x1
bl _B
mov x0,#0x0
ret
_B:
add x0, x0, #0x10
ret