==
文章目录
还有一篇写的不错的Blog可以作为参考:
https:;blog.csdn.net/Breeze_CAT/article/details/82465838
Chapter II
通用寄存器
前排提醒: 8086寄存器坑点(刷题整合)
- 8086中能够出现在[]中作为偏移地址的寄存器:
只有: BX, BP, SI, DI - 注意, BX & BP不允许出现在同一个[]中作为偏移地址
- 串处理指令中, 使用的默认寄存器:
SRC string: DS:SI
DST string: ES:DI - CF & OF位的神奇判定法:
CF是最高位产生了进位或借位时的标志
OF是次高位产生了进位或借位时的标志位 - 段寄存器的赋值
只有CS不可以直接赋值, 如: MOV CS, AX
而其他几个DS, ES, SS都可以直接MOV赋值 - SP栈顶指针的移动:
进栈的方向是LSB
即执行PUSH AX后, 栈顶指针SP-2, 而POP后, 栈顶指针SP+2
- 所有的通用寄存器都可以自由使用
但每个寄存器又有其单独的用途 - AX,BX,CX,DX 可以拆分为高8位和低8位使用, 分别是AH&AL, BH&BL…
而SP, BP, SI, DI四个只能以16位的方式使用 - SP, BP, SI, DI四个寄存器更经常的用途是在储存器寻址时, 提供偏移地址
AX(accumulator)累加器
最常用的寄存器, 主要用在算数运算中, 或在乘除指令中指定用来存放操作数, 但在与外部设备传输信息时必须使用此寄存器
BX(base)基址变址
注意, BX & BP不允许出现在同一个[]中作为偏移地址
常常与BP基址指针寄存器同时使用, BP作为基址寄存器, BX作为偏移地址寄存器
通常与CX组合存放32位操作数, BX存高位, CX存低位
BX:CX
CX(count)计数器
可作为隐含的计数器使用, 通常与BX组合存放32位操作数, BX存高位, CX存低位
DX(data)数据
通常与AX共同使用用作双字长运算, DX用于存放高位字, AX用于存放低位字
DX:AX
SP(stack pointer)堆栈指针
SP表示栈顶指针,指向栈顶地址.与SS相配合使用, 其中SS为栈段
BP(base pointer)基址指针
注意, BX & BP不允许出现在同一个[]中作为偏移地址
通常将BP会与SS堆栈段寄存器联用用来确定堆栈段中某一储存单元的地址, 段地址默认储存在SS中, BP可作为堆栈区中的一个基地址,以便访问堆栈中的信息, 如
MOV AX,[BP+SI+6]
MOV AX,[BP+DI+6]
DI(destination index)目的变址 & SI(source index)源变址
SI&DI具有自动增量和自动减量的功能, 很方便用于变址
在串处理指令中, 这两个寄存器作为隐含的源变址和目的变址寄存器, 其中SI与DS数据段寄存器联用, DI与ES附加段寄存器联用, 分别达到在数据段和附加段中寻址的目的
专用寄存器
IP(instruction pointer)指令指针
IP用来存放代码段中的偏移地址. 通常是由程序运行时操作系统自动操控的, 无需用户手动操作, 并且有时手动操作将导致程序出错
FIAGS(program status word, PSW)标志寄存器/程序状态寄存器
FLAGS用来存放条件码标识, 控制标识和系统标识, 与单片机中的标志寄存器类似, 此寄存器是按位使用的, 各个位的功能如下:
一,状态标志:
CF(carry flag):进位位
作为无符号数溢出标志
如果运算结果的最高位产生一个进位或错位,则CF置1,否则CF清零。
CF是最高位产生了进位或借位时的标志
OF是次高位产生了进位或借位时的标志位
PF(parity flag):奇偶位
如果运算结果低8位中“1”的个数为偶数时,则PF置1, 否则PF清0。
AF(auxiliary carry flag):辅助进位位
反应运算结果低四位产生进位或错位的情况
ZF(zero flag):零值位
如果运算结果为零则ZF置1。否则清零。
SF(sign flag):符号位
如果运算结果为负,即一个数的最高位为1,则SF置1,否则SF清零。
OF(overflow flag):溢出位
作为有符号数溢出标志
若运算结果超出补码表示范围(8位-128 ~ +127,16位-32768 ~+32767)。若溢出,OF置1,否则OF清0。
OF最有用的判定是:
首先是溢出的定义:
溢出是针对有符号数而言的
- 如果两个算子一个是有符号数, 一个是无符号数, 则运算结果必定不会溢出
- 如果两个算子同符号, 运算后符号相反, 则表示溢出了
即正数相加变成了负数, 就是溢出
特别说明:
在汇编中, CPU无法区分一个数是否是有符号数, 一般根据程序员的意愿来将一个储存单元内的二进制数解释为无符号数或有符号数
如乘法指令中, MUL用于无符号数, IMUL用于有符号数
二,控制标志
TF(trap flag):单步标志位
用于程序跟踪调试。当TF=1,CPU进入单步方式。
IF(interrupt flag):中断允许位
当IF=1时,CPU为开中断。 当IF=0时,CPU为关中断。
DF(direction flag):方向位
决定串操作指令执行时的指针寄存器的调整方向
2.4 存储器
2.4.1: 存储单元的地址和内容
数据在储存单元中的储存特点: 低位字节存入低地址,高位字节存入高地址, 即如果从储存单元中读取数据是从高位地址往低位地址读, 最后才能还原出数据
2.4.2: 实模式储存器寻址
关于实模式&保护模式(拓展知识)
实模式简单来说就是16位模式(其中80286有16位保护模式,但由于地址线的限制,实则都差不多),在16位模式下是使用20根地址线,可以寻址1MB内存,但又由于处理器内部的寄存器只有16位(以前的处理器),因此为了能访问整个1M内存,所以采用段地址+偏移地址的寻址方式(后头会相信说明)
保护模式是工作在32位模式下,具有32位的内部寄存器,以及32(or64)根地址线,最大访问空间为4GB内存,可以访问整个4G内存空间,但由于依然是在x86处理器的架构下,为了兼容,而依然采用段式寻址空间
保护模式可以看作是对实模式的扩充, 从16位处理器发展到32位处理器, 除了寻址范围的提升,32位处理器还发展出了很多的功能,与现代操作系统相辅相成,例如内存保护,分页系统,以及硬件支援的 虚拟内存,拥有这么多的功能,硬件自然要提供很多机制才能实现出来,这就叫做保护模式, 而实模式的保留则是为了兼容之前的16位程序, 所以现代的CPU在启动时,运行在实模式,但是启动过程中的某一个阶段切换成保护模式。
16位存储器寻址:
16位CPU中的寄存器只有16位, 即最多可以直接访问0~65535字节的存储空间, 而8086的总线有20根, 为了更好的利用剩余的4根总线, 开发者将CPU访问内存的方法设置为16位段地址+16位偏移地址, 为了使结果只有20位, 计算方法为将16位段地址向左以4个bit后与偏移地址按位相加形成物理地址
关于物理地址和逻辑地址:
物理地址是存储空间具体在地址总线上的位置
而逻辑地址是指令进行寻址时的EA(Effective Address)
二者还是有一定区别的!
段寄存器
上头说道了16位存储器寻址时的分段问题, 而后的处理器为了兼容同样也使用了段地址+偏移地址的方式访问储存器:
286以下有4个段寄存器: CS代码段 DS数据段 ES附加段 SS堆栈段
386开始多了两个段寄存器: FS GS (二者都没有专用名称)
在16位处理器中, 寄存器为16位, 所以偏移地址的表示最大为16位, 即64KB, 所以每个段最大可以占用64KB的储存空间, 但通常情况下, 各段在存储器中的大小分配是由操作系统完成的(即无需每个段都占用64KB的内存, 可以实现要多少分配多少, 所以最终可能每个段的大小不同, <64KB, 这种现象叫做段的重叠), 但是如果需要, 也可以手动分配空间
程序运行期间, 所有的信息都分类保存在各个段中, 如程序的代码保存在代码段CS, 数据保存在数据段DS等, 但是如果程序的某一段需要的空间>64KB, 则需要动态的修改该段的内容, 以保证所获得的信息的正确性
但是>64KB的情况现在基本不会用到, 做短期利益考虑, 不做拓展
段寄存器和存放偏移地址的寄存器之间的固定搭配(16位相应的删掉前头的E):这个表很重要
注意: 在8086寄存器组中,只有bx,bp,si,di这四个寄存器可以用在[……]中表示偏移地址
段寄存器详解:
-
CS & IP
段寄存器CS指向存放程序的内存段,IP是用来存放下条待执行的指令在该段的偏移量,把它们合在一起可在该内存段内取到下次要执行的指令。CS不可以直接赋值, 如: MOV CS, AX
这个段寄存器的值要由系统设置
其他的DS、ES、SS都可以用MOV赋值,但不能用立即数 -
SS & SP & BP
段寄存器SS指向用于堆栈的内存段,SP是用来指向该堆栈的栈顶,把它们合在一起可访问栈顶单元。
指针寄存器BP,其缺省的段寄存器也是SS,用BP可访问整个堆栈,不仅仅是只访问栈顶因为SP固定指向栈顶, 所以需要访问堆栈中的其他元素时, 需要使用BP
-
DS & ES
段寄存器DS指向数据段,ES指向附加段,在存取操作数时,二者之一和一个偏移量合并就可得到存储单元的物理地址。
通常,缺省的数据段寄存器是DS,只有一个例外,即在进行串操作时,其目的地址的段寄存器规定为ES。
80X86的数据储存方式:
80X86的CPU绝大多数是用小端模式进行存储
而ARM绝大多数都是大端存储
小端模式:
将数据的高位放在低字节, 低位放在高字节
取数据时, 向低字节方向进行解析
大端模式:
和小端模式相反
将数据的高位放在高字节, 低位放在低字节
去数据时, 向高字节方向进行解析
所以有如下代码:|
DATA SEGMENT
TABLE DW 10H,20H,30H,40H,50H,60H,70H,80H
ENTRY DW 5
DATA ENDS
;*******************
CODE SEGMENT
ASSUME CS: CODE, DS: DATA
START:
MOV AX, DATA
MOV DS, AX
;*******************
MOV BX, OFFSET TABLE
MOV CX, 10
RE:
MOV AX, [BX]
ADD BX, 1
LOOP RE
;*******************
MOV AX, 4C00H
INT 21H
CODE ENDS
;*******************
END START
每次MOV AX, [BX]后, AX的值为:
0010, 2000, 0020, 3000, 0030, 4000 …
Chapter III 指令系统和寻址方式
指令的一般格式(这一整行为一条指令)
操作码 操作数, 操作数, ... , 操作数
通常操作数字段为一个或两个, 较少的为3个, 通常称为一地址指令, 二地址指令, 三地址指令
80x86の寻址
寻址方式用来确定操作数地址, 从而找到操作数
每个指令通常都有其对应的寻址方式, 而掌握每一种寻址方式是使用指令的前提, 否则后头看指令时会一脸懵逼
但是后头的指令基本上没有说特别使用哪一种, 通常是除了立即数以外都能用, 所以就TM记着怎么用就好, 不用记相应的名字
除了立即寻址方式和寄存器寻址方式以外,其他各种寻址方式的操作数都在除代码段以外的储存区中, 即需要通过段基地址和偏移地址相加而取的
在80x86中, 通常将操作数的偏移地址称为有效地址, 有效地址可以用以下四种成分组成:
- 位移量(displacement)
位移量是存放在指令中的一个8位, 16位或32位的数(类似立即数的存储方式)
但他不是一个立即数,而是一个地址 - 基址(base)
基址是存放在基址寄存器(BX)中的内容
其指有效地址的基址部分。通常要来指向数据段中的数组和字符串的首地址 - 变址(index, 即下标)
变址是存放在变址寄存器(SI&DI)中的内容
它通常用来访问数组中的某个元素和字符串中的某个字符。 - 比例因子(scale factor, 386机型后有)
比例因子的值通常就是1,2,4,8, 且是乘上去的
比例因子用于在寻址过程中将变址寄存器的内容乘以比例因子来取得变址值, 所以在访问元素长度为2,4,8字节的数组特别有用
有效地址的计算公式:
EA(effective address) = 基址 + (变址 * 比例因子) + 位移量
其中除比例因子以外, 其他3个都可以是负数, 以保证指针移动的灵活性
至于为何要整上4种, 而不是仅仅基址+位移量两种, 是因为引入变址&比例因子使得有效地址(偏移量)的计算更加灵活
而在32位寄存器中, 既可以采用32位寻址,也可以用16位寻址
关于偏移地址:
EA的计算公式中, 除了基址, 后头的三个组成偏移地址
而在指令的表示中, []中括号内的通常是偏移地址
关于四种成分具体的区别:
其中, 一个EA有效地址中的位移量是直接给出的数字, 比例因子为特定的数字, 而基址和变址分别储存在相应的寄存器中, 具体如以下表格:
上表说明了各种访问类型相对应的段的默认选择(即程序默认使用的寄存器), 而作为底层语言, 程序员可以强制指定段寄存器使用, 称为段跨越前缀, 如:
;首先解释一下段前缀:
MOV AX,DS:[BX] ;其中DS:就是段地址, 类比于IP与端口的关系, 如: 192.168.1.1:8080
mov ax,[0000]
;程序默认使用的是ds:0000
;而使用了跨越段前缀就使用你指定的段寄存器
mov ax,es:[0000]
;程序就会使用es:0000
但是有三种情况不允许使用段跨越前缀:
- 串处理指令的目的串必须使用ES段
- PUSH指令的目的和POP指令的源必须使用SS段
- 指令必须存放在CS段中
MOV指令
MOV为传送指令:
MOV d,s ;d为目的操作数(destination), s为源操作数(source)
立即寻址方式
在程序的编码中, 操作数直接存放在指令中, 紧跟在操作码之后, 作为指令的一部分存放在代码段里(相当于预处理赋值)
立即寻址方式常用于给寄存器赋初值, 并且只能用于原操作数字段, 不能用于目的操作数字段
MOV AL, 5 ;执行后(AL)=05H
MOV AX, 3064H ;执行后(AX)+3064H
MOC EAX, 12345678H ;执行后(EAX)=12345678H
寄存器寻址方式
此时操作数存储在寄存器中, 指令指定寄存器号. 此种方法由于操作数储已经储存在寄存器中, 不需要访问储存器, 所以运算速度较高(相当于寄存器变量)
MOV AX,BX ;执行前: (AX)=3064H, (BX)=1234H, 执行后: (AX)=1234H, BX保持不变
MOV ECX, EDX ;大致同上
直接寻址方式
操作数的有效地址就是位移量, 即在有效地址中, 仅有位移量一个变量, 其他三个都空, 如:
与C++类似, 地址同样可以用符号代替(类比于变量), 称为符号地址:
MOV AX, VALUE 此时的VALUE即为存放在操作数单元的符号地址
寄存器的间接寻址方式
操作数的有效地址只有基址或变址, 即有效地址就在基址寄存器或变址寄存器中
这个和寄存器寻址方式有点区别, 前者是直接用寄存器, 而这个使用的是寄存器里的数
MOV AX, [BX] ;当(DS)=2000H, (BX)=1000H时, 物理地址为20000H+1000=21000H
寄存器相对寻址方式(直接变址寻址方式)
有效地址=基址寄存器或变址寄存器+位移量
如:
;count为16位位移量
MOV AX, COUNT[SI] ;这种不用+
MOV AX, [COUNT+SI]
;当(DS)= 3000H, (SI)= 2000H, COUNT= 3000H
则物理地址= 30000+2000+3000 =35000H
直接变址寻址方式也可以使用段跨越前缀:
MOV DL, ES:STRING[SI]
基址变址寻址方式(based indexed addressing)
有效地址=基址寄存器+变址寄存器
所以有效地址由两种成分组成。它所允许使用的寄存器及其对应的默认段见表3.1和表3.2.
MOV AX, [BX][DI]
MOV AX, [BX+DI]
;当(DS)=2100H, (BX)=0158H, (DI)=10A5H
则EA= 0158+10A5= 11FDH,物理地址= 21000+11PD=221FDH
;32位状态
EDX MOV, [EBEX] [ESIT]
这种寻址方式通常用于数组或表格处理,首地址可存放在基址寄存器中,而用变址寄存器来访问数组中的各个元素。由于两个寄存器都可以修改,所以它比直接变址方式更加灵活。
此种寻址方式使用段跨越前缀时的格式为:
MOV AX, ES: [BX] [SI]
注意: 在汇编中, 4,5,6种寻址方式很常用
相对基址变址寻址方式(relative based indexed addressing)
有效地址=基址寄存器+变址寄存器+位移量
所以有效地址由三种成分组成,如
MOV AX, MASK[BX][SI]
MOV AX, MASK[BX+SI]
MOV AX, [MASK+BX+ SI]
;如(DS)= 3000H, (BX)= 2000H. (SI)= 1000H,MASK= 0250H,
则物理地址=16d × (DS)+(BX)+(SD)+MASK
=30000 +2000-1000-020 = 33250H
;32位寻址方式
MOV EAX。ARRAY[EBX][ECX]
这种寻址方式通常用于对二维数组的寻址,例如,存储器中存放着由多个记录的文件,则位移量可指向文件之首,基址寄存器指向某个记录,变址寄存器则指向该记录中的一个元素。这种寻址方式也为堆栈处理提供了方便,一般(BP)可指向栈顶,从栈顶到数组的首址可用位移量表示,变址寄存器可用来访问数组中的某个元素。
这种寻址方式及以下几种寻址方式使用段跨越前缀的方式与之前类似
比例变址寻址方式
有效地址=变址寄存器*比例因子+位移量
如:
MOV EAX,COUNT[ESI*4]
基址比例变址寻址方式
有效地址=变址寄存器*比例因子+基址寄存器
如:
MOV ECX, [EAX][EDX*8]
相对基址比例变址寻址方式
有效地址=变址寄存器*比例因子+基址寄存器,+位移量
3.1.2在讲转移指令时在拓展,现在先放着
80x86指令系统
x86系统の主要指令可以分为以下6种:
- 数据传送指令
- 串处理指令
- 算数指令
- 控制转移指令
- 逻辑指令
- 处理机控制指令
数据传送指令
数据传送指令用于把数据, 地址或立即数传送到寄存器或储存单元中, 即相当于实现赋值功能
通用数据传送指令
MOV (move) ;传送
MOVSX(move with sign-extend) ;带符号扩展传送, 386后可用
MOVZX(move with zero-extend)1 ;带零扩展传送, 386后可用
PUSH(push onto the stack) ;进栈
POP(pop from the stack) ;出栈
PUSHA/PUSHAD(push all registers);所有寄存器进栈
POPA/POPAD( pop all registers) ;所有寄存器出栈
XCHG(exchange) ;交换
-
MOV传送指令
格式为: MOV DST, SRC
执行操作: (DST) <–(SRC)
其中DST为目的操作数, SRC 为源操作数MOV使用要点:
-
MOV指令不允许在储存器和储存器之间传送
(即两个操作数不允许都是储存器)其用于在CPU内部或CPU与储存器之间传送字,字节,双字节
-
MOV对于立即数的使用:
只有寄存器(不包括段寄存器)和地址可以使用立即数
其他所有的都不可以使用立即数
并且注意这个地址是符号地址或是段寄存器的存在相当于储存器
如:
MOV ES, 200H ;报错! ;段寄存器的存在相当于储存器
-
MOV的两个操作是的长度必须是相同的, 否则报错
当操作数长度不同时, 使用MOVSX & MOVZX
最好的方式就是任何MOV指令传送都通过一个通用寄存器(如AX)做中转, 这样最不容易出错
MOV补充:
MOV [BX],10H ;MOV支持这种操作
实际上相当于DST为储存器寻址方式, SRC为立即数寻址方式
-
-
MOVSX & MOVZX 带符号/带零拓展的传送指令
;reg为寄存器, mem为储存器
MOVSX REG1, REG2
MOVSX REG, MEM
目的操作数必须是寄存器
操作: 将源操作数符号扩展传送到目的寄存器中
格式与MOV相同, 但是要注意的是, 这两个指令的源操作数的长度一定要小于目的操作数的长度(相当于拓展)
-
PUSH & POP 进栈&出栈
格式:PUSH SRC POP DST
PUSH支持所有的寻址方式
(寄存器reg, 储存器mem, 立即数data, 段寄存器segreg)POP支持处立即数data以外的任何寻址方式
二者的执行效果:
相当于对堆栈指针SP移位, 16位操作±2字节, 32位操作±4字节, 而后将SRC的数据进栈弹出SRC或将栈顶元素存入DST
注意: 进栈的方向是LSB, 即执行PUSH AX后, 栈顶指针SP-2, 而POP后, 栈顶指针SP+2
-
PUSHA/PUSHAD & POPA/POPAD 所有寄存器进栈/出栈
其中AD版本是32位的操作, A版本是16位的操作此指令执行后将所有的通用寄存器进栈/出栈, 进栈顺序为AX CX DX BX SP BP SI DI (注意都是指令执行前的值), 出栈顺序相反, 执行后SP仍然指向栈顶
-
XCHG 交换指令
此指令可以在寄存器之间或寄存器与储存器之间交换信息
(高级语言中需要使用3条语句)格式:
XCHG OPR1 OPR2
使用要点:
-
两个操作数必须有一个在寄存器中
-
不允许使用段寄存器
-
累加器专用传送指令
此Part只限于EAX, AX, AL传送信息
-
IN输入指令 & OUT输出指令
IN & OUT用于所有的I/O端口与CPU之间的通信, 但是这个端口是啥并不知道
;长格式 IN AC(累加器), PORT(单, 双, 四字节) OUT PORT, AC ;短格式 IN AC, DX OUT DX, AC
长格式和短格式的区别:
PORT只能是单字节数据(即00H~FFH), 直接指向外部端口(但只能是前256个), 当需要访问更多的端口时, 只能使用短格式, 必须将端口号先保存到DX中(此时支持16位和32位), 在由DX输出 -
XLAT换码指令
通常用于编码的快速转换
首先在使用XLAT指令前, 通常已经在Data Segment数据段建立的一个转换表(如数码管的段显示表, 其实就TM是一个数组), 将此表的首地址加载给BX, 并在AX中装入偏移量, 用XLAT后, CPU会将DS+BX+AX指向的值(注意是指向的值)装入AX中XLAT OPR ;OPR为操作数, 通常是符号化的首地址, 这种写法提高程序的可读性 XLAT ;直接就这样, 简化版 ;执行效果: ;16位: 将BX+AL的值赋给AL, 此时必须零扩展到16位 ;32位: 将EBX+AL的值赋给AL, 此时必须零扩展到32位
如:
;执行前: (BX)=0040H, (AL)=0FH, (DS)=F000H, XLAT ;执行后, AX的值为F000H+0040H+0FH地址处储存的值
地址传送指令
注意这里和之前的MOV指令不同的是, MOV传送的是源操作数中的值, 而地址传送指令传送的是源操作数的地址
这里只介绍LEA & LDS, 而后的几种与LDS相似, 只是指向的寄存器不同而已
-
LEA(load effective address)有效地址送寄存器指令
格式:LEA REG, SRC ;将SRC的有效地址存入REG寄存器中
REG可以是16位或32位寄存器, 但是不可以使用段寄存器
SRC可以是除立即寻址方式和寄存器寻址方式以外的任何一种储存器寻址方式(即前头的8 种)
如:;执行前: (BX)=0400H, (SI)=003CH LEA BX, [BX+SI+0F62H] ;执行后: (BX)=0400+003C+0F62=139EH, 这里得到的是有效地址, 而不是地址指向的值
注意: 当地址长度与REG寄存器长度不同时(如16位转32位), 会默认对有效地址进行截断或零扩展
-
LDS(load DS with pointer)指针送寄存器和段寄存器指令
格式:LDS REG, SRC
SRC只能使用储存器寻址方式
当REG为16位寄存器时, 将SRC指向的储存单元中存放的偏移地址装入REG中, 并将SRC+2中的16位数装入指定的段寄存器中(LDS是DS段寄存器, 而其他几个指令对应其他的段寄存器)
当REG为32位寄存器时, REG被装入的是SRC指向储存单元中的32位偏移地址, 而后将指定段寄存器装入SRC+4中储存的16位数
标志寄存器传送指令
这4个指令使用频率不是很高, 主要功能是将FLAGS标志寄存器中的信息进行各种传送
LAHF ;(load AH with flags) 将FLAGS传送至AH
SAHF ;(store AH into flags) 将AH传送至FlAGS
PUSHF/PUSHFD ;(push the flags or extra-flags) 将FLAGS进栈
POPF/POPFD ;(pop the flags or extra-flags) 将栈顶的1Byte传送至FLAGS
类型转换指令
此类型指令均不影响标志位
CBW ;convert byte to word 字节转换为字, 将AL符号扩展到AX
;如果AL的最高有效位为0, 则AH=0, 否则AH=0FFH
CWD/CWDE ;convert word th double word 字转换为字节, 将AX符号扩展到DX,
;形成DX:AX双字, 如果AL的最高有效位为0, 则DX=0, 否则DX=0FFFFH
CDQ ;convert double to quad 双字转换为4字, 将EAX符号扩展到EDX,
;形成EDX:EAX中的4字
BSWAP ;byte swap 字节交换
;使指令指定的32位寄存器的字节次序变反, 即1,4互换, 2,3互换
算数指令
此类指令与FLAGS标志位关联紧密
加法指令
ADD DST, SRC (add) ;加法
ADC DST, SRC (add with carry) ;带进位加法
INC OPR (increment) ;加1, 相当于++
XADD DST, SRC (exchange and add) ;交换并相加, 486以后可用
ADD:
普通加法指令, 对CF进位标志有影响
指令对标志位的影响:
CF=1 最高有效位向高位有进位
CF=0 最高有效位向高位无进位
OF=1 两个同符号数相加(正数+正数 或 负数+负数),结果符号与其相反。
OF=0 两个不同符号数相加,或同符号数相加,结果符号与其相同。
ADC:
进位加法指令, 通常与ADD联用, 用于计算双精度数. 其中先将低位用ADD, 再将高位用ADC
计算时将CF也加入计算, 如果相加产生进位, 会再使用一个寄存器存放高位字, DX:AX, BX:CX
如:
;初始: DX=0002H, AX=0F365H, BX=0005H, CX=0E024H
ADD AX, CX
;执行后: AX=0D389H, SF=1, ZF=0, CF=1, OF=0
ADC DX, BX
;执行后: DX=0008H, SF=0, ZF=0, CF=0, OF=0
指令对标志位的影响:
CF=1 最高有效位向高位有进位
CF=0 最低有效位相高位无进位
OF=1 两个同符号数相加,结果符号与其相反,
OF=0 两个同符号数相加,或同符号相加,结果符号与其相同
INC:
++指令
指令对标志位的影响:
对CF无影响
OF=1 两个同符号数相加,结果符号与其相反,
OF=0 两个同符号数相加,或同符号相加,结果符号与其相同。
XADD:
XADD DST, SRC
交换并相加, 使用一个temp暂存中间结果(执行过程相当于高级语言的3条语句)
具体执行操作:
TEMP<–(SRC)+(DST)
(SRC)<–(DST)
(DST)<–TEMP
减法指令
SUB DST, SRC (subtract) ;减法
SBB DST, SRC (subtract with borrow) ;带借位减法
DEC OPR (decrement) ;自减一
NEG OPR (negate) ;求补
CMP OPR1, OPR2 (compare) ;比较
CMPXCHG DST, SRC (compare and exchange) ;比较并交换, 486后可用
CMPXCHG8B DST (compare and exchange 8 byte) ;比较并交换8字节, 奔腾后可用
CPU中的减法都是将SRC取反+1后加上DST, 与加法就差一个求补的过程
SUB:
普通减法指令, 与ADD相似, 当DST<SRC时会产生借位, 时CF=1
指令对标志位的影响:
CF=1 二进制减法运算中最高有效位向高位有借位(被减数小于减数,不够减的情况)
CF=0 二进制减法运算中最高有效为向高位无借位(被减数〉=减数,够减的情况)
OF=1 两数符号相反(正数-负数,或负数-正数),而结果符号与减数相同。
OF=0 同符号数相减时,或不同符号数相减,其结果符号与减数不同。
SBB:
带借位的减法指令, 与ADC类似, 与SUB联用, 用于计算双精度数, 低位用SUB, 高位用SBB
指令对标志位的影响:
CF=1 二进制减法运算中最高有效位向高位有借位(被减数小于减数,不够减的情况)
CF=0 二进制减法运算中最高有效为向高位无借位(被减数〉=减数,够减的情况)
OF=1 两数符号相反(正数-负数,或负数-正数),而结果符号与减数相同。
OF=0 同符号数相减时,或不同符号数相减,其结果符号与减数不同。
DEC:
自减一指令, 即–
指令对标志位的影响:
对CF无影响
OF=1 两数符号相反(正数-负数,或负数-正数),而结果符号与减数相同。
OF=0 同符号数相减时,或不同符号数相减,其结果符号与减数不同。
NEG:
求补指令, 将OPR取反后+1
指令对标志位的影响:
CF=1 不为0的操作数求补时
CF=0 为0的操作数求补时
OF=1 操作数为-128(字节运算)或操作数为-32768(字运算)
OF=0 当求补运算的操作数不为-128(字节)或-32768(字)时
CMP:
比较指令, 与SUB指令类似, 将OPR1-OPR2, 但是不保存结果, 只是根据结果操作CF位
指令对标志位的影响:
CF=1 二进制减法运算中最高有效位向高位有借位(被减数小于减数,不够减的情况)
CF=0 二进制减法运算中最高有效为向高位无借位(被减数〉=减数,够减的情况)
OF=1 两数符号相反(正数-负数,或负数-正数),而结果符号与减数相同。
OF=0 同符号数相减时,或不同符号数相减,其结果符号与减数不同。
CMPXCHG & CMPXCHG8B
比较并交换指令, SRC只能用8位, 16位, 32为寄存器, DST可用寄存器或任意寻址的方式
<这个比较作用比较迷惑, 先放着>
乘法指令
MUL SRC (unsigned multiple) ;无符号乘法
IMUL SRC (signed multiple) ;有符号乘法
;SRC可以使用处立即数外的任何一种寻址方式
MUL:
无符号乘法, 将SRC与ac相乘, 使用AL, AX或EAX, 并将结果扩容一倍分别保存在AX, DX:AX, EDX:EAX中, 具体使用的ac有SRC的长度决定
IMUL:
有符号乘法, 具体操作与MUL相同
其中使用MUL会将SRC解释为无符号数, 而IMUL会将SRC解释为有符号数, 所以混用这两个会出大问题
乘法指令对于符号位的影响:
乘法指令对于处CF&OF外的flag无定义(即使用乘法指令后其他位不确定)
- 对于MUL: 当乘积的高位部分全为0(即乘积没有超出原大小), CF=OF=0, 否则CF=OF=1
- 对于IMUL: 如果乘积的高位是低位的符号拓展, 则CF=OF=0, 否则CF=OF=1
注意: CF和OF一起变
IMUL的双, 三操作数:
IMUL允许使用双, 三操作数, 因为这种指令不会像无操作数的版本对结果进行拓展, 所以必须确保结果不会溢出, 而单操作数保证不会溢出, 具体使用:
IMUL REG, SRC ;执行效果: REG=REG*SRC , 其中REG不扩展
IMUL REG, SRC, IMM ;执行效果: REG=SRC*IMM , 其中REG不扩展
除法指令
DIV SRC (unsigned divide)
IDIV SRC (signed divide)
;SRC可以采用除立即数以外的任何一种寻址方式
注意: 除法指令的SRC不可以使用立即数
除法指令与乘法指令相似, 同样也是将ac除以SRC, 但是会窄化ac(乘法指令是扩展ac), 其中SRC最少为16位, 所以经常需要对SRC进行扩展
对于AC的大小选择根据SRC而定, AC的大小为SRC的两倍, 即当SRC为8bit时, 使用AX, 并将结果保存在AL中
除法指令需要注意的是余数的保存:
- 当SRC为8bit时, 商保存在AL, 余数保存在AH
- 当SRC为16bit时, 商保存在AX, 余数保存在DX
- 当SRC为32bit时, 商保存在EAX, 余数保存在EDX
除法指令对所有的FLAGS都无定义(即使用后所有位都不确定)
逻辑指令
这部分的功能与高级语言相同
逻辑运算指令
AND DST, SRC ;逻辑与
OR DST, SRC ;逻辑或
NOT OPR ;逻辑非
XOR DST, SRC ;异或
TEST OPR1, OPR2 ;测试
这几个指令(除TEST)都是将结果保存在第一个操作数中
并且, 指令的操作数必须有一个在寄存器中, 而其他的可以使用任意一种寻址方式
AND:
AND <寄存器/存储单元>,<立即数/寄存器/存储单元>
指令对标志位的影响:
指令执行后 CF 和 OF 置零,AF无定义。
PF=1 结果操作数中1的个数为偶数时置1
PF=0 结果操作数中1的个数为奇数时置0
OR:
OR <寄存器/存储单元>,<立即数/寄存器/存储单元>
指令对标志位的影响:
令执行后 CF 和 OF 置零,AF无定义。
PF=1 结果操作数中1的个数为偶数时置1
PF=0 结果操作数中1的个数为奇数时置0
NOT:
NOT <寄存器/储存单元>
对OPR取非, 并将结果存在OPR中
不允许使用立即数(就一个操作数TM怎么用立即数)
对FLAGS标志寄存器无影响
XOR:
XOR <寄存器/存储单元>,<立即数/寄存器/存储单元>
指令对标志位的影响:
令执行后CF 和 OF 置零,AF无定义。
PF=1 结果操作数中1的个数为偶数时置1
PF=0 结果操作数中1的个数为奇数时置0
TEST:
对两个操作数进行AND操作, 但是不保留结果, 仅仅是操作FLAGS的标志位, 而后可通过检验FLAGS标志位查看结果
指令对标志位的影响:
令执行后 CF 和 OF 置零,AF无定义。
PF=1 结果操作数中1的个数为偶数时置1
PF=0 结果操作数中1的个数为奇数时置0
位测试并修改指令
没啥用, 先放着
位扫描指令
没啥用, 先放着
移位指令
SHL OPR, CNT (shift logical left) ;逻辑左移
SHR OPR, CNT (shift logical right) ;逻辑右移
SAL OPR, CNT (Shift arithmetic left) ;算术左移
SAR OPR, CNT (shift arithmetic right) ;算术右移
ROL OPR, CNT (rotate left) ;循环左移
ROR OPR, CNT (rotate right) ;循环右移
RCL OPR, CNT (rotate left through carry ;带进位循环左移
RCR OPR, CNT (rotate right through carry ;带进位循环右移
SHLD DST, REG, CNT (shift left double) ;双精度左移
SHRD DST, REG, CNT (shift right double) ;双精度右移
其中:
- CNT为count, 表示移位次数, 可以是8bit立即数(0~255), 或者是存在CL中的数(只能是这两个的一种)
- OPR可使用处立即数以外的任何寻址方式(立即数无法作为有效位置保存结果)
具体操作:
其中:
- SAR算数右移为执行逻辑右移后, 在最高位填入原先最高位的值
(相当于不改变最高位) - ROL循环左移&ROR右移仅仅将被移出位存入CF并补充到移位序列的末尾(即最高位复制到进位标志位和最低位)
RCL & RCR而带进位循环左移&右移为将CF位也加入移位的序列(CF相当于中间缓冲)
对FLAGS的操作:
- 当CNT为1时, 对OF位有影响(最高位改变时OF置1, 否则置0), 否则OF位无定义
- 循环移位指令不影响其他标志位
- 移位指令影响SF & ZF & PF 位(根据移位后寄存器内的结果设置), 而AF位无定义
串处理指令
串指令用于处理存储在存储器里的数据串, 具体可理解为字符串, 字符数组这一类的东西, 类似于C++中的字符串处理函数等等
前排提醒:
串处理指令使用的默认寄存器:
SRC string: DS:SI
DST string: ES:DI
AC: AL 或 AX
所以使用串处理指令之前需要保证DS & ES都储存了对应字符串的段地址
所以串处理指令的操作数仅仅向编译器提供了类型, 以便编译器选用合适的指令和每次操作后指针的偏移量
方向标志在串处理指令中的作用:
主要是方向标志位DF的作用:
- DF=1:每次操作后使SI和DI减小
- DF=0:每次操作后使SI和DI增大
变更方向标志的两条指令:
CLD:该指令使DF=0 clear DF
STD:该指令使DF=1 set DF
通常需要在串指令之前做的操作:
- 把存放在数据段中的源串首地址(如反向传送则应是末地址)放入源变址寄存器中;
- 把将要存放数据串的附加段中的目的串首地址(或反向传送时的末地址)放入目的变址寄存器中;
- 把数据串长度放入计数寄存器;
- 建立方向标志。
- 而后再开始串指令操作
MOVS 串传送
CMPS 串比较
SCAL 串扫描
LODS 从串取
STOS 存入串
INS 串输入
OUTS 串输出
;配合使用的指令前缀:
REP string_primitive 重复
REPE/REPZ 相等/为零则重复
REPNE/REPNZ 不相等/不为零则重复
串处理指令基本都不改变FLAGS标志寄存器
REP:
-
REP是串处理的核心, 建立并简化了循环, 如果没有REP等循环操作, 串处理指令仅仅执行一次
-
串处理操作中的循环操作, 与手写循环相比更为简单, 重复执行相应的串操作直到计数寄存器为0为止
-
可以使用的指令为:
上头除CMPS和SCAS, 因为这两个需要利用其判比条件
(这俩是REPE/REPZ, REPNE/REPNZ专用) -
使用CX(16bit)或ECX(32bit)作为计数器
具体的选用根据地址长度(16bit/32bit)而定(自动判断)
具体执行的操作:
MOVS:串传送指令
用于将已存在的串传送到指定位置, 类似于MOV, 不过可以联用REP, 更加方便
MOVS DST, SRC
MOVSB ;byte
MOVSW ;word
MOVSD ;double
后三种格式明确指明了操作的字节长度, 但第一种必须在操作数中向编译器指明
不同的字节长度对应了SRC与DST在每次操作后的偏移量: 字节±1, 字±2, 以此类推
其中, 加减操作的决定有FLAGS的DF方向标志位决定(具体操作看上头)
MOVS的寻址方式是编译器默认的(使用两个隐含的寻址方式, 具体看上头), 两个操作数只是提供给编译器做类型检查用
不改变FLAGS标志寄存器
STOS:存入串指令
将一个值连续的存储到一段存储器中, 即相当于在一个数组中存入相同的值
常用STOS指令在初始化某一缓冲区
;根据DST自动判定类型以选用合适的长度
STOS DST
;指定字节长度
STOSB
STOSW
STOSD
该指令把AL、AX或EAX的内容存入由目的变址寄存器指向的附加段的某单元中,并根据DF的值及数据类型修改目的变址寄存器的内容(自动偏移)
不改变FLAGS标志寄存器
LODS从串取指令
从指定数据串中连续读取内容, 通常不与REP联用, 即很少循环这个指令
LODS SRC
LODSB
LODSW
LODSD
该指令把由SRC指向的内容送到AL、AX或EAX中,并根据方向标志和数据类型修改源变址寄存器的内容(自动偏移)
指令允许使用段跨越前缀来指定非数据
不改变FLAGS标志寄存器
与IO相关的串处理指令:
;串输入指令
INS DST, DX
INSB
INSW
INSD
;串输出指令
OUTS DX, SRC
OUTSB
OUTSW
OUTSD
其中端口号储存在DX中, 同样也是根据方向标志和数据类型修改源变址寄存器的内容(自动偏移)
REPE/REPZ & REPNE/REPNZ:
当相等/为零时重复串操作
当不相等/不为零时重复串操作
配合CMPS和SCAS指令使用, 不同的指令对应不同的跳出条件
具体执行的操作:
即: 当比较结束时, 只有两种情况:
- CX=0, ZF=1
- CX!=0, ZF=0 表明字符串
CMPS:串比较指令
CMPS SRC, DST
CMPSB
CMPSW
CMPSD
指令把由SRC指向的数据段中的一个字节、字或双字与由DST所指向的附加段中的一个字节、字或双字相减,但不保存结果,只根据结果设置FLAGS:
指令对条件码的影响:
CF=1 二进制减法运算中最高有效位向高位有借位(被减数小于减数,不够减的情况)
CF=0 二进制减法运算中最高有效为向高位无借位(被减数〉=减数,够减的情况)
OF=1 两数符号相反(正数-负数,或负数-正数),而结果符号与减数相同。
OF=0 同符号数相减时,或不同符号数相减,其结果符号与减数不同
SCAS串扫描指令
用的不多, 先放着
与转移地址有关的寻址方式
本部分主要是使用JMP等转移指令指令修改IP寄存器, 指向指定语句的地址, 使程序跳转到指定的语句执行
前排提醒: 几种寻址方式提供的都只有一个OPR, 真正区别的是前头的操作符
段内直接寻址
这玩意用的多 …程序简单, 无需使用其他的寻址方式
特征: 跳转的指针只有一个位移量, EA=IP+位移量
操作符NEAR PTR, SHORT
执行后: IP=IP+位移量
;格式:
JMP 位移量
;例如:
JMP NEAR PTR PTOGIA
JMP SHORT QUEST
;其中PROGIA和QUEST都是转向的符号地址
段内间接寻址
特征: EA=REG或MEM (某一寄存器或储存单元的内容)
操作符WORD PTR
JMP BX
JMP WORD PTR[BP+TABLE]
段间直接寻址
特征: 提供了一个转向段地址, 和一个偏移地址
操作符FAR PTR
执行后: CS= 偏移地址的段地址(转向段地址), IP=偏移地址
JMP FAR PTR NEXTROUTINT
段间间接寻址
特征: 用储存器中的两个相继字(组成Double Word), 来取代IP和CS的内容
操作符DWORD PTR
其中: IP=前两个字节, CS=后两个字节
其中, 储存单元的寻址可以使用处立即数和直接寄存器之外的所有方式
JMP DWORD PTR[INTERS+BX]
JMP DWORD PTR[EDI]
控制转移指令
各种转移指令, 结合指令寻址方式, 在各个语句之间进行跳转
无条件跳转指令
相当于C++中的goto, 100%跳转
通用格式:
JMP (跳转指令)跳转地址OPR
其中OPR通常直接使用符号地址(即相当于标识符)
而转移方式的名称都与对应的指令寻址方式相关, 直接可以想到
段内直接短转移
采用段内直接寻址方式
JMP SHORT OPR
执行的操作:(IP)<–(IP)+8位位移量
386及其后继机型则为:(EIP)<–(EIP)+8位位移量
如操作数长度为16位,则还需(EIP)<–(EIP)AND 0000FFFFH
段内直接近转移
采用段内直接寻址方式
这里与短转移的不同主要是: 操作数的地址为16位以上
JMP NEAR PTR OPR
执行的操作:(IP)<–(IP)+16位位移量
386及其后继机型为:(EIP)<–(EIP)+32位位移量
如操作数长度为16位,则(EIP)<–(EIP)AND 0000FFFFH
段内间接近转移
采用段内间接寻址方式
之前使用的都是相当于立即数的符号地址, 而这里使用的是正经的寻址方式
其将OPR的地址传给IP
JMP FAR PTR OPR
执行的操作:(IP)<–(EA)
386及其后继机型则为:(EIP)<–(EA)
如操作数长度为16位,则(EIP)<–(EIP)AND 0000FFFFH
段间直接远转移
采用段间直接寻址方式
这里使用直接寻址的方式, OPR通常直接使用符号地址
JMP DWORD PTR OPR
(IP)<–OPR的段内偏移地址
(CS)<–OPR所在段的段地址
386后序机型:(其实就是多个拓展区)
(EIP)<–OPR的段内偏移地址
(CS)<–OPR所在段的段地址
如操作数长度为16位,则(EIP)<–(EIP)AND 0000FFFFH
段间间接远转移
这里与上头的类似, 使用处立即数和寄存器之外的任何寻址方式
JMP DWORD PTR OPR
执行的操作:
(IP)<–(EA)
(CS)<–(EA+2)
386及其后继机型则为:
(EIP)<–(EA)
(CS)<–(EA+4)
如果操作数长度为16位,则(EIP)<–(EIP)AND O00FFFFH
条件转移指令
注意与if相反, 条件转移指令满足条件跳转
下头东西多, 所有冗余的记其中一个即可(达到应用的地步), 其他的保证看得懂就好
1、根据单个条件标志的设置情况转移
这组包括10种指令。它们一般适用于测试某一次运算的结果并根据其不同特征产生程序分支作不同处理的情况
快速浏览大表:
具体判定条件:
-
JZ(或JE)(Gump if zcro,or cqual)结果为零(或相等)则转移。
格式:JZ(或JE)OPR
测试条件:ZF=1 -
JNZ(或 JNE)(Gump if not zero.or not equal)结果不为零(或不相等)则转移。
格式:JNZ(或JNE)OPR
测试条件:ZF=0 -
JS(jump if sign)结果为负则转移。
格式:JS OPR
测试条件:SF=1 -
JNS(jump if not sign)结果为正则转移。
格式:JNS OPR
测试条件:SF=0 -
JO(jump if overflow)溢出则转移。
格式:JO OPR
测试条件:OF=1 -
JNO(Gump if not overflow)不溢出则转移。
格式:JNO OPR
测试条件:OF=0 -
JP(或JPE)(jump if parity,or parity even)奇偶位为1则转移。
格式:JP(或JPE)OPR
测试条件:PF=1 -
JNP(或JPO)(jump if not parity,or parity odd)奇偶位为0则转移。
格式:JNP(或JPO)OPR
测试条件:PF=0 -
JB(或JNAE,或JC)(Gump if below,or not above or equal,or carry)低于,或者不高于或等于,或进位为1则转移。
格式:JB(或JNAE,或JC)OPR
测试条件:CF=1 -
JNB(或JAE,或INC)Xiump if not blow,or above or equal,or not cary)不低于,或者高于或等于,或进位为零则转移。
格式:JNB(或JAE,或JNC)OPR
测试条件: CF=0
最后两种指令在这一组指令中可以只看作JC和JNC,它们只用CF的值来判别是否
转移。
2. 比较两个无符号数,并根据比较的结果转移
本部分的几个核心字母(与无符号数有区分):
- B: below
- A: above
- E: equal
快速浏览大表:
具体判定:
-
JB(或JNAE,或JC)低于,或者不高于或等于,或进位位为1则转移。
-
JNB(或JAE,或JNC)不低于,或者高于或等于,或进位位为0则转移。,的
以上两种指令与(1)组指令中的9和10两种完全相同。 -
JBE(或JNA)(jump if below or equal,or not above)低于或等于,或不高于则
转移。
格式:JBE(或JNA)OPRIO三并加离的。
测试条件:CFVZF=1 -
JNBE(或JA)(jump if not below or equal,or above)不低于或等于,或高于则
格式:JNBE(或JA)OPR格式:JNBE(或JA)OPR
测试条件:CFVZF=0
3. 比较两个带符号数,并根据比较结果转移
本部分的几个核心字母(与无符号数有区分):
- L: less
- G: greater
- E: equal
-
JL(或JNGE)(jump if less,or not greater or equal) 小于,或者不大于或等于则转移
格式:JL(或JNGE)OPR格式:JL(或JNGE)OPR
测试条件:SFVOF=1 -
JNL(或JGE)(jump if not less,or greater or equal)不小于,或者大于或等于则转移
格式:JNL(或JGE)OPR格式:JNL(或JGE)OPR
测试条件:SFVOF=0 -
JLE(或JNG)(jump if less or equal,or not greater)小于或等于,或者不大于则转移
格式:JLE(或JNG)OPR格式:JLE(或JNG)OPR
测试条件:(SFVOF)VZF-=1crcster)不小于或等于,或者大于则 -
JNLE(或 JG)(Gump if not less or cqual,or greater) 不小于等于, 或者大于则转移。
格式:JNLE(或JG)OPR格式:JNLE(或JG)OPR
测试条件:(SFVOF)VZF=0
4. 测试CX或ECX的值为0则转移指令
这一块就这两个指令
-
JCXZ(jump if CX register is zero)CX寄存器的内容为零则转移。
格式: JCXZ OPR
测试条件:(CX)=0 -
JECXZ (jump if ECX register is zero) ECX寄存器的内容为零则转移
格式:JECXZ OPR
测试条件: (ECX)=0
条件设置指令
暂时没啥用, 先放着
循环指令:
此为更为简单的实现循环的指令:
LOOP(loop)循环
LOOPZ/LOOPE(loop while zero,or equal)当为零或相等时循环
LOOPNZ/LOOPNE(loop while nonzero,or not equal)当不为零或不相等时循环
格式;
LOOP OPR
LOOPZ(或LOOPE)OPR
LOOPNZ(或LOOPNE)OPR
-
LOOP循环指令。
格式:LOOP OPR
测试条件:(Count Reg)=0 -
LOOPZ / LOOPE 当为零或相等时循环指令。
格式:LOOPZ(或LOOPE)OPR
测试条件:ZF=1且(Count Reg)=0 -
LOOPNZ / LOOPNE 当不为零或不相等时循环指令。
格式:LOOPNZ(或LOOPNE)OPR
测试条件:ZF=0且(Count Reg)=0
这三条指令的执行步骤是:
上述说明中的Count Reg:
- LOOP: 在实地址模式下使用CX寄存器, 保护模式下使用ECX寄存器
- LOOPW:在任何模式下都会使用CX寄存器作为计数器
- LOOPD:在任何模式下都会使用ECX寄存器作为计数器
子程序:
子程序相当于函数, 集成分割一定的功能
(子程序的定义在下头的PROC伪操作中)
主要使用两个指令:
CALL DST
RET
RET EXP
CALL & REP 指令的功能与JMP无条件跳转相似, 但其会操作堆栈来修改IP寄存器以实现不同的地址转移与返回, 可以理解为:
call指令,相当于
CALL ADDRESS
;相当于:
push IP ;具体应该说是call下面一行的ip
jmp ADDRESS
;*********************
ret ;指令,相当于
pop IP
ret num ;带参数的ret, n为立即数
;相当于:
POP IP
ADD SP, 2
ADD SP, num ;最后参数的作用!
CALL指令具体执行的操作对程序员不可见
几种不同的CALL具体过程如下:
<1> ret
用栈中的数据改动IP的地址,从而实现近转移
( ip ) = ( (ss)*16+ sp )
( sp ) =( sp ) + 2
相当于pop ip
<2>retf
用栈中的数据来改动CS以及IP的值,实现段间转移
( ip ) = ( (ss)*16+ sp )
( sp ) =( sp ) + 2
( cs ) = ( (ss)*16+ sp )
( sp ) =( sp ) + 2
相当于
Pop ip
Pop cs
<3> call xxx(行号)
先把当前IP压栈,然后跳转,相当于实现近转移
( sp ) = ( sp ) – 2
( (ss)*16+ sp ) = ( ip )
( ip ) = ( ip ) + 16位位移
相当于:
Push ip
Jmp near ptr xxx(行号)
<4>call far ptr
把CS。IP压栈处理,然后实现跳转,相当于段间转移。远转移
( sp ) = ( sp ) – 2
( (ss)*16+ sp ) = ( cs )
( sp ) = ( sp ) – 2
( (ss)*16+ sp ) = ( ip )
(cs) = 当前行号段地址
(ip) = 当前行号偏移地址
相当于:
Push cs
Push ip
Jmp far ptr xxx
<5> call reg(16bit)
跳转到16位寄存器上中存储的地址
( sp ) = (sp) – 2
( (SS)*16 + (sp) ) = (IP)
(IP) = ( 16bit Reg )
相当于:
Push IP
Jmp 16bit Reg
<6> call word ptr 内存单元地址
相当于
Push IP
Jmp word ptr 内存单元地址
如:call word ptr ds:[0]
<7> call dword ptr 内存单元地址
相当于
Push cs
Push ip
Jmp dword ptr 内存单元地址
比如:jmp dword ptr DS:[0];
CALL指令结合之前的几种转移地址的寻址方式使用
外部设备操作:
外部设备与主机(CPU和存储器, 键盘, 显示器等)的通信是通过外设接口进行的,每个接口由一组寄存器组成:
– 数据寄存器:存放要在外设和主机间传送的数据。
– 状态寄存器:保存外部设备或接口的状态信息。
– 命令寄存器: CPU给外设或接口的控制命令通过此寄存器送给外部设备。
实际上外部设备的操作就是操作这些个寄存器
DOS功能调用:
DOS功能调用是DOS操作系统为用户提供的许多功能子程序,可以实现输入输出(相当于标准输入输出)
基本的调用方式是:
- 设置调用所需的参数(其实就是寄存器);
- 功能号送AH寄存器;
- 用INT 21H来调用(INT为interrupt, CPU中断)
键盘输入:
功能号01
功能:从键盘输入一个字符
入口参数:无
出口参数:键入字符的ASCII码存AL寄存器
实例:
MOV AH , 01
INT 21H
显示输出:
功能号02
功能:将一个字符送显示器显示
入口参数:显示字符的ASCII码存DL寄存器
出口参数:无
实例:
MOV DL , ’A’
MOV AH , 02
INT 21H
字符串输入:
功能号0A
功能:从键盘输入一个字符串
入口参数:存放输入字符串的数据区地址存DX寄存器
出口参数:字符串的长度和每个字符的ASCII码存入数据区的相应位置
例如:
;数据段定义字符串缓冲区如下(都是伪操作):
maxlen db 16 ;最大字符数(包括\n)
actlen db ?
string db 16 dup(?)
;输入字符串的指令如下:
lea dx, maxlen ;注意送DX的是maxlen, 不是string (而读取时是string)
mov ah, 0ah
int 21h
注意:
最后存入的字符串中还有其他东西:
- 第1个字节: 最大字符数,由程序员给出(如上, 通常和字符串内存申请定义在一起)
- 第2个字节: 实际输入字符数,自动填入
- 第3个字节开始: 字符串按字节存入缓冲区
- 最后结束字符串的回车0DH也要占用1个字节
所以在这之后如果需要读取字符串, 则需要跳过前两个字节
字符串输出:
功能号09
功能:将一个字符串送显示器显示
入口参数:显示字符串的首地址存入DX寄存器
出口参数:无
例如:
;数据段定义字符串如下:
string db ‘hello,world!$’ ;最后的$相当于C原生字符串最后的\0, 起到终止符的作用
;输出字符串的指令如下:
lea dx, string ;注意这里就直接是string了
mov ah, 09h
int 21h
Chapter IV .asm程序格式:
各种伪操作:
汇编中の几种指令:
- 指令(之前Ch.3中的都是这玩意):
在程序运行期间由计算机执行 - 伪操作(相当于预处理指令):
又称伪指令,在汇编程序对源程序汇编期间由汇编程序处理的操作 - 宏指令(就是C++中的宏):
在汇编程序对源程序汇编期间由汇编程序展开宏操作
指令集的选择:
默认为只适用8086指令集, 如需其他的, 在汇编程序开头加一个就好:
段定义伪操作:
段在汇编中的作用相当于C++中的函数, 即各个段互相组合拼凑成了.asm程序
格式:
segment_name SEGMENT
. . .
segment_name ENDS
- 段定义中的内容:
对于数据段、附加段和堆栈段来说,一般是存储单元的定义、分配伪操作;
对于代码段则是指令及伪操
ASSUME伪操作:
明确段和段寄存器的关系
ASSUME的具体作用:
要用assume把段跟段寄存器对应起来的原因是原来的DOS找到的空闲内存的地址不是固定的,无法找到一个地址在任何时候都是空闲的。于是DOS需要可以重定位的程序,而当时的定位方式就是设置段寄存器的值使该程序能在可分配(空闲)的内存中可用。那就需要知道某个段被重定位时候需要修改哪个段寄存器的值才能正确执行。assume提供这种段和重定位代码时需要对应修改的寄存器的关系给编译器,编译器再这个信息写到二进制文件中去。比如DOS下的exe程序记录在文件头中。
ASSUME伪操作格式:
ASSUME segreg: seg_name, …
- 段寄存器名(segreg)必须是CS、 DS、 ES或SS
- 段名(seg_name)必须是由SEGMENT定义的段中的段名
例如:
data segment
. . .
data ends
code segment
assume cs: code , ds: data
start:
mov ax , data
mov ds , ax
. . .
mov ax, 4c00h ;这两条指令是DOS的返回指令, 使用完DOS后通常(基本)需要返回程序
int 21h
code ends
end start
MODEL伪操作:
为存储模型の定义与简化段定义伪操作, 想偷懒时用这个方法,
格式:
.MODEL memory_model [ , model options]
- 说明:
用来表示存储模型,即说明程序在内存中如何安放各个段。
有7种存储模型:
- Tiny: 所有数据和代码都放在一个段内
- Small: 数据和代码各自放在一个64KB段内,最常用的一种模型
- Medium: 代码使用多个段,数据合并成一个64KB的段组
- Compact: 代码放在一个64KB的代码段内,数据可放在多个段
- Large: 代码和数据都可用多个段
- Huge: 与Large模型相同,差别是允许数据段的大小超过64KB
- Flat: OS/2或其它保护模式的操作系统下允许使用32位偏移量
使用了MODEL后的各个段名:
由于使用MODEL后每个段都是匿名的, 所以需要特殊的使用方法
例如:
.model small
.stack 100h
.data ;使用时, 直接加个点就好(segment需要使用start和end)
array db 0, 1, 2, 3, 4, 5, 6, 7, 8
.code
start:
mov ax, @data ;@data为提取data段首地址
mov ds, ax
…
mov ax, 4c00h ;这两条指令是DOS的返回指令, 使用完DOS后通常(基本)需要返回程序
int 21h
end start
Mnemonics伪操作
用于数据定义与储存器分配(就比如上头的申请数组的伪操作)
其实就是创建数组和变量用的
- 格式:
[Variable]Mnemonic Operand1, … ,Operandn[;Comments]
说明:
- Variable:非必须, 作为符号地址使用,其值为操作数第1个字节的偏移地址, 类型为Mnemonics, 即每个元素的长度(相当于不同类型的指针), 所以对其进行算数运算也和指针一样
- Comments:非必须, 分号开始, 为注释部分, 可有可无,说明该伪操作的功能
- Mnemonics: 为助记符, 常用的有以下几种类型
- Operand:为存入申请的内存的初始值
操作数可以是常数、表达式、字符串,也可以为?,单纯申请存储空间,不存入数据
操作数可以使用复制操作符(dup)来复制某个或某些操作数
例如:
data_byte db 10,3*20,10h,?
data word dw 100,100h,-5,?
message db 'HELLO'
;使用dup(即duplicate缩写)重复申请一定数量的内存(根据Mnemonics决定)
;dup具体的语法就是下头的:
array1 db 2 dup(0,1,2,2)
array2 db 100 dup(?)
;注意dup操作可以嵌套
array3db 100 dup(0,2 dup(1,2),0,3)
注意: variable符号地址的使用:
只能将符号地址传递到对应类型的寄存器中:
oper1 db 1 , 2
oper2 dw 1234h , 5678h
;如这两个都是错误的
mov ax , oper1+1 ;为8bit
mov al , oper2 ;为16bit
如果需要强制使用这种方法, 需要使用ptr类型限定符
PTR强制转换:
汇编里面 ptr 用来临时指定类型
可以理解为,ptr是临时的类型转换,相当于C语言中的强制类型转换
格式: type PTR variable
其中, type可以是BYTE 、 WORD 、 DWORD 、FWORD 、 QWORD、 TBYTE
其作用相当于C++中的*解引用
具体是在从给定的地址指向的储存空间中取出数据时,指明按何种类型取出
详细解答:
mov cl,byte ptr [eax]
;[eax]是将eax中的值解释为一个地址
;而后byte ptr是将这个地址指向的储存空间中储存的值解释为byte
;而后MOV这个byte类型的值到cl中
mov cx, word ptr [eax]
;word ptr是将这个地址指向的储存空间中储存的值解释为word
mov ecx, dword ptr [eax]
;DWORD ptr是将这个地址指向的储存空间中储存的值解释为Double Word
例如:
oper1 db 1 , 2
oper2 dw 1234h , 5678h
. . .
mov ax , word ptr oper1+1
;(ax)=3402h 取的是oper1的第二个元素, 并将其解释为word
mov al , byte ptr oper2
;(al)=34h 取得是oper2的第一个元素, 并将其解释为byte
label伪操作
用于定义变量variable的类型属性,
前头的ptr限定符只是一次性的临时解释, label相当于是永久改变了标签的属性
格式: name label type
例如:
word_array dw 50 dup(?)
word_array label byte ;;此后word_array相当于db 100dup(?)
$伪操作:
$表示的是地址计数器的值
在汇编程序MASM对源程序汇编的过程中,使用地址计数器( location counter )保存当前正在汇编的指令的偏移地址(相当于自动控制地址计数器)
具体使用:
array dw 1 , 2 , $+4 ,3 ,4 , $+4
效果如下:
0078H的值为0078(当前地址计数器的值)+4
…
ORG伪操作:
用来设置当前地址计数器的值
如不希望连续分配内存时可以使用这个玩意:
格式: ORG constant expression
例如:
VECTORS SEGMENT
ORG 10 ;调整地址计数器的值为10D, 即0AH
VECT1 DW 47A5H ;偏移地址为0AH
ORG 20 ;调整地址计数器的值为20D, 即14H
VECT2 DW 0C596H ;偏移地址为14H
. . .
VECTORS ENDS
EVEN伪操作
作用是使下一个变量或指令开始于偶数字节地址
此为对齐操作
例如:
DATA_SEG SEGMENT
EVEN ;啥也不用加, 就一个EVEN
WORD_ARR DW 100 DUP(?)
. .
DATA_SEG END
ALIGN伪操作:
作用是使下一个变量或指令开始于2n
也是对齐操作
格式: ALIGN boundary 其中, boundary必须是2的幂
如:
.DATA
ALIGN 4
ARRAYDD 100 DUP(?)
基数控制伪操作:
汇编程序默认的数为十进制,(其实就是编写指令时用的所有数字都是10进制的)
当使用其它基数表示的常数时,需要使用标记:
- 二进制: B
- 十进制: D
- 十六进制: H,如果第一个字符是字母时,应在其前面加上数字0
- 八进制: O
- 字符串可以看成串常数,可以用单引号或双引号括起来,如‘ABCD’
过程定义伪操作PROC:
用于定义子程序(就是函数)
标准格式:
label PROC [attributes] [USES reglist], parameter_list
...
label ENDP
- label: 作为符号地址保存子程序的段地址
与其他标识符的定义有相同的要求 - PROC: 伪操作标志
- ENDP伪操作结束标志
- attributes: 子程序属性, 具体可以是以下任意内容
[distance] [langtype] [visibility] [prologuearg]
但是通常使用的只有distance:
- NEAR:
提供段内调用
当子程序与调用点定义在同一个段中时使用 - FAR:
提供段间调用
当子程序与调用点定义在不同的段中时使用
但是当定义在同一个段中时也可以使用
所以这是一个通用的选择
注意这两个控制的都是RET指令的返回方式
汇编语言指令格式
这部分除了name比较容易迷惑以外, 其他都好, 就直接抄PPT了
汇编语言源程序中的每个语句可以由4项组成,格式如下:
[ name ] operation operand [ ; comment]
;name: 标号变量, 常用与跳转与循环
;operation: 指令, 伪操作, 宏指令
;operand: 寄存器, 标号, 变量, 常数, 表达式
;comment: 注释
例如:
string db ‘hello’
start: mov al,string+2
jz match
match: lea si,string
name 名字项
源程序中可使用下列字符表示名字:
字母: a ~ z
数字: 0 ~ 9
特殊符号: ? . @ _ $
- 说明:
- 除数字外,其它所有字符都可以放在源语句的第一个位置
- 名字中如果用到 . ,则必须是第一个字符
- 汇编程序能识别31个字符
- 名字项可以作为标号或变量
- 名字项表示本语句的符号地址
- 可有可无,当需要用符号地址来访问该语句时才出现
name作为标号时:
- 标号的定义:
标号在代码段中定义,后面跟着冒号: - 标号的使用:
在转移指令中出现, 或作为过程名(子程序名)定义,在CALL指令的操作数字段出现.二者均表示转向地址 - 标号的段地址:
定义标号所在的段的起始地址,此值必须在段寄存器CS中 - 标号代表的偏移量:
从段的起始地址到定义标号的位置之间的字节数 - 标号的类型:
NEAR 为段内引用, FAR 为段外引用
name作为变量时:
- 变量在数据段(DATA SEGMENT)或附加段(EXTRA SEGMENT)中定义
经常在操作数字段出现 - 变量的段地址:
定义变量的段的起始地址,此值必须在段寄存器CS中 - 变量代表的偏移量:
从段的起始地址到定义变量的位置之间的字节数 - 变量的类型:
定义该变量所保留的字节数
operation操作项
- 指令:汇编程序MASM将其翻译为机器指令
- 伪操作:汇编程序MASM将根据所要求的功能进行
处理 - 宏指令:宏展开
operand操作数项:
- 操作数项由一个或多个表达式组成,多个操作数之间用逗号分开
- 对于指令,一般给出操作数地址
对于伪操作或宏指令,给出所要求的参数 - 可以是常数、寄存器、标号、变量或表达式
operand作为表达式时:
- 表达式是常数、寄存器、标号、变量与一些操作符相结合的序列
- 分为: 数字表达式和地址表达式
- 常用的操作符:
(1) 算术操作符
(2) 逻辑与移位操作符
(3) 关系操作符
(4) 数值回送操作符
(5) 属性操作符
operand含有算数操作符时:
- 包括: + - * / mod
- 可用于数字表达式或地址表达式中
- 当用于地址表达式时,运算结果要与明确的物理意义
经常使用的是:地址 + 数字量 或 地址 – 数字量
例程:
要求把首地址为BLOCK的字数组的第6个字中存储的数据传送到DX寄存器
MOV DX,BLOCK+6*2 ;直接寻址
;或:
LEA BX,BLOCK+6*2
MOV DX,[BX] ;寄存器间接寻址
;或:
MOV BX,6*2
MOV DX,BLOCK[BX] ;寄存器相对寻址
;或:
LEA BX,BLOCK
MOV SI,6*2
MOV DX,[BX][SI] ;基址变址寻址
数值回送操作符:
至今没有用到过, 仅仅记录PPT的内容
作用是把一些特征或存储器地址的一部分作为数值回送
TYPE
格式: TYPE expression
功能:
- 如果表达式是变量,则汇编程序回送该变量的以字节数表示的类型:
DB为1, DW为2, DD为4, DF为6,DQ为8,DT为10 - 如果表达式是标号,则汇编程序回送代表标号类型的数值: NEAR为 -1, FAR为-2
- 如果表达式是常数,则回送0
例如:
.DATA
ARRAY DW 1,2,3
ADD SI , TYPE ARRAY
;等效于:
ADD SI, 2
LENGTH
对于变量中使用DUP的情况,将回送分配给该变量的单元数,其它情况回送1
格式: LENGTH variable
例如:
FEES DW 100 DUP(?)
MOV CX , LENGTH FEES
;等效于:
MOV CX , 100
ARRAY DW 1,2,3
MOV CX , LENGTH ARRAY
;等效于:
MOV CX , 1
SIZE
用于回送分配给该变量的字节数,此值是LENGTH值和TYPE值的乘积
格式: SIZE variable
例如:
MOV CX , SIZE FEES
;等效为:
MOV CX , 200
MOV CX , SIZE ARRAY
;等效为:
MOV CX , 2
OFFSET:
用于汇编程序回送变量或标号的偏移地址值
格式: OFFSET variable或label
例如:
MOV BX , OFFSET oper1
;该指令与下面指令是等价的:
LEA BX , oper1
SEG:
与OFFSET类似, 但是回送变量或标号的段地址值
格式: SEG variable 或 label
例如:
MOV BX , SEG oper1
本部分记录上机细节问题
Chapter V: 程序设计
躺雷记录:
关于汇编中大小写的问题:
- 各条指令不区分大小写
- 但是用户定义的各个变量, 标号等, 将区分大小写
关于汇编中的中括号:
对于MOV:
num dword 2
mov eax,2
mov ebx,num
mov ecx,[num]
;执行完ebx==ecx==2
;证明了对于变量, 加不加[]得到的都是符号地址中储存的值
;想要获得符号地址的地址, 需要用LEA, 或者是ptr前缀
mov ebx,eax
;ebx==2
;将eax中的值传递给ebx中
mov ecx,[eax]
;报错,因为这里翻译成汇编是mov ecx,DS:[eax]
;证明了对于寄存器, 不加[]取的是寄存器中的储存的值
;而加[]是将其中的值解释为地址
;(BX)=1F23H
;(1F23H)=3
;(BL)=2
MOV [BX], BL
;执行后(1F23H)=BL=2
;即将BX中的值解释为地址 并将BL的值传递给该地址
对于LEA:
mov eax,2
lea ebx,[eax]
;执行后ebx=2
mov ebx,eax
;等同于上句
lea ebx,eax
;编译器报错: error A2070: invalid instruction operands
;证明了lea指令对于寄存器, 加[]取得是寄存器中的值
num dword 2
lea ebx,num
lea eax,[num]
; eax为num的地址,而ebx==eax
;证明了lea指令对于符号地址, 加不加[]效果相同
关于汇编中的立即数:
- 汇编中默认的立即数为10进制, 使用16进制需要特别声明
- 而针对16进制, 当首位为字母时, 需要在前头补0
如:
MOV AL, 0ABCDH ;必须加0, 否则报错
关于串处理指令:
由于串处理指令中使用的默认寄存器是:
SRC string: DS:SI
DST string: ES:DI
注意此处的DST使用的是ES前缀, 所以必须要设置ES
即将DST定义的segment name存到ES中
DATA SEGMENT
S1 DB 'personal computer'
S2 DB 'personal computer'
MESS1 DB 'match.$'
MESS2 DB 'no match.$'
DATA ENDS
...
MOV AX, DATA
MOV DS, AX
MOV ES, AX ;需要单独操作
关于Debug的应用:
Debug 是实模式(i7 8086K 8086模式)程序的调试工具。
使用它,可以查看 CPU 各种寄存器中的内容、内存的情况和在机器码级跟踪程序的运行。
Debug 的常用功能:
R 命令:查看、改变 CPU 寄存器的内容;
D 命令:显示寄存器中的内容;
E 命令:改写内存中的内容;
U 命令:将内存中的机器指令翻译成汇编指令;
T 命令:执行一条机器指令;
(只会用这个)
A 命令:以汇编指令的格式在内存中写入一条机器指令
汇编程序基础模板:
;******************
DATAS SEGMENT
;数据段, 用于变量的定义
DATAS ENDS
;******************
STACKS SEGMENT
;堆栈段, 当前程序暂时没有用到
STACKS ENDS
;******************
CODES SEGMENT
ASSUME CS:CODES,DS:DATAS,SS:STACKS ;assume只起声明的作用, 并不真正操作
START:
MOV AX,DATAS ;将DATAS送ds寄存器, 才有用
MOV DS,AX
;******************
;主程序编写部分
;******************
MOV AX, 4C00H ;相当于main函数最后的return
INT 21H ;只有在程序需要结束时才使用, 平时使用int 21时并不需要
CODES ENDS
END START ;标准格式
循环结构:
循环程序由三部分组成:
- 设置循环的初始条件
- 循环体
- 循环控制部分:
计数控制、特征值控制、地址边界控制
单循环:
循环输出BX内储存的值:
MOV CH, 4 ;循环控制, 相当于int i
rotate: MOV CL, 4 ;每次移位次数为4
ROL BX, CL ;移位指令
MOV AL, BL ;将移位后BX的第8位赋给AL
AND AL, 0FH ;将AH置零, 由于计算机最低只能进行字节操作, 这里需要4bit操作, 所以这么整
ADD AL, 30H ;转换为ASCII码
CMP AL, 3AH ;将小写a~f转化为大写A~F
JL printit ;跳转指令
ADD AL, 7 ;如果为a~f, 则转化为大写
printit:
MOV DL, AL ;显示输出的入口参数
MOV AH, 2 ;显示输出的功能号
INT 21H ;CPU中断
DEC CH ;循环控制
JNZ rotate ;跳回程序, 完成循环
把Y中1的个数存入COUNT单元中
;***************
DATAS SEGMENT
number dw 78a4h ;这玩意就是Y
address dw number
count dw ?
DATAS ENDS
;***************
CODES SEGMENT
ASSUME CS:CODES, DS:DATAS
START:
MOV AX, DATAS
MOV DS, AX
;***************
MOV CX, 0 ;重置CX,用于后头的计数
MOV BX, address ;将address中的值传给BX
MOV BX, [BX] ;将BX中的值指向的内存空间中的值传递给BX
;执行后(BX)=78a4H
REPEAT1:
TEST BX, 0FFFFH ;AND一下, 主要是刷新FLAGS, 用于后头的判定
JZ EXIT1 ;检测BX中的1是否全都移出了, 满足则代表循环结束
JNS SHIFT ;通过符号位判定最高位是否为1
INC CX ;如果为1, 则CX++
SHIFT:
SHL BX, 1 ;逻辑左移1位
JMP REPEAT1 ;建立循环
EXIT1:
MOV CX, count ;最后将CX中的结果装入count中
;***************
MOV AX, 4C00H
INT 21H
CODES ENDS
END START
插入排序:
;***************
DATAS SEGMENT
X DW ? ;-1
ARRAY_HEAD DW 3,5,15,23,37,49,52,65,78,99
ARRAY_END DW 105
N DW 32
DATAS ENDS
;***************
CODES SEGMENT
ASSUME CS:CODES, DS:DATAS
START:
MOV AX, DATAS
MOV DS, AX ;基本操作
;***************
MOV AX,N ;
MOV X,-1 ;执行后(X)=0FFFFH
MOV SI,0 ;初始化下标
COMPARE:
CMP ARRAY_END[SI],AX ;将array中的数从后往前与N比较
JLE INSERT ;如果array[si]<N, 则插入N
MOV BX,ARRAY_END[SI] ;否则将array[si]后移动一位
MOV ARRAY_END[SI+2],BX
SUB SI,2 ;改变下标, array是DW,所以SI-=2
JMP COMPARE ;跳转达成循环
INSERT:
MOV ARRAY_END[SI+2],AX ;在已经空出的位置插入N
;****************
MOV AX, 4C00H
INT 21H
CODES ENDS
END START
多重循环:
冒泡排序:
//CPP实现
for (i=0; i<n-1; ++i) //比较n-1轮
{
for (j=0; j<n-1-i; ++j) //每轮比较n-1-i次,
{
if (a[j] < a[j+1])
{
buf = a[j];
a[j] = a[j+1];
a[j+1] = buf;
}
}
}
;***************
DATAS SEGMENT
A DW 5, 8, 16, 32, 84
N DW 5
DATAS ENDS
;***************
CODES SEGMENT
ASSUME CS:CODES, DS:DATAS
;***************
START:
MOV AX, DATAS
MOV DS, AX ;基本操作
;***************
MOV CX,N ;将数组元素个数存放到CX
DEC CX ;CX--
LOOPOUT: ;外层循环
MOV DI,CX ;在DI中暂存外层循环数
MOV BX,0 ;BX作为数组下标, 初始为0
LOOPIN: ;内层循环
MOV AX,A[BX] ;将数组的A[I]装入AX
CMP AX,A[BX+2] ;A[I]与A[I+1]比较
JGE CONTINUE ;如果A[I]>=A[I+1]则跳过交换的步骤
XCHG AX,A[BX+2] ;交换数组两个元素的值
MOV A[BX],AX ;交换数组两个元素的值
CONTINUE:
ADD BX,2 ;循环控制: 下标++
LOOP LOOPIN ;每次LOOP都使CX--
MOV CX,DI ;重置CX, 即外层循环数
LOOP LOOPOUT ;每次LOOP都使CX--
;***************
MOV AX, 4C00H
INT 21H
CODES ENDS
END START
汇编附加段:
附加段用于数据的定义
但是实际上在8086中, 数据定义在附加段和数据段的作用是相同的, 只需要在使用时整对段地址就好, 即
;将对应的段地址存到段地址寄存器
MOV AX, DATA
MOV DS, AX
MOV AX, EXTRA
MOV ES, AX
但通常在附加段中保存大数据, 如串和数组
汇编的标号&标识符:
-
标号:
为指令的符号地址
长度限制31个字符 -
标识符:
为变量, 常量等的符号地址
长度通常也是31个字符
后头将Ch.6
Chapter VI: 子程序
躺雷记录:
过程定义伪操作PROC:
用于定义子程序(就是函数)
标准格式:
label PROC [attributes] [USES reglist], parameter_list
...
label ENDP
- label: 作为符号地址保存子程序的段地址
与其他标识符的定义有相同的要求 - PROC: 伪操作标志
- ENDP伪操作结束标志
- attributes: 子程序属性, 具体可以是以下任意内容
[distance] [langtype] [visibility] [prologuearg]
但是通常使用的只有distance:
- NEAR:
提供段内调用
当子程序与调用点定义在同一个段中时使用 - FAR:
提供段间调用
当子程序与调用点定义在不同的段中时使用
但是当定义在同一个段中时也可以使用
所以这是一个通用的选择
注意这两个控制的都是RET指令的返回方式
关于汇编中的start:
start为程序的入口, 程序加载到内存之后CS:IP会指向这个标号, 从START指向的指令开始运行
同时, start只是一个标号, 并没有强制的语法特定, 使用其他也可以…
如改成FUCK
也是可以运行的…
子程序的定义位置:
由于上头的start程序入口的问题, 所以子程序不能定义在start & END start中
可以定义的位置有两个:
- 与start & END start定义在同一代码段中, 但是需要定义在其之前或其之后
(类似于main与其他函数的关系) - 可以定义在其他代码段中
子程序的传参&返回值:
-
寄存器传参:
类似于形参传递.寄存器法寄存器法就是将入口参数和出口参数存放在约定的寄存器中。
-
优点:数据传递书读快、编程较方便、节省内存单元。
-
缺点:当传递参数过多时候,由于寄存器个数有限,及寄存器的频繁使用,将导致寄存器不足。
-
适用:参数较少的子程序
-
-
储存单元传参:
类似于函数使用全局变量把入口参数和出口参数都放在既定的储存单元中
-
优点:不占用寄存器、参数个数任意、每个子程序要处理的数据和送出的结构都有独立的存储单元
-
缺点:但用一定数量的存储单元,增加编程中对变量定义的难度
-
-
堆栈传参:
类似于传递变量指针堆栈法是利用堆栈来传递参数
通常将变量的地址保存到堆栈中-
优点:参数不占用寄存器,和存储单元。参数存放在公共堆栈区,处理完后客恢复。参数个数一般不限
-
缺点:由于参数和子程序混杂在一起,存取参数时候必须小心计算它在堆栈中的位置。要注意断点的保存和恢复
-
-
地址表传参:
类似于传递变量指针这种方法是把参数组成的一张参数表放在某个存储区中,然后只要主程序和子程序约定好这个存储区的首地址和存放的内容,在主程序中将参数传递给地址表,在子程序中根据地址表给定的参数就可以完成操作。
伪操作equ:
EQU 伪指令把一个符号名称与一个整数表达式或一个任意文本连接起来
可以理解为define宏定义, 功能与其相似
格式:
name EQU expression
name EQU symbol
name EQU <text>
- 第一种, expression 必须是一个有效整数表达式
- 第二种, symbol 是一个已存在的符号名称,已经用 = 或 EQU 定义过了
- 第三种, 任何文本都可以岀现在<…>内。当汇编器在程序后面遇到 name 时,它就用整数值或文本来代替符号。
例如:
//将几个DOS标志号和助记符相绑定
display equ 2h
key_in equ 1h
doscall equ 21h
10进制到16进制的转换程序:
DECIHEX SEGMENT
ASSUME CS: DECIHEX
;******************
DECIBIN PROC NEAR ;负责从键盘读取10进制数值
MOV BX, 00H
NEWCHAR:
MOV AH, 01H ;DOS调用,键盘输入值
INT 21H
SUB AL, 30H ;将读取的ASCII转化为数值
JL EXIT ;如果小于30H,则代表不是0~9,直接退出
CMP AL, 09H ;与09H比较
JG EXIT ;如果大于09H,则代表输入的不是0~9,直接退出
CBW ;将AL拓展到AX
XCHG AX, BX ;将AX中新输入的数与BX中原有的结果交换
MOV CX, 10 ;将乘数存入CX预备
MUL CX ;将AX中原有的数*10
XCHG AX, BX ;将*10后的结果与BX交换,
ADD BX, AX ;将新输入的值加到原有的值上
JMP NEWCHAR ;输入新字符, 达成循环
EXIT:
RET
DECIBIN ENDP
;******************
BINIHEX PROC NEAR ;负责将10进制转化为16进制并输出
MOV CH, 4
ROTATE:
MOV CL, 4 ;设定ROL的移位次数为4
ROL BX, CL ;将BX中的数循环左移4位, MSB的4位出现在LSB的4位中
MOV AL, BL ;将BL转移到AL中用于后头计算
AND AL, 0FH ;掩码覆盖MSB4位
ADD AL, 30H ;将数值转化为相应的ASCII码
CMP AL, 3AH ;检测是否大于9
JL PRINTIT ;如果大于9,则为A~F
ADD AL, 7 ;转化为A~F
PRINTIT:
MOV DL, AL ;将AL输出
MOV AH, 02H
INT 21H
DEC CH ;循环控制
JNZ ROTATE ;跳回达成循环, 如果CH==0,则退出循环
RET
BINIHEX ENDP
;******************
CRLF PROC NEAR ;格式控制,输出回车与换行</br>
MOV DL, 0DH
MOV AH, 02H
INT 21H
MOV DL, 0AH
MOV AH, 02H
INT 21H
RET
CRLF ENDP
;******************
MAIN PROC FAR ;MAIN主程序
PUSH DS ;由于是DOS调用Main,所以需要保存寄存器
SUB AX, AX
PUSH AX
CALL DECIBIN ;执行几个子程序
CALL CRLF
CALL BINIHEX
CALL CRLF
RET
MAIN ENDP
;******************
DECIHEX ENDS
END MAIN ;程序结尾必有的东西
输入16进制输出10进制:
程序框架:
本程序在main中调用各个子程序, 而main与几个子程序定义在同一个代码段中, 所以其他子程序为near
DISPLAY EQU 2H
KEY_IN EQU 1H
DOSCALL EQU 21H
;******************
HEXIDEC SEGMENT
;******************
MAIN PROC FAR ;MAIN主程序
ASSUME CS: HEXIDEC
START:
PUSH DS ;由于是DOS调用Main,所以需要保存寄存器
SUB AX, AX
PUSH AX
CALL HEXIBIN ;执行几个子程序
CALL CRLF
CALL BINIDEC
CALL CRLF
JMP MAIN ;跳回MAIN达成死循环
RET
MAIN ENDP
;*******************
HEXIBIN PROC NEAR;输入10进制ASCII字符并转化为16进制数值储存
MOV BX, 0 ;初始化BX
NEWCHAR:
MOV AH, KEY_IN ;键盘输入
INT DOSCALL
;0~9 ?
SUB AL, 30H ;判定输入的值是否在数字之前
JL EXIT ;ASCII在数字之前, 直接结束循环
CMP AL, 10D ;判定输入的值是否在数字之后
JL ADD_TO ;如果小于10,则是0~9
;a~f ?
SUB AL, 27H ;如果输入大于9,则跳转到字符a开始
CMP AL, 0AH ;边界值a
JL EXIT ;ASCII在a~f之前, 直接结束循环
CMP AL, 10H ;边界值f
JGE EXIT ;ASCII在a~f之后, 直接结束循环
;(BX)<-(BX)*16+(AX)
ADD_TO:
MOV CL, 4 ;设置SHL移位次数
SHL BX, CL ;逻辑左移
MOV AH, 0 ;AX高位置零
ADD BX, AX ;将BX左移后空出的4位填入新输入的数值
;完成循环
JMP NEWCHAR ;输入新字符,直到最后一个不满足条件才跳出
EXIT:
RET
HEXIBIN ENDP
;*********************
;将BX中的数转换为10进制并输出
;按位取并转化为ACSII输出
BINIDEC PROC NEAR
MOV CX, 10000D
CALL DEC_DIV
MOV CX, 1000D
CALL DEC_DIV
MOV CX, 100D
CALL DEC_DIV
MOV CX, 10D
CALL DEC_DIV
MOV CX, 1D
CALL DEC_DIV
RET
BINIDEC ENDP
;****************
;****************
;被除数 BX
;除数 CX
;商 AX
;余数 DX
DEC_DIV PROC NEAR
MOV AX, BX ;将被除数拷贝到AX中执行运算
MOV DX, 0 ;DX初始化
DIV CX ;除以CX,按位取出BX中的10进制数
MOV BX, DX ;将余数丢到BX中重新作为除数
MOV DL, AL ;将商丢到DL中
ADD DL, 30H ;转化为ASCII数字
MOV AH, DISPLAY ;召唤DOS显示
INT DOSCALL
RET
DEC_DIV ENDP
;*****************
CRLF PROC NEAR ;格式控制,输出回车与换行</br>
MOV DL, 0DH
MOV AH, 02H
INT 21H
MOV DL, 0AH
MOV AH, 02H
INT 21H
RET
CRLF ENDP
;******************
HEXIDEC ENDS
;******************
END START
ASCII码表:
上机程序分析:
都是之前的程序拼起来的
实验4:
DATA SEGMENT
CRLF_STR DB 13,10,'$'
X DW -1
ARRAY_HEAD DW 5H,10H,18H,22H,2AH,3CH,45H,58H
ARRAY_END DW 6FH
N DW ?
COUNT DW 10
DATA ENDS
;*******************
CODE SEGMENT
;*******************
MAIN PROC FAR
ASSUME CS:CODE,DS:DATA
PUSH DS
SUB AX, AX
PUSH AX
MOV AX,DATA
MOV DS,AX
;*******************
CALL HEXIBIN
CALL CRLF
CALL INSERT_ARR
CALL PRINT_ARR
MOV AX,4C00H
INT 21H
MAIN ENDP
;*******************
HEXIBIN PROC NEAR ;输入16进制数值并转化为ASCII码
MOV BX,0
NEWCHAR:
MOV AH,1H
INT 21H
SUB AL,30H
JL EXIT
CMP AL,10D
JL ADD_TO
SUB AL,27H
CMP AL,0AH
JL EXIT
CMP AL,10H
JGE EXIT
ADD_TO:
MOV CL,4
SHL BX,CL
MOV AH,0
ADD BX,AX
JMP NEWCHAR
EXIT:
RET
HEXIBIN ENDP
;*******************
INSERT_ARR PROC NEAR
MOV AX,BX ;
MOV X,-1 ;执行后(X)=0FFFFH
MOV SI,0 ;初始化下标
COMPARE:
CMP ARRAY_END[SI],AX ;将array中的数从后往前与N比较
JLE INSERT ;如果array[si]<N, 则插入N
MOV BX,ARRAY_END[SI] ;否则将array[si]后移动一位
MOV ARRAY_END[SI+2],BX
SUB SI,2 ;改变下标, array是DW,所以SI-=2
JMP COMPARE ;跳转达成循环
INSERT:
MOV ARRAY_END[SI+2],AX ;在已经空出的位置插入N
RET
INSERT_ARR ENDP
;*******************
PRINT_ARR PROC NEAR
MOV CX,COUNT
MOV SI,0
OUTPUT:
MOV BX,ARRAY_HEAD[SI]
ADD SI,2
PUSH CX
CALL BINIHEX
MOV DL,32
MOV AH,2
INT 21H
POP CX
DEC CX
JNZ OUTPUT
RET
PRINT_ARR ENDP
;*******************
BINIHEX PROC NEAR
MOV CH,4
ROTATE:
MOV CL,4
ROL BX,CL
MOV AL,BL
AND AL,0FH
ADD AL,30H
CMP AL,3AH
JL PRINTIT
ADD AL,7
PRINTIT:
MOV DL,AL
MOV AH,2
INT 21H
DEC CH
JNZ ROTATE
RET
BINIHEX ENDP
;*******************
CRLF PROC NEAR
MOV DL, 0DH
MOV AH, 02H
INT 21H
MOV DL, 0AH
MOV AH, 02H
INT 21H
RET
RET
CRLF ENDP
;*******************
CODE ENDS
;*******************
END MAIN
试卷组成:
填空 & 选择
每个2分, 总共50
程序填空
15空, 30分
编程题
2题10分
都是上课讲过的程序
刷题坑点整合:
80X86的数据储存方式:
80X86的CPU绝大多数是用小端模式进行存储
而ARM绝大多数都是大端存储
小端模式:
将数据的高位放在低字节, 低位放在高字节
取数据时, 向低字节方向进行解析
大端模式:
和小端模式相反
将数据的高位放在高字节, 低位放在低字节
去数据时, 向高字节方向进行解析
所以有如下代码:|
DATA SEGMENT
TABLE DW 10H,20H,30H,40H,50H,60H,70H,80H
ENTRY DW 5
DATA ENDS
;*******************
CODE SEGMENT
ASSUME CS: CODE, DS: DATA
START:
MOV AX, DATA
MOV DS, AX
;*******************
MOV BX, OFFSET TABLE
MOV CX, 10
RE:
MOV AX, [BX]
ADD BX, 1
LOOP RE
;*******************
MOV AX, 4C00H
INT 21H
CODE ENDS
;*******************
END START
每次MOV AX, [BX]后, AX的值为:
0010, 2000, 0020, 3000, 0030, 4000 …
MOV补充:
MOV [BX],10H ;MOV支持这种操作
实际上相当于DST为储存器寻址方式, SRC为立即数寻址方式
关于算数指令:
不可以使用立即数的指令:
- 除法: DIV & IDIV
- 乘法: MUL & IMUL
而加法和减法都可以使用立即数
关于数值回送操作符:
总之, 滚回去再看, 以为不考, 都忘了
SEG补充:
MOV BX , SEG oper1
其oper1只能是variable或label, 不能是其他的任何东西, 即使使用的是variable或label的地址也不行
如:
MOV AX, OFFSET ARR
MOV BX, SEG [AX] ;还是报错
关于寻址方式:
你以为不会考吗?
滚回去看!
关于双精度数的比较:
首先, 汇编中所有的数都默认当做是有符号数, 即使有无符号完全依靠程序员的解释
所以, 对于有符号双精度数的比较
- 高位使用有符号的比较法
LESS & GREATER 系列JMP - 低位使用无符号的比较法
BELOW & ABOVE 系列的JMP
如:
CMP DX, BX
JL LABLE1 ;符号位在高字节
JG LABLE2
CMP AX, CX
JB LABLE1 ;低字节相当于无符号位
JAE LABLE2
关于8086寻址:
注意, 之前给出的EA公式不是白给的!
EA(effective address) = 基址 + (变址 * 比例因子) + 位移量
8086几个地址要点:
-
最后得到的EA一定要是TMD20位, 否则就TM各种报错!
-
段地址一定要使用段寄存器段寄存器, 否则报错
(包含隐藏的默认使用段寄存器的情况)
如:MOV 1234H:2H, 2 ;报错 ;而这种方法就OK: MOV AX, 1234H MOV DS, AX MOV DS:2H, 2
-
除了段寄存器中的段地址, 后头所有的都TM属于偏移地址
而偏移地址都TM需要一个中括号[]而能够出现在中括号之中的, 只有那4个寄存器, 和立即数
BX, BP, SI, DI, 立即数其他任何东西要是出现, 狗腿给你打断
-
各种隐含段寄存器的情况:
主要用作计算物理地址的题-
访问指令时:
默认的段寄存器为CS -
访问堆栈时:
默认的段寄存器为SS注意, 当偏移地址中有BP时, 也视作访问堆栈, 其默认寄存器也是SS
-
访问数据时:
默认的段寄存器为DS -
访问目的串时:
默认的段寄存器为ES
-
关于程序填空の技巧:
- 明确其每个寄存器的作用