我是靠谱客的博主 还单身发夹,最近开发中收集的这篇文章主要介绍《汇编语言》——王爽第三版笔记(10-12章),觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

书籍电子版 提取码: b62a

第10章CALL和RET指令

ret和retf

ret用栈中数据修改IP,近转移

retf用栈中数据修改CS和IP,远转移

用汇编语法来解释:

reg = pop IP
regf = pop IP pop CS

call指令

  1. 将当前IP或CS和IP压栈
  2. 转移

要注意的是: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是乘法指令

  1. 乘数

    1. 同是8位
    2. 同是16位
  2. 结果

    1. 8位乘法的结果放在ax中
    2. 16位乘法结果高位放在dx中,低位放在ax中

在这里插入图片描述

mul reg
mul 内存单元

例如:

mul byte ptr ds:[0]
mul word ptr ds:[0]

模块化程序设计

将复杂的现实问题转化成相互联系,不同层次的子问题。

参数和结果传递的问题

子程序的三个重要信息

  1. 程序的功能
  2. 参数
  3. 结果(返回值)

那么这里我们应该将参数和结果放在什么位置?

用寄存器来存储参数和结果是最常用的方法

明确程序调用的参数传递问题:

调用者将参数送入参数寄存器,从结果寄存器取得返回值;子程序从参数寄存器取得参数,将返回值送入到结果寄存器。

批量数据传递

如果子程序需要多个参数,那么就可以用到内存中的地址。

用栈传递参数

结合c语言的函数调用,看一下栈传递参数的思想:

将参数全部压入栈,然后将IP压入,此是栈顶是IP,再压入一个存放返回值的寄存器。函数体结束后,先弹出返回值,再弹出IP,将栈清空即可。

在这里插入图片描述
在这里插入图片描述

寄存器冲突问题

如果子程序调用时,寄存器不够用,可以用栈来保存。

在这里插入图片描述

实验10:编写子程序

第11章:标志寄存器

标志寄存器的作用:

  1. 用来存储相关指令的某些执行结果
  2. 用来为CPU执行相关指令提供行为依据
  3. 用来控制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
  1. 如果sf = 1,而of = 0

    of为0,也就是没有溢出,你期望的结果 = 实际的结果

    sf = 1,ah - bh < 0,所以ah < bh

  2. 如果sf = 1, 而of = 1

    of为1,也就是溢出了,你期望的结果和实际的结果相反

    sf = 1实际的结果为负,你期望的结果为正,就是说本身而言ah > bh

  3. 如果sf = 0, 而of = 0

    of为0,没有溢出,实际与期望相同。

    sf = 0,ah >= bh

  4. 如果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以下几种情况会产生中断:

  1. 除法错误,中断类型码:0
  2. 单步执行, 中断类型码:1
  3. 执行into指令, 中断类型码:4
  4. 执行int指令, int n 指令中的n为字节型立即数,是提供给CPU的中断类型码。

中断信息中必须包含识别来源的编码。8086CPU用中断类型码的数据来标识中断信息的来源。

中断类型码为一个字节型数据,可以表示256种中断信息的来源。

中断信息的来源,简称为中断源。上面就是4种中断源。

中断处理程序

CPU在收到中断信息后,就应该转去执行该中断信息的处理程序。但是如何定位这个中断处理程序呢?

中断向量表

中断向量,就是中断处理程序的入口地址。

中断向量表:就是中断处理程序 入口地址的列表

中断向量表在内存中存放,对于8086pc机,中断向量表指定放在内存地址0处,从0000:0000到0000:03ff的1024个单堍存放着中断向量表。

N号中断源对应的中断处理程序入口的偏移地址的内存单元地址:4N

N号中断源对应的中断处理程序入口的段地址的内存单元地址:4N + 2

中断类型码作为中断向量表中的表项号来查找表项,再当相应的程序入口去

在中断向量表中,一个表项中两个字:高地址字存放段地址,低地址字存放偏移地址。

中断过程

  1. (从中断信息中)取得中断类型码
  2. 标志寄存器的值入栈
  3. 设置标志寄存器的第8位TF和第9位IF的值为0
  4. CS的内容入栈
  5. IP的内容出栈
  6. 从内存地址为中断类型码*4和中断类型码*+2的两个字单元中读取中断处理程序的入口地址设置CS和IP

中断处理程序和iret指令

中断处理程序应该在内存中,而中断处理程序的入口地址应该在中断向量表表项中。

中断程序与子程序有点像:

  1. 保存用到的寄存器
  2. 处理中断
  3. 恢复用到的寄存器
  4. 用iret指令返回
iret可以理解为
pop ip
pop cs
popf

编程处理0号中断

重新定义0号中断的中断处理程序:

  1. 向量表中断源对应中断处理程序
  2. 中断处理程序写在哪?
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

中断后:

  1. 程序将do0拷到内存0:200
  2. 将do0的入口地址放入向量表中
  3. 运行do0打印字符串

单步中断

CPU执行完一条指令之后,如果检测到标志寄存器的TF位为1,则产生单步中断,中断类型码为1

  1. 取中断类型码1
  2. 标志寄存器入栈,TF、IF设置为0
  3. CS、IP入栈
  4. 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章)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(53)

评论列表共有 0 条评论

立即
投稿
返回
顶部