01 - 初识汇编

初识汇编

汇编语言的发展

机器语言

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

下图表明了各语言之间的关系以及最终如何运行在计算机:


image.png

说明:

  • 汇编语言机器语言一一对应,每一条机器指令都有与之对应的汇编指令
  • 汇编语言可以通过编译得到机器语言机器语言可以通过反汇编得到汇编语言
  • 高级语言可以通过编译得到汇编语言/机器语言,但汇编语言/机器语言几乎不可能还原成高级语言(因为不同的设备,对应不同的CPU架构,而CPU架构对应不同的指令集)

汇编语言的特点

  • 可以直接访问、控制各种硬件设备,例如存储器、CPU等,能最大限度的发挥硬件的功能
  • 能够不受编译器的限制,对生成的二进制代码进行完全的控制
  • 目标代码简洁,占用内存少,执行速度快
  • 汇编指令是机器指令的助记符,同机器指令一一对应。每一种CPU都有自己的机器指令集/汇编指令集,所以汇编语言不具备可移植性
  • 知识点过多,要求过高,需要开发者对CPU等硬件结构有所了解,不易于编写、调试,以及维护
  • 不区分大小写,例如mov和MOV是一样的

汇编的用途

  • 编写驱动程序、操作系统(比如Linux内核的某些关键部分)
  • 对性能要求极高的程序或者代码片段,可与高级语言混合使用(内联汇编)
  • 用于软件安全方面
    • 病毒的分析与防治
    • 逆向、加壳、脱壳、破解、外挂、免杀、加解密、黑客等

汇编语言的种类

目前讨论比较多的汇编语言有:

  1. 8086汇编(8086处理器是16bit的CPU)
  2. Win32汇编
  3. Win64汇编
  4. ARM汇编(嵌入式、Mac、iOS)
  5. 其它汇编

我们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以后

必备常识

想要学好汇编,需要以下几个认识:

  1. 需要了解CPU等硬件的架构
  2. 程序的执行过程
    程序的执行过程.png

    程序/App从磁盘中加载到内存中,在内存中的文件叫image(镜像文件),早期都是直接拷贝,后面为了安全有了加壳加密签名验证。在cpu眼中所有的数据都是0和1,那么怎么区分指令和数据呢?通过pc寄存器区分。

总线

总线是CPU与内存之间的桥梁,如下图所示是iPhone X上的A11(CPU芯片)

A11 CPU芯片.png

  • 每一个CPU芯片都有许多管脚,这些管脚和总线相连,CPU通过总线跟外部器件进行交互
  • 总线:一根根导线的集合
  • 总线的分类
    • 地址总线:CPU是通过地址总线来指定存储单元的
    • 数据总线:CPU与内存/其他部件之间的数据传送通道
    • 控制总线:CPU通过控制总线对外部器件进行控制
      总线分类.png

下面举个小例子说明CPU如何从内存中读取数据

image.png

【第一步】通过地址总线,找到需要访问的内存地址,假设此时需要寻址至内存的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),以下是内存条的图示:

内存条的图示.png

数据总线

数据总线的宽度决定了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次

内存

各类存储区的逻辑连接.png

各类存储器的逻辑连接-物理地址对应图.png

各类存储器的物理地址情况.png
  • 内存地址空间的大小受CPU地址总线宽度的限制。8086的地址总线宽度为20,可以定位2^20个不同的内存单元(内存地址范围0x00000~0xFFFFF),所以8086的内存空间大小为1MB

  • 0x00000~0x9FFFF:主存储器。可读可写

  • 0xA0000~0xBFFFF:向显存中写入数据,这些数据会被显卡输出到显示器。可读可写

  • 0xC0000~0xFFFFF:存储各种硬件\系统信息。只读

数据的宽度

数学上的数字是没有大小限制的,可以无限大
但是在计算机中,由于硬件的制约,数据都是有长度限制的(称为数据宽度),超过最多宽度的数据会被丢弃。

#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]));
    }
}

以上代码,通过断点调度可以发现,当cTemp被赋值了一个超过int最大值的数据时,会发生溢出。即超过的部分被丢弃


image.png

计算机中常见的数据宽度

  • 位(Bit):1个位就是1个二进制位,即0或1
  • 字节(Byte):1个字节由8个Bit组成,内存中的最小单元Byte
  • 字(Word):1个字由两个字节组成(16位),2个字节分别称为高字节和低字节
  • 双字(DoubleWord):1个双字由两个字组成(32位)

CPU & 寄存器

内部部件之间由总线连接


image.png
  • 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,并将结果存储到蓝色内存空间

image.png

【第一步】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指令

你可能感兴趣的:(01 - 初识汇编)