目录
tips:之后经常会提到的概念:标志位
常见的标志位包括:
- 零标志位(Zero Flag,ZF):当上一个操作的结果为零时,将设置该标志位。
- 进位标志位(Carry Flag,CF):当上一个操作的结果产生了进位或借位时,将设置该标志位。
- 溢出标志位(Overflow Flag,OF):当上一个操作的结果产生了溢出时,将设置该标志位。
- 符号标志位(Sign Flag,SF):当上一个操作的结果为负数时(最高位为1),将设置该标志位。
- 奇偶校验标志位(Parity Flag,PF):当上一个操作的结果具有偶数个二进制位为1时,将设置该标志位。
这些标志位通常用于指令的条件分支操作,例如 jz(零标志位为真时跳转)、jnz(零标志位为假时跳转)等。
80x86汇编语言指令的一般格式为
[标号:] 指令助记符 [操作数] [;操作数]
方括号[]中的内容为可选项
指令中的各部分意义
1.标号
标号是一个符号地址,用来指示指令在内存中的位置,方便其他指令能引用该指令
通常作为转移指令的操作数,用来表示转向的目标地址(使用标号时:不可省略)
2.指令助记符
表示指令名称,是指令功能的英文缩写,后面会具体介绍
3.操作数
表示指令要操作的数据或数据所在的地址。操作数可以是寄存器、常量、变量,也可以是表达式
一般带有 0 / 1 / 2 个操作数
对于双操作数指令,左边的操作数参与指令操作并存放操作结果,称为目的操作数(DST)
右边的操作数仅仅参与指令操作数(自己的内容不变),称为源操作数(SRC)
两个操作数之间记得用“ ,”隔开
双操作数指令的规定:
1.SRC(源操作数)的长度与DST(目的操作数)的长度必须明确且一致(同时为8位或16位)
2.目的操作数与源操作数不能同为存储器,不允许在两个存储单元之间直接传送数据
( 80x86 架构的硬件设计限制了直接的内存到内存的操作。CPU 的 ALU(算术逻辑单元)通常设计为在寄存器和内存之间进行数据传输和运算,而不是直接在两个内存地址之间进行操作,并且内存到内存的操作会增加指令的复杂性,降低了指令效率)
3.目的操作数不能为CS或IP,因为CS:IP指向的是当前要执行的指令所在的地址
4.目的操作数不能是立即数
(立即数是编码在处理器指令代码中的,而处理器指令代码执行时通常禁止修改。所以,结果无法存在指令代码中)
4.注释
和C/C++一样对指令功能进行解释
指令用法
指令可以分为以下五类
- 数据传送指令
- 算术运算指令
- 逻辑指令与移位指令
- 串操作指令
- 程序转移指令(后面会学)
今天只学前四种指令
1.数据传送指令
有四类数据传送指令
1.通用数据传送指令
(1)MOV传送指令
MOV指令为双操作数指令
指令格式:MOV DST , SRC
实现将源操作数的值传递给目的操作数
a.内存访问
例如 MOV [BX], AX 指令合法
但是 MOV [BX] ,0 指令错误
(因为源操作数为立即数且长度不确定,目的操作数是内存单元,当以低地址方访问内存单元时[BX]并不能说明时字节单元还是字单元所以其长度也是不确定的)
(MOV [FFFF], AX指令本质上是错误的,因为AX两个字节无法找到以FFFF为段基址的下一个地址)
解决方法:在指令中指定内存单元的类型
例如: MOV BYTE PTR[BX] ,0 ;BYTE PTR说明是字节操作
MOV WORD PTR[BX] , 0 ;WORD PTR 说明是字操作
b.段基址寄存器的传送
段基址寄存器需通过寄存器(非段寄存器)得到段基址
不能直接由符号地址、段寄存器、立即数得到
例如: MOV DS, AX ;是正确的
但是 MOV DS, DATA_SEG ;是错误的
MOV CS , AX ;指令合法但是代码段寄存器不能赋值
c.传送变量
假设TABLE是16位的变量
(变量和常量的区别在这个大大的文章里:【进击的汇编小小白】--常量与变量_字变量和字节变量存储顺序-优快云博客)
MOV BX, TABLE ;是正确的
其余满足双操作数的要求即可
d.传送地址
有效地址总是16位,所以要注意字长的匹配
使用OFFSET 得到变量的偏移地址,于是操作可写为:
MOV BX, OFFSET TABLE
(2)PUSH进栈指令
命令格式为:PUSH SRC
实现的操作为:(SP)<-(SP)-2 ((SP)+1,(SP))<-(SRC)
这条指令表示将源操作数压入目的操作数的栈堆
目的操作数的地址由SS:SP指定,指令中无需给出
栈堆是LIFO内存区,SP总是指向栈顶,因此入栈时要将栈顶指针SP-2(2个字节)
由此指向新的内存地址用来接收16位源操作数,同时指向新的栈顶(堆栈操作以字为单位)
(3)POP出栈指令
命令格式: POP DST
实现的操作为: (DST)<-((SP)+1,(SP)) (SP)<-(SP)+2
和进栈操作类似,但是是将堆栈中操作数弹出给目的操作数
弹出后SP+2,下移一个字,指向新的栈顶
22 |
20 |
0 |
这时SP指向22且上方为低地址下方高地址
出栈后
20 |
0 |
这时SP指向20
将50进栈后
50 |
20 |
0 |
这时SP指向50
(4)XCHG交换指令
指令格式为:
XCHG OPR1 ,OPR2
该条指令把两个操作数互换位置
该指令为双操作数指令,且均是目的操作数,既要遵循双操作数指令规定也不能用立即数寻址
例如:
XCHG AX , BX ;正确的
XCHG [BX] , VAR ; 错误的,不能同为内存单元
XCHG AX , 5H ;错误的,操作数不能为立即数
2.累加器专用传送指令
I/O端口是CPU与外设传送数据的接口
累加器专用传送指令只限于AX,AL(累加器)
常见的累加器专用传送指令如下:
(1)IN输入指令
把端口号PORT或由DX指向的端口的数据输入累加器
根据端口号的长度分为长格式和短格式
a.长格式
IN AL, PORT(字节)
IN AX, PORT(字)
端口号范围为00H~FFH时,可以使用长格式指令(机器指令长度为2个字节且端口号占1个字节)
b.短格式
IN AL, DX(字节)
IN AX, DX (字)
端口号范围为0100H~0FFFH时,必须使用短指令格式(机器指令长度为1个字节)
端口号存放在DX寄存器中
下面是长格式和短格式的举例:
IN AX, 61H
MOV BX, AX
这是长格式,直接将端口号61H的16位数据传给AX后,AX中的数据又传给BX寄存器
MOV DX, 2F8H
IN AL, DX
这是短格式,先把端口号2F8H传给DX寄存器,然后DX将其中的的8位数据输入累加器AL
(2)OUT输出指令
将累加器的数据输出到端口PORT或由DX指向的端口,与IN输入指令类似
也有长指令和短指令两种
例如:
OUT PORT, AL(字节)
其实使用方法和IN类似,就是把端口号/DX寄存器和累加器的位置互换
这里就不多做解释
(3)XLAT换码指令
命令格式为: XLAT
执行的操作为将BX+AL的值作为有效地址求出其中一个字节送入AL寄存器
DATA SEGEMNT
ORG 1000H ;告诉汇编程序在开始执行时将某段机器语言装载到内存中的哪个地址
STRING DB 'ABCDEFG'
DATA ENDS
CODE SEGMENT
MAIN PROC FAR ;在汇编语言中,main proc far 是一种用于定义子程序的伪指令
ASSUME CS:CODE, DS:DATA
START:
MOV AX,DATA
MOV DS,AX
MOV BX,0100H
MOV AL,04H
XLAT
INT 21H
MOV AH,4CH
INT 21H
MAIN ENDP
CODE ENDS
END START
有些代码看不懂网上搜了一下,有些在代码段注释,有些在以下内容:
proc 表示子程序的开始,endp 表示子程序的结束,两者必须成对出现。far 和 near 是子程序调用时的参数,用于指定子程序的调用方式。
far调用表示段间远调用,即调用程序和子程序不在同一代码段中,可以被相同或不同代码段的程序调用;near调用表示段内近调用,即调用程序和子程序在同一代码段中,只能被相同代码段的其他程序调用。
执行XLAT之后AX的值发生变化(其实就是AL发生变化)(注意是以BX+AL为地址找寻字节存入AL)
3.地址传送指令
常用的地址传送指令如下:
(1)LEA有效地址寄存器指令
指令格式为 LEA REG, SRC
实现把源操作数SRC的有效地址送到指定的寄存器(REG)
源操作数必须来自主存,目的操作数必须是寄存器
例如:
用于取变量的有效地址
LEA BX, TABLE;
LEA BX , [2022H];
上面两条指令和下面的MOV指令等价
MOV BX , OFFSET TABLE;OFFSET用来取寄存器或内存单元的有效地址
MOV BX , OFFSET [2022H];
MOV和LEA指令的区别
mov 是对值进行操作,而 lea 则是对值所对应的地址进行操作,相当于C语言中的“&”符号
(2)LDS指针送寄存器和DS指令
指令格式: LDS REG, SRC
实现把SRC指向的内存单元中两个字送到指定的寄存器REG和DS
例如:
LDS SI, [BX]
指令执行后将(DS:BX)指向的内存单元指向的第一个字传给SI,第二个字传给DS
(3)LES指针送寄存器和ES指令
指令格式: LES REG, SRC
实现的效果和LDS类似,只是第二个字传给ES寄存器
2.算数运算指令
1.数据类型扩展指令
当指令需要双操作数时,为了实现双操作数的长度一致,需要将某一操作数的数据类型进行拓展
将操作数视为有符号数,从而将数据类型拓展
CBW(Convert byte to Word) : 字节拓展成字(默认寄存器AL内容拓展到AX)
CWD(Convert Word to Double word ):字扩展成双字(默认将寄存器AX内容拓展到DX,AX中)
例如:
MOV AH, 11H
MOV DX, 1111H
MOV AL, 52H
CBW ; 指令结束后,AX=0052H
CBD ; 指令结束后,DX=0000H , AX=0052H
CBW执行后AX变化,CWD执行后DS也变化
(整数拓展补0,负数拓展补F)
(正负数判断可以将16进制看成2进制,第一位为1就是负数)
2.加法指令
(1)ADD加法指令
指令格式为 ADD DST, SRC
实现将目的操作数和源操作数相加结果存进目的操作数
指令执行后会影响标志寄存器中的CF和OF标志位
ADD计算要看是否是有符号运算
对于无符号数:
MOV AL, 72H
ADD AL, 93H
运算结果为05H,1进位到CF中,所以如果是无符号数CF=0才是正确结果
对于有符号数
MOV AL, 92H
ADD AL, 93H
运算结果为25H,因为AL寄存器最多表示-128,产生进位到OF,所以有符号数OF=0才是正确结果
以上会产生错误的结果都叫做溢出
(2)ADC带进位加法指令
指令格式为: ADC DST, SRC
实现将目的操作数,源操作数,CF标志位三者相加的结果传给目的操作数
(其中CF为运算前CF标志位的值)
SHI
AX运算完CF进位,随后ADC运算得出DX时也将CF产生的进位加入运算
(因此低位加法的CF=1不影响结果正确性)
(3) INC 加1指令
指令格式为 INC OPR
实现将操作数自加1,不影响CF标志位
3.减法指令
(1)SUB减法指令
命令格式为: SUB DST, SRC
实现将(DST-SRC) 的操作结果传给DST
与加法指令类似,执行后会影响标志寄存器的CF和OF标志位
(2) SBB带借位减法指令
命令格式为: SBB DST, SRC
实现将 DST-SRC-CF的值传递给DST
和加法ADC类似
(3)DEC减1指令
格式为 : DEC OPR
实现操作数自减1
(4) NEG求相反数指令
命令格式为 : NEG OPR
相当于执行常数0减去操作数OPR
(5)CMP比较指令
命令格式为: CMP OPR1, OPR2
实现将OPR1减去OPR2,只对CF,OF标志位产生影响,并没有返回值
4.乘法指令
乘法指令中目的操作数默认为累加器AX
1.MUL无符号数乘法指令
命令格式为: MUL SRC
当源操作数为字节时,实现将AL*SRC的结果传给AX
当操作数为字时,实现将AX*SRC的结果传给(DX,AX)
2.IMUL有符号数乘法指令
格式和操作与MUL指令相同
SRC(默认是AL寄存器)可以是寄存器或变量但不能是立即数(立即数长度不确定)
运算结果的长度默认为乘数的两倍,不会出现溢出情况
例如:
MOV AL, 0F1H
MOV BL, AL
MUL BL
IMUL BL
上述例子中如果用MUL结果就是E2E1
如果用IMUL,AL和BL都表示负数,转换成10进制相乘,结果用16进制表示即可
5.除法指令
除法和乘法默认的寄存器是类似的
(1)DIV无符号除法指令
命令格式为: DIV SRC 当源操作数为字节时, 实现将AX/SRC的商传给AL,余数传给AH
当操作数为字时,实现将(DX,AX)/SRC的商传给AX,余数传给DX
(2)IDIV有符号除法指令
和乘法很类似,反过来就可以了
但是要考虑除法有溢出问题
如果被除数的高位绝对值大于等于除数的绝对值,商就会产生溢出
BCD的十进制调整指令
为了简化二进制的算术运算转化为十进制的过程,计算机提供了一组十进制调整指令
(使用BCD码表示十进制数)
(1)DAA(加法的十进制调整指令)
命令格式为: DAA
实现在加法指令中将AL中的和调整为正确的BCD码(压缩型)格式
计算方法如下:
a.如果AL低4位大于9或AF=1(即低4位大于10D),则AL=AL+6H
b.如果AL高4位大于9或CF=1(即高4位大于10D),则AL=AL+60H
(2)DAS(减法的十进制调整指令)
命令格式为: DAS 可以将AL算出来的差调整为BCD码(压缩型)格式
计算方法如下:
a.如果AL低4位大于9或AF=1(即低4位大于10D),则AL=AL-6H,AF=1
b.如果AL高4位大于9或CF=1(即高4位大于10D),则AL=AL-60H ,CF=1
3.逻辑指令与移位指令
1.逻辑指令
逻辑指令按二进制位进行操作,因此操作数应看成二进制位串
(1)AND(与指令)
命令格式: AND DST, SRC
实现DST和SRC的按位与操作,结果送入DST
(2)OR(或指令)
命令格式:OR DST, SRC
实现DST和SRC的按位或操作,结果送入DST
(3) NOT (非指令)
命令格式: NOT OPR
实现将OPR按位取反后将结果输入OPR
(4)XOR(异或指令)
命令格式: XOR DST, SRC
实现将DST和SRC操作数按位异或,结果送入DST
(5)TEST(测试指令)
命令格式: TEST OPR1, OPR2
根据两个操作数相与的结果置标志位
作用详细说明
将两个操作数进行按位AND,设结果是TEMP
- SF = 将结果的最高位赋给SF标志位,例如结果最高位是1,SF就是1
- 看TEMP是不是0,如果TEMP是0,ZF位置1;如果TEMP不是0,ZF位置0
- PF = 将TEMP的低8位,从第0位开始,逐位取同或。也就是第0位与第1位的同或结果,去和第2位同或,结果再去和第3位同或…直到和第7位同或。PF位是奇偶校验位,如果结果低8位中1的个数是偶数,PF=1;否则PF=0
- CF位置0
- OF位置0
逻辑指令的功能:
除了具有常规的逻辑运算功能外,通常还可以用来对操作数的某些位进行处理
例如:
AND AL, 0FH
该指令执行的结果将AL寄存器的高4位屏蔽
因为0和谁进行与操作都是0
OR AL, 03H
该指令执行的结果是将AL的最低两位取反
总之就是根据需要找到合理的16进制数然后进行AND(与)或者OR(或)操作
同时TEST 指令通常用于测试某些位是否被设置
MOV AL, 86H
TEST AL, 86H;因为按位取与结果的最高位为1,所以SF置1,意思结果为负数
JS NEXT ;于是跳转到标号NEXT执行
2.移位指令
用的不多,所以这里转载一下(有兴趣就看看了)
移位指令:移位指令详解-优快云博客
4.串操作指令
串操作指令用于处理内存中的数据串,但每一次只执行处理单个字节或字
所以对于数据串,需要重复执行串操作指令才能处理完整个串
串操作指令有如下几种:
- MOVS: 串传送
- CMPS: 串比较
- SCAS:串扫描
- STOS: 串存入
- LODS:从串中取数
串操作指令的重复有特定的前缀指令配合,下面介绍几种
串操作指令重复前缀指令:
- REP(Repeat):REP的作用是重复执行串操作指令,直到寄存器CX=0为止,每执行一次串操作指令CX自减一(总的执行次数等于CX寄存器的初始值)
- REPE/REPZ(Repeat while Equal/Zero):相等/为0时重复 。当用于比较两个字符串是否相等时,每次将目的串和源串的一个字节进行比较,若相等则继续执行串操作指令。若不相等或CX=0则停止
- REPNE/REPNZ(Repeat while Not Equal/Zero): 不相等/不为0则重复,执行过程和REPE类似
1. MOVS串传送指令
主要有三种:
- MOVSB(以字节为单位传送)
- MOVSW(以字为单位传送)
- MOVS DST, SRC :将源串传送到目的串中
因为MOVS指令默认目的串地址ES:[DI],源串地址为DS:[SI],因此前两种都省略操作数
第三种格式的指令以字节为单位传送时,命令格式为:
MOVS ES:BYTE PTR[DI], DS:[SI]
执行 REP MOVS指令前,应先做好:
把原串首地址送给SI寄存器
把目的串首地址送给DI寄存器
把数据串长度放入CX计数寄存器
设置方向标志DX(CLD/STD)
前两种格式的操作为:
MOVSB 使用后从(DS:SI)向(ES:DI)传送一个字节,DI+/-1,SI+/-1
MOVSW使用后从(DS:SI)向(ES:DI)传送一个字, DI+/-2,SI+/-2
当DF=0时, SI,DI用+;当DF=1时,SI,DI用-
DF为方向标志,方向标志DF的设置靠以下两条指令
CLD(设置为正向,DF=0) STD(设置为反向,DF=1)
下面代码用来把MESS中的内容传给BUFF
DATA SEGMENT
MESS DB 'COMPUTERR SOFTWARE $ '
DATA ENDS
EXT SEGMENT
BUFF DB 19 DUP(?) ;DUP操作符开辟19个字节空间
EXT ENDS
CODE SEGMENT
ASSUME CS:CODE, DS:DATA, ES:EXT
START:
MOV AX, DATA ;
MOV DS, AX
MOV AX, EXT
MOV ES, AX
LEA SI, MESS
LEA DI, BUFF ;以上命令是给DS:SI和ES:DI赋初值
MOV CX, 19
CLD ; 设置正向
REP MOVSB ;完成串的传送
MOV BX, ES
MOV DS, BX
LEA DX, BUFF
MOV AH, 9
INT 21H
MOV AH, 4CH
INT 21H
CODE ENDS
END START
以上代码可以用来实现字符串的传送
2.CMPS串比较指令
有三种格式:
- CMPSB(字节)操作
- CMPSW(字)操作
- CMPS DST, SRC
和MOVS命令类似,默认的目的串和源串地址都和MOVS一样
CMPS串操作指令把两个串的对应位置的字节或字相减且不保存结果,只根据结果设置标志位ZF
与前缀指令REPE联合使用时可比较两个串是否相等
一旦比较过程中发现有不相等就终止重复执行 (ZF=0)
因此可以用ZF标志位的值判断两个串是否相等,ZF=1代表相等
注意是执行完之后停止(指针停在不相同的字符之后)
下面代码用来比较两个字符串是否相等
DATA SEGMENT
MESS1 DB 'COMPUTER SOFTWARE $'
MESS2 DB 'COMKUTER SOFTWARE $'
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE, DS:DATA
START:
MOV AX, DATA
MOV DS, AX
MOV ES, AX
LEA SI, MESS1
LEA DI, MESS2 //上面四句都是给几个寄存器传送默认地址
MOV CX, 19
CLD
REPE CMPSB
JZ YES //如果字符串相等,则转到YES地址给DL赋值
MOV DL, 'N'
JMP DISP //JMP是无条件跳转(如果JZ没跳转,那么意味着字符串不相等,所以给DL赋值‘N’后转到DISP)
YES: MOV DL,'Y'
DISP:MOV AH, 2 //无论是YES还是DISP都在此输出单个字符
INT 21H
MOV AH, 4CH
INT 21H
CODE ENDS
END START
运行结果为:
这里提到了JZ等跳转指令,优快云上有不错的介绍:
jmp、JE、JZ、JNE、JNT指令_je指令-优快云博客
3. SCAS串扫描指令
有三种命令格式:
- SCASB(字节)操作
- SCASW(字)操作
- SCAS DST
由于串扫描指令只涉及目标串,所以默认地址由ES:[DI]提供
串扫描指令实现的操作为:把AL/AX寄存器中的内容与默认地址指向内存单元的内容比较
每次比较完DI自加1(字节)或2(字)
与CMPS串比较指令相似但不保存结果,只根据结果设置标志位
与前缀指令REPNE联用可在目的串中查找有没有与AL/AX寄存器中的内容相同的字节或字
指令结束后可根据ZF是否等于1判断是否找到相同内容(ZF=1则找到)
下面的代码用来查找附加段中名为“MESS"的字符串中是否有空格符
把首次发现的空格符改为‘#’,存回该单元并显示‘Y',否则显示’N‘
EXT SEGMENT
MES DB 'COMPUTER SOFTWARE $'
EXT ENDS
CODE SEGMENT
ASSUME CS:CODE,ES:EXT
START:
MOV AX,EXT
MOV ES,AX
LEA DI,MES
MOV CX,19
MOV AL,20H
CLD
REPNE SCASB
JZ YES
MOV DL,'N'
JMP DISP
YES:DEC DI ;删除DI当前指向的空格符
MOV BYTE PTR ES:[DI], 23H ;23H意味着‘#’,将‘#’赋到DI地址
MOV DL,'Y' ;注意在YES:后给DL赋值‘Y'
DISP:MOV AH,2 ;相当于统一输出端
INT 21H
MOV AX,EXT
MOV DS,AX ;DS寄存器不能直接用立即寻址,得把基地址赋给AX,后用AX赋给DX
LEA DX,MES
MOV AH,9
INT 21H
MOV AH,4CH
INT 21H
CODE ENDS
END START
我这里还把更改后的字符串输出了(没有输出换行因为驻波还不会。。。)
代码结果为:
4.STOS串存入指令
指令格式有三种:
- STOSB(字节)操作
- STOSW(字)操作
- STOS DST
该指令把AL/AX寄存器中的内容存入由DI指向的附加段的内存单元中
执行完DI自动加1(字节)或加2(字)
与REP前缀指令联用,可把累加器的内容存入一个连续的内存缓冲区(长度由CX控制)
所以STOS可以用于初始化某一块内存缓冲区
下面的例子就初始化了长度为9的MESS字符串
EXT SEGMENT
MESS DB ''
EXT ENDS
CODE SEGMENT
ASSUME CS:CODE,ES:EXT
START:
MOV AX,EXT
MOV ES,AX
LEA DI,MESS
MOV CX,9
MOV AL,0 ;将串中所有字符设为0
CLD
REP STOSW ;将AL的值输入ES:[DI]指向的缓冲区内存单元中进行初始化
MOV AX,EXT
MOV DS,AX
LEA DX,MESS
MOV AH,2
INT 21H
CODE ENDS
END START
输出结果为:(很正常是空)
5.LODS命令
命令格式有三个
- LODSB(字节 )
- LODSW(字)
- LODS SRC
这里只涉及源串,所以地址默认为 DS:[SI]
实现将DS:[SI]指向的内容传递给AL\AX寄存器
指令执行一次SI自动加1(字节)或加2(字)
这个指令意义不大,可以用MOV代替(因为就算用REP也只是使AX的值等于最后一次的值)
这里对串操作指令的特性做总结
串操作指令特性及用法
指令 | 前缀指令 | 源串地址 | 目的串地址 | 字节变址 | 字变址 | 关注标志 |
MOVS | REP | DS:SI | ES:DI | +/-1 | +/-2 | |
CMPS | REPE | DS:SI | ES:DI | +/-1 | +/-2 | ZF |
SCAS | REPNE | AL/AX | ES:DI | +/-1 | +/-2 | ZF |
STOS | REP | AL/AX | ES:DI | +/-1 | +/-2 | |
LODS | REP | DS:SI | AL/AX | +/-1 | +/-2 |