汇编语言的发展
机器语言
由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++、Objective-C、swift,更符合,更能让人理解的语言,对于iOS来讲,Objective-C向swift转变是一个必然的过程,在接触swift的过程中,我发现swift与Android的Kotlin,Python,Java等等这些语言有了一些共通的地方,使得不同专业的同学对于跨专业操作编的更加的容易,我有一个疑问,在不久的未来,AI可以商用,甚至民用、通用,语言会不会大一统呢(这里也是个人胡说。。。)
回到高级语言,特点是通用性强,缺点也很明显,体积更大。
代码在终端上的编译过程:
- 汇编语言与机器语言是一一对应的关系,每一条机器指令都有与之对应的汇编指令
- 汇编语言与机器语言是可以互相转化的,反汇编:可行的,拿到二进制可以反汇编成汇编语言
- 高级语言可以通过编译得到汇编语言\机器语言,但是机器语言/汇编语言几乎不可能还原成高级语言
- 不同的高级语言(Objective-C/Swift)可能对应的汇编是一样的。
- 同样的语言,不同的代码,可能对应的汇编语言也可能是一样的。
- 高级语言与汇编语言的对应关系:(一对多)
汇编语言的特点
- 可以直接访问,控制各种硬件设备,比如存储器、CPU等,可以最大程度上发挥硬件的功能
- 可以不受编译器的限制,对生成的二进制代码进行完全的控制
- 目标代码简短,占用内存较少,执行速度快
- 汇编指令是指机器指令的助记符,同机器指令一一对应 ,每一种CPU都有自己的机器指令集\汇编指令集,所以汇编语言不具备可移植性。
- 知识点很多,开发者需要对CPU硬件有一定的了解,不易于编写、调试、维护
- 不区分大小写,MOV与mov是一样的
汇编语言的用途:(见仁见智)
- 编写驱动程序,操作系统(Linux内核的某些关键部分)
- 对性能要求极高的程序或者代码片段,可与高级语言混合使用(内敛汇编)
- 软件安全
1、病毒分析与防治
2、逆向、加壳、脱壳、破解、外挂、免杀、加密解密、漏洞 - 理解整个计算机系统最佳起点与最有效的途径
- 为编写高质量的代码打下基础
- 代码的本质
1、函数的本质
2、 ++a底层是如何执行的
3、编译器到底帮助我们干了什么
4、DEBUG和RELEASE模式有什么关键的地方被我们忽略
借用大佬的话:
越底层越单纯、汇编是程序员需要用心理解的一门语言(串改了)
汇编的种类:
8086(处理器是16bit的cpu)(16位)
Win32汇编:
Win64汇编
ARM汇编(嵌入式、MAC,iOS)
CUP架构:(CPU不同的型号)不同的cpu架构对应不同的cup指令集,M1系列同iPhone同架构(ARM)
iPhone使用的CPU架构,但是不同的设备是有差异的,因为CPU的架构不同
架构 | 设备 |
---|---|
armv6 | iPhone、iPhone2、iPhone3G、第一代、第二代iPod Touch |
armv7 | iPhone3GS、iPhone4S、iPad、iPad2、IPad3(The New iPad)、iPad mini、IPad Touch3G、iPod Touch4 |
armv7s | iPhone5、iPhone5C、iPad4(iPad withRetina Display) |
arm64 | iPhone5S--iPhoneX,iPad Air,iPad mini2--- |
必备知识:
- CPU硬件结构:
- App执行、编译过程
- 硬件相关最为总要的是CPU/内存
- 在汇编中,大部分指令都是与CPU、内存相关的
CPU
总线:
CPU与内存的桥梁:是一根根导线的集合。每个cpu有很多针脚,通过这些针脚与外部链接,与外部交互。
总线的分类:
地址总线:功能是寻址,总线的宽度决定了寻址能力,每根导线可以表示(0和1),CPU跟内存是有关系滴,在早期的时候如果内存过大,CPU的寻址能力不够,内存访问不到。但是也有解决方案,CPU进行两次寻址,然后相加,但是这样的方式就注定了速度变慢。现在CPU寻址能力是饱和的,64位cpu。8086的地址总线宽度是20根,所以寻址能力是1M(2^20)
控制总线:它的宽度决定了CPU对其他控件的控制能力,能有多少控制
数据总线 :它的宽度决定了CPU的单次数据传送量,也就是数据传送速度,或者可以描述为CPU的吞吐量
通常讲32位CPU、64位CPU指的是CPU的数据总线宽度。对于一个引用数据对象,在编程过程中,一个指针8占用字节,一次放电就可以搞定(64位)
8086CPU数据总线宽度是16,单次传输数据2字节
32位CPU一次放电可以传递4字节
64位CPU一次放电可以传递8字节
CPU是如何工作的:
此图演示的是CPU从内存3号单元读取数据:
- cpu通过地址总线将3传输给内存(我将要访问地址3)
- cpu通过控制总线将命令传输给内存(我将要读/写操作)
- cpu通过数据总线如果是读操作:那么内存将3内的数据通过数据总线传输给CPU,如果是写操作,CPU将数据传递给内存3
App的加载流程:
App通过编译最后生成的Mach-o(二进制文件)文件要存放在本地磁盘中(硬盘),知道要运行的时候回将App加载进内存,这个时候叫Image(镜像文件),然后通过CPU对内存的读/写操作,通过控制指令对终端设备中的各个模块进行控制。
二进制文件与镜像文件的区别是什么?
在早期来讲是一样的,只是copy了一份放在了内存中,镜像是早期遗留下来的说法,方便CPU的使用。现在来讲区别我还不清楚,学习过之后进行补充
内存
此图是8086CPU的内存空间:8086CPU的地址总线宽度为20,可以定位到2^20个不同的内存单元(内存地址的范围是0x00000-0xFFFFFF),所以8086的内存空间大小为1M。
- 0x00000-0x9FFFF:主存储器,可读可写
- 0xA0000-0xB0000:向内存中写入数据,这些数据会被显卡输出到显示器,可读可写
- 0xC0000-0xFFFFF:存储器中各种硬件/系统信息,只读。
这个作为早期的内存结构,现在的内存是访问不到系统信息的,因为做了虚拟地址的隔离
进制
学习进制的障碍
进制学习的主要障碍是我们的教育目前都是以十进制来传递给我们的,学习使用了很多年,已经深入骨髓,在学习其他进制的时候,总是要以十进制为依托,先入为主,器思考,这种方式本质上就是错误的。现在程序员接触到的二进制、八进制、十六进制,每一种进制都是完美的。都有各自的计算规则。不需要按十进制来思考!不需要按十进制来思考!不需要按十进制来思考!重要的事情讲三遍。
进制的定义
- 二进制由2个符号组成:0、1逢二进一
- 八进制由8个符号组成:0、1、2、3、4、5、6、7逢八进一
- 十进制由10个符号组成:0、1、2、3、4、5、6、7、8、9逢十进一
- 十六进制由16个符号组成:0、1、2、3、4、5、6、7、8、9、A、B、C、D、E、F逢16进一
其实我们国家古代的人是非常智慧的,十六进制使用的是非常早的,“半斤八两”讲的就是十六进制。一斤是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
. . .
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 = 8 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
. . .
1*1 = 1
1*2 = 2 2*2 = 4
1*3 = 3 2*3 = 6 3*3 = 11
1*4 = 4 2*4 = 10 3*4 = 14 4*4 = 20
1*5 = 5 2*5 = 12 3*5 = 17 4*5 = 24 5*5 = 31
1*6 = 6 2*6 = 14 3*6 = 22 4*6 = 30 5*6 = 36 6*6 = 44
1*7 = 7 2*7 = 16 3*7 = 25 4*7 = 34 5*7 = 43 6*7 = 52 7*7 = 61
. . .
二进制:从0写到1111
0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1111
十六进制:
0 1 2 3 4 5 6 7 8 9 A B C D E F
数据宽度
数学上的数字,是没有大小限制的,可以无限大,但是在计算机中,由于受硬件的制约,数据都是有长度限制的(成为数据宽度),超过最大数据宽度的数据会被丢弃
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int test() {
int cTemp = 0x1FFFFFFFF;
return cTemp;
}
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
printf("cTemp:%x\n",test());
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
会有一个警告
Implicit conversion from 'long' to 'int' changes value from 8589934591 to -1,cTemp的值为-1
计算机中常见的数据宽度
- 位(Bit):1个位就是1个二进制位(0或者1)
- 字节(Byte):1个字节由8个Bit组成(8位),内存中最小的单元式Byte
- 字(Word): 1个字由2个字节组成(16位),这2个字节分别成为高字节和低字节
- 双字(DoubleWrod): 1个双字由2个字组成(32位)
计算机储存数据会分为有符号和无符号数,无符号数,直接运算。有符号数:正数:0 - 7,负数:-8 - F,
CPU&寄存器
内部部件之间由总线连接
CPU包含控制器、运算器和寄存器,其中寄存器的作用就是用来临时数据的存储。
CPU的运算速度是非常快的,为了性能CPU在内部开辟了一小块临时存储区域,并在进行运算时,先将数据从内存复制到这一小块临时存储区域中,运算时就在这一小块存储区域进行。我们称这一小块临时存储区域为寄存器。
对于arm64的CPU来讲,如果寄存器以“x”开头,则表明是一个64位寄存器,如果以“w”开头则表明是一个32位寄存器,在系统中没有提供16位和8位的寄存器提供访问和使用。其中32位寄存器是64位寄存器的低32位部分,并不是独立存在的。
- 对于程序员来说,CPU最重要的部分就是寄存器,可以通过改变寄存器的内容来实现对CPU的控制
- 不同的CPU,寄存器的个数,结构是不同的
浮点寄存器
因为浮点数的存储以及其运算的特殊性,CPU中专门提供浮点寄存器来处理浮点数
-
浮点寄存器64位:D0-D31 32位:S0-S31
向量寄存器
现在的CPU支持向量运算(向量运算在图形处理相关领域使用的非常的多),为了支持向量运算计算机系统也提供了众多的向量寄存器
- 向量寄存器128位:V0-V31
通用寄存器
- 通用寄存器也成为数据地址寄存器,通常用来做数据运算的临时存储、做累加、技术、地址保存的功能,定义这些寄存器主要的作用是用于在CPU指令中保存操作数,在CPU中当做一些常规变量来使用。
- ARM64拥有32个64位寄存器x0-x30,以及XZR(零寄存器),这些通用寄存器有时也有特定的用途。
- w0-w28这些事32位寄存器,因为64位CPU可以兼容32位,所以只使用64位CPU的低32位即可。
- 比如w0就是x0的低32位寄存器(下面图中x0是0x0000000016ce13858,w0是0xce13858)
注意:
对于8086CPU,有一种特殊的寄存器,CS、DS、SS、ES四个寄存器来保存这些段的及地址,这个是属于Intel架构的CPU,ARM中并没有
使用流程:
通常,CPU会先将内存中的数据储存到寄存器中,然后在对通用寄存器中的数据进行运算。
假设内存有红色内存空间的值是3,现在想把他+1,并将结果存储在蓝色空间内:
- 第一步:CPU首先会将红色空间内的值放到x0寄存器中:mov x0,红色空间
- 第二步:然后让x0寄存器与1相加,add x0,1
- 第三步:最后将值赋值给蓝色内存空间:mov 蓝色空间,x0
pc寄存器
指令指针寄存器,它指示了CPU当前要读取指令的地址
在内存或者磁盘上,指令和数据没有任何区别,都是二进制信息cpu在工作的时候吧有的信息当做指令,有的信息看做数据,为同样的信息赋予了不同的意义
比如:1110 0000 0000 0011 0000 1000 1010 1010,可以当做数据0xE003008AA,也可以当做指令mov x0,x8
cpu根据什么将内存中的信息当做指令处理
CPU将PC寄存器只想的内存单元的内容看做指令
如果内存中的某段内容曾被CPU执行过,那么它所在的内存单元一定被CPU指向过
高速缓存
iPhoneX上搭载的ARM处理器A11它的1级缓存的容量是64KB,2级缓存的容量是8M
CPU每执行一条指令前都需要从内存中将指令读取到CPU内并执行。而寄存器的运行速度相比内存读写要快的多,为了性能,CPU还集成了一个高速缓存区域,当程序运行时,先将要执行的指令代码以及数据复制到高速缓存中区(由系统完成),CPU直接从高速缓存依次读取指令来执行
b指令与bl指令
- CPU从何处执行指令是有pc寄存器中的内容决定的,我们可以通过改变pc寄存器中的内容来控制CPU执行目标指令
- ARM64提供了一个mov指令(传送指令),可以用来修改修改大部分寄存器的值,比如:
- mov x0,#10(#代表后面是一个常数)
- 但是不可以修改pc寄存器的,ARM64没有提供这样的功能
- ARM64提供了另外的指令来修改pc寄存器的值,这些指令统称为转移指令,简称为bl
- b指令已经可以实现功能:作用是跳过某一段指令,如果使用bl,则是在跳转的同时,会将bl下面的指令存入lr(x30)寄存器中,用来记录回家的路
其他
数量单位:(1M=1024K 1K=1024)
容量单位:(byte)(1024B = 1KB 1024KB = 1MB)
带宽(100M):是指100Mbps,传输速率的单位,每秒钟传输多少个二进制位,bit位,100M带宽理论上的下载速度是12.5M