机器语言
由于使用机器语言表示时不方便记忆,于是开始使用助记符来代替机器语言,,例如下面使用助记符表示的加减乘除
加:INC EAX 通过编译器 0100 0000
减:DEC EAX 通过编译器 0100 1000
乘:MUL EAX 通过编译器 1111 0111 1110 0000
除:DIV EAX 通过编译器 1111 0111 1111 0000
最终的代码在终端设备上显示的过程是这样的
有以下几点说明:
- 汇编语言主要有以下几个特点:
- 按理说,汇编这么难,为什么还要学呢,以及学了能做什么?
- 汇编语言主要有以下几个特点:
-
1、是理解整个计算机系统的最佳起点和最有效途径
-
2、为编写高效代码打下基础
-
3、理解代码的本质,例如:
-
函数的本质是什么?
-
++a底层是如何执行的?
-
编译器在底层到底帮我们做了哪些工作?
-
DEBUG模式和RELEASE模式到底有哪些地方是不同的,以及被我们忽略的?
所以综上所述,汇编是所有程序猿都需要了解的一门非常重要的语言,这也是为什么大学中计算机相关专业学生的必修课,就好比修房子,地基稳了,高楼才能平地起!
-
1、8086汇编(8086处理器是16bit的CPU)
-
2、Win32汇编
-
3、Win64汇编
-
4、ARM汇编(嵌入式、Mac、iOS)
-
...
想要学好汇编,需要有以下几个认知
- 总线是CPU与内存之间的桥梁,如下图所示是iPhone X上的A11(CPU芯片)
从图中可以看出:每一个CPU芯片都有很多管脚,这些管脚和总线相连,CPU通过总线跟外部器件进行交互
-
它的宽度决定了CPU的寻址能力,即地址总线决定了CPU所能访问的最大内存空间的大小,例如10根地址线能访问的最大内存是 2^10 = 1024位二进制数据(即1B)
-
地址总线是地址线数量之和
-
8086地址总线宽度是20,所以寻址能力是1M(即2^20)
-
内存地址的单元是 字节byte(简写为B),每个字节里面可以放8位(即bit),以下是内存条的图示
内存条图示
-
它的宽度决定了CPU的单次数据传送量(即吞吐量),也就是数据传送速度即CPU和外界的数据传送速度
每条数据线一次只能传输一位二进制数据,例如 8根数据线一次可传送一个8位二进制数据(即1个字节的数据)
数据总线是数据线数量之和
8086的数据总线宽度是16,所以单次最大传递2个字节的数据
它的宽度决定了CPU对其他器件的控制能力,能有多少种控制,即CPU对外部器件的控制能力
控制总线是控制线数量之和
其中内存中的低地址是给用户用的,高地址是给系统用的,如下所示
内存图示
进制
-
八进制由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
<!--目的-->
传统定义的十进制和自定义的十进制不一样,如果不告诉别人符号表,别人是无法拿到具体的数据的。这样的应用场景主要是用于加密
八进制乘法表
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
实战四则运算
277 236 276 234
+ 333 - 54 * 54 / 4
-------- -------- -------- --------
632 162 1370 47
+ 1666
数据的宽度
断点调试-02
-
位(Bit):1个位就是1个二进制位,即0或1
-
字节(Byte):1个字节由8个Bit组成,内存中的最小单元Byte
-
字(Word):1个字由两个字节组成(16位),第2个字节分别称为高字节和低字节
-
双字(DoubleWord):1个双字由两个字组成(32位)
-
无符号数,直接换算
-
有符号数,符号放在第1位,第1位是0即正数,为1即负数:
-
正数:0 1 2 3 4 5 6 7
-
负数:F E D B C A 9 8
表示:-1 -2 -3 -4 -5 -6 -7 -8
刚才通过10进制运算可以转换,然后查表,但是如果是其他进制,就不能转换,要学会直接查表
内部部件之间是由总线连接,如下图所示
针对arm64的CPU来说,
-
对于程序员来说,CPU中最主要的部件是寄存器,可以通过改变寄存器的内容来实现对CPU的控制
-
不同CPU,寄存器的个数和结构是不相同的
-
浮点寄存器
-
64位:D0 - D31
-
32位:S0 - S31
通用寄存器
-
通常,CPU会先将内存中的数据存储到通用寄存器中,然后再对寄存器中的数据进行运算
假设内存中有块红色内存空间的值是3,现在想把它的值加1,并将结果存储到蓝色内存空间内存案例
-
CPU首先会将红色内存空间的值放到X0寄存器中:mov X0,红色内存空间
-
然后让X0寄存器与1相加:add X0,1
-
最后将值赋值给内存空间:mov 蓝色内存空间,X0
-
寄存器案例分析
pc寄存器调试
-
register write pc 0x104c31ecc
-
register read pc 此时是读不出来的,因为断点断住了,如果step into,此时断点断在哪里?最终通过验证发现,会断在cc的下一行
pc寄存器调试-04
pc寄存器调试-05
此时pc指向cc,会将cc中的指令拿出来执行,执行完毕后走到cc的下一条指令,而此时d0的指令是还没有执行的
CPU每执行一条指令前都需要从内存中将质量读取到CPU内存并执行,而寄存器的运行速度相比内存读写要快很多,为了性能,CPU还集成了一个高速缓存区域。当程序运行时,先将要执行的指令代码以及数据复制到高速缓存中(由操作系统完成),然后CPU直接从高速缓存依次读取指令来执行
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
<!--结果-->
流程:
mov x0,#0xa0 -- x0:0xa0
mov x1,#0x00 -- x1:0x00
add x1, x0, #0x14 -- x1:0xa0+0x14=0xb4
mov x0,x1 -- x0:0xb4
add x0, x0, #0x10 -- x0:0xb4+0x10=0xc4
ret -- 回到bl跳转的下一行
mov x0,#0x0 -- x0:0x00
x0的值:0x00
汇编代码验证-汇编代码
然后开始lldb调试,下面是一步步到0x00的过程
-
1、汇编概述
-
使用助记符代替机器指令的一种编程语言
-
汇编和机器指令是一一对应的关系,拿到二进制就可以反汇编
-
由于汇编和CPU的指令集是对应的,所以汇编不具备移植性
-
-
2、总线:是由一堆导线的集合
-
地址总线:其宽度决定了寻址能力
-
数据总线:其宽度决定了CPU数据的吞吐量
-
控制总线:其宽度决定了CPU对其他器件的控制能力
-
-
3、进制
-
计算机中的数据是有宽度的,超过了就会溢出
-
1024 = 1KB,1024KB = 1MB,1024MB = 1GB
-
1024 = 1k,1024k = 1M,1024M = 1G
-
3个二进制 使用一个8进制标识
-
4个二进制 使用一个16进制标识
-
两个16进制可以标识一个字节,即8位
-
任意进制都是由对应个数的符号组成的,符号可以自定义
-
2/8/16是相对完美的进制,他们之间的关系
-
数量单位
-
容量单位
-
数据的宽度
-
-
4、寄存器:CPU为了性能,自内部开辟了一小块临时存储区域
-
PC寄存器里面的值保存的就是CPU接下来需要执行的指令地址
-
改变PC的值可以改变程序的执行流程
-
mov指令不能更改PC寄存器的值,需要通过bl跳转指令来改变PC寄存器的值
-
ARM64拥有32个64位的通用寄存器X0-X30以及XZR(零寄存器)
-
为了兼容32位,所以arm64位拥有W0-W28以及WZR 30个32位寄存器
-
32位寄存器并不是独立存在的,例如 W0是X0的低32位
-
浮点向量寄存器:用于浮点数/向量的存储及运算
-
异常状态寄存器
-
通用寄存器:除了存放数据有时也有特殊的用途
-
PC寄存器:指令指针寄存器
-