汇编语言笔记
本笔记由jzh整理《汇编语言(基于X86处理器》而成,未经允许不得转载
文章目录
0.前序
嗨,朋友。
也许长时间对于计算机的学习已经让你身心疲惫,不想继续阅读枯燥而没有感情的乏味书籍,特别是在面对汇编语言这些本身趣味性就不高的内容,更是无法提起学习的动力。
但这份笔记一定会带给你不一样的感受,他并不追求高大上的计算机理论知识,并不在一开始就用最难的知识来吓到你,而是从一个与你一样的计算机汇编语言的初学者的视角出发,一点点告诉你汇编语言的知识点。
他像一个和你一起学习的伙伴,而不是高高在上的老师。会和你说自己在学习汇编的时候的苦恼,在学习时候的“小聪明”,每一章的最后还有让人啼笑皆非的tips,让你有动力继续学下去。
也许这本书并不是最专业的书籍,但却绝对值得你一读。
悟为
为好友写于2021年
1.基本概念
1.1.欢迎来到汇编语言的世界
1.环境配置:Microsoft宏汇编器(MASM)或Microsoft visual studio
2.汇编器:将汇编语言转化为机器语言
3.链接器:将汇编器生成的单个文件组成为一个可执行文件
4.调试器:检查寄存器和内存状态
1.2.虚拟机概念
1.虚拟机概念是一种说明计算机硬件和软件关系的有效方法
2.虚拟机层次概念:
level4 : 高级语言 |
---|
level3 :汇编语言 |
level2 : 指令集架构(IAS) |
level1 :数字逻辑 |
1.3.数据表示
1.二进制整数:无符号二进制整数;二进制数与十进制数互转;二进制加法;整数存储大小
2.十六进制整数:无符号十六进制整数;无符号十六进制数与十进制数互转;十六进制加法
3.有符号二进制数:补码;加减法
4.字符储存:ASCII;UTF-8;UTF-16; UTF-32
1.4.布尔表达式
1.与或非:not; and; or
1.5.小结
1.汇编器:一种程序,用于把源程序从汇编语言转换为机器语言
2.链接器:将汇编器生成的单个文件组成为一个可执行文件
3.调试器:检查寄存器和内存状态
tips
-
汇编语言与机器语言一一对应
-
有一说一,我都不知道这本书作者第一章讲了些啥,计算机组成原理都加上了,搞得我都快劝退了,呜呜呜
-
下一章还不是汇编语言,
我裂开了 -
我的建议是第一章和第二章先略过吧~~(误)~~
2.X86处理器架构
2.1:一般概念
1.X86处理器的型号:intel32; intel64;(大概率你用的电脑也是x86的吧)
2.寄存器:直接位于CPU内的高速储存位置的存储器,访问速度远高于传统存储器
p.s. CS:PC 指向代码的开头
2.时钟:对CPU内部操作与系统其他组件进行同步
3.控制单元(CU):协调参与机器指令执行的步骤序列
4.算术逻辑单元(ALU):执行算数运算
5.内存存储单元:用于在程序运行时保存指令和数据
6.总线:一组并行线,将数据从计算机一个部分传到另一个部分
6.1数据总线:传指令和数据(双向)
6.2控制总线:对设备进行同步,传送控制信号和状态信号(单向,但可能向外或者向内)
6.3地址总线:保存指令和数据地址(译码器,与内存有关)
7.指令执行周期:其步骤主要分为取指,译码,执行
8.读取内存、加载、执行程序
2.2: 32位X86处理器
1.操作模式:保护模式,虚拟8086模式,实地址模式,系统管理模式
1)保护模式:最安全
2)虚拟8086模式:既安全又是实地址
3)实地址模式:直接访问系统内存和硬盘
tips: 物理地址与逻辑地址(段基值,偏移量)
2.执行环境
1)通用寄存器(球球了,这个记住吧,真的很重要)
32位 | 16位 | 8位(高) | 8位(低) |
---|---|---|---|
EAX | AX | AH | AL |
EBX | BX | BH | BL |
ECX | CX | CH | CL |
EDX | DX | DH | DL |
tips
1)乘法默认EAX
2)CPU默认用ECX做循环(参见后面的loop)
3) ESP用于寻址堆栈数据
4)ESI和EDI用于高速存储器传输指令
5)EBP引用堆栈的函数参数和局部变量
6) CS:IP 段基值和偏移量
2)段寄存器
3)指令指针
4)EFLAGS寄存器:设置标志位,EFLAGS为1;清除标志位,EFLAGS为0
5)控制标志位
6)状态标志位
1.进位(CF)
2.溢出(OF)
3.符号(SF)
4.零(ZF)
5.辅助进位(AC或AF)
6.奇偶校验(PF)
7.单步标志位(TF):触发单步中断,调试程序
8.中断标志位(IF):是否处理可屏蔽中断
9.方向标志位(DF):控制串操作指令存取数据的方向
3.MMX寄存器:单指令,多数据
4.XMM寄存器:浮点单元(FPU)
2.3 64位X86-64处理器
基本上和32位差不多
除了:
16个64位通用寄存器(32只有8个)
8个80位浮点寄存器
1个64位RFLAGS(但只能用低32位,好鸡肋啊)
1个64位RIP
8个64位MMX
16个128位XMM(32位只有8个XMM)
操作数大小 | 可用寄存器 |
---|---|
8位 | AL;BL;CL;DL;DIL;SIL;BPL;SPL;R8L;R9L;R10L;R11L;R12L;R13L;R14L;R15L; |
16位 | AX;BX;CX;DX;DI;SI;BP;SP;R8W;R9W;R10W;R11W;R12W;R13W;R14W;R15W |
32位 | EAX;EBX;ECX;EDX;EDI;ESI;EBP;ESP;R8D;R9D;R10D;R11D;R12D;R13D;R14D;R15D |
64位 | RAX;RBX;RCX;RDX;RDI;RSI;RBP;RSP;R8;R9;R10;R11;R12;R13;R14;R15 |
2.4 典型x86计算机组件
1.主板
2.内存
2.5 输入输出系统
1.I/O访问层次
2.6 小结
1.32位和64位寄存器,好好搞,本章就这个是重点,其他都是废话
2.寄存器:直接位于CPU内的高速储存位置的存储器,访问速度远高于传统存储器
2.时钟:对CPU内部操作与系统其他组件进行同步
3.控制单元(CU):协调参与机器指令执行的步骤序列
4.算术逻辑单元(ALU):执行算数运算
5.内存存储单元:用于在程序运行时保存指令和数据
6.总线:一组并行线,将数据从计算机一个部分传到另一个部分
6.1数据总线:传指令和数据
6.2控制总线:对设备进行同步
6.3地址总线:保存指令和数据地址
tips
我现在严重怀疑作者想要劝退一些人,才这么写。前两章和汇编关系不是很大,但对我而言已经很劝退了,是不是我太菜了,,我裂开了。强烈建议跳过这俩,我吐了
第一章重点是配置一下环境
第二章重点是看看寄存器,稍微理解一下
没了。。。
对了,下章多看看,比前面硬核,真的开始了汇编之旅
3.汇编语言基础
3.1 基本语言元素
先来一个汇编语言代码
main PROC
MOV eax,5
add eax,6
INVOKE ExitProcess,0
main ENDP
就是把5移动到 EAX 寄存器中,然后再把6加到 EAX 寄存器中,就结束了
(震惊!!!你的第一个汇编语言代码居然不是hello world)
接下来,我们添加一个变量吧
.data
sum DWORD 0
.code
main PROC
mov eax,5
add eax,6
mov sum,eax
INVOKE ExitProcess,0
main ENDP
就是sum = 5 + 6
啊啊啊啊,好累啊啊,我死了
和其他语言一样,我们先从常量和变量开始吧
1.整数常量
[{+|-}]数字[数制系统]
垃圾总述,直接上例子
E.G.
26 10
26d 10
1101001 10
1101001b 2
42q 8
42o 8
1Ah 16
0A3h 16 注意:如果16进制开头是字母,要加前导0
2.整型常量表达式
运算符 | 名称 | 优先级 |
---|---|---|
() | 圆括号 | 1 |
+,- | 正负(+1,-4) | 2 |
*,/ | 乘除 | 3 |
MOD | 取mod*(泪目,回到高中VB)* | 3 |
+, - | 加减 | 4 |
3.实数常量
[sign]integer.[integer][exponent]
直接上例子吧
2.
+3.0
-44.2E+05
26.E5
小数点别忘了,不管怎么样都要有
4.字符(串)常量(学完这个就会hello world了)
‘hello world’
“hello world”
“a ‘hello’ b”
‘a “hello” b’
这个和python好像啊,单双引号可以混用
5.保留字:别用就可以了
6.标识符:就是约等于变量,现在可以这么理解
7.伪指令:嵌入源代码中的命令,由汇编器识别和执行
不区分大小写
E.G.
.data .DATA .Data 是一样的
定义段:.data,.code等等
8.指令
标号(可选)
指令助记符(必选)
操作数(可选)
注释(可选)
[label:] mnemonic [operands] [;comment]
L1: mov ax, bx ;move bx to ax
tips:NOP指令:空指令,用于对齐,对齐了,算起来快
3.2 示例
; AddTwo.asm - 两个32位整数相加
; 第三章示例
.386 ;32位程序
.model flat, stacall ;flag内存模式, stdcall调用规范
.stack 4096 ;堆栈4096,一个内存页大小
ExitProcess PROTO, dwExitCode:DWORD ;声明结束标准
.code
main PROC
mov eax,5
add eax,6
INVOKE ExitProcess,0
main ENDP
END main
3.3 汇编、链接和运行程序
1.汇编-链接-执行周期
2.列表文件:包括程序源文件副本,行号,每条指令的数字地址,每条指令的机器代码字节和符号表
; AddTwo.asm - 两个32位整数相加
; 第三章示例
.386 ;32位程序
.model flat, stacall ;flag内存模式, stdcall调用规范
.stack 4096 ;堆栈4096,一个内存页大小
ExitProcess PROTO, dwExitCode:DWORD ;声明结束标准
00000000 .code
00000000 main PROC
00000000 B8 00000005 mov eax,5
00000005 83 C0 06 add eax,6
INVOKE ExitProcess,0
00000008 6A 00 push +00000000h
0000000A E8 00000000 E call ExitProcess
0000000F main ENDP
END main
invoke伪指令使得汇编器生成PUSH和CALL语句
3.4 定义数据
1.内部数据类型:按数据大小(字节、字、双字)、是否有符号、是整数还是实数
[name] directive initializer [,initializer]…
类型 | 用法 |
---|---|
BYTE | 8位无符号 |
SBYTE | 8位有符号 |
WORD | 16位无符号 |
SWORD | 16位有符号 |
DWORD | 32位无符号 |
SDWORD | 32位有符号 |
FWORD | 48位 |
QWORD | 64位 |
TBYTE | 80位 |
REAL4 | 32位IEEE |
REAL8 | 64位IEEE |
REAL10 | 80位IEEE |
E.G.
count DWORD 12345
就是count = 12345
2.伪指令
伪指令 | 用法 | 伪指令 | 用法 |
---|---|---|---|
DB | 8位整数 | DQ | 64位整数或实数 |
DW | 16位整数 | DT | 80位整数 |
DD | 32位整数或实数 |
3.初始化
sum DWORD 0
就是把sum初始化为0
当然,如果你不愿意初始化的话,也可以用?去初始化
即
sum DWORD ?
所以,,我们加个变量吧
; AddTwo.asm - 两个32位整数相加
; 第三章示例
.386 ;32位程序
.model flat, stacall ;flag内存模式, stdcall调用规范
.stack 4096 ;堆栈4096,一个内存页大小
ExitProcess PROTO, dwExitCode:DWORD ;声明结束标准
.data
sum DWORD 0
.code
main PROC
mov eax,5
add eax,6
mov sum,eax
INVOKE ExitProcess,0
main ENDP
END main
4.定义BYTE和SBYTE
BYTE和SBYTE都是8位的,所以我们添加进的数据也都是8位的
E.G.
value1 BYTE 'a'
value2 BYTE 0
value3 BYTE 255
value4 SBYTE -128
value5 SBYTE 127
5.偏移量
即该元素(的首元素)的位置,记住这一点
6.多初始值
如果我们要用数组的话,我们可以用一个变量去代表数组
E.G.
list1 BYTE 10,20,30,40
list2 BYTE 10,20,30,40
BYTE 50,60,70,80
这两种在现在是一样的,但记住,在未来,这俩还是有一点点区别的
当然,我们也可以使用不同的基数来定义
E.G.
list1 BYTE 10b,20,30o,40h
7.定义字符串
E.G.
greeting1 BYTE "Good afternoon",0
请记住这个0,0是字符串结尾标识符,真的很重要
8.DUP操作符:使用一个整数表达式,为多个数据项分配储存空间
E.G.
BYTE 20 DUP(0) ;20个字节,值都是0
BYTE 20 DUP(?) ;20个未定义的字节
BYTE 4 DUP("STACK") ;20个字节
9.定义WORD/SWORD
和BYTE/SBYTE一样,只不过BYTE是8位,WORD是16位的
所以直接上E.G.
word1 WORD 65535
word2 WORD -32768
myList WORD 1,2,3,4,5
10.定义DWORD/SDWORD
和BYTE/SBYTE一样,只不过BYTE是8位,DWORD是32位的
(略)
11.定义QWORD
(略)
12.定义浮点类型
直接上例子吧
rVal1 REAL4 -1.2
rVal2 REAL8 3.2E-260
rVal3 REAL10 4.6E+4096
shortArray REAL4 20 DUP(0.0)
13.加法程序
所以我们开始为上面那个加法程序加点料吧
.368
.model flat,stdcall
.stack 4096 ;堆栈
ExitProcess PROTO, dwExitCode:DWORD
.data
firstval DWORD 20002000h
secondval DWORD 11111111h
thirdval DWORD 22222222h
sum DWORD 0
.code
main PROC
mov eax, firstval
add eax, secondval
add eax, thirdval
mov sum, eax
INVOKE ExitProcess, 0
main ENDP
END main
14.小端顺序和大端顺序
12345678h
小端 | 大端 | 地址 |
---|---|---|
78 | 12 | 0000 |
56 | 34 | 0001 |
34 | 56 | 0002 |
12 | 78 | 0003 |
15.未声明初始化的数据
用.data?
而不要放在.data里面,这样子可以减少编译程序的量
3.5 符号常量
1.等号伪指令:
COUNT = 500
2.当前地址计数器:
selfPtr DWORD $
$ 的作用就是不要自己数有几个数组
list BYTE 10,20,30,40
var = $-list
3.EQU伪指令:将不同文本连接起来
name EQU expression ;有效的整数表达式
name EQU symbol ;已存在的符号
name EQU <text> ;文本或数字
就是将name = expression || symbol || text
4.TEXTEQU伪指令:类似于EQU,创建了文件宏
name TEXTEQU %constExpr
name TEXTEQU textmarco
name TEXTEQU <text>
3.6 64位编程
我觉得约等于32位
除了:
.386 ;32位程序
.model flat, stacall ;flag内存模式, stdcall调用规范
.stack 4096 ;堆栈4096,一个内存页大小
以上,32位有,64位无
还有就是指令长度等等问题了
其他区别需要自己寻找了,此处省略
(但我怀疑我这里大写特写32位,然后考试和上课是64位,那不裂开)
3.7 小结
终于终于终于,汇编语言基础结束了,最基础的汇编语言的语法也结束了,你学废了么?
眼睛:我看完了;脑子:这啥?这又是啥?我是啥?(误)
需要了解的名字:
1.字符常量
2.保留字
3.标识符
4.伪指令
5.指令助记符
6.操作数
7.逻辑段
8.源文件
9.列表文件
10.链接器
11.符号常量
你学会了啥:
最基本的汇编语言指令怎么写,但你还是不会helloworld
tips
建议好好看此章,这个保证了以后课你能不能听懂,也保证了你能不能真正学会汇编语言,而不是混过去。
我所希望的大学生都能扎扎实实把每一门专业课学好,特别是计算机这种工科专业
吃饭的家伙总要学好,总不能现学现卖吧
虽然你可能觉得汇编语言没什么用,去看看最初我写的前言吧,或者想想看你的绩点吧。
好像扯远了,,回来回来
相信我,这章你肯定会回来看的,,(我也一样)
补充:寻址方式:寄存器寻址,立即数寻址,存储器寻址
直接寻址方式,寄存器间接寻址方式,变址寻址,基址寻址,基址变址寻址(具体见PPT)
4.数据传送、寻址和算术运算
4.1 数据传送指令
1.x86指令格式
[lebel:] mnemonic [operands][; comment]
指令包含的操作数一般是0、1、2、3个,包含超过三个
操作数是啥,不懂的记得回去看
操作数有三种:
1.立即数——用
数字和文本的表达式
2.寄存器操作数——cpu的寄存器
3.内存操作数——引用内存位置
2.MOV指令(数据传输指令):将源操作数复制到目标操作数
MOV a,b
就是把b里的数复制到a里面去
即
a = b;
但它也有前提:
1.a和b一样大
2.a和b不能都是内存操作数
3.指令指针寄存器不能做a
E.G.
.data
var1 WORD 0
.data?
var2 WORD ?
.code
mov ax, var1
mov var2, ax
就是
var2 = var1 //好累啊
2.1.覆盖值
.data
oneByte BYTE 78h
oneWord WORD 1234h
oneDWord DWORD 12345678h
.code
mov eax,0
mov al,oneByte ;eax = 00000078h
mov eax,oneWord ;eax = 00001234h
mov eax,oneDWord ;eax = 12345678h
mov ax,0 ;eax = 12340000h
2.2.符号全零
因为mov不能将一个小的数复制到大的空间里面,所以我们可以先将大空间全部清理,然后传入大空间的低位
.data
count WORD 1
.code
mov ecx,0
mov cx,count
但如果我们有符号,负的就有bug了
所以我们想到了一个方法,也就是符号扩展
将最高位扩展到前面去即可
3.MOVZX指令:全零扩展传送
.data
byteVal BYTE 10001111b
.code
movezx ax,byteVal ;ax = 0000000010001111b
4.MOVESX:符号扩展传送
.data
byteVal BYTE 10001111b
.code
movesx ax,byteVal ;ax = 1111111110001111b
5.LAHF和SAHF
LAHF:将EFLAGS寄存器的低字节复制到AH
SAHF:将AH复制到EFLAGS寄存器的低字节
6.XCHG指令
交换a,b
mov指令是a = b
而XCHG是swap(a,b)
提醒一下,c++中algorithm(算法)的库里面有swap函数,可以直接用
xchg ax,bx
7.直接-偏移量操作
就和数组差不多,两个选择
[array+1]
或者
array[1]
4.2 加法和减法
1.INC和DEC指令
inc a ;a++
dec b ;b--
只能是寄存器和内存操作数
2.ADD指令(ADC)
add dest, sourse ;dest = dest + sourse
tips:标志位也会变
3.SUB指令(SBB)
sub dest,sourse ;dest = dest - sourse
tips:标志位也会变
4.NEG指令
neg a ;a的补码,也就是取反加一
tips:标志位也会变
所以我们可以通过以上完成加减的正常运算了
5.标志位
1.进位标志位(CF):无符号整数溢出 11111111 + 00000001
2.溢出标志位(OF):有符号整数溢出 01111111 + 00000001
3.零标志位(ZF):结果为0 00000001 - 00000001
4.符号标志位(SF):如果为负数,则符号标志位置1(0不是负号)
5.奇偶标志位(PF):1的个数如果是偶数,则置1
6.辅助进位标志位(AF):有进位或者借位
4.3 与数据相关的运算符和伪指令
1.OFFSET运算符
返回数据标号的偏移量(按字节计算)
.data?
bVal BYTE ?
wVal WORD ?
dVal DWORD ?
dVal2 DWORD ?
.code
mov esi, OFFSET bVal ;esi = 00404000h
mov esi, OFFSET wVal ;esi = 00404001h
mov esi, OFFSET dVal ;esi = 00404003h
mov esi, OFFSET dVal2 ;esi = 00404007h
所以我们可以通过OFFSET运算符来解决数组问题
如取数组第几个
.data
myArray WORD 1,2,3,4,5
.code
mov esi, OFFSET myArray + 4
2.PTR运算符
PTR可以重写一个已经被声明的操作数的大小类型
.data
mydouble DWORD 12345678h
.code
mov ax,mydouble ;错误
mov ax,WORD PTR mydouble ;正确,把低位5678h放进来
因为X86是小端储存格式,所以是5678h
3.TYPE运算符
返回变量单个元素大小,以字节为单位计算的
.data?
var1 BYTE ?
var2 WORD ?
var3 DWORD ?
var4 QWORD ?
.code
TYPE var1 ;1
TYPE var2 ;2
TYPE var3 ;4
TYPE var4 ;8
4.LENGTHOF运算符
计算数组中元素的个数
.data
byte1 BYTE 1,2,3,4
BYTE 5,6,7
byte2 BYTE 1,2,3,4,
BYTE 5,6,7
.code
LENGTHOF byte1 ;4
LENGTHOF byte2 ;7
5.SIZEOF运算符
返回大小,即LENGTHOF和TYPE的乘积
.data
byte1 BYTE 1,2,3,4
.code
SIZEOF byte1 ;4
6.ALIGN伪指令
ALIGN bound ;bound取1,2,4,8,16
将变量对齐到字节边界,字边界,双字边界,段落边界
可以让CPU更快的计算
7.LABEL伪指令
插入一个标号,定义大小属性,但不分配空间
4.4 间接寻址
1.任何一个寄存器加上[]就是一个间接操作数
寄存器上存上地址,[寄存器]就是解地址
数组也是同理
.data
array BYTE 10h,20h,30h
.code
mov esi,OFFSET arrayB
mov al,[esi]
inc esi
mov al,[esi]
inc esi
mov al,[esi]
2.变地址操作数
arr[i]
[arr+i]
两种形式
3.指针
一个变量包含另一个变量的地址
用OFFSET定义指针
4.TYPEDEF
创建用户定义类型
PBYTE TYPEDEF PTR BYTE
4.5 JMP和LOOP指令
1.无条件转移和条件转移
2.JMP指令
top:
inc a
jmp top ;不断的循环
3.LOOP指令
按照ECX计数器循环
mov ax,0
mov eax,5
L1:
inc ax
loop L1
每次循环eax都减1,直到为0
4.嵌套循环
.data
count DWORD ?
.code
mov ecx,100
top1:
mov count ecx
mov ecx,20
top2:
loop top2
mov ecx,count
loop top1
4.6 小结
我快要写死了,能不能给我点精神奖励呢?
当然能看到这里已经很不错了,值得鼓励
让我尊称你为巨巨
巨佬,ddw啊
我想放弃了。。。。
1.本章需要了解
MOV
MOVZX
MOVSX
XCHG
操作数类型
INC
DEC
ADD
SUB
NEG
状态标志
OFFSET
PTR
TYPE
LENGTHOF
SIZEOF
TYPEDEF
JMP
LOOP
tips
数据传送、寻址和算术运算结束了,接下来的学习会越来越有意思,加油啊
5.过程
5.1 堆栈操作
1.堆栈的数据结构:新值添加到栈顶,删除值也在栈顶移除(LIFO)
2.运行时堆栈
ESP:扩展堆栈指针
使用CALL,RET,PUSH,POP等指令来间接进行修改
2.1 入栈操作
ESP先向下移动一格(高地址向低地址扩展),然后数据入栈,
ESP指针总是指向最后压入堆栈的数据项
2.2 出栈操作
先删除ESP所指的数据,然后ESP向上移动一格
或者直接ESP向上移动一格
因为理论上ESP下面的值是多少其实是无所谓的
2.3 堆栈的意义
当寄存器用于多个目的时,我们可以用堆栈作为寄存器的一个保护区(临时储存区)
3.PUSH和POP指令
3.1 PUSH是插入指令
PUSH eax ;将eax的值入栈
3.2 POP是弹出指令
POP eax ;将栈顶的值放到eax中,并删除这个值
3.3 PUSHFD和POPFD指令
PUSHFD将EFLAGS寄存器的值压入栈
POPFD将栈的值弹出到EFLAGS寄存器
PUSHFD
POPFD
入栈必须要有一个出栈(确保push后面一定有个pop)
4.PUSHAD,PUSHA,POPAD,POPA
PUSHAD和PUSHA按照EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI顺序压入
POPAD和POPA按照相反的顺序弹出
只不过PUSHA,POPA是16位的,而PUSHAD,POPAD是32位的
5.2 定义并使用过程
1.PROC伪指令
;定义的过程
;前面最好有一串注释,这样子对程序猿友好一点
sample PROC
;
;
;
ret
sample ENDP
2.全局标号
jmp Destination ;因为标号只能在被定义的过程中可见,所以如果要jmp到其他地方,我们需要全局标号
;全局标号
Destination::
3.CALL和RET指令
CALL sth ;过程调用sth
;然后直接执行了sth,直到sth里面有RET出现,这样子就返回到原来的地方
4.过程调用嵌套
就是在一个过程中调用另外一个过程,不断套娃就出来了
main proc
call sub1
exit
main endp
;
sub1 proc
call sub2
ret
sub1 endp
;
sub1 proc
ret
sub1 endp
5.向过程中传递寄存器参数
在调用过程时,我们需要准备好过程中所需要的东西(放在寄存器里面)
然后输出的时候需要先push一个寄存器,组最后再pop回来,
先要保存,再变化,最后再变回来,万一这东西除了这里要用,其他地方也有用呢?
所以需要先push再pop回来
6.保存和恢复寄存器
一遍遍push,pop会不会太累啊,
所以聪明的计科人想到了模块化
也就推出了USES运算符
自动的push和pop,,但不能push和pop返回值,不然你啥也没干
arraysum proc uses esi ecx ;esi,ecx输入 eax输出 所以只要出现esi,ecx即可
mov eax,0
L1:
add eax,[esi]
add esi,TYPE DWORD
loop L1
ret
arraysum endp
这个等同于
arraysum proc ;esi,ecx输入 eax输出 所以只要出现esi,ecx即可
push esi ;先esi后ecx
push ecx
mov eax,0
L1:
add eax,[esi]
add esi,TYPE DWORD
loop L1
pop ecx ;先ecx再esi
pop esi
ret
arraysum endp
5.3 链接到外部库
相当于c和c++里面的#include<>
相当于python里面的import
这样子方便一点点
5.4 Irvine32链接库
如何使用
include Irvine32.inc
.data
.code
能干啥
过程 | 过程 |
---|---|
closefile | readkey |
clrscr | readstring |
creatoutputfile | settextcolor |
crlf | str_compare |
delay | str_copy |
dumpmem | str_length |
dupregs | str_trim |
getcommandtail | str_ucase |
getdatetime | waitmsg |
getmaxxy | writebin |
getmsecond | writebinb |
gettextcolor | writechar |
gotoxy | writedec |
isdigit | writehex |
msgbox | writehexb |
msgboxask | writeint |
openinputfile | writestackframe |
parsedecimal32 | writestackframename |
parseinteger32 | writestring |
random32 | writetofile |
randomize | writewindowsmsg |
randomrange | |
readchar | |
readdec | |
readfromfile | |
readhex | |
readint |
具体内容需要查表
5.5 小结
库是不需要背出来的,只需要记住一些常用的就可以了
本节需要了解什么是堆栈,如何操作,如何调用过程,如何调用库
tips
回想一下,前面的知识有没有学会,我到这已经快要打字打死了,可怜可怜我吧,www
6.条件处理
6.1 条件分支
条件分支:允许作决策的编程语句使程序猿可以改变控制流
6.2 布尔和比较指令
-
5种操作符:AND, OR, XOR, NOT, TEST
-
CPU状态标志:零标志位,进位标志位,符号标志位,溢出标志位,奇偶标志位
-
AND指令
AND destination, source
destination = destination & source
AND指令可以将1转变为0
-
OR指令
OR destination, source
destination = destination ^ source
OR指令可以将0转化为1
-
位映射集:用位向量把一个二进制数中的位映射为数组的对象
1.补集:用NOT
2.交集:用AND
3.并集:用OR
-
XOR指令
XOR destination, source
异或运算可以检查奇偶性
-
NOT指令
NOT reg
将操作数里面的所有位都取反
NOT不影响标志符号
-
TEST指令
TEST指令在两个操作数的对应位之间进行AND操作
但TEST指令不能修改目标操作数
test al, 00001001
-
CMP指令
比较指令
CMP destination,source
CMP结果 ZF CF destination<source 0 1 destination>source 0 0 destination=source 1 0 CMP结果 标志位 destination<source SF != OF destination>source SF = OF destination=source ZF = 1 其实CMP指令是从目的操作数中减去源操作数的隐含减法的操作,并且不修改任何操作数
-
置位和清除单个CPU标志位
通过使用AND,OR,TEST等指令来修改,通过修改该数来修改该数的标志位
6.3 条件跳转
1.Jcond指令
Jcond destination
当条件为真时,我们就可以跳转到下一个地方
也就是高级语言里面的IF语句
;如何实现
cmp eax,5
je L1
2.条件跳转指令类型
JZ ;ZF = 1
JNZ ;ZF = 0
JC ;CF = 1
JNC ;CF = 0
JO ;OF = 1
JNO ;OF = 0
JS ;SF = 1
JNS ;SF = 0
JP ;PF = 1
JNP ;PF = 0
JE ;LEFT = RIGHT
JNE ;LEFT != RIGHT
JCXZ;CX = 0
JECXZ;ECX = 0
JRCXZ;RCX = 0(64bit)
;无符号
JA ;LEFT > RIGHT
JNBE;LEFT > RIGHT
JAE ;LEFT >= RIGHT
LNB ;LEFT >= RIGHT
JB ;LEFT < RIGHT
JNAE;LEFT < RIGHT
JBE ;LEFT <= RIGHT
JNA ;LEFT <= RIGHT
;有符号
JG ;LEFT > RIGHT
JNLE;LEFT > RIGHT
JGE ;LEFT >= RIGHT
LNL ;LEFT >= RIGHT
JL ;LEFT < RIGHT
JNGE;LEFT < RIGHT
JLE ;LEFT <= RIGHT
JNG ;LEFT <= RIGHT
3.给个例子吧
.data
V1 WORD ?
V2 WORD ?
V3 WORD ?
.code
mov ax,V1
cmp ax,V2
JBE L1
mov ax,V2
L1: cmp ax,V3
jmb L2
mov ax,V3
L2:
6.4 条件循环指令
1.LOOPZ和LOOPE指令
LOOPZ destination
LOOPZ是为零跳转,LOOPE是相等跳转
2.LOOPNZ和LOOPNE指令
使用方法同上
只不过
LOOPNZ是不为零跳转,LOOPNE是不相等跳转
6.5 条件结构
即通过使用多个if语句,for语句,while语句来实现每一个程序的选择与循环
其他就没啥。。。
6.6 有限状态机
即是一个根据输入改变状态的机器或程序
有兴趣可以用c++实现一下,但用汇编就多此一举了。
6.7 条件控制流的伪指令
.BREAK
.CONTINUE
.ELSE
.ELSEIF condition
.ENDIF
.ENFW
.IF condition
.REPEAT
.UNTIL condition
.UNTILCXZ
.WHILE condition
以上是那些简单的伪指令,这样子就不需要用前面那么累的东西了
当然等不等于也有逻辑运算符
==
!=
>
>=
<
<=
!
ab ;and a,b
||
&
carry ?
overflow ?
parity ?
sign ?
zero ?
这样子也太香了吧,基本上和写c语言差不多了
6.8 小结
本章需要了解那些选择结构,已经它们的细节,也需要了解条件判断语句的底层原理
知道AND,OR,XOR,NOT,TEST等语句的内容和意义
主要是背诵
tips
本章背诵的内容较多,需要多回去看看,多背背
如果来不及了,就直接看最后一部分,又好理解,又好背诵
啊啊啊啊,快了快了,第六章了,我已经停下写笔记写了好几天了,等写完就可以学新知识了
7.整数运算
7.1 移位和循环移位指令
SHL ;左移
SHR ;右移
SAL ;算术左移
SAR ;算术右移
ROL ;循环左移
ROR ;循环右移
RCL ;带进位的循环左移
RCR ;带进位的循环右移
SHLD;双精度左移
SHRD;双精度右移
0.左移和右移就不加以介绍了,不会的请回小学二年级学习
1.算术右移和逻辑右移有什么区别
也就是逻辑移动都是用0代替空缺的位置
而算术移动都是用最高位来填充(记住,只有算术右移才有,算术左移和逻辑左移是一个东西)
mov al,0F0h
sar dl,1
以上是代码实例,也就是往右移动一格
cf就是移出去的那一位,
如果移动了多位,应该是最后一位吧(我猜的)
3.左移右移的妙用
因为计算机是用二进制完成的,所以当你左移时是乘以2,当你右移时是除以2,这在快速的运算时很重要
4.循环移位也就是循环绕着转,然后CF就是和普通移位(没有循环)一样
5.双精度移位:就是两个字节进行正常的移位,自己脑补一下
(主要是这图太多了,我不想画了,就简略一点点)
7.2 移位和循环移位的应用
应用其实蛮多的,但需要你自己去想象
这里用书中的例子
1.多个双字的移位
2.二进制乘法
3.显示二进制位
4.提取文件日期字段
7.3 乘法和除法指令
1.MUL指令
MUL reg
被乘数 | 乘数 | 乘积 |
---|---|---|
AL | reg/mem8 | AX |
AX | reg/mem16 | DX:AX |
EAX | reg/mem32 | EDX:EAX |
有没有发现MUL的乘积的比特数是乘数(被乘数)的两倍,所以完全不需要担心溢出的问题
代码举例
mov al,5h
mov bl,10h
mul bl
p.s. 我发现16位乘法也可以直接算诶。我好笨啊
2.IMUL指令
有符号的除法指令,和mul就相差一个符号
3.DIV指令
无符号除法
被除数 | 除数 | 商 | 余数 |
---|---|---|---|
AX | reg/mem8 | AL | AH |
DX:AX | reg/mem16 | AX | DX |
EDX:EAX | reg/mem32 | EAX | EDX |
4.IDIV指令
有符号的除法
mov dx,0
mov ax,009bh
mov bx,2
idiv bx
5.符号扩展指令
CBW:字节转字
CWD:字转双字
CDQ:双字转四字
7.4 扩展加减法
扩展精度加减法:对基本上没有大小限制的数进行加减法的技术、
1.ADC (带进位的加法)
2.SBB(带借位的减法)
adc a,b
sbb a,b
7.5 小结
本章主要是了解如何移位,如何乘除即可,其他书本上的细节本人认为意义不是很大,在以后的学习和使用中碰到的概率也是蛮小的,所以书上很多的内容在我的笔记中省略了,也是减轻大家复习的时间
tips
需要了解计算机如何使用移位和乘法的,所以需要更好的来回看,而且现在的你应该已经入门了,所以我所写的笔记是会越来越精简,甚至会只写它是什么,它要怎么用,以及它的原理。
8.高级过程
本章介绍如何使用函数,如何使用递归,如何传递参数等等问题
8.1 堆栈帧
1.实际参数与形式参数:自己上百度搜素
2.堆栈帧:一块堆栈保留区域,用于存放被传递的实际参数、子程序的返回值、局部变量等
3.普通的寄存器参数的方法耗时耗力,还极有可能是错误的,所以我们建议使用堆栈,我们只需要将所需要的参数压入栈即可
4.一般我们压入:值参数(不修改外部的数据)、引用参数(修改外部数据),想一想以前的swap函数
5.如何传递数组:我们只需要将数组的首地址传出去即可
6.堆栈帧的创建方法
1.被传递的实际参数,若有,则压入堆栈(反向压入)
2.被调用时,返回值压入
3.执行时,EBP压入
4.设置EBP等于ESP(作为基态)
5.若有局部变量,就修改esp来预留空间
6.若需要保存寄存器,就压入堆栈
7.寄存器参数的缺点:会导致代码混乱,消除性能优势。所以我们使用堆栈参数来实现。
值传递传递值,引用传递传递地址
;这个是值传递(通过数值去传递,不会改变原来的变量)
addtwo PROC
push ebp
mov ebp,esp
mov eax,[ebp + 12] ;显式的基址偏移量寻址
add eax,[ebp + 8]
pop ebp
ret
addtwo ENDP
int addtwo(int a, int b)
{
return a + b;
}
push OFFSET val2
push OFFSET val1
call Swap
;调用swap函数,使用引用传参
当子程序返回,必须将参数从堆栈中删除,否则会导致内存泄漏
8.清除堆栈的方法:在call后紧跟一句在ESP上加上一个数,这个数是所占堆栈空间的总和或者在最后加上ret num(num是所占堆栈空间的总和)
9.保存和恢复寄存器:先push再pop,提示一下,要中心对称,先入后出
10.局部变量:
sub esp,8 ;创建局部变量
mov DWORD PTR [ebp-4],10 ;第一个局部变量
mov DWORD PTR [ebp-8],20 ;第二个局部变量
mov esp,ebp ;删除局部变量
;当然如果增加易读性,我们可以命名
X EQU DWORD PTR [ebp-4]
Y EQU DWORD PTR [ebp-8]
;然后就可以
mov X,10
mov Y,20
11.LEA指令:返回间接操作数的地址
lea eax ; 返回eax的地址
12.ENTER和LEAVE指令
ENTER:自动创建堆栈帧
enter 8,0 ;为局部变量保留8个字节
;等价与
push ebp
mov ebp, esp
sub esp,8
LEAVE:结束一个堆栈帧
enter 8,0
leave
;等价于
push ebp
mov ebp, esp
sub esp,8
mov esp,ebp
pop ebp
13.LOCAL指令
在Microsoft里是enter的替补
local var1:BYTE
8.2 递归
也就是自己call自己
endless PROC
call endless
endless ENDP
加上普通的判断语句就可以实现递归计算
8.3 INVOKE,ADDR,PROC,PROTO
1.INVOKE
INVOKE DumpArray, OFFSET array, LENGTHOF array, TYPE array
;等价于
push TYPE array
push LENGTHOF array
push OFFSET array
call DumpArray
2.ADDR
ADDR在使用invoke调用时,可以传递指针参数
INVOKE FillArray, ADDR myArray
3.PROC
PROC就是一个声明
label PROC [attributes] [USES reglist], parameter_list
4.PROTO
PROTO指定程序外部过程
ExitProcess PROTO
8.4 新建多模块程序
可以使用INCLUDE和EXTERN
INCLUDE Irvine32.inc
EXTERN subl@0:PROC
8.5 小结
旧账重启,我停更了大半个月,发现我啥也没做,对不起
这一章需要了解的就是前面的一部分
了解堆栈帧,递归和外部引用
其他就粗浅知道即可
tips
啊啊啊,,快结束了,我也快死了,ddl也快到了,,gg了
大家加油啊,相信我,我会把欠的账都补补齐的。
9.字符串和数组
9.1 字符串基本指令
普通的指令可能只能执行一次或者一对数据,我们只需要使用重复前缀就可以了
REP | ECX>0时重复 |
---|---|
REPZ,REPE | 零标志位为1且ECX>0时重复 |
PEPNZ,REPNE | 零标志位清零且ECX>0时重复 |
方位标志键:
CLD 方位标志位清零
STD 方位标志位置一
cld ;清除方位标志位
mov esi,OFFSET string1 ;ESI指向源串
mov edi,OFFSET string2 ;EDI指向目的串
mov ecx,10 ;计数器赋值为10
rep movsb ;传送10个字节
9.1.1 MOVSB MOVSW MOVSD
功能:传送字符串:将ESI寻址的内存数据复制到EDI寻址的内存位置
MOVSB 传送(复制)字节 ESI或者EDI增加或减少1
MOVSW 传送(复制)字 ESI或者EDI增加或减少2
MOVSD 传送(复制)双字 ESI或者EDI增加或减少4
.data
sourse DWORD 20 DUP(0FFFFFFFh)
target DWORD 20 DUP(?)
.code
cld
mov ecx,LENGTHOF sourse
mov esi,OFFSET sourse
mov edi,OFFSET target
rep movsd
9.1.2 CMPSB CMPSW CMPSD
功能:比较字符串:比较分别由ESI和EDI寻址的内存数据
CMPSB 比较字节
CMPSW 比较字
CMPSD 比较双字
mov esi,OFFSET sourse
mov edi,OFFSET target
cmpsd
9.1.3 SCASB SCASW SCASD
功能:扫描字符串:比较累加器与EDI寻址的内存数据
SCASB 字节
SCASW 字
SCASD 双字
.data
alpha BYTE "ABCDEFGH",0
.code
mov edi,OFFSET alpha
mov al,'F'
mov ecx,LENGTHOF alpha
cld
repne scasb
jnz quit
dec edi
9.1.4 STOSB STOSW STOSD
功能:保存字符串:将累加器内容保存到EDI寻址的内存位置
.data
count = 100
string1 BYTE count DUP(?)
.code
mov al, 0FFh
mov edi,OFFSET string1
mov ecx,count
cld
rep stosb
9.1.5 LODSB LODSW LODSD
功能从字符串加载到累加器:将ESI寻址的内存数据加载到累加器
INCLUDE Irvine32.inc
.data
array DWORD 1,2,3,4,5,6,7,8,9,10
multilier DWORD 10
.code
main PROC
cld
mov esi,OFFSET array
mov edi,esi
mov ecx,LENGTHOF array
L1:lodsd
mul multiplier
stosd
loop L1
exit
main ENDP
END main
9.2 部分字符串过程
9.2.1 Str_compare
调用格式: INVOKE Str_compare,ADDR string1, ADDR string2
关系 | 进位标志位 | 零标志位 | 为真则分支(指令) |
---|---|---|---|
string1 < string2 | 1 | 0 | JB |
string1 = string2 | 0 | 1 | JE |
string1 > string2 | 0 | 0 | JA |
9.2.2 Str_length
调用格式:INVOKE Str_length, ADDR myString
9.2.3 Str_copy
调用格式:INVOKE Str_copy, ADDR source, ADDR target
9.2.4 Str_trim
从空字节结束字符串中移除所有与选定的尾部字符匹配的字符
调用格式:INVOKE Str_trim, ADDR string, char_to_trim
9.2.5 Str_ucase
把一个字符串全部转换为大写字母,无返回值
调用格式:INVOKE String_ucasn, ADDR myString
64位的话请使用Irivne64的库
9.3 二维数组
二维数组一般有两种,一种是行主序,一种是列主序
一般汇编语言有两种操作方式,即基址-变址和基址-变址-位移量
基址-变址操作数
基址-变址操作就是将两个寄存器(一个是基址,一个是变址)相加,生成一个偏移地址
[base + index]
二维数组中可以把行偏移量放在基址寄存器中,列偏移量放在变址寄存器中
基址-变址-位移量
基址-变址-位移量操作数是用一个偏移量,一个基址寄存器,一个变址寄存器和一个可选比例因子来生成有效地址
[base + index + diaplacement]
diaplacement[base + index]
在二维数组中可以将偏移量作为数组名,基址操作数为行偏移量,变址操作数为列偏移量
当然,我觉得还不如使用一个一维数组,可能计算公式麻烦一点,但表达起来就简单了很多(误)
9.4 整数数组的检索和排序
冒泡排序和对分查找
其实后面还有很多排序和查找的方法,但在这里就不一一枚举了,本人的算法笔记和数据结构笔记都在编写中(本人拖延症比较严重)……
9.4 小结
本章需要了解字符串的一些基本操作和一些字符串函数的意义,其他就没什么再了解的意义了
是书编写的越来越没东西了,还是我看书越来越不认真了,
但我真觉得这些意义不是很大,会用就可以了
重点还是前面的部分
tips
没有tips的tips 😆
10.结构和宏
10.1 结构
结构:是一组逻辑相关变量的模板或模式
CORRD 结构
COORD STRUCT
X WORD ?
Y WORD ?
COORD ENDS
定义结构
使用struct和ends的伪指令
name STRUCT
...
name ENDS
无定义:?
字符串文本:用引号括起来
整数:常数或常数表达式
数组:DUP运算符号
对齐结构字段
ALIGN datatype
;BYTE,SBYTE
;WORD,SWORD
;DWORD,SDWORD
;QWORD
;REAL4
;REAL8
;structure
;union
为什么需要对齐:为了更快的运算速度
声明结构变量
identifier structureType <initalizer-list>
;例子
COORD STRUCT
X WORD ?
Y WORD ?
COORD ENDS
.data
point1 COORD <5,10>
point2 COORD <5>
point3 COORD <,10>
point4 COORD <> ;随机值或者默认初始值
;当然<>可以改为{}
引用结构变量
1.引用成员
;e.g.
.data
worker employee <>
.code
mov dx,worker.years
mov worker.salaryhistory, 20000
使用.来调用内部成员,这点和c++差不多
当然,使用OFFSET运算符可以获得一个地址
mov eax,OFFSET worker.lastname
2.间接和变址操作数
引用间接操作需要指针
mov esi,OFFSET worker
mov ax,(employee PTR[esi]).years
变址操作数
.data
department Employee 5 DUP(<>)
.code
mov esi,TYPE Employee
mov department[esi].Years,4
结构套娃
当然,结构可以套着结构
和c++类似
不再赘述
A STRUCT
X WORD ?
Y WORD ? ;这个将要改变
A ENDS
B STRUCT
X WORD ?
Y WORD ?
B ENDS
C STRUCT
X A <>
Y B <>
C ENDS
.data
c C <>
.code
mov c.X.Y,10
声明和使用联合
前面是c++里面的struct,而我们将学习c++里面的union
unionname UNION
union-fields
unionname ENDS
;提示,如果union在结构体里面,就是另外一个故事了
;下面是在结构体里面,而上面是在外面
name STRUCT
...
UNION unionname
union-fields
ENDS
name ENDS
其他的使用和c++差不多,所以我在这也不赘述了,以免浪费看官的时间
10.2 宏
宏是一个命名的汇编语句块,就是c++里面的define
在内部会自动展开,替换这个宏,也成为内联展开
宏的定义
macroname MACRO parameter1,parameter2...
....
ENDM
;宏规定形参 REQ
;宏注释;;
;LOCAL 可以把两个相同名字换成唯一标识符号(汇编语言可能不允许重名)
print MACRO char:REQ
push eax ;;我是注释
mov al,char
call WriteChar
pop eax
ENDM
;E.G.
printx MACRO
mov al,'x'
call WriteChar
ENDM
print MACRO char
push eax
mov al,char
call WriteChar
pop eax
ENDM
调用宏
和C++一样,直接使用,并添加参数即可
但提醒一下,宏可能有一定的bug
宏也可以嵌套,就和函数嵌套一样,不浪费时间了
10.3 条件汇编伪指令
这个好像只适用于宏吧
IF expression
IFB<>
IFNB<>
IFIDN<>,<> 区分大小写比较
IFIDNI<>,<>不区分大小写比较
IFDIF<>,<>
IFDIFI<>,<>
IFDIF name
IFNDEF name
ENDIF
ELSE
ELSEIF expression
EXITM
默认参数 paramname : =
LT <
GT >
EQ =
NE !=
LE <=
GE >=
& 替换
<>文字文本运算符
!文字字符运算符
%展开运算符
10.4 定义重复语句块
WHILE
重复执行一个语句块,直到为真
WHILE constexpression
statements
ENDM
REPEAT
重复固定次数
REPEAT constexpression
statements
ENDM
FOR
C++里面的for循环
FOR parameter,<ARG1,ARG2,…>
statements
ENDM
FORC
为文本设计的for循环
FORC parameter,
statements
ENDM
10.5 小结
本章需要了解struct,union和宏,以及与宏相关的一系列的函数和伪代码,这个专题还是蛮重要的,希望大家能够好好复习,我写不动了,都快要放弃写作了
tips
还有最后一章了,加油啊
聪明的童鞋们会发现我少了两个单元,这两个单元我觉得无伤大雅,暂时对我的意义很小,如果有需要的童鞋们可以自行查看,我这就不去写了
11.浮点数处理与指令编码
11.1 浮点数二进制表达
浮点数包括符号,有效数字和阶码
单精度:32位,1位符号,8位阶码,23位有效数字的小数部分
双精度:64位,1位符号,11位阶码,52位有效数字的小数部分
扩展双精度:80位,1位符号,15位阶码,1位有效数字的整数部分,63位有效数字的小数部分
若有疑问请查阅《计算机概论》等书籍,本部分非重点,就此略过
11.2 浮点单元
FPU寄存器栈
FPU寄存器栈通过表达式的堆栈来完成计算的内容
FPU有8个独立的可寻址的80位数据寄存器,通过将寄存器中缀转后缀来完成运算
A+B -> AB+
(A+B)*(C+D) -> AB+CD+*
(A-B)/D->AB-D/
((A+B)/C)*(E-F)->AB+C/EF-*
专用寄存器
操作码寄存器:保存最后执行的非控制指令的操作码
控制寄存器:执行运算时,控制精度和FPU使用舍入方法
状态寄存器:包含栈顶指针、条件码和异常警告
标志寄存器:指明每个寄存器的内容
最后指令指针寄存器:保存指向最后执行的非控制指令的指针
最后数据(操作数)指针寄存器:保存指向数据操作数的指针
舍入
因为计算机的存储空间有限,所以不可能准确保存数据,可能需要进行舍入
四种舍入方式(FPU控制字)
舍入到最接近的偶数 00
向负无穷舍入 01
向正无穷舍入 10
向0舍入 11、
浮点数异常
#I无效操作
#Z除零
#D非规格化操作数
#O数字上溢
#U数字下溢
#P模糊精度
浮点数指令集
1.初始化FINIT
建议开始时就调用FINIT
2.浮点数类型
REAL4 32位
REAL8 64位
REAL10 80位
3.加载浮点数值(FLD)
将浮点操作数复制到栈顶
FLD m32fp
FLD m64fp
FLD m80fp
FLD ST(i)
加载常数
FLD1 1
FLDL2T log2 10
FLDL2E log2 e
FLDPI π
FLDLG2 log10 2
FLDLN2 loge 2
FLDZ 0.0
4.保存浮点数值
FST,FSTP
FST m32fp
FST m64fp
FST m80fp
FST ST(i)
FSTP dblThree
FSTP dblFour
5.算术运算指令
FCHS修改符号,无操作数
FABS绝对值,无操作数
FADD加法(若FADD ST(i)就是和ST(0)加,如果两个的话就两个相加存在第一个,和ADD类似)
FADDP相加并出栈
FIADD整数加法
FSUB和加法类似,但变成了减法
FSUBR源操作数减目标操作数
FSUBP相减并出栈
FOSUB整数减法
FMUL乘法
FMULP乘法并出栈
FIMUL整数乘法
FDIV除法
FDIVR源操作数除以目的操作数
FDIVP相除并出栈
FIDIV整数除法
6.FCOM比较浮点数
FCOM没有操作数就是ST(0)和ST(1)比较,有操作数就是操作数和ST(0)比较,,最多只能有一个操作数
FCOMP
FCOMPP
条件 | 零标志位C3 | 奇偶标志位C2 | 进位标志位C0 | 使用条件跳转指令 |
---|---|---|---|---|
ST(0)>SPC | 0 | 0 | 0 | JA.JNBE |
ST(0)>SPC | 0 | 0 | 1 | JB.JNAE |
ST(0)>SPC | 1 | 0 | 0 | JE.JZ |
无序 | 1 | 1 | 1 | 无 |
7.读写浮点数值
ReadFloat
WriteFloat
ShowFPUStack
11.3 x86指令编码
了解一下汇编指令如何转变成机器语言的方法
1.指令格式
指令前缀:覆盖默认操作数大小
操作码:指定指令的特定变体
Mod R/M:字段指定寻址模式和操作数
伸缩引字节:用于计算数组索引偏移量
地址位移:字段保存了操作数的偏移量
立即数:字段保存了常量操作数
2.单字节指令
没有操作数的指令,我们只需找到对应的操作码即可
3.立即数送寄存器
按照小端顺序添加指令
4.寄存器模式指令
将指令分为mod,reg,r/m,然后一一转化即可
mov ax,dx 11 000 010
具体请查阅表格
11.4 小结
本章需要了解浮点数和指令编码,这两个是最重要的内容,需要好好学习
Tips
哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦,终于学完了
我也终于写完了
完结撒花
泪目了
当然有很多没有做完,我就先记在这里
就不定期更新了
1.汇编语言是啥,能干啥
2.本笔记的结构
3.最后加个英语整理,学计算机还得学英语吧
4.最后在加个名词汇总,也方便我复习吧
12.后记
断断续续阅此书,絮絮叨叨记新知。
见者若获新玄机,不枉本人几多夜。
新手莫奉为圭臬,还需努力自苦读。
高手莫笑此记误,若知槽点望联系。
如有问题,请联系我,谢谢
当然,如果对您有帮助,可以进行打赏:happy:🤝
联系:1499765600@qq.com