计算机底层原理——汇编语言

前言

备注:该技术博客的内容是我根据技术视频整理与总结的(并非复制粘贴)。原视频源于【遇见狂神说】

如果我们想要做高级程序员,汇编语言是我们必经之路,汇编让我们跳出传统的编程思想,往底层学习,对我的技术提升非常非常重要。总而言之,想要成为高级程序员,我们必须要学会汇编语言,汇编语言是非常重要的计算机底层技术,一般用于底层的编写。不懂汇编的程序员算不上一个好的程序员,充其量是一个熟练使用某种语言的工程师,而编程高手一定要研究底层

1.机器语言

何为语言,就是人和人之间交流的工具。而汇编语言就是计算机的语言。

机器语言(二进制):

主流的电子计算机使用二进制,计算机只认识 0和1,因为在电路中只有两种状态,要么通电要么断电,我们用数字表示这两种状态就是0和1,我们可以用0和1与计算机交流。

机器语言就是由0和1构成的语言,我们很难理解,几乎看不懂。而我们需要将这些复杂的机器语言(一堆0和1的数字)简化,就需要助计符(INC DEC MUL DIV等),也就是汇编语言。

我们掌握了汇编语言就可以操作计算机的底层,深入一点就是可以直接操作计算机里面的 位。
汇编语言助记机器语言,所以说我们学会了汇编语言就学会了机器语言。

学习汇编就是为了理解计算机怎么操作,每一行代码怎么被计算机执行,这些原理非常重要!

在这里插入图片描述
在这里插入图片描述

2.进制思想本质

学习进制的障碍:
很多人搞不懂进制的主要原因是因为我们从小接受了十进制的概念,逢十进一的原则深入人心。
人本能的选择就是 十进制。

常见进制:
1进制:逢一进一,1 1
2进制:逢二进一,计算机进制
8进制:逢八进一,8个符号组成:0 1 2 3 4 5 6 7
10进制:逢十进一,10个符号组成:0 1 2 3 4 5 6 7 8 9
16进制: 逢十六进一,16个符号组成:0 1 2 3 4 5 6 7 8 9 a b c d e f

扫描二维码关注公众号,回复: 11642752 查看本文章

进制其实非常简单,只需要会 查数

测试:
在这里插入图片描述
进制可以自定义:
小朱同学的十进制:0 2 4 7 8 a b r d f,可以随便定义。
由此我可以使用进制加密,防止被爆破(暴力破解)。

3.二进制

目前的计算机(电子计算机)使用二进制 0 1

现在的计算机(电子计算机):
都是通过集成电路来实现电路的有电和无电,高电平和低电平,表现出来两个值0 1。

在这里插入图片描述
由于二进制写起来非常麻烦,我们就需要简写二进制,所以我们就去写16进制。

在这里插入图片描述

拓展:
未来的量子计算机:(传道)
可以实现量子计算的机器。

量子计算机的 单位:昆比特。也就是所谓的(量子比特),量子的两态(光子,磁场)来表示。
量子的计算速度远远超越了现在的电子计算速度。
光子:正交偏振方向。
磁场:电子的自旋方向。

如今我们已经到了21世纪,计算力已经快到尽头了,没办法突破(落伍)的本质问题!
我们想要突破这个本质问题,就要使用量子计算机。核心要素就是提高计算机的计算力!

2019年,Google研究人员展示其最新54比特量子计算机,该计算机只用200秒便可计算完毕当前世界最大的超级计算机需1万年进行的运算。
2020年.6.18,霍尼韦尔公司推出了量子体积64的量子计算机
霍尼韦尔还表示,将在一年之内得到至少10个有效量子比特,相当于1024个量子体积。
如果可以量产,大规模普及到民用之后,我们这些程序员是第一批使用他们的人。因为我们要
针对量子计算机写程序。

我们为什么学习理解二进制?
如果我们了解寄存器,内存,位的概念,计算机底层的每一个位都是有含义的(汇编入门理解的基础)。每一个0和1都代表一种状态,计算机会按照这种状态去执行特定的功能。程序运行时候会快速发生变化,每一个变化就会产生不同的状态,就比如:移动鼠标为什么会动,这底层如何实现非常的复杂。

4.数据宽度

计算机的内存是有限制的,内存不可能是无穷大的。所以我们就需要给数据增加数据宽度。

在计算计领域,我们要记住:所有的内存,所有的操作,都要给数据加上宽度,尤其是C,C++,Java这种强类型语言,都需要定义数据类型!为什么要需要定义类型?因为计算机底层需要我们这些数据定义宽度。

有了宽度,就有了一些基本的。常用量包含字节,字,双字等…

位 (bit):0 1
字节 (byte):(8位) 0-0xFF
字 (word):0-0xFFFF
双字 (dword):0-0xFFFFFFF

在计算机中,每一个数据都需要给它定义类型。定义类型的原因就是给它定义宽度。在内存中的宽度。

5.有符号数和无符号数

计算机它并不知道我们写的数字是正还是负。我们可以通过正负号来判断,而计算机如何去表示正负呢?我们接下来了解一下!

数据都是有宽度的。 那么每个数据代表什么意思呢?

规则
就好比我们解析一个音频:比如说为什么网易云可以放出MP3?那是因为有个MP3的编码格式,我们根据这个格式解码才能放出声音对应的格式。如果是一个视频就要遵守视频的解码规则…

现在我们需要给二进制解码增加一个规则。
1.无符号数规则:
你这个数字是什么,那就是什么,无关正负。

1 0 0 1 1 0 1 0  转换十六进制为: 0x9A   十进制为:154

2.有符号数规则:
最高位是符号位。 如果最高位是1,就代表一个负数。如果最高位是0,就代表是一个正数。

1 0 0 1 1 0 1 0  如何转换十六进制?

这里就涉及一套计算机规则:就是原码反码补码。

6.原码反码补码

为什么学原码反码补码?
因为我们之后要用它来计算。

编码规则:(无符号数编码规则没什么可说的,写的数字是什么就是什么)

有符号数编码规则有三种状态:**原码,反码,补码。**我们来依此学习一下这三种状态。

1.原码: 最高位是符号位,对齐它的位进行本身的绝对值即可。
2.反码: 分为正数和负数

  • 正数:反码和原码相同。
  • 负数:符号位一定是1,其余位对原码取反。

3.补码:

  • 正数:补码和原码相同
  • 负数:符号一定是1,对反码进行+1

举个例子:

现在我说的这些都是 8 位 

如果是正数,都是一样的。
对1取值:
原码:0 0 0 0  0 0 0 1
反码:0 0 0 0  0 0 0 1
补码:0 0 0 0  0 0 0 1

如果是负数
对-1取值:
原码:1 0 0 0  0 0 0 1
反码:1 1 1 1  1 1 1 0
补码:1 1 1 1  1 1 1 1-7取值:
原码:1 0 0 0  0 1 1 1
反码:1 1 1 1  1 0 0 0
补码:1 1 1 1  1 0 0 13+5取值:
3的二进制是 11
5的二进制是 101
加起来是 1000

我们现在要理解一句话,如果看到一个二进制的数字。需要了解它是有符号数还是无符号数

拓展:
接下来先给大家扩展一个寄存器:里面可以存值。通用的寄存器有8个,可以存储任意的值。我可以通过mov指令向某个寄存器存值,如下图:

在这里插入图片描述
现在我要写一个-1,我们来看看会有怎样的区别:

在这里插入图片描述
这里的FFFF FFFF是我们存的-1在寄存器中的值。一个F就是 1111,首先最高位的值是1,所以代表他是一个负数。我们正常存1的时候首位明显是0,而存-1就变成FFFF FFFF。因为-1在计算机中是补码的方式存储的,所以负数在我们计算机中使用补码方式存储的。所以学习通过直接操作查看是最有效的

FFFF FFFF代表三十二个1,如果是无符号的话,代表它是正数。如果是有符号的话,代表它是一个负数,是有很大的本质区别的。

我们搞懂原码反码补码之后,以后知道了计算机底层是怎么存储数字的:正数就正常的存,因为无论原码反码补码,正数都是相同的。而负数主要存的是补码!如果了解了这些,就是为了接下来的位运算打交道。

7.位运算

我们之前说过,计算机现在可以存储所有的数字(正数,浮点数,字符),不论正数还是负数都可以存储。如果可以把这些数字加以运算,我们就可以做到世界上的一切。无论多复杂的运算,底层都是加减乘除。我们只要把位运算的位如何操作运算记住、突破就可以了。

接下来我们学习位运算

首先有一个面试高频题:2*8最高效的计算方式?
这道题不论怎样都非常慢,只有通过位运算才是最快的,比如左移、右移。而且要记住一句话:很多底层的调试器(例如C语言调试器),当我们手写调试器的时候就需要通过来判断CPU的状态。

位运算就是我们常说的与或非 异或运算等…我们一个一个来看:

与运算
在JAVA语言中用 & 符号去表示,但是在汇编中用 and 代表与。下面图片方便我们的理解:

在这里插入图片描述

1011 0001
1101 1000
-------------------- 与运算的结果
1001 0000

或运算
在JAVA语言中用(|)表示,在汇编语言中用or表示,同样根据或运算也有一张电路图可以帮助理解:

在这里插入图片描述

1011 0001
1101 1000
--------------- 或运算
1111 1001

异或运算
在我们JAVA语言中用(^)表示,在汇编语言中xor表示。说白了记住一句话:不一样就是1。再来一张电路图理解:

在这里插入图片描述

1011 0001
1101 1000
--------------------异或运算
0110 1001

非运算(单目运算符)
我们所谓的取反(非),在JAVA语言中是(!),在C语言中是(~),在汇编语言中是not。
说白了一句话:0就是1,1就是0。

1101 1000
----------------- 非运算 
0010 0111

通过这些可以完成我们的加减乘除。怎么通过位运算实现加减乘除呢?

位运算
它是一个移动位,分为左移,右移。(左移*2,右移/2)。

左移(shl <<):

0000 0001  所有的二进制位全部左移若干位,高位就丢弃,低位补0
0000 0010

右移(shr >>):

0000 0001  所有二进制全部右移若干位,低位就丢弃,高位补01(根据符号位决定,负数补1,正数补00000 0000

如果想要取值(C++)
int a = 10;
printf("%d\n",a>>2);

总结: 汇编的本质就是操作二进制。

通过二进制、位运算实现 加减乘除。

8.位运算的加减乘除

接下来我们讲,如何通过位运算实现加减乘除。我们的计算机只认识0和1,但是基本的数学是建立在加减乘除上。(加法搞定,一切都搞定了)

举个例子:求4+5?

计算机是如何操作的呢?
0000 0100
0000 0101
-------------------------(加法,计算机是不会直接加)
0000 1001


那么计算机的实现原理是什么呢?
怎么将两个数加起来?核心是:异或。

第一步,异或(不一样为1):如果不考虑进位,异或就可以直接出结果
0000 0100
0000 0101
-------------------------
0000 0001


第二步,计算机在这个异或的结果上在做与运算操作:
与运算(判断进位),如果与运算结果为0,那么就没有进位。
0000 0100
0000 0101
-------------------------
0000 0100


第三步,将与运算的结果左移一位。
0000 0100 ——> 0000 1000 (进位的结果)


第四步,还是一个异或运算。(第一步异或出来的结果和第三步与运算的进位结果再一次异或)
0000 0001
0000 1000
------------------------
0000 1001


第五步,再去做一个与运算,判断它是否有进位。(与第二步一样)
0000 0001
0000 1000
------------------------
0000 0000


最后一步与运算结果为
0000 0000
电路都断了,灯泡全灭,通过不了,所以最终结果就是:
与运算为0的结果的上一个异或运算结果:
0000 1001 (二进制的9)


如果与运算不为0,继续循环。

举个例子:求4-5?

我们说了,计算机没有所谓的减法,那么计算机是如何操作的呢?
4-5 说白了就是 4+(-5)

0000 0100
1111 1011 (代表二进制的-5)
------------------------(减法,计算机是不会直接减)
1111 111181就是ff,所以ff在十进制中代表-10000 0100
1111 1011
------------------------ 异或(如果不考虑进位,异或就可以直接出结果)
1111 1111


0000 0100
1111 1011
------------------------ 与运算(判断进位),如果与运算结果为0,那么就没有进位。
0000 0000


最终结果
1111 1111 (十六进制的ff,十进制的-1)

举个例子:
乘法: x * y,本质就是y个x相加,还是加法。
除法: x / y,本质就是减法,就是x能减去多少个y。

结论:
计算机只会做加法!

9.汇编语言环境说明

目前为止,我们可以从零设计一套自己的进制规则。自己设计电路来实现加减乘除。但是最终乘除的结果是一个二进制,例如:我们有32个灯泡,就可以显示32个数值,最终亮起灯泡的数就是最终的结果。手动转换这个结果和值!(十进制和二进制的转换)

机器语言并不会做很多事情,它很“笨”。机器语言说白了就是位运算,(加减乘除)
都是电路来实现的。这就是计算机最底层的本质。

但是,我们发现,学完了这些东西依旧是不懂,只是对现在的程序做了一些提高的理解。但是想通过理解写程序写不出来,难道我们真的写不出来吗?

通过机器语言来实现加法计算器,这就是设计电路

我们通过指令来代替我们的一些二进制编码!比如说:刚才的加法运算是通过各种操作能否通过一个符号计算呢?比如说我就想叫它(ADD指令),假设我给计算机发一个ADD指令,它通过识别我的指令转换成机器语言(也就是编译)ADD指令转换为二进制操作。
在这里插入图片描述
汇编语言说白了,底层还是二进制,但是二进制写起来太麻烦了。这个时候我们通过汇编指令给计算机发一些操作,然后让计算机执行。这个地方就要涉及到编译器!因为我们说的编译命令不是机器直接能识别的,需要把命令转码,转成计算机认识的信息,不然没法识别。这个时候就涉及到编译器的发展

如果学底层,一定不要用特别智能的编译器(IDEA,VSCODE等),就是用越远古的越好(记事本,vim等)。很多人学习C语言使用,用的是vim编辑器去写C语言,用gcc来执行。这是学习C的正确方式。底层的大佬几乎都是最原始的idea。

在学习汇编之前,先要掌握环境的配置

  1. Vc6(程序到汇编的理解,通过C语言实现)
  2. OD
  3. 抓包工具
  4. 加密解密工具

尽量不要使用java去学汇编,学完了汇编去学jvm就会觉得很简单。但是如果我学java再学汇编就有点痛苦,建议使用C++学汇编。因为C++可以直接看到内存的地址,可以打印通过指针直接找到内存的地址,通过地址判断信息。

学汇编不是为了写代码,就是为了理解程序的本质
如果懂汇编,就能够理解所有复杂的概念。

在这里插入图片描述
如果我们是一个做应用的程序员,别人调试不了的程序,如果学过汇编,都可以调试。因为知道底层堆栈到底做了什么。如果是做安全的(反外挂,反病毒),就要理解汇编到二进制的全部知识。

现在的计算机至少是32位,还有的是64位。我们要知道,它是由32位演化过来的。底层架构没有发生变化,只是多了寄存器,主要是寻址能力增加

汇编入门: 了解汇编和程序的对应关系,程序的本质即可。

在这里插入图片描述
学会了这些,不理解的java原码就理解了。汇编非常重要!这对我们向上学习有很大的帮助。有些编程技术学进不去,很大原因就是因为底层不够。底层精通了在学习编程语言就发现太轻松了!

10.寄存器的理解

学习汇编,要学习三个重要的概念:

  1. 寄存器
  2. 内存
  3. 汇编指令

通用寄存器:可以存储任何值

存储数据:CPU>内存>硬盘
CPU分为32位和64位。

  • 32位:8 16 32
  • 64位:8 16 32 64

32位的通用寄存器只有8个:
在这里插入图片描述
寄存器中的存值范围:0 ~ FFFFFFFF

计算机如何向寄存器中存值呢?
对于二进制来说,就是直接修改值。但是修改值需要找到对应的位置,所以才有内存地址

mov指令

mov  存的地址,存的数
mov  存的地址1,存的地址1

在这里插入图片描述
可以将数字写入到寄存器,可以将寄存器中的值,写到寄存器。

计算机的本质:计算力! 就是为了计算!(鼠标光标在移动都是在计算)

不同的寄存器:

32位代表八个F(FFFF FFFF),一个F代表4位(1111)


		FFFF	FF    
32168位
EAX     AX		AL
ECX		CX		CL
EDX		DX		DL
EBX		BX		BL
ESP		SP		AH
EBP		BP		CH
ESI		SI		DH
EDI		DI		BH

对于8位:L代表低8位,H代表高8位
16位是FFFF 高八位占前两个FF,低八位占后两个FF

在这里插入图片描述
除了这些通用寄存器之外,那么其他的寄存器每一位都有自己特定的功能(比如:开机关机)。
我们一般写值都会写到通用寄存器中。

11.内存

寄存器很小,而且不够用。所以我们会把数据放到内存中。

有句话:每个应用程序进程都有4GB的内存空间。 程序用的内存就是空头支票,虽然每个应用程序的进程都有4GB内存空间,但是真正到机器上使用时候并没有那么大。

在这里插入图片描述
程序真正运行的时候,才会用到物理内存。

1B = 8bit
1KB = 1024B
1MB = 1024KB
1GB = 1024MB

假设是4GB内存电脑,就等于4096m => 最终计算为位,就是可以存储的最大容量。
计算机中的内存地址很多,空间很大。

内存地址:
存一个数:占用大小,数据宽度。存到哪里呢?

计算机中的内存地址很多,空间很大。我们要给空间取名字,每个空间分配一个地址,名字。

在这里插入图片描述
这些给内存起的编号就是我们的内存地址。32位(8个十六进制的值)

32位:决定寻址能力!
FFFFFFFF + 1 = 100000000,最大的值。
位是怎么限制内存大小呢?
100000000 内存地址 * 8位 :800000000。
转换为十进制 / 8:4,294,967,296字节。
按照规则/1024最终发现就是 4GB(不能超过)。
64位,绰绰有余。

所以每个内存地址都有一个编号:可以通过编号向里面存值

在这里插入图片描述
很多人学C语言搞不懂指针,原因就是 不懂内存

内存如何存值?(mov指令)
存值需要知道数据宽度(byte word dword),地址位置(自定义:0xFFFFFFFF)
不是任意的地址都可以写东西,只有程序申请过的内存地址我们才可以使用。

汇编如何向内存中写值:
mov  数据宽度  内存地址,1

mov  byte ptr ds:[0x19ff70],1

传递的值的大小一定要和数据宽度要相等,如果大放不进去。

在这里插入图片描述
内存地址有多种写法

  1. ds:[0x19FF70+4](内存地址偏移):加 偏移(4),地址就变成了:0x19FF74
  2. ds:[eax](寄存器):把寄存器中的值放到内存中。
  3. ds:[eax + 4](寄存器偏移

数组为例:
ds:[reg + reg * {1,2,4,8}]
ds:[reg + reg * {1,2,4,8} + 4] 偏移

12.总结

学到这里,在学其他的汇编内容已经很轻松了,包括学计算机操作原理也很轻松。
如果能够全部理解,再看自己写的程序就会豁然开朗很多。

猜你喜欢

转载自blog.csdn.net/weixin_46594796/article/details/108017547