汇编语言 第四版 王爽 全书汇总笔记

第一章 基础知识

汇编语言的三类指令

1. 汇编指令

汇编指令是直接被CPU执行的指令,它们在汇编时被转换为对应的机器码。主要包括:

  • 数据传送指令:如MOV,用于在寄存器、内存和I/O端口之间传送数据。

    MOV AX, BX ; 将BX中的数据传送到AX
  • 算术与逻辑指令:如ADDSUB,用于进行数学计算和逻辑操作。

    ADD AX, BX ; 将BX中的数据加到AX中
  • 控制转移指令:如JMPCALL,用于改变程序执行流程。

    JMP LABEL ; 无条件跳转到LABEL标签
2. 伪指令

伪指令是指导汇编器工作的指令,它们在汇编过程中被处理,但不生成机器码。主要包括:

  • 段定义指令:如SEGMENTENDS,用于定义和结束一个段。

    DATA SEGMENT
  • 数据定义指令:如DBDW,用于定义字节或字数据。

    DB 0x55 ; 定义一个字节数据0x55
  • 常量定义指令:如EQU,用于定义常量。

    MAXLEN EQU 255 ; 定义常量MAXLEN为255
3. 符号指令

符号指令用于标识变量、常量、代码段等,在汇编过程中被替换为具体的地址或值。它们没有对应的机器码。

  • 标签:用于标识代码中的位置。

    LABEL: ; 定义一个标签LABEL
  • 宏定义:如MACRO,用于定义宏,简化代码编写。

    MYMACRO MACRO ; 宏定义内容 ENDM

CPU对存储器的读写

存储器结构与存储单元

存储器(Memory)由多个存储单元(Memory Cell)组成,每个存储单元都有唯一的地址(Address),用于存储数据(Data)。存储器可以分为两大类:

  • 随机存取存储器(RAM):可读可写,用于存储临时数据。RAM是易失性存储器,断电后数据会丢失。
  • 只读存储器(ROM):只能读取,用于存储固件和不可变数据。ROM是非易失性存储器,断电后数据仍然保存。

每个存储单元通常存储一个字节(8位)或一个字(16位)的数据,存储单元按照地址从零开始顺序编号。CPU通过地址访问这些存储单元中的数据。

总线系统

CPU通过总线(Bus)与存储器进行通信。总线系统包括:

地址总线

数据总线

数据总线是一组用于在CPU、内存和外设设备之间传输数据的信号线。数据总线的宽度决定了每次传输的数据量。例如,32位数据总线一次可以传输32位(4字节)的数据。

控制总线

控制总线是一组用于传输控制信号的信号线,这些信号用于协调和管理系统各部分之间的操作。控制信号包括读/写命令、时钟信号、中断请求等。

  • 地址总线是一组用于传输存储单元地址的信号线。它的主要作用是指定内存或外设设备中的位置,以便CPU可以读写数据。地址总线的宽度决定了CPU可以访问的最大内存空间。例如,16位地址总线可以访问2^16 = 65,536个存储单元。

  • 功能:传输内存地址。
  • 影响:地址总线的宽度决定了CPU可以寻址的内存空间范围。例如,32位地址总线可以寻址4GB的内存空间。
  • 方向:地址总线一般是单向的(寻址信息通常是从CPU发出)
  • 功能:传输数据,包括指令和数据字。
  • 影响:数据总线的宽度影响了系统的数据传输速率和性能。例如,64位数据总线相比于32位数据总线,可以一次传输更多的数据,从而提高系统性能。
  • 方向:数据总线是双向的,因为数据既可以从CPU发送到内存或I/O设备,也可以从内存或I/O设备发送到CPU。
  • 功能:传输控制信号以管理和协调系统操作。
  • 影响:控制总线的效率和设计直接影响系统的整体性能和稳定性。它们确保数据和地址的传输按照正确的时序进行。
  • 方向:控制总线是双向的,因为控制信号既可以从CPU发送到其他组件,也可以从其他组件发送到CPU。
读操作

读取存储器的过程可以分为以下几个步骤:

  1. 地址传输

    • CPU将要读取的数据地址放入地址总线。地址总线的宽度决定了CPU能够访问的最大内存地址范围。
      MOV AX, [1234H] ; 读取内存地址1234H的数据到AX寄存器
  2. 控制信号发送

    • CPU通过控制总线发送读命令(Read Command)到存储器。控制信号通知存储器准备好数据。
      ; 控制总线发送读信号
  3. 数据传输

    • 存储器接收到读命令后,将指定地址的数据放入数据总线。数据总线的宽度决定了一次能够传输的数据量。
      ; 数据总线传输数据
  4. 数据接收

    • CPU从数据总线读取数据,并将其存入指定的寄存器或内存位置。
      ; CPU从数据总线接收数据并存入AX
写操作

写入存储器的过程可以分为以下几个步骤:

  1. 地址传输

    • CPU将要写入数据的地址放入地址总线。
      MOV [1234H], AX ; 将AX寄存器的数据写入内存地址1234H
  2. 数据传输

    • CPU将要写入的数据放入数据总线。
      ; 数据总线传输要写入的数据
  3. 控制信号发送

    • CPU通过控制总线发送写命令(Write Command)到存储器。控制信号通知存储器接收数据。
      ; 控制总线发送写信号
  4. 数据存储

    • 存储器接收到写命令后,将数据总线上的数据写入指定地址的存储单元。
      ; 存储器将数据写入指定地址

内存地址空间的分配

在计算机体系结构中,地址总线是单向的,即CPU向内存或I/O设备发送地址信息。CPU通过逻辑地址空间访问物理存储器,当CPU在某个地址空间中执行读写操作时,实际上是在相应的物理存储器单元中进行这些操作(对ROM写无效,ROM是只读)

8086PC内存地址空间的分配
  • 主存储器地址空间 (RAM)

    • 地址范围:00000h 到 9FFFFh
    • 描述:这是系统的主内存区域,用于存放运行中的程序和数据。
  • 显示地址空间

    • 地址范围:A0000h 到 BFFFFh
    • 描述:这是显示卡的显存地址空间。当向这个区域的内存单元中写数据时,实际上是向显示存储器中写入数据,这些数据会被显示卡输出到显示器上。
    • 详细说明:在这个区域内,A0000h 到 AFFFFh 通常用于EGA/VGA显卡的显存,B0000h 到 B7FFFh 用于单色显示器(MDA),B8000h 到 BFFFFh 用于彩色显示器(CGA)。
  • 各类ROM地址空间

    • 地址范围:C0000h 到 FFFFFh
    • 描述:这部分内存用于存储只读存储器(ROM)中的数据,包括BIOS程序和扩展卡的固件等。
    • 详细说明:C0000h 到 C7FFFh 一般用于显示适配器的BIOS,E0000h 到 FFFFFh 则是系统BIOS区域。

CPU 与不同类型设备交互

“CPU 与所有设备(包括内存和外设)的交互,其主要区别在于访问的地址空间不同。CPU 通过不同的地址空间(内存地址空间、I/O 地址空间或内存映射 I/O 地址空间)与这些设备进行数据传输和控制操作。 

示例一:CPU 与 RAM 交互
  1. 读操作

    • 发送地址:CPU 将要读取的内存地址(例如 0x0000)放在地址总线上。
    • 控制信号:CPU 通过控制总线发送读命令。
    • 数据传输:RAM 控制器读取地址 0x0000 处的数据,并通过数据总线传送给 CPU。假设返回的数据是 0x1234。
    CPU -> 地址总线 -> 0x0000
    CPU -> 控制总线 -> 读命令
    RAM -> 数据总线 -> 0x1234
    
  2. 写操作

    • 发送地址和数据:CPU 将要写入的内存地址(例如 0x0000)和数据(例如 0x5678)放在地址总线和数据总线上。
    • 控制信号:CPU 通过控制总线发送写命令。
    • 数据写入:RAM 控制器将数据 0x5678 写入地址 0x0000。
    CPU -> 地址总线 -> 0x0000
    CPU -> 数据总线 -> 0x5678
    CPU -> 控制总线 -> 写命令
    RAM <- 数据总线 <- 0x5678
    
示例二:CPU 与显卡显存交互(内存映射 I/O)
  1. 写操作

    • 发送地址和数据:CPU 将要写入的显存地址(例如 0xA0000)和数据(例如图像数据)放在地址总线和数据总线上。
    • 控制信号:CPU 通过控制总线发送写命令。
    • 数据写入:显卡控制器将数据写入显存的指定地址 0xA0000。这些数据将被显示在屏幕上。
    CPU -> 地址总线 -> 0xA0000
    CPU -> 数据总线 -> 图像数据
    CPU -> 控制总线 -> 写命令
    显存 <- 数据总线 <- 图像数据
    
示例三:CPU 与键盘交互(I/O 端口映射)
  1. 读操作

    • 发送 I/O 端口地址:CPU 将要读取的 I/O 端口地址(例如 0x60)放在地址总线上。
    • 控制信号:CPU 通过控制总线发送读命令。
    • 数据传输:键盘控制器读取按键数据,并通过数据总线传送给 CPU。假设返回的按键数据是 0x1E(代表某个按键)。
    CPU -> 地址总线 -> 0x60
    CPU -> 控制总线 -> 读命令
    键盘控制器 -> 数据总线 -> 0x1E
    

第2章寄存器

0. 典型的CPU结构

典型的CPU由运算器、控制器、寄存器等器件构成,这些器件通过片内总线相连。

  • 运算器:进行信息处理
  • 控制器:控制各种器件进行工作
  • 寄存器:进行信息存储

1. 8086 CPU的寄存器

8086 CPU有14个16位寄存器:

  • 通用寄存器:AX、BX、CX、DX
  • 索引寄存器:SI、DI
  • 指针寄存器:SP、BP、IP
  • 段寄存器:CS、SS、DS、ES
  • 状态寄存器:PSW

16位结构CPU的特性

  • 运算器一次最多可以处理16位的数据
  • 寄存器的最大宽度为16位
  • 寄存器和运算器之间的通路为16位

数据处理

  • 字节 (byte):8位,可以存在8位寄存器中
  • 字 (word):16位,由两个字节组成,可以存在一个16位寄存器中

存储模式

8086采用小端模式:高地址存放高位字节,低地址存放低位字节。

2. 寄存器

16位寄存器

这些寄存器一次可以存储一个16位的数据(一个字)。

  • AX、BX、CX、DX:这些是通用的16位寄存器。
    • AX:累加器寄存器(Accumulator Register)
    • BX:基址寄存器(Base Register)
    • CX:计数器寄存器(Count Register)
    • DX:数据寄存器(Data Register)

8位寄存器

这些寄存器是上面16位寄存器的一部分,可以分别存储8位的数据(一个字节)。

  • AHAL:高8位和低8位组成AX寄存器。
  • BHBL:高8位和低8位组成BX寄存器。
  • CHCL:高8位和低8位组成CX寄存器。
  • DHDL:高8位和低8位组成DX寄存器。

3. 物理地址生成

8086 CPU有20位地址总线,可以传送20位地址,达到1MB寻址能力。然而,内部一次性处理、传输、暂时存储的地址为16位。8086 CPU通过在内部用两个16位地址合成的方法来形成一个20位的物理地址:

  • 物理地址 = 段地址 × 16 + 偏移地址

例如,8086 CPU要访问地址为123C8H的内存单元:
1230H左移一位(空出4位)加上00C8H合成123C8H。

4. 段寄存器

通过将内存划分为段,用段地址指示段,用偏移地址访问段内的单元,可以用分段的方式来管理内存。段寄存器有4个:CS、DS、SS、ES,提供内存单元的段地址。

段的定义

  • 数据段:用来存放数据
  • 代码段:用来存放代码
  • 栈段:用来存放栈

段的起始地址

一个段的起始地址一定是16的倍数,偏移地址为16位,变化范围为0-FFFFH,所以一个段的长度最大为64KB。

5. 指令执行

CS和IP

CS为代码段寄存器,IP为指令指针寄存器。CPU将CS、IP中的内容当作指令的段地址和偏移地址,用它们合成指令的物理地址,然后执行指令。8086 CPU的工作过程简要描述如下:

  1. 从CS指向的内存单元读取指令,读取的指令进入指令缓冲器
  2. IP = IP + 读取指令的长度,从而指向下一条指令
  3. 执行指令
  4. 重复上述过程

JMP指令

能够改变CS、IP内容的指令被统称为转移指令,例如:

  • jmp 段地址:偏移地址:用指令中给出的段地址修改CS,偏移地址修改IP
  • jmp 某一合法寄存器:仅修改IP的内容,如jmp ax

第三章 寄存器(内存访问)

3.1 内存中字的存储


内存中的字是如何存储的呢?这里举一个例子:
CPU中使用16位寄存器存储一个字单元,其中高八位存放高位字节,低八位存放低位字节。

我们拿0—3一共四个内存空间存放数据20000(4E20H)和18(0012H) 

上图的0,1,2,在这里插入图片描述3……就是能访问到的地址,用16进制来表示一个字节,比如20H,4EH等等。                          由于内存单元是字节单元,则一个字就要用两个连续的内存单元来存。                                      图中0、1两个内存单元存放数据4E20H,0是低地址单元,1是高地址单元。                                      高地址内存单元中存放字型数据的高位字节,低地址内存单元存放字型数据的低位字节。                                                                      以              起始地址为N的字单元简称N地址字单元。

在这里插入图片描述

3.2 DS和address

在8086CPU中,DS(数据段寄存器)和 CS(代码段寄存器)是两种重要的段寄存器

主要区别

  • DS(数据段寄存器)

    • 用于存储数据段的基地址。
    • 数据段用于存放程序的数据(如变量、数组等)。
    • 内存访问指令默认使用DS寄存器来确定数据段的基地址。
  • CS(代码段寄存器)

    • 用于存储代码段的基地址。
    • 代码段用于存放程序的指令(代码)。
    • CPU在执行指令时使用CS寄存器来确定代码段的基地址

3.3 字的传送

8086CPU是16位结构,有16根数据线,一次可以传送16位的数据,也就是一个字(word)。

  • 一个字需要两个连续的内存单元来存储。
  • 低位字节存放在低地址单元,高位字节存放在高地址单元。
  • 字单元由两个连续的内存单元组成。例如,地址为N的字单元,N地址存放低位字节,N+1地址存放高位字节。

3.4 mov、add、sub指令

这三个指令都有两个操作对象。

mov 

mov指令不会将内存地址本身传送到寄存器,而是传送内存地址中存储的数据。

1. mov ax, [1234H]
  • 作用:将内存地址 DS:1234H 中的数据传送到 AX 寄存器。
  • 解释:这条指令表示从内存地址 1234H(相对于数据段寄存器 DS 的偏移量)中读取数据,然后将这个数据存储到 AX 寄存器中。
  • 示例
    mov ax, [1234H] ; 将内存地址DS:1234H中的数据传送到AX寄存器
2. mov ax, 1234H
  • 作用:将立即数 1234H 传送到 AX 寄存器。
  • 解释:这条指令表示将立即数 1234H 直接赋值给 AX 寄存器,而不涉及任何内存访问。
  • 示例
mov ax, 1234H        ; mov 寄存器, 数据
mov bx, ax           ; mov 寄存器, 寄存器
mov ax, [1234H]      ; mov 寄存器, 内存单元
mov [1234H], ax      ; mov 内存单元, 寄存器
mov ds, ax           ; mov 段寄存器, 寄存器
add 
add ax, 10213            ; add 寄存器, 数据  (需要转化为十六进制再加)
add ax, bx           ; add 寄存器, 寄存器
add ax, [1234H]      ; add 寄存器, 内存单元
add [1234H], ax      ; add 内存单元, 寄存器
sub
sub ax, 1            ; sub 寄存器, 数据
sub ax, bx           ; sub 寄存器, 寄存器
sub ax, [1234H]      ; sub 寄存器, 内存单元
sub [1234H], ax      ; sub 内存单元, 寄存器

3.5 数据段

我们可以根据需要,将一组长度小于64KB、地址连续、起始地址为16的倍数的内存单元定义为一个数据段,用来作为专门存放数据的内存空间

3.6 栈

栈是一种先进后出的存储数据结构。从程序的角度来说,有一个标记,一直指向栈顶元素。

3.7 CPU提供的栈机制

8086CPU提供入栈和出栈的指令,分别是push(入栈)和pop(出栈),在编程中可以将一个内存段看作栈来使用。

  • push ax:将寄存器AX中的数据送入栈中。
  • pop ax:将栈顶的数据取出送入AX中。这两个操作的基本单位都是字(16位)。

在8086CPU中,段寄存器SS(栈段寄存器)和寄存器SP(栈指针)一起使用,SS存放栈段的基地址,SP存放栈顶的偏移地址,任意时刻,SS指向栈顶元素。

3.8 栈顶超界的问题

编程中根据可能用到的最大栈空间,来安排栈的大小,防止入栈的数据导致超界,出栈时也要注意,防止栈空时继续出栈导致的超界。

栈空间之外的空间里很可能存放了其他用途的数据、代码等,如果入栈出栈超界,导致这些代码或数据改变,会造成严重的后果。

3.9 push、pop指令

Push和pop指令后可以加寄存器、段寄存器、内存单元等。确定栈时需要初始化栈顶,使用mov ss, ax指令。

Push和pop指令访问的内存空间的地址不是在指令中给出的,而是由SS

指出的,同时也修改SP(存储偏移地址)中的内容。

  • push:SP = SP - 2
  • pop:SP = SP + 2

栈为空时,SS:SP指向最底部单元(eg. 10000H ~ 1000FH    初始SS:SP= 1000:0010)

3.10 栈段

问题一:

如果我们将10000H~1FFFFH这段空间当做栈段,初始状态是空的,此时,SS=1000H,SP=?

换位思考:

栈为空,相当于栈中唯一的元素出栈,出栈后,SP=SP+2。

SP原来为FFFEH,加2后SP=0,所以,当栈为空的时候,SS=1000H,SP=0。

问一个栈段最大可以设为多少?:

所以栈顶的变化范围是0~FFFFH,所以一个栈段的容量最大是64KB

(容量为10000H,即 2^16 btye = 65536 btye  = 64KB)

第四章 第一个汇编程序

4.1 一个源程序从写出到执行的过程

1.编写汇编语言(用编辑器进行汇编语言的编写)

2.对源程序进行编译连接(对源程序进行编译,产生目标文件;再连接程序对目标文件进行连接,生产可执行文件)

3.执行可执行文件中的程序(执行文件需要将机器码和数据载入内存,然后CPU执行)

4.2 源程序

例如源程序:

1.伪指令

汇编语言中的两种指令:伪指令,汇编指令。

汇编指令有对应的机器码,伪指令没有对应机器码不被CPU执行而被编译器执行。

  A.××× segment·····×××ends是成对出现的伪指令,用来定义一个代码段。

  B.end(不是ends)是一个汇编程序结束的标志。

  C.assume(假设)功能:将指定用途的段和相关的段寄存器关联起来。比如上面的代码将cs和定义的代码段codesg联系起来。

2.源程序中的“程序”

  这里的源程序指最终由计算机执行、处理的指令或数据(最后的二进制机器码)。

3.程序的结构

1首先是定义段 abc segment ·····abc  ends,

2在里面写汇编指令把·····换成汇编指令,

3指出何处结束end,

4用assume将段寄存器与abc联系起来。

4.程序的返回

  举例:一个p2程序要执行必须有一个正在执行的p1程序把p2加载入内存后,才能将CPU交给p2,p2随之运行,p2结束后返回控制权给p1                                                                  这个返回过程就是:程序返回     

mov ax,4c00H  

int 21H

这两条指令实现的功能就是程序返回。

5.语法错误和逻辑错误

语法错误就是编译器发现的错误

运行的结果与预期的不同就是逻辑错误。

4.3 汇编程序从写出到执行过程

编程- >(.asm)

- 编译  -> (.obj)

- 连接(Link) -> (.exe)

- 加载(command) -> 内存中程序 -> CPU运行

4.4 跟踪程序执行过程

不同的加载方式:

command加载内存,运行程序(无法逐条查看指令执行过程,放弃了对CPU的控制)

利用debug加载内存,设置CS:IP指向程序入口(可以单步执行程序,不放弃对CPU的控制)

 内存分布

PSP( 0 ~ 255):        程序段前缀的数据区(利用PSP与被加载的程序进行通信)

程序区(SA (DS)+ 10H:0 )

存入内存区段地址到DS,初始化其他相关寄存器后。设置CS:IP指向程序入口

第五章 [BX]和loop指令

1、[bx] 和 loop指令


[bx] 的含义:

[bx]同样表示一个内存单元,它的偏移地址在bx中,段地址默认在ds中

 loop指令的格式是:loop 标号

CPU执行loop指令的时候,要进行两步操作:

(cx) = (cx) - 1;

判断 cx 中的值,不为零则转至标号处执行程序,如果为零则向下执行。

例如:计算2^{12}

assume cs:code 

code segment 
	mov ax, 2
	
	mov cx, 11 ;循环次数
s:  add ax, ax 
	loop s     ;在汇编语言中,标号代表一个地址,标号s实际上标识了一个地址,
               ;这个地址处有一条指令:add ax,ax。
               ;执行loop s时,首先要将(cx)减1,然后若(cx)不为0,则向前
               ;转至s处执行add ax,ax。所以,可以利用cx来控制add ax,ax的执行次数。
	
	mov ax,4c00h 
	int 21h 
code ends 
end


2,loop 和 [bx] 的联合应用

计算ffff:0 ~ ffff:b单元中的数据的和,结果存储在dx中

问题分析:

这些内存单元都是字节型数据范围0 ~ 255 ,12个字节数据和不会超过65535,dx可以存下
对于8位数据不能直接加到 dx
解决方案:

用一个16位寄存器来做中介。将内存单元中的8位数据赋值到一个16位寄存器a中,再将ax中的数据加到dx

assume cs:code 

code segment 
	mov ax, 0ffffh ;在汇编源程序中,数据不能以字母开头,所以要在前面加0。
	mov ds, ax 
	mov bx, 0   ;初始化ds:bx指向ffff:0
	mov dx, 0   ;初始化累加寄存器dx,(dx)= 0
	
	mov cx, 12  ;初始化循环计数寄存器cx,(cx)= 12
s:  mov al, [bx]
	mov ah, 0
	add dx, ax  ;间接向dx中加上((ds)* 16 +(bx))单元的数值
	inc bx      ;ds:bx指向下一个单元
	loop s 
	
	mov ax, 4c00h 
	int 21h 
code ends 
end


3、段前缀

mov ax, ds:[bx]
mov ax, cs:[bx]
mov ax, ss:[bx]
mov ax, es:[bx]
mov ax, ss:[0]
mov ax, cs:[0]


这些出现在访问内存单元的指令中,用于显式地指明内存单元的段地址
的“ds:”,“cs:”,“ss:”,“es:”,在汇编语言中称为段前缀。

段前缀的使用

将内存ffff:0 ~ ffff:b单元中的数据复制到0:200 ~ 0:20b单元中。

1.DL和DS的关系
  • EDX 是一个 32 位寄存器,可以分为多个部分:EDX(32 位),DX(16 位),DH(高 8 位)和 DL(低 8 位)。
  • 操作 DL 只会影响 EDX 寄存器的低 8 位,不会影响 EDX 的其他部分,如 DH 或者整个 DX(16 位)。

2. ES寄存器

ES(Extra Segment)寄存器是 x86 架构中的一个 16 位段寄存器,主要用于字符串操作和数据传输。它通常与 DI(目的索引)寄存器结合使用,以指向附加的数据段。常见用法包括字符串指令(如 MOVSBCMPSB)中的目标段地址,帮助在内存中进行数据移动和比较。

assume cs:code 

code segment 
	mov ax, 0ffffh 
	mov ds, ax   ;(ds)= 0ffffh 
	mov ax, 0020h
    mov es, ax   ;(es)= 0020h     0:200 等效于 0020:0
    mov bx, 0    ;(bx)= 0,此时ds:bx指向ffff:0,es:bx指向0020:0
    
	mov cx,12   ;(cx)=12,循环12次
s:  mov dl,[bx] ;(d1)=((ds)* 16+(bx)),将ffff:bx中的字节数据送入dl 
	mov es:[bx],dl ;((es)*16+(bx))=(d1),将dl中的数据送入0020:bx 
	inc bx  ;(bx)=(bx)+1
	loop s 
	
	mov ax,4c00h 
	int 21h 
code ends 
end

注意

1.汇编源程序中,数据不能以字母开头

如题如果在源码中的数据是以字母开头的数据,则编译器会报错

例如如下代码,执行编译

assume cs:code
code segment
start:
	mov ax,ffffH
code ends
end start

执行编译会有如下报错: 

第6章 包含多个段的程序

在代码段中使用数据 

dw定义的数据位于代码段最开始,偏移地址为:0

这些数据位于cs:0   cs:2    ...                        cs:E   

;计算 8 个数据的和存到 ax 寄存器
assume cs:code 

code segment 

	dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h ;define word 定义8个字形数据      

	start:	mov bx, 0  ;标号start
			mov ax, 0  
			
			mov cx, 8
	s:		add ax, cs:[bx]
			add bx, 2
			loop s 
			
			mov ax, 4c00h 
			int 21h 
code ends
end start    ;end除了通知编译器程序结束外,还可以通知编译器程序的入口在什么地方
	     	 ;用end指令指明了程序的入口在标号start处,也就是说,“mov bx,0”是程序的第一条指令。

 在代码段中使用栈

;利用栈,将程序中定义的数据逆序存放。
assume cs:codesg 

codesg segment 
	dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h ; 0-15单元
	dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ; 16-47单元作为栈使用
			
	start:	mov ax, cs 
			mov ss, ax 
			mov sp, 30h ;将设置栈顶ss:sp指向栈底cs:30。   30h = 48d
			mov bx, 0
			
			mov cx, 8
	s:		push cs:[bx]
			add bx, 2
			loop s    ;以上将代码段0~15单元中的8个字型数据依次入栈
			
			mov bx, 0
			
			mov cx, 8
	s0:		pop cs:[bx]		
			add bx,2
			loop s0   ;以上依次出栈8个字型数据到代码段0~15单元中
			
			mov ax,4c00h 
			int 21h 
codesg ends 
end start	;指明程序的入口在start处

 将数据、代码、栈放入不同的段

1.名称为“data”的段名,相当于标号

程序对段名的引用,eg.  mov ds,data(错误的)   data被编译器处理为一个表示段地址的数值 

assume cs:code,ds:data,ss:stack 

data segment 
	dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h ;0-15单元
data ends 

stack segment 
	dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ;0-31单元
stack ends 

code segment 
	start:	mov ax, stack;将名称为“stack”的段的段地址送入ax
			mov ss, ax
			mov sp, 20h  ;设置栈顶ss:sp指向stack:20。 20h = 32d
			
			mov ax, data ;将名称为“data”的段的段地址送入ax
			mov ds, ax   ;ds指向data段
			
			mov bx, 0    ;ds:bx指向data段中的第一个单元
			
			mov cx, 8
	s:	    push [bx]
			add bx, 2
			loop s       ;以上将data段中的0~15单元中的8个字型数据依次入栈
			
			mov bx, 0
			
			mov cx, 8
	s0:		pop [bx]
			add bx, 2
			loop s0      ;以上依次出栈8个字型数据到data段的0~15单元中
			
			mov ax, 4c00h 
			int 21h 
code ends
end start
;“end start”说明了程序的入口,这个入口将被写入可执行文件的描述信息,
;可执行文件中的程序被加载入内存后,CPU的CS:IP被设置指向这个入口,从而开始执行程序中的第一条指令

 第七章

1.指令

and指令:逻辑与指令,按位进行与运算。

mov al, 01100011B
and al, 00111011B

执行后:al=00100011B即都为1才为1

or指令:逻辑或指令,按位进行或运算。

mov al, 01100011B
or al, 00111011B

执行后:al=01111011B 即只要有一个为1就为1

2.(ASCLL)以字符形式给出的数据 

assume cs:code,ds:data 

data segment 
	db 'unIx'   ;相当于“db 75H,6EH,49H,58H”
	db 'foRK'
data ends 

code segment
start:	mov al, 'a'  ;相当于“mov al, 61H”,“a”的ASCI码为61H;
		mov b1, 'b'
		
		mov ax, 4c00h 
		int 21h 
code ends
end start

大小写转换的问题
在这里插入图片描述

小写字母的ASCII码值比大写字母的ASCII码值大20H

大写字母ASCII码的第5位为0,小写字母的第5位为1(其他一致)

3、不同的寻址方式的灵活应用


1.[idata]用一个常量来表示地址,可用于直接定位一个内存单元;


2.[bx]用一个变量来表示内存地址,可用于间接定位一个内存单元;


3.[bx+idata]用一个变量和常量表示地址,

可在一个起始地址的基础上用变量间接定位一个内存单元;


4.[bx+si]用两个变量表示地址;


5.[bx+si+idata]用两个变量和一个常量表示地址。

 示例代码:将datasg段中每个单词改为大写字母


;将datasg段中每个单词改为大写字母
assume cs:codesg,ds:datasg,ss:stacksg 

datasg segment
	db 'ibm            ' ;16
	db 'dec            ' 
	db 'dos            '
	db 'vax            '  ;看成二维数组
datasg ends 

stacksg segment ;定义一个段,用来做栈段,容量为16个字节
	dw 0, 0, 0, 0, 0, 0, 0, 0
stacksg ends 

codesg segment 
	start:	mov ax, stacksg 
			mov ss, ax
			mov sp, 16 
			mov ax, datasg 
			mov ds, ax 
			mov bx, 0 ;初始ds:bx
			
			;cx为默认循环计数器,二重循环只有一个计数器,所以外层循环先保存cx值,再恢复,我们采用栈保存
			mov cx, 4
	s0:		push cx	;将外层循环的cx值入栈
			mov si, 0
			mov cx, 3	;cx设置为内层循环的次数
	s:		mov al, [bx+si]
			and al, 11011111b ;每个字符转为大写字母
			mov [bx+si], al 
			inc si
			loop s 
			
			add bx, 16 ;下一行
			pop cx	;恢复cx值
			loop s0 ;外层循环的loop指令将cx中的计数值减1
			
			mov ax,4c00H 
			int 21H 
codesg ends
end start

第八章 数据处理的两个基本问题

1、 bx、si、di和bp


在8086CPU中,只有这4个寄存器可以用在“[…]”中来进行内存单元的寻址。

在[ ]中,这4个寄存器可以单个出现,

或只能以4种组合出现:bx和si、bx和di、bp和si、bp和di。

只要在[……]中使用寄存器bp,而指令中没有显性地给出段地址, 段地址就默认在ss中

2、机器指令处理的数据在什么地方

数据处理大致可分为3类:读取、写入、运算。

在机器指令这一层来讲,并不关心数据的值是多少,而关心指令执行前一刻,它将要处理的数据所在的位置

指令在执行前,所要处理的数据可以在3个地方:CPU内部、内存、端口


3、汇编语言中数据位置的表达


汇编语言中用3个概念来表达数据的位置

立即数(idata)

mov ax, 1                 ;对于直接包含在机器指令中的数据(执行前在CPU的指令缓冲器中)
add bx, 2000h             ;在汇编语言中称为:立即数(idata)
or bx, 00010000b
mov al, 'a'

寄存器

mov ax, bx     ;指令要处理的数据在寄存器中,在汇编指令中给出相应的寄存器名。
mov ds, ax 
push bx 
mov ds:[0], bx 
push ds 
mov ss, ax
mov sp, ax

段地址和偏移地址

;指令要处理的数据在内存中,在汇编指令中可用[X]的格式给出EA,SA在某个段寄存器中。
mov ax, [0]
mov ax, [di]
mov ax, [bx+8]
mov ax, [bx+si]
mov ax, [bx+si+8]   ;以上段地址默认在ds中

mov ax, [bp]
mov ax, [bp+8]
mov ax, [bp+si]
mov ax, [bp+si+8]   ;以上段地址默认在ss中

mov ax, ds:[bp]
mov ax, es:[bx]
mov ax, ss:[bx+si]
mov ax, cs:[bx+si+8] ;显式给出存放段地址的寄存器

寻址方式

在这里插入图片描述

4. 指令要处理的数据有多长

8086CPU的指令,可以处理两种尺寸的数据,byte和word

通过寄存器名指明要处理的数据的尺寸。

有些指令默认了访问的是字单元还是字节单元
例如,push [1000H],push 指令只进行字操作。

 5、寻址方式的综合应用

 在这里插入图片描述

mov ax, seg 
mov ds, ax 
mov bx, 60h   ;确定记录地址,ds:bx 

mov word ptr [bx+0ch], 38   ;排名字段改为38  [bx].0ch
add word ptr [bx+0eh], 70   ;收入字段增加70  [bx].0eh
mov si, 0   ;用si来定位产品字符串中的字符
mov byte ptr [bx+10h+si], 'V'   ;[bx].10h[si]
inc si 
mov byte ptr [bx+10h+si], 'A'
inc si 
mov byte ptr [bx+10h+si], 'X'
/*定义一个公司记录的结构体*/
struct company
{
    char cn[3];/*公司名称*/
    char hn[9];/*总裁姓名*/
    int pm;/*排名*/
    int sr;/*收入*/
    char cp[3];/*著名产品*/
};
//sizeof (struct company) == 24

int main()
{
    /*定义一个公司记录的变量,内存中将存有一条公司的记录*/
    struct company dec = {"DEC", "Ken Olsen", 137, 40, "PDP"};

    int i;

    dec.pm = 38;
    dec.sr = dec.sr + 70;

    i = 0;
    dec.cp[i] = 'V'; //mov byte ptr [bx].10h[si], 'V'
    i++;
    dec.cp[i] = 'A';
    i++;
    dec.cp[i] = 'X';

    return 0;
}

6、div指令、dd、dup、mul指令


div是除法指令

除数:有8位和16位两种,在一个寄存器或内存单元中。

被除数:默认放在AX或DX和AX中,
如果除数为8位,被除数则为16位,默认在AX中存放;
如果除数为16位,被除数则为32位,在DX和AX中存放,DX存放高16位,AX存放低16位。

结果:
如果除数为8位,则AL存储除法操作的商,AH存储除法操作的余数;
如果除数为16位,则AX存储除法操作的商,DX存储除法操作的余数。

;利用除法指令计算100001/100。
;100001D = 186A1H
mov dx, 1
mov ax, 86A1H ;(dx)*10000H+(ax)=100001
mov bx, 100
div bx

;利用除法指令计算1001/100
mov ax, 1001
mov bl, 100
div b1


伪指令dd

db和dw定义字节型数据和字型数据。

dd是用来定义dword(double word,双字)型数据的伪指令

操作符dup

dup在汇编语言中同db、dw、dd等一样,也是由编译器识别处理的符号。
它和db、dw、dd等数据定义伪指令配合使用,用来进行数据的重复

db 3 dup (0)       ;定义了3个字节,它们的值都是0,相当于db 0,0,0。
db 3 dup (0, 1, 2) ;定义了9个字节,它们是0、1、2、0、1、2、0、1、2,相当于db 0,1,2,0,1,2,0,1,2。
db 3 dup ('abc', 'ABC') ;定义了18个字节,它们是abcABCabcABCabcABCC,相当于db 'abc', 'ABC' ,'abc' , 'ABC, 'abc', 'ABC'。

第九章  

可以修改IP或者同时修改CS和IP的指令统称为转移指令

只修改IP的为段内转移,如:jmp ax

同时修改CS和IP的为段间转移,如:jmp 1000:0

段内转移又分为:短转移和近转移

短转移IP的修改范围为-128~127

近转IP的修改范围为-32768~32767

8086转移指令分类

· 无条件转移指令(如:jmp)

· 条件转移指令

· 循环指令(如:loop)

· 过程

· 中断

9.1 操作符offset

offset在汇编语言中是由编译器处理的符号,功能是取得标号的偏移地址

assume cs:codesg
codesg segment
	start:	mov ax,offset start		; 相当于mov ax,0
		s:	mov ax,offset s			; 相当于mov ax,3
codesg ends
end start

有如下程序段,将该程序中s处的一条指令复制到s0处 

assume cs:codesg
codesg segment
	s:	mov ax,bx			; 机器码占两个字节
		mov si,offset s
		mov di,offset s0
		mov ax,cs:[si]
		mov cs:[di],ax
	s0: nop			
		nop					; nop的机器码占两个字节
codesg ends
end s

9.2 jmp指令


jmp为无条件转移,转到标号处执行指令可以只修改IP,也可以同时修改CS和IP;

jmp指令要给出两种信息:

转移的目的地址
转移的距离(段间转移、段内短转移,段内近转移)


​ jmp short 标号         jmp near ptr 标号         jcxz 标号         loop 标号 等几种汇编指令,

它们对 IP的修改

是根据转移目的地址和转移起始地址之间的位移来进行的。在它们对应的机器码中不包含转移的目的地址,而包含的是到目的地址的位移距离

1、依据位移进行转移的jmp指令


jmp short 标号(段内短转移)

指令“jmp short 标号”的功能为(IP)=(IP)+8位位移,转到标号处执行指令

(1)8位位移 = “标号”处的地址 - jmp指令后的第一个字节的地址;

(2)short指明此处的位移为8位位移;

(3)8位位移的范围为-128~127,用补码表示

(4)8位位移由编译程序在编译时算出。

assume cs:codesg
codesg segment
  start:mov ax,0
        jmp short s ;s不是被翻译成目的地址
        add ax, 1
      s:inc ax ;程序执行后, ax中的值为 1 
codesg ends
end start

CPU不需要这个目的地址就可以实现对IP的修改。这里是依据位移进行转移 

读取和执行过程:

(CS)=0BBDH,(IP)=0006,上一条指令执行结束后CS:IP指向EB 03(jmp short s的机器码);


读取指令码EB 03进入指令缓冲器;


(IP) = (IP) + 所读取指令的长度 = (IP) + 2 = 0008,CS:IP指向add ax,1;


CPU指行指令缓冲器中的指令EB 03;


指令EB 03执行后,(IP)=000BH,CS:IP指向inc ax

jmp near ptr 标号 (段内近转移) 

指令“jmp near ptr 标号”的功能为:(IP) = (IP) + 16位位移。

2、转移的目的地址在指令中的jmp指令


jmp far ptr 标号(段间转移或远转移)

指令 “jmp far ptr 标号” 功能如下:

(CS) = 标号所在段的段地址;
(IP) = 标号所在段中的偏移地址。


far ptr指明了指令用标号的段地址和偏移地址修改CS和IP

assume cs:codesg
codesg segment
   start: mov ax, 0
		  mov bx, 0
          jmp far ptr  s ;s被翻译成转移的目的地址0B01 BD0B
          db 256 dup (0) ;转移的段地址:0BBDH,偏移地址:010BH
    s:    add ax,1
          inc ax
codesg ends
end start

3、转移地址在寄存器或内存中的jmp指令

jmp 16位寄存器 功能:IP =(16位寄存器)

转移地址在内存中的jmp指令有两种格式:

  • jmp word ptr 内存单元地址(段内转移)

功能:从内存单元地址处开始存放着一个字,是转移的目的偏移地址。

mov ax, 0123H
mov ds:[0], ax
jmp word ptr ds:[0]
;执行后,(IP)=0123H
  • jmp dword ptr 内存单元地址(段间转移)

功能:从内存单元地址处开始存放着两个字,高地址处的字是转移的目的段地址,低地址处是转移的目的偏移地址。

  1. (CS)=(内存单元地址+2)
  2. (IP)=(内存单元地址)
mov ax, 0123H
mov ds:[0], ax;偏移地址
mov word ptr ds:[2], 0;段地址
jmp dword ptr ds:[0]
;执行后,
;(CS)=0
;(IP)=0123H
;CS:IP 指向 0000:0123。

4、jcxz指令和loop指令


jcxz指令

jcxz指令为有条件转移指令,所有的有条件转移指令都是短转移,

在对应的机器码中包含转移的位移,而不是目的地址。对IP的修改范围都为-128~127。

指令格式:jcxz 标号(如果(cx)=0,则转移到标号处执行。)

当(cx) = 0时,(IP) = (IP) + 8位位移

8位位移 = “标号”处的地址 - jcxz指令后的第一个字节的地址;


8位位移的范围为-128~127,用补码表示;


8位位移由编译程序在编译时算出。


当(cx)!=0时,什么也不做(程序向下执行)

loop指令

loop指令为循环指令,所有的循环指令都是短转移,在对应的机器码中包含转移的位移,而不是目的地址。

对IP的修改范围都为-128~127。

指令格式:loop 标号 ((cx) = (cx) - 1,如果(cx) ≠ 0,转移到标号处执行)。

(cx) = (cx) - 1;如果 (cx) != 0,(IP) = (IP) + 8位位移。

8位位移 = 标号处的地址 - loop指令后的第一个字节的地址;


8位位移的范围为-128~127,用补码表示;


8位位移由编译程序在编译时算出。


如果(cx)= 0,什么也不做(程序向下执行)

第十章 call和ret指令


call和ret指令都是转移指令,它们都修改IP,或同时修改CS和IP。

1、ret 和 retf

ret指令用栈中的数据,修改IP的内容,从而实现近转移;

retf指令用栈中的数据,修改CS和IP的内容,从而实现远转移。

CPU执行ret指令时,相当于进行: pop IP:

(1)(IP) = ( (ss) * 16 + (sp) )

(2)(sp) = (sp) + 2

CPU执行retf指令时,相当于进行:pop IP, pop CS

(1)(IP) = ( (ss) * 16 + (sp) )

(2)(sp) = (sp) + 2

(3)(CS) = ( (ss) * 16 + (sp) )

(4)(sp) = (sp) + 2

assume cs:code 
stack seqment
	db 16 dup (0)
stack ends 

code segment
		mov ax, 4c00h
		int 21h 
 start:	mov ax, stack 
 		mov ss, ax
 		mov sp, 16
		mov ax, 0
		push ax ;ax入栈
		mov bx, 0
		ret ;ret指令执行后,(IP)=0,CS:IP指向代码段的第一条指令。可以push cs  push ax  retf
code ends
end start

2、call 指令


call指令经常跟ret指令配合使用,因此CPU执行call指令,进行两步操作:

(1)将当前的 IP 或 CS和IP 压入栈中;

(2)转移(jmp)。

call指令不能实现短转移,除此之外,call指令实现转移的方法和 jmp 指令的原理相同。

call 标号(近转移)

CPU执行此种格式的call指令时,相当于进行 push IP jmp near ptr 标号

call far ptr 标号(段间转移)

CPU执行此种格式的call指令时,相当于进行:push CS,push IP jmp far ptr 标号

call 16位寄存器

CPU执行此种格式的call指令时,相当于进行: push IP jmp 16位寄存器

call word ptr 内存单元地址

CPU执行此种格式的call指令时,相当于进行:push IP jmp word ptr 内存单元地址

mov sp, 10h
mov ax, 0123h
mov ds:[0], ax
call word ptr ds:[0]
;执行后,(IP)=0123H,(sp)=0EH
call dword ptr 内存单元地址

CPU执行此种格式的call指令时,相当于进行:push CS push IP jmp dword ptr 内存单元地址

mov sp, 10h
mov ax, 0123h
mov ds:[0], ax
mov word ptr ds:[2], 0
call dword ptr ds:[0]
;执行后,(CS)=0,(IP)=0123H,(sp)=0CH

3、call 和 ret 的配合使用

分析下面程序

assume cs:code
code segment
start:	mov ax,1
	    mov cx,3
     	call s ;(1)CPU指令缓冲器存放call指令,IP指向下一条指令(mov bx, ax),执行call指令,IP入栈,jmp
     	
	    mov bx,ax	;(4)IP重新指向这里  bx = 8
     	mov ax,4c00h
     	int 21h
     s: add ax,ax
     	loop s;(2)循环3次ax = 8
	    ret;(3)return : pop IP
code ends
end start

4.mul 指令

mul是乘法指令,使用 mul 做乘法的时候:相乘的两个数:要么都是8位,要么都是16位。

8 位: AL中和 8位寄存器或内存字节单元中;

16 位: AX中和 16 位寄存器或内存字单元中。

结果:

8位:AX中;

16位:DX(高位)和 AX(低位)中。

格式:mul 寄存器 或 mul 内存单元

;计算100*10
;100和10小于255,可以做8位乘法
mov al,100
mov bl,10
mul bl

;结果: (ax)=1000(03E8H) 
;计算100*10000
;100小于255,可10000大于255,所以必须做16位乘法,程序如下:
mov ax,100
mov bx,10000
mul bx

;结果: (ax)=4240H,(dx)=000FH     (F4240H=1000000)

5.DIV指令:


div 指令是8086汇编中的除法运算指令,它的结果不是浮点数,而是两个整数:商和余数。

被除数:

默认放在AX或(DX和AX)中,如果除数为8位,被除数为16位,被除数默认在AX中存放,

如果除数为16位,被除数为32位,被  除数则在(DX和AX)中存放,DX存放高16位,AX存放低16位。

格式如下:

div reg

div 内存单元

结果:

如果除数 B 是8位,那么除法的结果AL保存商,AH保存余数,

如果除数 B 是16位,那么除法的结果 AX保存商,DX保存余数。

第十一章 标志寄存器

1.标志寄存器


CPU内部的寄存器中,有一种特殊的寄存器(对于不同的处理机,个数和结构都可能不同)具有以下3种作用。

(1)用来存储相关指令的某些执行结果;

(2)用来为CPU执行相关指令提供行为依据;

(3)用来控制CPU的相关工作方式。

这种特殊的寄存器在8086CPU中,被称为标志寄存器(flag)。

8086CPU的标志寄存器有16位,其中存储的信息通常被称为程序状态字

在8086CPU的指令集中,

(1)运算指令(进行逻辑或算术运算)的执行是影响标志寄存器的,比如,add、sub、mul、div、inc、or、and等

(2)传送指令的执行对标志寄存器没有影响,比如,mov、push、pop等

1、零标志位 (ZF)


零标志位(Zero Flag)。它记录相关指令执行后,其结果是否为0。

如果结果为0,那么zf = 1(表示结果是0);

如果结果不为0,那么zf = 0。

mov ax, 1
sub ax, 1 ;执行后,结果为0,则zf = 1

mov ax, 2
sub ax, 1 ;执行后,结果不为0,则zf = 0


2、奇偶标志位 (PF)


奇偶标志位(Parity Flag)。它记录相关指令执行后,其结果的所有bit位中1的个数是否为偶数。

如果1的个数为偶数,pf = 1,如果为奇数,那么pf = 0。

mov al, 1
add al, 10 ;执行后,结果为00001011B,其中有3(奇数)个1,则pf = 0;

mov al, 1
or al, 2  ;执行后,结果为00000011B,其中有2(偶数)个1,则pf = 1;

3、符号标志位(SF)


符号标志位(Symbol Flag)。它记录相关指令执行后,其结果是否为负。

如果结果为负,sf = 1;

如果非负,sf = 0。

计算机中通常用补码来表示有符号数据。

对于同一个二进制数据,计算机可以将它当作无符号数据来运算,也可以当作有符号数据来运算

00000001B,可以看作为无符号数1,或有符号数+1;
10000001B,可以看作为无符号数129,也可以看作有符号数-127。

SF标志,就是CPU对有符号数运算结果的一种记录,它记录数据的正负。在我们将数据当作有符号数来运算的时候,可以通过它来得知结果的正负。

如果我们将数据当作无符号数来运算,SF的值则没有意义,虽然相关的指令影响了它的值

mov al, 10000001B 
add al, 1   ;执行后,结果为10000010B,sf = 1,
            ;表示:如果指令进行的是有符号数运算,那么结果为负;
mov al, 10000001B
add al, 01111111B   ;执行后,结果为0,sf = 0,
                    ;表示:如果指令进行的是有符号数运算,那么结果为非负


4、进位标志位(CF)


进位标志位(Carry Flag)。一般情况下,在进行无符号数运算的时候,它记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位

在这里插入图片描述

97H - 98H 产生借位CF = 1 ==>
 
(al) = 197H - 98H = FFH

5、溢出标志位(OF)


溢出标志位(Overflow Flag)。一般情况下,OF记录了有符号数运算的结果是否发生了溢出

如果发生溢出,OF = 1;如果没有,OF = 0。

CF和OF的区别:CF是对无符号数运算有意义的标志位,而OF是对有符号数运算有意义的标志位

mov al, 98
add al, 99   ;执行后将产生溢出。因为进行的"有符号数"运算是:(al)=(al)+ 99 = 98 + 99=197 = C5H 为-59的补码
             ;而结果197超出了机器所能表示的8位有符号数的范围:-128 ~ 127。
             ;add 指令执行后:无符号运算没有进位CF=0,有符号运算溢出OF=1
             ;当取出的数据C5H按无符号解析C5H = 197, 当按有符号解析通过SP得知数据为负,即C5H为-59补码存储,
             
mov al,0F0H  ;F0H,为有符号数-16的补码   -Not(F0 - 1)
add al,088H  ;88H,为有符号数-120的补码   -Not(88- 1)
              ;执行后,将产生溢出。因为add al, 088H进行的有符号数运算结果是:(al)= -136 
              ;而结果-136超出了机器所能表示的8位有符号数的范围:-128-127。
              ;add 指令执行后:无符号运算有进位CF=1,有符号运算溢出OF=1

2、adc指令和sbb指令

adb指令


adc是带进位加法指令,它利用了CF位上记录的进位值。

指令格式:adc 操作对象1, 操作对象2

功能:操作对象1 = 操作对象1 + 操作对象2 + CF

mov ax, 2
mov bx, 1
sub bx, ax  ;无符号运算借位CF=1,有符号运算OF = 0
adc ax, 1   ;执行后,(ax)= 4。adc执行时,相当于计算:(ax)+1+CF = 2+1+1 = 4。

在这里插入图片描述

;计算1EF000H+201000H,结果放在ax(高16位)和bx(低16位)中。
;将计算分两步进行,先将低16位相加,然后将高16位和进位值相加。
mov ax, 001EH 
mov bx, 0F000H 
add bx, 1000H
adc ax, 0020H


sbb指令

sbb是带借位减法指令,它利用了CF位上记录的借位值。

指令格式:sbb 操作对象1, 操作对象2

功能:操作对象1 = 操作对象1 - 操作对象2 - CF

;计算 003E1000H - 00202000H,结果放在ax,bx中,程序如下:
mov bx, 1000H
mov ax, 003EH
sub bx, 2000H
sbb ax, 0020H

3、cmp指令

1.cmp指令


cmp是比较指令,cmp的功能相当于减法指令,只是不保存结果。cmp指令执行后,将对标志寄存器产生影响

cmp指令格式:cmp 操作对象1,操作对象2

例如:


指令cmp ax, ax,做(ax)-(ax)的运算,结果为0,但并不在ax中保存,

仅影响flag的相关各位。


指令执行后:zf=1,pf=1,sf=0,cf=0,of=0。

2.cmp进行无符号数比较 


 

上面的表格可以正推也可以逆推(等价)

3.cmp进行有符号数比较

SF只能记录实际结果的正负,发生溢出的时候,实际结果的正负不能说明逻辑上真正结果的正负。
但是逻辑上的结果的正负,才是cmp指令所求的真正结果,所以我们在考察SF的同时考察OF,就可以得知逻辑上真正结果的正负,同时就知道比较的结果。

mov ah, 08AH  ; -Not(8A-1) = -118  即当成有符号数时为-118
mov bh, 070H  ; 有符号数时最高位为0为正数, 70H = 112
cmp ah, bh    ;(ah)-(bh)实际得到的结果是1AH 
		      ; 在逻辑上,运算所应该得到的结果是:(-118)- 112 = -230
		      ; sf记录实际结果的正负,所以sf=0

4.cmp ah, bh 

(1)如果sf=1,而of=0 。

of=0说明没有溢出        =>        逻辑上真正结果的正负=实际结果的正负;

sf=1,实际结果为负,=>        所以逻辑上真正的结果为负

所以(ah)<(bh)

(2)如果sf=1,而of=1:

of=1,说明有溢出,        =>        逻辑上真正结果的正负≠实际结果的正负;

sf=1,实际结果为负。        =>         逻辑上真正的结果必然为正


说明了(ah)>(bh)。

(3)如果sf=0,而of=1

of=1,说明有溢出        =>        逻辑上真正结果的正负≠实际结果的正负;

sf=0,实际结果非负。=>      那么逻辑上真正的结果必然为负。

说明了(ah)<(bh)。


(4)如果sf=0,而of=0


of=0,说明没有溢出        =>        逻辑上真正结果的正负=实际结果的正负;

sf=0,实际结果非负        =>        所以逻辑上真正的结果非负,

所以(ah)≥(bh)。

4、检测比较结果的条件转移指令


可以根据某种条件,决定是否修改IP的指令,例:

jcxz它可以检测cx中的数值,如果(cx)=0,就修改IP,否则什么也不做。

所有条件转移指令的转移位移都是[-128,127]。

多数条件转移指令都检测标志寄存器的相关标志位=        =>根据检测的结果来决定是否修改IP

这些条件转移指令通常都和cmp相配合使用,它们所检测的标志位,都是cmp指令进行无符号数比较的时记录比较结果的标志位

根据无符号数的比较结果进行转移的条件转移指令

(它们检测zf、cf的值)

指令            含义                    检测的相关标志位
je            等于则转移                zf = 1
jne          不等于则转移            zf = 0
jb            低于则转移                cf = 1
jnb          不低于则转移            cf = 0
ja            高于则转移                cf = 0 且 zf = 0
jna          不高于则转移            cf = 1 且 zf = 1


j:jump,

e:equal,

b:below,

a:above,

n:not

5、DF标志和串传送指令


方向标志位。在串处理指令中,控制每次操作后si、di的增减。

df = 0每次操作后si、di递增;
df = 1每次操作后si、di递减。


格式:movsb
功能:将ds:si指向的内存单元中的字节送入es:di中,然后根据标志寄存器df位的值,将si和di递增或递减

格式:movsw
功能:将ds:si指向的内存字单元中的字送入es:di中,然后根据标志寄存器df位的值,将si和di递增2或递减2。

格式:rep movsb
movsb和movsw进行的是串传送操作中的一个步骤,一般来说,movsb和movsw都和rep配合使用,
功能:rep的作用是根据cx的值,重复执行后面的串传送指令

8086CPU提供下面两条指令对df位进行设置。

cld指令:将标志寄存器的df位置0
std指令:将标志寄存器的df位置1

;将data段中的第一个字符串复制到它后面的空间中。
data segment 
	db 'Welcome to masm!'
	db 16 dup (0)
data ends

mov ax, data 
mov ds, ax 
mov si, 0   ;ds:si 指向data:0
mov es, ax 
mov di, 16  ;es:di指向data:0010

mov cx, 16  ;(cx)=16,rep循环16次
cld  ;设置df=0,正向传送
rep movsb

6、pushf和popf


pushf的功能是将标志寄存器的值压栈,而popf是从栈中弹出数据,送入标志寄存器中

pushf和popf,为直接访问标志寄存器提供了一种方法。

7. 标志寄存器在Debug中的表示 

实验

DEBUG 指令 (实验一)

A: 以汇编指令的格式在内存中写入一条机器指令

功能:允许用户以汇编语言的格式输入指令,DEBUG 程序会将这些汇编指令转化为机器码并存入内存。

语法A 段地址:偏移地址

示例

A 1000:0100
MOV AX, 4C00
INT 21

解释:在内存地址 1000:0100 处输入 MOV AX, 4C00INT 21 两条指令。

D: 查看内存

功能:显示内存中的内容。

语法D 段地址:起始偏移地址 [结束偏移地址]

示例

D 1000:0100

解释:查看从内存地址 1000:0100 开始的内容。

E: 向内存中写入机器码

功能:向内存中写入数据,允许用户直接以机器码的形式向内存中写入数据。

语法E 段地址:偏移地址 数据 [数据 [...]]

示例

E 1000:0100 4C 00 21

解释:在内存地址 1000:0100 处写入数据 4C 00 21

G: 程序直接运行到指定地址位置

功能:将程序运行到指定的内存地址。

语法G 偏移地址

示例

G 0100

解释:从偏移地址 0100 处开始运行程序。

R: 查看, 改变寄存器

功能:显示或修改寄存器的内容。

语法R [寄存器]

示例

R AX
AX 0000
:1234

解释:查看 AX 寄存器的值,并将其修改为 1234

T: 执行一条机器指令

功能:单步执行一条机器指令。

语法T

示例

T

解释:执行当前内存位置的一条机器指令。

U: 查看内存指令翻译的汇编指令

功能:反汇编内存中的机器指令。

语法U 段地址:偏移地址

示例

U 1000:0100

解释:反汇编从内存地址 1000:0100 开始的指令。

P: 当遇到 int 21H 正常结束指令时使用P指令

功能:遇到 int 21H 结束指令或 loop 指令时执行到下一个非循环指令。

语法P

示例

P

解释:执行到 int 21H 或下一个非循环指令。

实验思考

(1)2000:0为什么不写为2000:0000

在DOS DEBUG工具中,段地址和偏移地址的表示法通常是简洁的,只需要指定实际有意义的部分。例如,2000:02000:0000确实表示的是同一个物理内存地址,但通常在写入时,只需要写出有意义的部分即可

(2)2000H的H是?

在汇编语言和低级编程中,地址或数值后缀H表示该数值是以十六进制(hexadecimal)表示的。

H表示这是一个十六进制数。

(3)ROM无法修改

只读存储器(ROM),因此无法通过DEBUG工具直接修改它。这解释了为什么你的修改没有生效。ROM的内容在生产时已经固定,通常无法在运行时改变 

实验内容
步骤3生产日期 

步骤4 

 实验二

写入指令到内存(部分) 

执行push [4]之后 

2. 

为什么栈结构指向内存出现数据? 

可以发现初始没有执行这段代码时,我们使用d命令观察2000:00内存,都是00,怎么创建栈结构指向这段内存时,我们发现有些数据了。这些数据是什么?


可以看出这里面有cs值、ip值、ax值(这个容易看出来),"9D 05"是标志寄存器flag的值。
后面在将内中断的时候,就明白了。t命令实际是引发了单步中断,执行中断例程时,CPU会将一些中断例程使用的的寄存器变量自动压栈到栈中,此例中就包括了上述的寄存器变量的值。

单步中断P249
我们在使用Debug的t命令的时候,没有用想过这样的问题,Debug如何能让CPU在执行一条指令后,就显示各个寄存器的状态?我们知道,CPU在执行程序的时候是从CS:IP指向的某个地址的开始,自动向下读取指令执行。也就是说,如果CPU不提供其他功能的话, 就按这种方式工作,只要CPU一加电,它就从预设的地址开始一直执行下去,不可能有任何程序能控制它在执行完一条指令后停止,去做别的事情。可是,我们在Debug中看到的情况却是,Debug可以控制CPU执行被加载程序中的一条指令,然后让它停下来,显示寄存器的状态。
Debug有特殊的能力吗?我们只能说Debug利用了CPU提供的一种功能。只有CPU提供了在执行一条指令后就转去做其他事情的功能,Debug或是其他的程序才能利用CPU提供的这种功能做出我们使用T命令时的效果。

中断过程P238
8086CPU在收到中断信息后,所引发的中断过程。
(1)(从中断信息中)取得中断类型码;
(2)标志寄存器的值入栈(因为在中断过程中要改变标志寄存器的值,所以先将其保存在栈中);
(3)设置标志寄存器的第8位TF和第9位IF的值为0(这一步的目的后面将介绍);
(4)CS的内容入栈;
(5)IP的内容入栈;
(6)从内存地址为中断类型码4和中断类型码4+2的两个字单元中读取中断处理程序的入口地址设置IP和CS。

实验三 

3.查看PSP的内容

PSP地址(ds 存放PSP的段地址:SA,PSP偏移地址为0)

程序地址(SA + 10H(CS): 0)

实验四 [bx]和loop的使用

(1)向内存0:200~0:23F 依次传送数据 0~63(3FH)

程序中只能用9条指令,包括mov ax,4c00h和int 21h

assume cs:code

code segment

    mov ax,0020H  ; 将数据段寄存器 DS 指向段地址 200h
    mov ds,ax

    mov bx,0  ; 初始化 BX 寄存器,作为内存偏移地址的起始值

    mov cx,40H  ; 初始化 CX 寄存器,循环计数器,设为 3Fh,即 63

s:  mov [bx],bx  ; 将 BX 的值存入 DS:BX 指向的内存地址
    inc bx  ; BX 增加 1,指向下一个内存地址
    loop s  ; CX 减 1,如果 CX 不为 0,则跳转到标号 s

    mov ax,4c00h  ; 终止程序
    int 21h
code ends

end

(2)补全下面的程序,其功能是将mov ax,4c00之前的指令复制到内存0:200处,补全程序,上机调试,跟踪运行结果

提示:

  1. 复制了什么?从哪里到哪里? :CS:0 (程序段起始)        ->        0020:0
  2. 复制的是什么?有多少字节,你如何知道要复制的字节数量?                                                    程序段,0~17H,u 0指令(直到int 32)
 执行之前,内存状态:

执行之后,内存状态:

可见mov ax,4c00之前的指令复制到内存0:200处,

 代码:
assume cs:code
code segment

	mov ax,cs
	mov ds,ax
	mov ax,0020h
	mov es,ax
	mov bx,0
	mov cx,23
s:  mov al,[bx]
	mov es:[bx],al
	inc bx
	loop s
	
	mov ax,4c00h
	int 21h
code ends
end

实验五  编写,调试具有多个段的程序 

一。将下面的程序编译连接,用Debug加载、跟踪,然后回答问题。

assume cs:code,ds:data,ss:stack

data segment

      dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h

data ends

stack segment

      dw 0,0,0,0,0,0,0,0

stack ends

code segment

start:  mov ax,stack

      mov ss,ax

      mov sp,16

      mov ax,data

      mov ds,ax

      push ds:[0]

      push ds:[2]

      pop ds:[2]

      pop ds:[0]

      mov ax,4c00h

      int 21h

code ends

end start

 1.根据内存布局:ds:0为PSP,然后ds:100为程序段起始(故ds:100查看到dw数据)

故code段地址为076A(075A:100)

2.指向完mov ax,stack后,ax为stack段地址

故stack段地址为076B

3.执行完mov ax,data后,ax为data的段地址

故data的段地址为076A

故设程序加载后,CODE段的段地址为X,则DATA段的段地址为 X-2 ,STACK段的段地址为 X-1 。 

4.程序返回前,各寄存器状态

②CPU执行程序,程序返回前,CS= 076C,SS= 076B,DS= 076A。

 

 5.CPU执行程序,程序返回前data段中数据变化:

 执行前: 

执行后:

故data段中的数据 不变 

 二.类似

assume cs:code,ds:data,ss:stack
 
data segment
    dw 0123h,0456h
data ends
 
stack segment
    dw 0,0
stack ends
 
code segment
start:
    mov ax,stack      
    mov ss,ax
    mov sp,16         
 
    mov ax,data       
    mov ds,ax
 
    push ds:[0]
    push ds:[2]
    pop ds:[2]
    pop ds:[0]
 
    mov ax,4c00h
    int 21h
code ends
end start
1.该段实际占据空间为:

对于如下定义的段:

name segment

    ......

name ends
  • 内存对齐:x86架构中,段的起始地址必须是16字节的倍数。
  • 内存分配:段的大小必须是16字节的倍数,即使实际数据量小于16字节。
  • 计算公式:实际占用空间 = (N / 16的整数部分 + 1)* 16字节。
具体规则
  1. N ≤ 16:实际占用16字节。
  2. N > 16:实际占用(N / 16的整数部分 + 1)* 16字节。

三. 

assume cs:code,ds:data,ss:stack
 
code segment
start:
    mov ax,stack      
    mov ss,ax
    mov sp,16         
 
    mov ax,data       
    mov ds,ax
 
    push ds:[0]
    push ds:[2]
    pop ds:[2]
    pop ds:[0]
 
    mov ax,4c00h
    int 21h
code ends
 
data segment
    dw 0123h,0456h
data ends
 
stack segment
    dw 0,0
stack ends
 
end start
1.code段数据:cs:30

      这次只不过是将data和stack段放到了code段后面了。那么就要注意它们段地址的变化了。

2.stack的段地址:076E 

3. data的段地址:076D

4. CPU执行程序,程序返回前code段中数据变化:
 执行前:
 执行前:

5.程序返回前,各寄存器状态

CPU执行程序,程序返回前,CS= 076A,SS= 076E,DS= 076D。

6.程序加载后,段地址关系

故设程序加载后,CODE段(076A)的段地址为X,则DATA段(076D)的段地址为 X + 4,STACK段(076E )的段地址为 X + 4 。  

四。 如果将(1)、(2)、(3)题中的最后一条伪指令“end start”改为“end”(也就是说不指明程序的入口),则那个程序仍然可以正确执行?请说明原因。

      答案:如果不指名程序的(code段的)入口,并且使用end替换end start,都能正常运行。但只有(3)题中程序可以正确的执行(因为只有它是在内存中可执行代码在最前面)。

      讲解:因为如果不指名入口,程序会从加载进内存的第一个单元起开始执行,前二个题中,定义的是数据,但CPU还是将数据当做指令代码执行了。只不过程序执行时逻辑上是错误了。但真的能执行的。

      如果指明了程序的入口,CPU会直接从入口处开始执行真正的机器码,直到遇到中断指令返回。此种方式能够确保程序逻辑上的正确。因此有必要为程序来指明入口。

五 将a段和b段数据依次相加,结果存入c 

 易错:
(1)mov ax,es:[bx]   会读取字型数据
(2)注释时使用中文:会出现out of memory的错误

assume cs:code
a segment
    db 1,2,3,4,5,6,7,8
a ends
 
b segment
    db 1,2,3,4,5,6,7,8
b ends
 
c segment
    db 0,0,0,0,0,0,0,0
c ends
 
code segment
start:
    mov ax,c
    mov ds,ax   

    mov ax,a
    mov es,ax   

    mov bx,0
    mov cx,8

s0: mov ax,es:[bx]  
    mov [bx], ax
    inc bx
    loop s0

    mov ax,b
    mov es,ax  

    mov bx,0
    mov cx,8

s1: mov ax,es:[bx]
    add [bx], ax
    inc bx
    loop s1

    mov ax,4c00h
    int 21h
code ends
end start

六   用push指令将a段中前8个字型数据逆序存储到b段中

执行完结果:

assume cs:code
 
a segment
    dw 1,2,3,4,5,6,7,8,9,0ah,0bh,0ch,0dh,0eh,0fh,0ffh
a ends
 
b segment
    dw 0,0,0,0,0,0,0,0
b ends
 
code segment
start:
    mov ax,a
    mov ds,ax

    mov ax,b
    mov ss,ax
    mov sp,10H
    
    mov bx,0
    mov cx,8

s:  push [bx]
    add bx,2
    loop s

    
    mov ax,4c00h
    int 21h
code ends
end start

实验六  将datasg段中的每个单词的前4个字母改为大写字母

执行前 

结果: 

代码 
assume cs:codesg,ss:stacksg,ds:datasg

stacksg segment
	dw 0,0,0,0,0,0,0,0
stacksg ends

datasg segment
	db '1. display      '
	db '2. brows        '
	db '3. replace      '
	db '4. modify       '
datasg ends

codesg segment

  start:
       mov ax,stacksg
       mov ss,ax
       mov sp,16
       
       mov ax,datasg
       mov ds,ax
       
       mov bx,0
       mov cx,4
 
  s0:  push cx
       mov cx,4 
       mov si,3
 
  s1:  mov al,[bx+si]
       and al,11011111b
       mov [bx+si],al
       inc si
       loop s1
       add bx,16
       pop cx
       loop s0

codesg ends

end start			

实验七 寻址方式在结构化数据访问中的应用

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

注意:

    1.div word ptr es:[di],div 内存单元,需要指出大小 (word ptr和btye ptr)

结果: 

代码: 

assume cs:code,ds:data,ss:stack

stack segment stack
	db 0,0,0,0,0,0,0,0
stack ends

data segment
    ;年份
    db '1975', '1976', '1977', '1978', '1979', '1980', '1981', '1982', '1983'
    db '1984', '1985', '1986', '1987', '1988', '1989', '1990', '1991', '1992'
    db '1993', '1994', '1995'
    ;收入
    dd 16, 22, 382, 1356, 2390, 8000, 16000, 24486, 50065, 97479, 140417, 197514
    dd 345980, 590827, 803530, 1183000, 1843000, 2759000, 3753000, 4649000, 5937000
    ;员工
    dw 3, 7, 9, 13, 28, 38, 130, 220, 476, 778, 1001, 1442, 2258, 2793, 4037, 5635, 8226
    dw 11542, 14430, 15257, 17800
data ends

table segment
    db 21 dup ('year sumn ne ?? ')
table ends

code segment
start:  
    mov ax,data
    mov es,ax	;设置年份段地址

	mov ax,0
	mov bp,ax	;设置年份偏移地址

    add ax,54H	
    mov si,ax	;设置收入偏移地址

    add ax,54H	
	mov di,ax	;设置员工偏移地址

	mov ax,table
	mov ds,ax	;设表格段地址
	
	mov ax,stack
	mov ss,ax
	mov sp,16	;设置栈顶
	
	mov bx,0	;定位结构数据

	mov cx,21

s:	mov ax,es:[bp]
	mov [bx],ax
	mov ax,es:[bp+2]
	mov [bx+2],ax		;赋值年份
	

	mov ax,es:[di]	
	mov [bx+10],ax   	;赋值雇员
	

	mov ax,es:[si+2]
	mov [bx+7],ax   	;赋值收入高16位
	mov ax,es:[si]
	mov [bx+5],ax		;赋值收入低16位,被除数低16位

	mov dx,es:[si+2]	;被除数高16位
	
	div word ptr es:[di]

	mov [bx+13],ax		;赋值人均收入

	add bp,4		;增加年份偏移地址	
	add di,2		;增加员工偏移地址	
	add si,4		;增加收入偏移地址	
	add bx,16		;增加结构数据偏移地址

	loop s

	;结束程序
	mov ax,4c00h
	int 21h

code ends

end start

实验八 奇怪的程序

是否可以运行?为什么运行结果是这样?

答: 可以,

assume cs:code
 
code segment 
    mov ax,4c00H
    int 21H
    
  start:
    mov ax,0
    
    s:
    nop 
    nop
    mov di,offset s
    mov si,offset s2
    mov ax,cs:[si]
    mov cs:[di],ax
    
    s0:
    jmp short s
    
    s1:
    mov ax,0
    int 21H
    mov ax,0
 
    s2:
    jmp short s1
    nop
 
code ends
end start
 

注意:

前提:

注意:

1.NOP占用1字节

2.将cs:[si]为s2处的jmp short s1指令赋值到 cs:[di]为start:s处3.

3.s1为0018,short s1为0(故jmp short s1为jmp 8,也就是jmp到s处)

结果:

assume cs:code
 
code segment 
    mov ax,4c00H
    int 21H
    
  start:
    mov ax,0
    
    s:
    nop 
    nop
    mov di,offset s
    mov si,offset s2
    mov ax,cs:[si]
    mov cs:[di],ax
    
    s0:
    jmp short s
    
    s1:
    mov ax,0
    int 21H
    mov ax,0
 
    s2:
    jmp short s1
    nop
 
code ends
end start
 

实验九

前置知识

  1. B8000H~BFFFFFH为 dosbox 显示字符区的总地址
  2. 显示字符窗口一共25行,每行 8 个字符,每个字符可有256种属性
  3. 一个字符在 dosbox 的显示字符区中就要占两个字节,分别存放字符的 ASCLL 码和属性
  4. 低位存放 ASCLL 码,高位存放属性
  5. 所以打开显示字符窗口的时候,只会显示 4000 个的内存单元,即,显示地址为 B8000H~B8F9F 的内存单元中的内容,回车可显示下一行
  6. 160个内存单元算一行,一行中两个内存单元组成一列,这一列前面为字符,后面为属性
debug 字符显示区相关知识

屏幕截图 2023-05-23 201027.png

属性值

属性字节格式:按位设置字节属性,0是关闭,1是开启
7            6            5            4            3            2            1            0
闪烁    背景红    背景绿    背景蓝    高亮    字体红    字体绿    字体蓝

例子:​

1. 绿色​ :00000010b —> 2h ----> 2

​ 2. 绿底红色:00100100b —> 24h ----> 36

		 3. 白底蓝色:01110001b  ---> 71h ----> 113

代码

assume cs:code
 
data segment
	db "welcome to masm!"
	db 02H,24H,71H
data ends

code segment 
    
start:	mov ax,data
	mov es,ax	;设置字符串段地址
	mov bx,0	;设置字符串偏移地址
		
	mov ax,16
	mov si,ax	;设置属性偏移地址

	mov ax,0B800H
	mov ds,ax	;设置缓冲区段地址

	mov ax,07C0H
	mov di,ax	;缓冲区开始偏移地址
	
	mov cx,16

s:	mov al,es:[bx]
	mov ds:[di],al	;赋值字符

	mov al,es:[si]	;赋值属性
	mov ds:[di+1],al	
	
	add di,2
	inc bx

	loop s

	mov ax,4c00H
	int 21H
code ends
end start
 

实验 10

一、子程序1 显示字符串

1.实验任务

编写一个子程序,向外提供可以调用的接口,能够在指定的行号、列号,用指定的颜色,显示一个用0结束的字符串。

2.代码:
assume cs:code
data segment
  db 'Welcome to masm!',0
data ends
 
code segment
start:
    mov dh,8    ;指定行号
    mov dl,3    ;指定列号
    mov cl,2    ;指定颜色
 
    mov ax,data    ;设置ds
    mov ds,ax

    mov si,0    ;设置ds:[si]指向当前要显示的字符
 
    call show_str    ;调用子程序,显示字符串
 
    mov ax,4c00H    ;程序返回
    int 21H
 
show_str:
		;设置缓冲区段地址
    mov ax,0B800H	
    mov es,ax	

        	;设置并计算偏移地址
    mov al,160	
    mul dh	;计算行偏移量
    
    mov dh,0
    add ax,dx
    add ax,dx	;计算增加列偏移量
    mov bx,ax

    mov al,cl

    mov cx,16

s:  mov ah,[si]
    mov es:[bx],ah	;设置字符
    mov es:[bx+1],al	;设置属性
       inc si
    add bx,2
    loop s

    ret

 
code ends
end start

二、子程序2 解决除法溢出的问题

1.实验任务


(1)写一个子程序,功能是进行不会产生溢出的除法运算。被除数为dword型,除数为word型,结果为dword型。

参数:ax = dword型数据的低16位 ;dx = dword型数据的高16位 ;cx = 余数

返回: dx = 结果的高16位 ;ax = 结果的低16位 ; cx = 余数

(2)可以参考的公式如下。

X 为被除数,H 为X的高16位,L 为X的低16位 , N 除数。

int()表示取商,rem()表示取余数。

则  X/N = int( H/N ) * 65536 + [rem( H/N) * 65536 + L]/N   。

2.结果 

 3.代码:
assume cs:code,ss:stack
 
stack segment ;定义栈段
  db 0
stack ends
 
code segment  ;定义代码段
start:
 
  mov ax,stack    ;设置栈顶
  mov ss,ax
  mov sp,10H

  mov ax,4240H    ;被除数的低16位
  mov dx,000FH    ;被除数的高16位
  mov cx,0AH    ;除数
  call divdw    ;调用子程序
 
  mov ax,4c00H          ;程序返回
  int 21H
 
divdw:    ;功能:改进后的不会溢出的div指令(当前修改为16位除16位(32位除16位,dx = 0))
         ;参数:  ax被除数低16位 ,  dx被除数高16位   ,   cx除数
         ;返回值  ax结果低16位 ,  dx结果高16位   ,   cx余数
		   ;需要两个临时数据存放点(栈和)
   push ax
   push dx

   		;计算高位部分(32位(实际为16位)除16位)
   mov dx,0
   pop ax		;设置被除数(原DX)
   div cx		;除法

   mov bx,ax		;存储INT(H/N),返回值的高16位

 		;  计算低位部分(32位除16位)
      ;此时dx即为Rem(H/N)被除数高位
   pop ax		;被除数低位(原AX)
   div cx		;除法(此时ax已经为结果的低16位)
		
		;返回值
   mov cx,dx		;返回余数
   mov dx,bx		;返回高16位
   ret
  
code ends
end start

三、子程序3 数值显示


1.实验任务


(1)将data段中的数据以十进制的形式显示出来。应该编写一个通用的子程序,将一个给定的word型数据转为以十进制显示的字符串。而字符的显示功能,可以调用上边写的子程序1来完成。

子程序描述:

名称:dtoc

功能:将word型数据转变为表示十进制数的字符串,字符串以0为结尾符。

参数:ax = word型数据 ; ds:si 指向字符串的首地址

返回:无

2.遇到的问题:

(1)最后8,38两数的存储错误

(2)debug可以单步执行至完成,直接执行失败

原因:debug程序会初始化各个寄存器的值,而直接执行不会初始化各寄存器,我程序中bp没有初始化赋值为0

3.代码:
assume cs:code,es:data,ss:stack,ds:display
 
data segment  ;定义数据段
  dw 123,12666,1,8,3,38
data ends
 
display segment ;定义显示数据段
  db 16 dup(0)
display ends

stack segment ;定义栈段
  db 0
stack ends
 
temp_data segment  ;定义临时数据段
  db 16 dup(0) 
temp_data ends

code segment  ;定义代码段
start:
 
  mov ax,stack    ;设置栈顶
  mov ss,ax
  mov sp,10H

  mov ax,data   ;待转换数据段
  mov es,ax	     


  mov ax,display ;显示数据段
  mov ds,ax

  call dtoc

  call show_str    ;调用子程序,显示字符串


  mov ax,4c00H          ;程序返回
  int 21H
 
dtoc:    ;功能:word数据改为十进制数字符串,以'0'为结尾(将转换后的数存储到ds:si)
     		
   mov cx,6 ;存储待转换数据个数
   mov di,0	;存储显示数据偏移地址
   mov bx,0	;存储待转换数据偏移地址

 words:		;全部数据转换

   push cx	
   mov ax,es:[bx]  ;被除数(低16位)   
   
   mov bp,0

 word_:		;功能:单次数据转换时,cx不改变

   mov dx,0	;高十六位
   mov cx,10	;除数
   call divdw
     
   add cx,30H	;转字符(余数
   
   push cx     ;转换的字符
   mov cx,ax	;判断是否完全转换(商为0
   add cx,1

   inc bp	;单次数据长度
   loop word_
   		
   mov cx,bp
   mov bp,0
   add bx,2
   
 store: 		;功能: 单次数据存储
   pop ax
   mov [di],al
   inc di
   loop store

   inc di
   pop cx   

   loop words
   ret
  

divdw:    ;功能:改进后的不会溢出的div指令(当前修改为16位除16位(32位除16位,dx = 0))
         ;参数:  ax被除数低16位 ,  dx被除数高16位   ,   cx除数
         ;返回值  ax结果低16位 ,  dx结果高16位   ,   cx余数
   push dx
 		;计算低位部分(32位除16位),ax初始已经赋值
   mov dx,0

   div cx		;除法(此时ax已经为结果的低16位)
		
		;返回值
   mov cx,dx		;返回余数
   
   pop dx
   ret
 
show_str:   ;功能:打印数据段中的字符串
                           ;设置参数
   mov dh,8    ;指定行号
   mov dl,3    ;指定列号
   mov cl,2    ;指定颜色
   mov cx,24   ;打印的字数

   mov ax,display   ;待打印数据段地址
   mov ds,ax

                           ;执行过程
   mov si,0    ;设置ds:[si]指向当前要显示的字符
   
		;设置缓冲区段地址
    mov ax,0B800H	
    mov es,ax	
 
        	;设置并计算偏移地址
    mov al,160	
    mul dh	;计算行偏移量
    
    mov dh,0
    add ax,dx
    add ax,dx	;计算增加列偏移量
    mov bx,ax
 
    mov al,cl

 s:  mov ah,[si]
    mov es:[bx],ah	;设置字符
    mov es:[bx+1],al	;设置属性
    inc si
    add bx,2
    loop s
 
    ret


code ends
end start

课程设计一

一、课程设计任务

实验任务:将  实验7 寻址方式在结构化数据访问中的应用 中提供的公司数据,呈现在屏幕上,效果如下。

注意:

(1)由于之前的子程序不够互相独立,工作量主要在于完善子程序和一定程度上调整

(2)在调试时可以先申请一块普通内存,查看变化(debug显存实时在变化)

结果: 

代码: 

assume cs:code,ds:data,ss:stack

stack segment 
	db 0,0,0,0,0,0,0,0
stack ends

data segment
    ;年份
    db '1975', '1976', '1977', '1978', '1979', '1980', '1981', '1982', '1983'
    db '1984', '1985', '1986', '1987', '1988', '1989', '1990', '1991', '1992'
    db '1993', '1994', '1995'
    ;收入
    dd 16, 22, 382, 1356, 2390, 8000, 16000, 24486, 50065, 97479, 140417, 197514
    dd 345980, 590827, 803530, 1183000, 1843000, 2759000, 3753000, 4649000, 5937000
    ;员工
    dw 3, 7, 9, 13, 28, 38, 130, 220, 476, 778, 1001, 1442, 2258, 2793, 4037, 5635, 8226
    dw 11542, 14430, 15257, 17800
data ends

display segment 
	db 1600 dup (0)
display ends


code segment
start:  
   mov ax,data
   mov es,ax	;设置年份段地址

	mov ax,0
	mov bp,ax	;设置年份偏移地址

   add ax,54H	
   mov si,ax	;设置收入偏移地址

   add ax,54H	
	mov di,ax	;设置员工偏移地址

	mov ax,0b800H
	mov ds,ax	;设表格段地址
	mov bx,0	;定位结构数据

	mov ax,stack
	mov ss,ax
	mov sp,16	;设置栈顶
	
	mov cx,21

 data_store:
	call store_year   ;  存储年份

   add bx,32
   
   mov dx,es:[si+2]  ;赋值收入高16位
	mov ax,es:[si]    ;赋值收入低16位
   call dtoc 

   add bx,64

	mov ax,es:[di]	   ;雇员数
   mov dx,0          
   call dtoc         

   add bx,32

	mov dx,es:[si+2]	;被除数高16位
   mov ax,es:[si]    ;被除数低16位
	
	div word ptr es:[di]

   mov dx,0
   call dtoc   ;赋值人均收入

	add bp,4		;增加年份偏移地址	
	add di,2		;增加员工偏移地址	
	add si,4		;增加收入偏移地址	
	add bx,32		;增加结构数据偏移地址
   loop data_store
   mov ax,4c00H    ;程序返回
   int 21H

store_year:    ;存储年份
   mov al,es:[bp]
	mov [bx],al
   mov [bx+1],word ptr 2

	mov al,es:[bp+1]
	mov [bx+2],al		
   mov [bx+3],word ptr 2

   mov al,es:[bp+2]
	mov [bx+4],al		
   mov [bx+5],word ptr 2

   mov al,es:[bp+3]
	mov [bx+6],al		
   mov [bx+7],word ptr 2
   ret


dtoc:    ;功能:dword数据改为十进制数字符串,以'0'为结尾
         ;参数 ax字符低16位,  dx字符高16位   ds:bx指向首地址
   push cx
   push bp
   push bx
   mov bp,0 		

 word_:		;功能:单次数据转换时,cx不改变

   mov cx,10	;除数
   call divdw
     
   add cx,30H	;转字符(余数
   
   push cx     ;转换的字符
   mov cx,ax	;判断是否完全转换(商为0
   add cx,1

   inc bp	;单次数据长度
   loop word_

   mov cx,bp
 store:     ;将数据写入显存
   pop ax
   mov [bx],al
   mov [bx+1],word ptr 2
   add bx,2
   loop store

   mov ax,0
   pop bx
   pop bp
   pop cx
   ret
  

divdw:    ;功能:改进后的不会溢出的div指令(当前修改为16位除16位(32位除16位,dx = 0))
         ;参数:  ax被除数低16位 ,  dx被除数高16位   ,   cx除数
         ;返回值  ax结果低16位 ,  dx结果高16位   ,   cx余数
		   ;需要两个临时数据存放点(栈和)
   push bx
   push ax
   push dx


   		;计算高位部分(32位(实际为16位)除16位)
   mov dx,0
   pop ax		;设置被除数(原DX)
   div cx		;除法

   mov bx,ax		;存储INT(H/N),返回值的高16位

 		;  计算低位部分(32位除16位)
      ;此时dx即为Rem(H/N)被除数高位
   pop ax		;被除数低位(原AX)
   div cx		;除法(此时ax已经为结果的低16位)
		
		;返回值
   mov cx,dx		;返回余数
   mov dx,bx		;返回高16位
   pop bx
   ret
 


code ends

end start

实验11

注意:

(1)完成这个程序只需要熟悉cmp本质通过影响标志寄存器sf(溢出),of(为负),实现判断减法结果大小

(2)了解各种条件转移语句,jna(不等于),jb(小于),ja(大于)

j:jump,

e:equal,

b:below,

a:above,

n:not

代码:

assume cs:code,ds:data
data segment
    db 'welcome to masm!haha,I am happy!',0
data ends
 
code segment
start:
    mov ax,data
    mov ds,ax
    mov si,0
    call letterc

    mov ax,4c00H            
    int 21H
 
letterc:
    push si
    push ds

comp:    
    mov al,[si]
    cmp al, 0       ;检测字符是否为0,字符串结尾检测

    je exit         ;如果为0,跳转到exit标号,退出

    cmp al,'a'
    jb next_char    ;如果小于字符a,跳转到下个字符

    cmp al,'z'
    ja next_char    ;如果大于字符z,跳转到下个字符

    and al, 11011111B   ;条件都不满足,开始转换为大写字母
    mov [si],al

next_char:
    inc si
    jmp short comp

exit:
    pop ds
    pop si
    ret


code ends
end start
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值