初识汇编
汇编语言的发展
机器语言
由0和1
组成的机器指令
,以下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
等。这些高级语言比汇编更加接近人类的自然语言。例如:
- 加:
A+B
通过编译器 0100 0000 - 减:
A-B
通过编译器 0100 1000 - 乘:
A*B
通过编译器 1111 0111 1110 0000 - 除:
A/B
通过编译器 1111 0111 1111 0000
下图表明了各语言之间的关系以及最终如何运行在计算机:
说明:
-
汇编语言
与机器语言
一一对应,每一条机器指令都有与之对应的汇编指令 -
汇编语言
可以通过编译
得到机器语言
,机器语言
可以通过反汇编
得到汇编语言
-
高级语言
可以通过编译
得到汇编语言/机器语言
,但汇编语言/机器语言
几乎不可能
还原成高级语言
(因为不同的设备,对应不同的CPU架构,而CPU架构对应不同的指令集)
汇编语言的特点
- 可以
直接访问、控制各种硬件设备
,例如存储器、CPU等,能最大限度的发挥硬件的功能 - 能够
不受编译器的限制
,对生成的二进制代码进行完全的控制 - 目标代码
简洁
,占用内存少
,执行速度快
- 汇编指令是机器指令的助记符,同机器指令一一对应。每一种CPU都有自己的机器指令集/汇编指令集,所以
汇编语言不具备可移植性
。 - 知识点过多,要求过高,需要开发者对CPU等硬件结构有所了解,
不易于编写、调试,以及维护
-
不区分大小写
,例如mov和MOV是一样的
汇编的用途
-
编写驱动程序、操作系统
(比如Linux内核的某些关键部分) - 对性能要求极高的程序或者代码片段,可与高级语言混合使用(内联汇编)
- 用于
软件安全
方面- 病毒的分析与防治
- 逆向、加壳、脱壳、破解、外挂、免杀、加解密、黑客等
汇编语言的种类
目前讨论比较多的汇编语言有:
- 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 以后 iPhoneX , iPad Air, iPad mini2以后 |
必备常识
想要学好汇编,需要以下几个认识:
- 需要了解CPU等硬件的架构
- 程序的执行过程
程序/App从磁盘中加载到内存中,在内存中的文件叫image(镜像文件)
,早期都是直接拷贝,后面为了安全有了加壳加密签名验证。在cpu眼中所有的数据都是0和1,那么怎么区分指令和数据呢?通过pc寄存器
区分。
总线
总线是CPU与内存
之间的桥梁
,如下图所示是iPhone X上的A11(CPU芯片)
- 每一个CPU芯片都有许多管脚,这些管脚和总线相连,
CPU通过总线跟外部器件进行交互
- 总线:
一根根导线的集合
- 总线的分类
-
地址总线
:CPU是通过地址总线来指定存储单元的 -
数据总线
:CPU与内存/其他部件之间的数据传送通道 -
控制总线
:CPU通过控制总线对外部器件进行控制
-
下面举个小例子说明CPU如何从内存中读取数据
【第一步】通过地址总线,找到需要访问的内存地址,假设此时需要寻址至内存的3号单元
【第二步】通过控制总线,告诉内存当前需要执行的操作,假设当前的操作是
读
【第三步】通过控制总线传递的内容,内存知道CPU是想读取3号单元的内容,因此,内存通过数据总线将3号单元的数据返回给CPU
地址总线
地址总线
的宽度
决定了CPU的寻址能力
,寻址能力
是指CPU一次所能访问内存地址的最大值
。
例如:2根地址线有四种组合:00,01,10,11
,于是CPU就最多只能访问这四个地址00~11,即2^2 = 4。
同理:10根地址总线能访问的最大内存是 2^10 = 1024(即1K)
8086
的地址总线宽度是20,所以寻址能力是2^20 = 1M
内存地址的单元是字节byte
(简写为B),每个字节里面可以放8位
(即bit),以下是内存条的图示:
数据总线
数据总线
的宽度决定了CPU的单次数据传送量
(即吞吐量),也就是数据传送速度。
每条数据线一次
只能传输一位
二进制数据,因此传输1字节的数据,则需要8根数据总线。
8086
的数据总线宽度是16,所以单次最大传递2个字节
的数据(16/8=2)
通常我们所说的32位
CPU指单次最大传递32/8=4字节
、64
位CPU指单次最大传递64/8=8字节
,这里的32、64指的就是数据吞吐量
控制总线
控制总线
的宽度决定了CPU对其他器件的控制能力
,能有多少种控制,即CPU对外部器件的控制能力
总线小练习
【问题1】一个CPU 的寻址能力为8KB,那么它的地址总线的宽度为?
答:地址总线的宽度决定了CPU的寻址能力
。
地址总线宽度与寻址能力的关系为:2^n = 8K,因此n = 13
【问题2】8080,8088,80286,80386 的地址总线宽度分别为16根,20根,24根,32根.那么他们的寻址能力分别为多少____KB, ____MB,____MB,____GB?
答:16根地址总线的寻址能力:2^16 = 64KB
20根地址总线的寻址能力:2^20 = 1024KB = 1MB
24根地址总线的寻址能力:2^16 = 16MB
32根地址总线的寻址能力:2^16 = 4096MB = 4GB
【问题3】8080,8088,8086,80286,80386 的数据总线宽度分别为8根,8根,16根,16根,32根.那么它们一次可以传输的数据为:____B,____B,____B,____B,____B
答:数据总线的宽度决定了CPU的单次数据传送量
。
8080有8根数据总线,它一次可以传输的数据为:8/8 = 1B
8088有8根数据总线,它一次可以传输的数据为:8/8 = 1B
8086有16根数据总线,它一次可以传输的数据为:16/8 = 2B
80286有16根数据总线,它一次可以传输的数据为:16/8 = 2B
80386有32根数据总线,它一次可以传输的数据为:32/8 = 4B
【问题4】从内存中读取1024字节的数据,8086至少要读____次,80386至少要读取____次
答:8086有16根数据总线,一次可以传输的数据为16/8 = 2B,若需要从内存中读取1024字节,则需要读1024/2 = 512次
80386有32根数据总线,一次可以传输的数据为:32/8 = 4B,若需要从内存中读取1024字节,则需要读1024/4 = 256次
内存
内存地址空间的大小受CPU地址总线宽度的限制。8086的地址总线宽度为20,可以定位2^20个不同的内存单元(内存地址范围0x00000~0xFFFFF),所以8086的内存空间大小为1MB
0x00000~0x9FFFF:主存储器。可读可写
0xA0000~0xBFFFF:向显存中写入数据,这些数据会被显卡输出到显示器。可读可写
0xC0000~0xFFFFF:存储各种硬件\系统信息。只读
数据的宽度
数学上
的数字是没有大小限制的,可以无限大
。
但是在计算机
中,由于硬件的制约,数据都是有长度限制
的(称为数据宽度),超过最多宽度的数据会被丢弃。
#import <UIKit/UIKit.h>
#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]));
}
}
以上代码,通过断点调度可以发现,当cTemp被赋值了一个超过int最大值的数据时,会发生溢出。即超过的部分被丢弃
计算机中常见的数据宽度
-
位(Bit)
:1个位就是1个二进制位,即0或1 -
字节(Byte)
:1个字节由8个Bit组成,内存中的最小单元Byte -
字(Word)
:1个字由两个字节组成(16位),2个字节分别称为高字节和低字节 -
双字(DoubleWord)
:1个双字由两个字组成(32位)
CPU & 寄存器
内部部件之间由总线连接
CPU除了有
控制器
、运算器
还有寄存器
。其中寄存器的作用
就是进行数据的临时存储
。CPU的运算速度是非常快的,为了性能CPU在内部开辟了一小块临时存储区域,并在进行运算时先将数据从内存中复制到这一小块临时区域中。运算就在这一小块临时存储区进行,称这一小块临时存储区域为
寄存器
对于
arm64系
的CPU来说, 如果寄存器以x开头
则表明的是一个64位的寄存器
,如果以w开头
则表明是一个32位的寄存器
,其中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位寄存器的低32位
- 例如w0就是x0的低32位
注意:了解过8086汇编的一定知道,还有一种特殊的寄存器段寄存器:CS、DS、SS、ES。这四个寄存器主要用来保存这些段的基地址,是属于Intel架构的CPU的,在arm中并没有
- 通常,
CPU会先将内存中的数据存储到通用寄存器中,然后再对寄存器中的数据进行运算
假设内存中有块红色内存空间的值是3,现在想把它的值加1,并将结果存储到蓝色内存空间
【第一步】CPU首先会将红色内存空间的值放到X0寄存器中:
mov X0,红色内存空间
【第二步】然后让X0寄存器与1相加:
add X0,1
【第三步】最后将值赋值给内存空间:
mov 蓝色内存空间,X0
PC寄存器(program counter)
-
指令指针
寄存器,它指示了CPU当前要读取指令的地址
- 在内存/磁盘上,指令和数据没有任何区别,都是二进制信息
- CPU在工作时,将有的信息看作指令,有的看作数据,为同样的信息赋予了不同的意义
例如:1110 0000 0000 0011 0000 1000 1010 1010
可以当做数据 0xE003008AA,也可以当做指令 mov x0, x8 - CPU将
PC寄存器
指向的内存单元的内容看作指令
- 如果内存中的某段内容曾经
被CPU执行过
,那么它所在的内存单元必然被PC寄存器指向过
高速缓存
iPhoneX上搭载的arm处理器A11,它的1级缓存的容量是64kb,2级缓存的容量是8M
CPU每执行一条指令前都需要从内存中将指令读取到内存并执行,而寄存器的运行速度相比内存读写要快很多,为了性能,CPU还集成了一个
高速缓存区域
。当程序运行时,先将要执行的指令代码以及数据复制到高速缓存中(由操作系统完成),然后CPU直接从高速缓存依次读取指令来执行
bl指令
- CPU从何处执行指令,是由
PC寄存器
中的内容决定的,可以通过改变PC寄存器
的内容来控制CPU执行目标指令
- arm64提供了一个
mov指令
(传送指令),可以用来改变大部分寄存器的值,例如mov x0,#10
(等价于x0=10)、mov x1,#20
(等价于x1=20) -
mov指令并不能用于设置PC寄存器的值
,arm64没有提供这样的功能 - arm64提供了另外的指令来修改
PC寄存器
的值,这些指令统一称为转移指令
,其中最简单的是bl指令