概述
书籍电子版 提取码: b62a
第10章CALL和RET指令
ret和retf
ret用栈中数据修改IP,近转移
retf用栈中数据修改CS和IP,远转移
用汇编语法来解释:
reg = pop IP
regf = pop IP pop CS
call指令
- 将当前IP或CS和IP压栈
- 转移
要注意的是:call指令在执行时,当前IP已经指向了它的后一个的位置
依据位移进行转移的call指令
call 标号
push IP
jmp near ptr 标号(16位寄存器)
转移的目的地址在指令中的call指令
call far ptr 标号 (段间转移)
push CS
push IP
jmp far ptr 标号
转移地址在寄存器中的call指令
call 16位寄存器
push IP
jmp 16位reg
寄存器和要push栈中的IP没有关系,它只是保存要转移到的地址。要存到栈中的IP还是原来的call地址的后面一位。
转移地址在内存中的call指令
- call word ptr 内存单元地址
push IP
jmp word ptr 内存单元地址
- call dword ptr 内存单元地址
push CS
push IP
jmp dword ptr 内存单元地址
不难看出call指令的目地是保存下一步要执行的地址到栈中,然后跳到别的位置去。显然这与高级语言中的调用函数密不可分。
检测点:10.5
call和ret的配合使用
显然call和ret可以组合使用,实现子程序的机制。
assume cs:code
code segment
main: :
:
call sub1
:
:
mov ax, 4c00h
int 21h
sub: :
:
call sub2
:
:
ret
sub2: :
:
ret
code ends
end main
mul指令
mul是乘法指令
-
乘数
- 同是8位
- 同是16位
-
结果
- 8位乘法的结果放在ax中
- 16位乘法结果高位放在dx中,低位放在ax中
mul reg
mul 内存单元
例如:
mul byte ptr ds:[0]
mul word ptr ds:[0]
模块化程序设计
将复杂的现实问题转化成相互联系,不同层次的子问题。
参数和结果传递的问题
子程序的三个重要信息
- 程序的功能
- 参数
- 结果(返回值)
那么这里我们应该将参数和结果放在什么位置?
用寄存器来存储参数和结果是最常用的方法
明确程序调用的参数传递问题:
调用者将参数送入参数寄存器,从结果寄存器取得返回值;子程序从参数寄存器取得参数,将返回值送入到结果寄存器。
批量数据传递
如果子程序需要多个参数,那么就可以用到内存中的地址。
用栈传递参数
结合c语言的函数调用,看一下栈传递参数的思想:
将参数全部压入栈,然后将IP压入,此是栈顶是IP,再压入一个存放返回值的寄存器。函数体结束后,先弹出返回值,再弹出IP,将栈清空即可。
寄存器冲突问题
如果子程序调用时,寄存器不够用,可以用栈来保存。
实验10:编写子程序
第11章:标志寄存器
标志寄存器的作用:
- 用来存储相关指令的某些执行结果
- 用来为CPU执行相关指令提供行为依据
- 用来控制CPU的相关工作方式
8086CPU的标志寄存器有16位,其中存储的信息通常被称为程序状态字(PSW),前面已经学过
ax, bx, cx, dx, si, di, bp, sp, IP, cs, ss, ds, es
13个寄存器,下面是最后一个寄存器flag。
其中每一位都有其相应的含义:
ZF标志(zero flag)
零标志位:如果指令执行后,结果为0,那它就为1,否则为0
一般都是逻辑或算术运算的指令才会对其有影响,push、mov、pop移动指令对其没有影响
PF标志(Parity flag)
奇偶标志位:一个数的二进制数表示有偶数个1,它就为1,否则为0
SF标志(Symbol flag)
符号标志位:如是这个数是负数,它就为1,非负就为0
CF标志 (Carry flag)
进位标志位:无符号数运算,如果存在相高位进位或者借位就为1,否则为0。
OF标志(Over flag)
溢出标志位:对于n位寄存器,有符号运算,结果超出了它的表示范围为就是溢出。它就为1,否则为0。
对于这个圆来说,用四位二进制的补码方式进行存储,可以表示的有符号数范围是[-8,7]。圆上的运算规则为,某点的数与正数相加,顺时针移动该正数位,与负数相加就逆时针移动多少位。根据这个规则,在这个圆上的两个异号的数相加是不会出这个圆的。对于两个符号相异的有符号相加来说是不会产生溢出的。
比如:
-8 + 7 = -1
-8顺时针移动7位 => -1
7逆时针移动8位 => -1
而同号可能会产生溢出。
比如:
-8 + -7 本应该等于-15
结果:
-8逆时针移动7位 => 1
-15就超过了4位二进制数补码规则能表示的范围,就是溢出了,得到的结果是一个我们不想要的数1。
对于CPU来说,有符号数,用最高位的进位和次高位的进位的异或来表示OF。
例1:
mov al, 0f0h
add al, 88h
人脑来算:(注意这里只考虑有符号数)-16 + (-120) = -136 < -128溢出
CPU来算:
11110000
+ 10001000
__1_0___________
1 01111000
最高位进位1,次高位进位0
所以1 XOR 0 = 1溢出。
例2:
mov al, 0fh
mov al, 78h
人脑来算:-16 + 136 = 120 < 127不溢出
CPU来算:
11110000
+ 01111000
__1_1________
1 01101000
最高位进1,次高位也进1
所以1 X OR 1 = 0 没有溢出。
adc指令
带进位的加法指令:
与add命令相同,只不过是带进位的加法,也就是说,它会加上CF上面的进位。
用这个指令就可以实现多位数据的运算操作。
128位数据 = 16字节 = 16个Byte型数据
assume cs:code, ds:data, ss:stack
data segment
db 11h,11h,11h,11h,11h,11h,11h,11h,11h,11h,11h,11h,11h,11h,11h,11h
db 88h,88h,88h,88h,88h,88h,88h,88h,88h,88h,88h,88h,88h,88h,88h,88h
data ends
stack segment
stack ends
code segment
start: mov ax, data
mov ds, ax
mov di, 16
mov cx, 8
mov ax, stack
mov ss, ax
call add128
mov ax, 4c00h
int 21h
add128: push cx
push ax
push si
push di
sub ax, ax
s: mov ax, [si]
adc ax, [di]
mov [si], ax
inc si
inc si
inc di
inc di
loop s
pop di
pop si
pop ax
pop cx
ret
code ends
end start
为什么不写
add si, 2
add di, 2
因为这样可能会影响到CF标志位。如果是inc操作就不会。
sbb指令
带借位的减法指令:
与adc指令相同,不过是用于减法。
cmp指令
比较指令:
cmp 操作对象1, 操作对象2
原理就是减法,只是不保存结果。也就是说与sub不同的是操作对象1的值没有改变。
比较的结果可以通过各标志位看出来。
mov ax, 8
mov bx, 3
cmp ax, bx
8 - 3 = 5
zf = 0, pf = 1, sf = 0, cf = 0, of = 0
显然,对于无符号数,很轻易的就可以看出ax与bx的大小关系。
但是,对于有符号数而言,要考虑的就是溢出标志位和进给标志位了。
cmp ah, bh 对于ah和bh而言,你期望的是它们相减会得到一个数,这个数可能不在可以表示的范围内,所以就溢出了,导致最终结果与实际结果相反。
例如:
mov ah, 22h
mov bh, 0a0h
sub ah, bh
期望的结果:34-(-96) = 130 因为已经超过了127,所以溢出了
实际的结果:82h = -126
-
如果sf = 1,而of = 0
of为0,也就是没有溢出,你期望的结果 = 实际的结果
sf = 1,ah - bh < 0,所以ah < bh
-
如果sf = 1, 而of = 1
of为1,也就是溢出了,你期望的结果和实际的结果相反
sf = 1实际的结果为负,你期望的结果为正,就是说本身而言ah > bh
-
如果sf = 0, 而of = 0
of为0,没有溢出,实际与期望相同。
sf = 0,ah >= bh
-
如果sf = 0, 而of = 1
of为1,有溢出,实际与期望相反。
sf = 0为非负,相反为负数,所以ah < bh
检测比较结果的条件转移指令
前面学过的jcxz就是一个条件转移指令。
所有的条件转移指令的转移位移都是[-128, 127]
像call和ret一样,条件转移指令与cmp连用可以实现高级语言中的if语句
1. 编程,统计data段中数值为8的字节的个数,用ax保存统计结果
用否定写:
assume cs:code, ds:data, ss:stack
data segment
db 8, 11, 8, 1, 8, 5, 63, 38
data ends
stack segment
stack ends
code segment
start:
mov ax, data
mov ds, ax
mov si, 0
mov cx, 8
mov ax, 0
s:
mov bl, [si]
cmp bl, 8
jne a
inc ax
a:
inc si
loop s
mov ax, 4c00h
int 21h
code ends
end start
用肯定写:
assume cs:code, ds:data, ss:stack
data segment
db 8, 11, 8, 1, 8, 5, 63, 38
data ends
stack segment
stack ends
code segment
start:
mov ax, data
mov ds, ax
mov si, 0
mov cx, 8
mov ax, 0
s:
mov bl, [si]
cmp bl, 8
je a
jmp short o
a:
inc ax
o:
inc si
loop s
mov ax, 4c00h
int 21h
code ends
end start
注意要考虑cx为0后程序loop跳出也会继续向下执行。要把loop s放在最后。
DF(Direction flag)标志和串传送指令
当然也可以一次传送一个字:
movsw
那si和di一次就是加(减)两个了。
显然这种指令多用于循环中,循环多用cx来记录终止。
cld df置0
std df置1
rep movsw
pushf和popf
与push和pop的功能相同。只不过对于寄存器
就是这家伙。
例题:执行完后ax = ?
mov ax, 0
push ax
popf ;flag = 0
mov ax, 0fff0h
add ax, 0010h ; fff0 + 10 = 1 0000 CF = 1, PF = 1, ZF = 1, OF = 0 所以flag = 0000 0000 0100 0101 = 0045h
pushf ;flag 入栈
pop ax ; flag出栈给ax = 0045h
adn al, 11000101b ;al = 45h
and ah, 00001000b ;ah =00h
综上ax = 0045h
标志寄存器在Debug中的表示
实验11 编写子程序
第12章:内中断
任何一个通用CPU都具备一种能力,可以在执行完当前正在执行的指令之后,检测到从CPU外部发送过来的或内部产生的一种特殊信息,并且可以立即对所接收到的信息进行处理。
这种特殊的信息,我们称其为:中断信息
内中断的产生
8086CPU以下几种情况会产生中断:
- 除法错误,中断类型码:0
- 单步执行, 中断类型码:1
- 执行into指令, 中断类型码:4
- 执行int指令, int n 指令中的n为字节型立即数,是提供给CPU的中断类型码。
中断信息中必须包含识别来源的编码。8086CPU用中断类型码的数据来标识中断信息的来源。
中断类型码为一个字节型数据,可以表示256种中断信息的来源。
中断信息的来源,简称为中断源。上面就是4种中断源。
中断处理程序
CPU在收到中断信息后,就应该转去执行该中断信息的处理程序。但是如何定位这个中断处理程序呢?
中断向量表
中断向量,就是中断处理程序的入口地址。
中断向量表:就是中断处理程序 入口地址的列表
中断向量表在内存中存放,对于8086pc机,中断向量表指定放在内存地址0处,从0000:0000到0000:03ff的1024个单堍存放着中断向量表。
N号中断源对应的中断处理程序入口的偏移地址的内存单元地址:4N
N号中断源对应的中断处理程序入口的段地址的内存单元地址:4N + 2
中断类型码作为中断向量表中的表项号来查找表项,再当相应的程序入口去
在中断向量表中,一个表项中两个字:高地址字存放段地址,低地址字存放偏移地址。
中断过程
- (从中断信息中)取得中断类型码
- 标志寄存器的值入栈
- 设置标志寄存器的第8位TF和第9位IF的值为0
- CS的内容入栈
- IP的内容出栈
- 从内存地址为中断类型码*4和中断类型码*+2的两个字单元中读取中断处理程序的入口地址设置CS和IP
中断处理程序和iret指令
中断处理程序应该在内存中,而中断处理程序的入口地址应该在中断向量表表项中。
中断程序与子程序有点像:
- 保存用到的寄存器
- 处理中断
- 恢复用到的寄存器
- 用iret指令返回
iret可以理解为
pop ip
pop cs
popf
编程处理0号中断
重新定义0号中断的中断处理程序:
- 向量表中断源对应中断处理程序
- 中断处理程序写在哪?
assume cs:code
code segment
start: mov ax, cs
mov ds, ax
mov si, offset do0
mov ax, 0
mov es, ax
mov di, 200h
mov cx, offset do0end-offset do0
cld
rep movsb
mov ax, 0
mov es, ax
mov word ptr es:[0*4], 200h
mov word ptr es:[0*4 + 2], 0
mov ax, 4c00h
int 21h
do0:
jmp short do0start
db "overflow!"
do0start:
mov ax, cs
mov ds, ax
mov si, 202
mov ax, 0b800h
mov es, ax
mov di, 0
mov cx, 9
s:
mov al, [si]
mov es:[di], al
inc si
add di, 2
loop s
mov ax, 4c00h
int 21h
do0end:
nop
code ends
end start
中断后:
- 程序将do0拷到内存0:200
- 将do0的入口地址放入向量表中
- 运行do0打印字符串
单步中断
CPU执行完一条指令之后,如果检测到标志寄存器的TF位为1,则产生单步中断,中断类型码为1
- 取中断类型码1
- 标志寄存器入栈,TF、IF设置为0
- CS、IP入栈
- IP = 1*4,CS = 1*4+2
响应中断的特殊情况
有些情况下,CPU执行完当前指令也不会响应中断
比如栈的操作应该连续完成,也就是设置SS与SP,保证ss:ip 指向正确的栈顶。
所以在编码时应该要将两者放在一起。下面是错误写法。
mov ax, 1000h
mov ss, ax
mov ax, 0
mov sp, 0
最后
以上就是还单身发夹为你收集整理的《汇编语言》——王爽第三版笔记(10-12章)的全部内容,希望文章能够帮你解决《汇编语言》——王爽第三版笔记(10-12章)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复