《汇编语言》学习笔记
寄存器(reg): CPU中存储数据的器件
在8086CPU中,有14个寄存器,可以分为8个通用寄存器、1个指令指针寄存器、1个标志寄存器以及4个段寄存器
通用寄存器:用于传送和暂存数据,也可参与算术逻辑运算,并保存运算结果。
AH&AL = AX (accumulator):累加寄存器,主要是在进行运算的时候存放操作数,同时所有的I/O指令都使用这一寄存器与外界设备传送数据
BH&BL = BX (basic):基址寄存器,用于地址的索引
CH&CL = CX (count):计数寄存器,用于计数,在移位操作以及循环(loop)时作为隐含的计数器
DH&DL = DX(data):数据寄存器,用于数据传递。在书中以8086CPU为例,如果是要用32位的数作为被除数时,通过使用DX与AX存放数据,DX放高16位,AX放低16位
SP(Stack Pointer):堆栈指针,与SS配合使用,可指向目前的堆栈位置(也就是当前栈顶的偏移地址)
BP(Base Pointer):基址指针寄存器,可用作SS的一个相对基址位置
SI(Source Index):源变址寄存器,可用来存放相对于DS段之源变址指针,主要用于对内存单元操作时与DS一起表示DS:SI处的内存单元
DI(Destination Index):目的变址寄存器,可用来存放相对于ES 段之目的变址指针,主要用于对内存单元操作时与DS一起表示DS:DI处的内存单元
SI和DI寄存器不可以分为两个8位的寄存器使用
在没有寄存器参与的指令中,应该使用指令X ptr
参与以指明要操作的内存单元的大小为byte
大小或word
大小,否则CPU无法指定要访问的单元是字单元还是字节单元
指针指令寄存器:用于控制程序中指令的执行顺序
IP:指向段内指令的偏移量,在8086机中,CPU将CS:IP指向的内容当作指令执行。
标志寄存器
PSW:也可称作flag寄存器,16位的存放条件标志、控制标志寄存器,在0、2、4、6、7、8、9、10、11位上分别对应了CF、PF、AF、ZF、SF、TF、IF、DF、OF标志
CF(Carry Flag):记录了运算结果给的最高有效位向更高位的进位值,或从更高位的借位值
PF(Parity Flag):记录相关的指令执行后结果的所有bit位中的1的个数是否是偶数,若是偶数则对应为1
AF(Auxiliary Carry Flag):
ZF(Zero Flag):零标志,记录在相关指令执行后结果是否为0,结果为0时(ZF)=1
SF(Sign Flag):记录相关指令执行后结果是否为负,如果是负则对应为1,可以理解为它记录了结果的最高位符号位
TF(Trap Flag):
IF(Interrupt-enable Flag):
DF(Direction Flag):控制每次操作之后SI和DI的增减,(DF) = 1时两者递增,反之递减
OF(Overflow Flag):溢出标记位,相加操作移除
段寄存器(sreg):对内存的分段管理而设置
CS(Code Segment):代码段寄存器
DS(Data Segment):数据段寄存器,在mov操作仅仅指定偏移地址的情况下,默认内存单元段地址由该寄存器存储
SS(Stack Segment):堆栈段寄存器,存放栈顶的段地址
ES(Extra Segment):附加段寄存器
- 在8086机中,不能通过mov指令对CS:IP的值进行修改,但是可以通过
jmp ax
指令进行类似mov IP,ax
的操作;不仅是不能对CS:IP进行修改,在8086CPU中,由于硬件设计问题,mov不支持将数据直接送入段寄存器的操作。
在访问内存单元的时候,可以使用段寄存器:[偏移地址]
的形式对内存单元访问
字节和字
- 字节:记为
byte
,一个字节由8个bit位组成,存在8位寄存器中 - 字:记为
word
,由两个字节组成
当一个字存储在16位寄存器中的时候,假设在AX寄存器中,则AH存放高8位,AL存放低8位
汇编指令
mov
mov 寄存器,数据
:将数据直接送入寄存器中(在未指明段时一般是指通用寄存器)mov 寄存器,寄存器
:将一个寄存器中的数据送到另外一个寄存器mov 寄存器,内存单元
:将指定内存单元的数据送入寄存器,可以使用类似mov ax,[0]
的形式,在方括号中的值或者数字是相对与DS数据段里面的偏移地址mov 内存单元,寄存器
:将寄存器里面的数据送入内存单元mov 段寄存器,内存单元
:将指定内存单元的数据送入段寄存器mov 内存单元,段寄存器
:将指定段寄存器的数据送入内存单元mov 段寄存器,寄存器
:将寄存器里面的数据送到段寄存器,前面说的是不能用mov
指令将数据直接送入,但是可以通过先把数据存入寄存器,再从通用寄存器转移到段寄存器mov 段寄存器,寄存器
:将段寄存器里面的数据送到寄存器,因为8086CPU内部有寄存器到段寄存器的通路,反之也可以
add
和sub
指令与mov
一样,有两个操作对象,其形式与mov
指令类似
[address]表示一个偏移地址为address的内存单元,在进行mov操作的时候段地址默认为DS寄存器中的地址
同时[address]也可以用[bx]的形式,偏移地址在bx寄存器中
add & sub
add 操作对象1,操作对象2
:把操作对象1和操作对象2的和放入操作对象1中,与mov
指令的形式类似sub 操作对象1,操作对象2
:操作对象1减去操作对象2的值放入操作对象1中,同上
inc & dec
inc 操作对象
:将操作对象的值自加1dec 操作对象
:将操作对象的值自减1
div & mul
div指令的除数在reg或者内存单元中,被除数默认放在AX和DX中div reg(内存单元)
:用在reg或者内存单元中的除数去除被除数,得到的商存放在AL中,余数放在AH中,如果除数是16位,则AX存储除法操作的商,DX存储除法的余数
mul指令要求两个相乘的数位数必须相同,如果都是8位,一个默认放在AL中,另外一个放在8位reg或者内存单元中;如果都是16位,一个放在AX中,另外一个放在16位reg或者内存单元中mul reg(内存单元)
:8位乘法的结果默认放在AX中,16位乘法的结果的高位放在DX中,低位放在AX中
push & pop
push
和pop
主要是进行对栈的操作,在8086CPU中以字为单位进行操作push 寄存器
:首先SP = SP - 2,然后将寄存器中的数据存放到SS:SP指向的内存单元(因为栈是从高地址向低地址方向增长)pop 寄存器
:将SS:SP的数据存放到寄存器中,然后SP = SP + 2,进行pop操作后,之前SS:SP指向的内存单元数据还存在,但是现在已经不在栈中
除了对寄存器的操作,这两个指令还可以使用push 段寄存器
、push 内存单元
、pop 段寄存器
、pop 内存单元
的指令进行对栈的操作
- 在8086CPU中,不会保证对栈的操作不好越界,在栈满的时候进行
push
和栈空的时候pop
都会导致栈顶超界的问题,只能在编程过程中根据要存入的数据大小来安排栈的大小; - 编写汇编程序的过程中,可以通过先声明对应的数据,然后让SS:SP指向数据的地址,然后使用这一部分空间当作栈
loop:循环
loop 标号
:令cx寄存器中的值自减1,然后判断是否为0,为0时结束循环然后向下执行程序,否则跳转到标号的位置执行程序
loop对应章节(P104)知识点:汇编程序中,数据不能以字母开头,在字母前要加一个0
dw(define word) & db(define byte)
dw(db) data1,data2,.....
:主要用于定义字型数据和字节型数据db '(string)'
:定义字符串类型,字符串的存储是存对应的ASCII码dd data
:(double word)定义双字类型数据
字符的转换:
大写字母 + 20H = 小写字母
小写字母 - 20H = 大写字母
位运算
and 操作对象1,操作对象2
:将操作对象1和操作对象2进行按位与运算后放入操作对象1中or 操作对象1,操作对象2
:将操作对象1和操作对象2进行按位或运算后放入操作对象1中xor 操作对象1,操作对象2
:将操作对象1和操作对象2进行按位异或运算后放入操作对象1中not 操作对象
:将操作对象按位取反shl(shr) 操作对象
:将操作对象逻辑左(右)移一位,直接在右(左)边补一个0sal(sar) 操作对象
:将操作对象算数左(右)移一位,直接在右(左)边补一个0,但是与逻辑移位操作不同的是,最高位的符号位保存不变,也就是说保留了算术中的符号
移位操作的最高位会被移入PSW寄存器的CF位中
jmp:无条件跳转
jmp reg(idata)
:段内转移,将偏移地址(IP)修改为idata或者reg内所存的地址jmp address1:address2
:直接修改CS为address1,IP为address2,实现段间转移jmp short ptr 标号
:(IP) = (IP) + 8位偏移量,段内短转移jmp near ptr 标号
:(IP) = (IP) + 16位偏移量,段内近转移jmp far ptr 标号
:实现段间转移,也称为远转移,同时修改段地址和偏移地址jmp word ptr 内存单元地址
:实现段内转移,修改(IP)为内存单元中存放的偏移量jmp dword ptr 内存单元地址
:实现段间转移,将(CS)修改为内存单元地址的高地址,(IP)修改为内存单元地址的低地址,即(CS) = (内存单元地址) + 2、(IP) = (内存单元地址)
在编译器将指令转换为指令码时,会通过汇编指令中的标号计算出位移量以实现跳转
offset:取得标号的偏移地址
jcxz:有条件转移指令
jcxz 标号
:如果(cx) = 0,转移到标号除执行
相当于:
1 | if((cx) == 0) jmp short 标号 |
ret & retf
主要用于实现(CS:IP)的跳转ret
:将(IP)修改为栈顶的字,相当于pop IP
retf
:将(IP)修改为(SS:SP)的字、将(CS)修改为(SS:SP + 2)的字,相当于pop IP
然后pop CS
call
call 标号
:将当前的(IP)压入栈,然后跳转到标号位置,相当于:
1 | push IP |
call far ptr 标号
:实现段间跳转,先把CS、IP依次压栈,然后跳转到标号位置,相当于:
1 | push CS |
call reg
:将当前的(IP)压入栈,然后IP修改为reg中存储的偏移地址,相当于:
1 | push IP |
call word ptr 内存单元
:与上面类似,先把(IP)压栈,然后(IP)修改为内存单元中存储的偏移地址
1 | push IP |
call dword ptr 内存单元
:先把CS、IP依次压栈,然后(CS)、(IP)依次修改为内存单元地址中的两个字
1 | push CS |
dup
db(dw) X dup (Y)
:将Y重复定义X次
cmp
比较指令,相当于sub
cmp 操作对象1,操作对象2
:计算操作结果1 - 操作结果2,根据结果对标志寄存器进行设置je 地址
:对于cmp指令的结果,如果两数相等就转移jne 地址
:对于cmp指令的结果,如果两数不相等就转移jb 地址
:低于则转移jnb 地址
:不低于则转移ja 地址
:高于则转移jna 地址
:不高于则转移
movsb & movsw
movsb
:将DS:SI指向的内存单元中的字节送入ES:DI中,根据DF位使SI和DI递增或递减movsw
:将DS:SI指向的内存单元中的字送入ES:DI中,根据DF位使SI和DI递增或递减
rep
一般是配合movsb
和movsw
一起使用rep movsb(sw)
:根据CX的值,实现CX个字符的传送
cls & std
cls
:(Clear Direct Flag)将标志寄存器的DF位置0std
:(Set Direct Flag)将标志寄存器的DF位置0
pushf & popf
pushf
:将标志寄存器的数据popf
:从栈顶弹出数据送入标志寄存器
汇编程序
一个简单的汇编语言源程序:
1 | assume cs:codesg |
伪指令
汇编语言源程序中,有汇编指令和伪指令两种,下面三个指令是伪指令段名 segment
:与段名 ends
是成对使用的伪指令,定义一个段,同时需要有一个名称来标识段名 ends
:标识一个段的结束end [标号]
:汇编程序的结束标记,在编译器遇到end
指令的时候,就会停止对程序的编译,同时还可以加一个标号在后面通知编译器程序的入口assume 段寄存器:段名,[段寄存器:段名],...
:用于使某一个段寄存器和程序终端某一个段关联
对assume伪指令的深入了解:Link
标号
在伪指令中,使用某个名称作为段的一个标号,汇编过程中编译、连接程序处理为一个段的段地址
标号仅仅是指定了某个标号对应的指令行,并没有嵌套程序的作用,如果没有跳转,程序会继续往下运行
程序返回:一个程序结束后将CPU的控制器交还给它得以运行的程序的过程
1 | mov ax,4C00h |
这一段汇编指令所实现的功能就是程序返回,在程序结束运行后,放回到command中,CPU继续执行command
程序返回的两个指令的原理:
编译 & 连接:P83 与 P85
彩色字符显示缓冲区
- 显示缓冲区的内存地址是
B8000H - BFFFFH
- 00 - 01 单元对应显示器的第1列,02 - 03 单元对应显示器的第2列,依次类推
- 显示缓冲区的偶地址存放字符的ASCII码,奇地址存放字符的颜色属性