语言
机器语言
# 我们目前主流的电子计算机
# 状态:0 和 1
# 最早的程序员:穿孔卡带
比如:
加 0000
减 0001
乘 0010
除 0011
汇编语言
- 将复杂的机器语言简化成助记符,即汇编语言。
- 编译器:将人能够理解的语言转换为机器能够理解的语言。
- 汇编语言一般用于底层的编写和单片机。
加 INC -编译器-> 0000
减 DEC -编译器-> 0001
乘 MUL -编译器-> 0010
除 DIV -编译器-> 0011
高级语言
- 高级语言向人更便于理解的方向演化。
- C语言
加 A+B -编译器-> 0000
减 A-B -编译器-> 0001
乘 A*B -编译器-> 0010
除 A/B -编译器-> 0011
反汇编工具:OD
进制-进位计数制
思想:每一种进制都是完美的,都有自己的计算方式。
- 1进制:一进一,结绳记事。1 1
- 2进制:二进一,计算机。0 1
- 3进制:三进一。0 1 2
- 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
# 一进制
1
1 1
1 1 1
1 1 1 1
1 1 1 1 1
# 三进制
0 1 2
10 11 12
20 21 22
100 101 102
110 111 112
120 121 122
200 201 202
# 七进制
0 1 2 3 4 5 6
10 11 12 13 14 15 16
20 21 22 23 24 25 26
# 一组符号,逢几进几,符号可以随意定义
证明:1 + 1 = 3
创造自己的三进制:0 1 3
0 1 3 10 11 13 30 31 33 100 101 103
# 运算的本质就是查数
0是0位 1是1位 3是2位
1 + 1 = 3 1向后移动1位
3 + 3 = 11 3向后移动2位
创造自己的四进制:0 1 3 a
0 1 3 a 10 11 13 1a 30 31 33 3a
# 运算的本质就是查数
0是0位 1是1位 3是2位 a是3位
1 + 1 = 3 1向后移动1位
3 + 3 = 10 3向后移动2位
创造自己的进制
进制加密
进制运算
运算的本质就是查数
# 运算的本质就是查数
# 八进制
0 1 2 3 4 5 6 7 10 11 12 13 14 15 16 17 20 21 22 23 24 25 26 27 30 31 32 33
# 八进制计算下面的结果
2+3=5 2向后移动3位
2*3=6 2个3位
4+5=11 4向后移动5位
4*5=24 4个5位
八进制加法表
# 277+333=632
277
333 +
------------
632
# 减法的本质是加法
# 237-54=163
237
54 -
-----------
163
八进制乘法表
276*54=20250
276
54 *
---------------
1370
1666 +
---------------
20250
# 除法的本质是乘法
234/4=
234
4 /
-------------
47
结论:无论是什么进制,本身都是有一套完美的运算体系的,我们都可以通过列表的方式将它计算出来。
二进制
# 十六进制,二进制的简写
# 十进制 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
# 二进制 0 1 10 11 100 101 110 111 1000 1001 1010 1011 1100 1101 1110 1111
# 十六进制 0 1 2 3 4 5 6 7 8 9 a b c d e f
为什么要学习理解二进制?
数据宽度
- 计算机:内存!给数据增加数据宽度。
- C/C++/Java等强类型语言都需要定义数据的类型,因为计算机底层需要我们给这些数据定义宽度。
位:0 1
字节:8位,0~0xff
字:16位,0~0xffff
双字:32位,0~0xffffffff
有符号数和无符号数
无符号数规则
- 数字是什么,那就是什么。
1 0 0 1 1 0 1 0 十六进制:0x9a 十进制:154
有符号数规则
- 最高位是符号位:1(负数) 0(正数)
1 0 0 1 1 0 1 0 负数如何转换为十六进制和十进制?
原码反码补码
有符号数的编码规则
- 原码:最高位符号位,对其它的位进行本身绝对值即可。
- 反码:
- 正数:反码和原码相同。
- 负数:符号位一定是1,其余位对原码取反。
- 补码:
- 正数:补码和原码相同。
- 负数:符号位一定是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 1
如果看到一个二进制的数字,需要了解他是有符号数还是无符号数。
计算机存储的是补码
寄存器:mov 寄存器,值
计算机存1,存的是1的补码。1->0x00000001
计算机存-1,存的是-1的补码。-1->0xffffffff
位运算
与运算(and &)
1011 0001
1101 1000
-------------与运算:都是1,结果才是1
1001 0000
或运算(or |)
1011 0001
1101 1000
-------------或运算:有一个1,结果就是1
1111 1001
异或运算(xor ^)
1011 0001
1101 1000
-------------异或运算:不相同,结果就是1
0110 1001
非运算(单目运算符 not ~)
1011 0001
-------------非运算:取反
0100 1110
通过这些位运算可以完成加减乘除!
位运算(移动位)
#正数的情况下,对于十进制来说,左移*2,右移/2
0000 0001 1
0000 0010 2
0000 0100 4
0000 1000 8
左移(shl <<)
0000 0001
-----------左移:所有二进制位全部左移若干位,高位丢弃,低位补0
0000 0010
右移(shr >>)
0000 0001
-----------右移:所有二进制位全部右移若干位,低位丢弃,高位由符号位决定(正数补0,负数补1)
0000 0000
位运算的加减乘除
计算机只会做加法
加法
# 计算机是怎么计算的?
# 4+5=?
0000 0100
0000 0101
------------加法:计算机是不会直接加的
0000 1001
# 计算机的实现原理
# 第一步,异或运算(相加):得到,异或运算结果A。
0000 0100
0000 0101
------------ ^
0000 0001 A
# 第二步,与运算(判断进位):得到与运算结果B,判断与运算结果B,如果与运算结果B为0,没有进位,
# 操作结束。最终结果就是异或运算结果A。
0000 0100
0000 0101
------------ &
0000 0100 B
# 第三步,左移(得到进位):与运算结果B不为0,左移一位,得到,进位结果C。
0000 0100 B
------------ <<1
0000 1000 C
# 第四步,异或运算(相加):异或运算结果A和进位结果C进行异或运算,得到,异或运算结果D。
0000 0001 A
0000 1000 C
------------ ^
0000 1001 D
# 第五步,与运算(判断进位):得到与运算结果E,判断与运算结果E,如果与运算结果E为0,没有进位,
# 操作结束。最终结果就是异或运算结果D。如果与运算结果E不为0,有进位,从第三步循环执行。
0000 0001 A
0000 1000 C
------------ &
0000 0000 E
# 所以,最终的结果就是与运算为0时,上一步异或运算的结果。
减法
# 4-5=4+(-5)=?
0000 0101 5
1000 0101 -5 原码
1111 1010 -5 反码
1111 1011 -5 补码
0000 0100
1111 1011
-----------
1111 1111
# 计算机的实现原理
# 第一步,异或运算(相加):得到,异或运算结果A。
0000 0100
1111 1011
------------ ^
1111 1111 A
# 第二步,与运算(判断进位):得到与运算结果B,判断与运算结果B,如果与运算结果B为0,没有进位,
# 操作结束。最终结果就是异或运算结果A。
0000 0100
1111 1011
------------ &
0000 0000 B
汇编语言环境说明
- 通过指令来代替二进制操作!
- 通过汇编指令,经过编译器编译,然后让计算机执行。
- 底层的大佬几乎都用最原始的IDE,因为没有智能的编译器,能更好的查看底层。
- 在学汇编之前,需要掌握的环境配置:
- VC6(C/C++开发工具,程序到汇编的理解)。
- OD(反汇编工具)。
- 抓包工具。
- 加密解密工具。
- 学汇编不是为了写代码,而是为了理解程序的本质。
- 32位和64位,本质架构区别不大,只是寻址能力增加。
- 汇编入门:了解汇编和程序的对应关系,程序的本质即可!
- 下图为OD界面。
通用寄存器
通用寄存器
- 32位的通用寄存器只有8个。
计算机如何向寄存器存值
mov指令
# 将数字写入寄存器
mov 存的地址,存的值
# 将寄存器A中的值写到寄存器B
mov 存的地址B,存的地址A
不同的寄存器
FFFFFFFF FFFF FF
32位 16位 8位
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位
其他寄存器
内存
- 寄存器很小,不够用。所以,把数据放到内存中。
- 32位电脑,每个应用程序进程都有4GB的内存空间(虚拟内存)。
内存地址
- 存一个数,有两个问题:
- 占用的大小:由数据宽度决定。
- 存在哪里:由内存地址决定。
- 计算机中内存空间很大,每个空间分配一个地址,名字,就是内存地址。
- 这些给内存起的编号,就是我们的内存地址。
- 32位的电脑,内存地址就是8个16进制的值。
- 每个内存地址都有一个编号,可以通过这些编号向里面存值。
内存如何存值
- 数据宽度:byte word dword
- 地址的位置:0xFFFFFFFF
- 不是任意的地址都可以写东西的,必须是申请使用的。只有程序申请过的内存地址我们才可以使用。
# 汇编如何向内存中写值
mov 数据宽度 内存地址,值
mov byte/word/dword/qword ptr ds:[0x0019FF70],1
mov byte ptr ds:[0x0019FF70],1
# 传递的值的大小一定要和数据宽度对应。
# 内存地址有多种写法
ds:[0x0019FF70+4] 内存地址偏移
ds:[eax] 寄存器
ds:[eax+4] 寄存器偏移