本文最后更新于 2025年3月12日 晚上
去年学的东西了,突然看到了,想了一下还是传上来,王爽的《汇编语言》第四版(第一本完全看完的跟计算机有关的书www)
一、基础知识 1.1 机器语言 1.2 汇编语言产生 汇编语言的主体是汇编指令,汇编指令是机器的助记符。
1.3 汇编语言的组成 1.汇编指令(机器码助记符)
2.伪指令(由编译器)
3.其他符号(由编译器识别)
1.4 存储器 cpu,内存,磁盘
1.5 指令和数据 1.6 存储单元 存储器分为若干个存储单元,从 0 开始编号
1.7 CPU对存储器进行读写 CPU 对数据进行读写,需要和外部器件进行信息交互:
1.存储单元地址(地址信息)
2.器件的选择,读或写命令(控制信息)
3.读或写的数据(数据信息)
连接 CPU 和其他芯片
1.8 地址总线 CPU 通过地址总线指定存储单元
地址总线能传送多少个不同的信息,CPU 就可以对多少个存储单元进行寻址。
一个 CPU 有 N 根地址总线,则可以说这个 CPU 地址总线宽度为 N,CPU 可以寻找 2 的 N 次方个内存单元。
1.9 数据总线 CPU和其他器件的数据传输,数据总线宽度决定了 CPU 和外界的数据传输速度。
1.10 控制总线 CPU 对外部器件的控制,控制总线是不同控制线的集合
小结 1)汇编指令是机器指令的助记符,同机器指令一一对应;
2)每一种 CPU 都有自己的汇编指令集;
3)CPU 可以直接使用的信息在存储器中存放;
4)在存储器中指令和数据没有任何区别,都是二进制信息;
5)存储单元从零开始存储顺序编号;
6)一个存储单元可以存储 8 个 bite(b);
7)每一个 CPU 芯片都有许多管脚,管脚和总线相连。
地址总线宽度决定了 CPU 的寻址能力;
数据总线宽度决定了 CPU 与其他器件进行数据传时的一次数据传输量;
控制总线宽度决定了 CPU 对系统中其他器件的控制能力。
1.11 主板、接口卡 1.12 各类存储器芯片 随机存储器(RAM)、只读存储器(ROM)
随机存储器 RAM
装有 BIOS 的 ROM
接口卡上的 RAM
1.13各类存储器芯片 装有 BIOS 的 ROM BIOS:基本输入输出系统
1.15内存地址空间 存储器在物理上是独立器件,相同点:都通过 CPU 和总线相连,CPU 进行读写时都通过控制线发出内存读写命令。
二、寄存器(CPU 工作原理) 一个 16 位寄存器可以存储数据最大值为 2^16-1
2.1 通用寄存器 AX 的低八位(0位-7位)构成了 AL 寄存器,高8位(8 位-15 位)构成了 AH 寄存器。
AH 和 AL 寄存器是可以独立使用的 8 位寄存器。
2.2 汇编指令 汇编指令不区分大小写
1 2 3 4 5 mov ax,18 将18送入AX中 AX=18 mov ah,78 将78送入AH AH=78 add ax,8 将寄存器中AX中的数据加上8 AX=AX+8 mov ax,bx 将BX中的数据送入AX中 AX=BX add ax,bx 将AX,BX中的内容相加,结果存在AX中 AX=AX+BX
2.3 物理地址 CPU访问内存单元时给出内存单元的地址。所有的内存单元构成的存储空间是一个一维的线性空间,这个唯一地址成为物理地址。
2.4 16 位结构的 CPU 16 位结构运算器一次最多可以处理 16 位数据,寄存器最大宽度为 16 位,寄存器和运算器之间的通路是 16 位的。
地址加法器工作原理
地址加法器合成物理地址的方法:物理地址=段地址x16+偏移地址
移位次数和各种形式数据关系:几进制左移就乘以几
2.5 段的概念 将若干地址连续的内存单元看成一个段
1)一个段的起始地址一定是 16 的倍数.
2)偏移地址位 16 位,16 位地址的寻址能力为 64k,所以一个段的最大长度为 64k, 2^16=64
小结 CPU 访问内存单元时,必须提供物理地址
CPU 可以通过不同的段地址和偏移地址组成物理地址
2.6 段寄存器 段寄存器是提供段地址,8086CPU 有四个段寄存器:CS,DS,SS,ES,当 8086CPU 要访问内存时,由这四个段寄存器提供内存单元的段地址。
CS 为代码段寄存器,IP 为指令指针寄存器。
2.7 CS 和 IP CPU 将 CS 和 IP 中的内容当作指令的段地址
CS 存放指令的段地址,IP 存放指令的偏移地址
同时 修改 CS、IP 的内容:
jmp 段地址:偏移地址
1 2 3 jmp 2AE3:3 #跑到2AE33 jmp 3:0B16 #跑到00B46H 3=00030
功能:用指令中给出的段地址修改 CS,偏移地址修改 IP
1 2 3 4 5 仅修改IP内容:jmp 某一合法寄存器 jmp ax(类似于mov IP,ax) jmp bx
比如:
功能:用寄存器中的值修改 IP
2.8 代码段 将一组内存单元定义为一个段
CPU 只认被 CS:IP 指向的内存单元中的内容为指令
三、寄存器(内存访问) 3.1 内存中字的存储 低地址存低位,高地址存高位
字型考虑两个字节
两个连续的内存单元,N 号单元和 N+1 可以看成两个内存单元,也可以看成一个地址为 N 的字单元中的高位字节单元和低位字节单元。
3.2 DS 和 [address] DS 段寄存器存放要访问数据的段地址
mov 指令能**1.将数据直接送入寄存器 **
2.将寄存器的内容送入另一个寄存器
3.将内存单元的内容传入一个寄存器
mov 指令能将一个内存单元的内容 送入一个寄存器
1 2 3 4 5 6 7 mov 寄存器名,内存单元地址 e.g mov bx,1000H mov ds,bx mov a1,[0] //内存单元偏移地址为 0,段地址默认放在 ds 中 mov ds,1000H //错误表达 8086CPU 不支持直接将数据送入段寄存器操作,用 bx 进行中转
数据->通用寄存器->段寄存器
内存单元到寄存器
寄存器到内存单元
将 a1 中的数据传入内存单元10000H 中
10000H->1000:0 地址1000H,偏移地址为0
1 2 3 mov bx,1000H mov ds,bx mov [0],a1
3.3 字的传送 1 2 3 4 mov bx,1000H mov ds,bx mov ax,[0] //1000:0处的字型数据送入ax mov [0],cx //cx中的16位数据送到1000:0处
3.4 mov、add、sub 指令 1 2 3 4 5 mov/add/sub 寄存器,数据 mov ax,8 mov/add/sub 寄存器,寄存器 mov ax,bx mov/add/sub 寄存器,内存单元 mov ax,[0] mov/add/sub 内存单元,寄存器 mov [0],ax mov/add/sub 段寄存器,寄存器 mov ds,ax
3.5 数据段 将 123B0H~123B9H 的内存单元定义为数据段,累加这个数据段 的前三个单元的数据
1 2 3 4 5 6 mov ax,123BH mov ds,ax //将123BH送入ds中,作为数据段的段地址 mov a1,0 //用a1存放累加结果 add a1,[0] //将数据段第一个单元(偏移地址为0)中的数值加到a1中 add a1,[1] //以此类推 add a1,[2]
eg.写几条指令,累加数据段中的前三个字型数据
1 2 3 4 5 6 7 8 mov ax,123BH mov ds,ax mov ax,0 add ax,[0] add ax,[2] add ax,[4] //一个字占两个单元,偏移地址为0,2,4
小结
字在内存中存储时,需要两个连续的内存单元,低位字节放低地址单元中,高位放高地址中
mov 指令中只给出单元的偏移地址,段地址默认在 DS 寄存器中
内存和寄存器传字型数据时,高地址单元和高 8 位寄存器、低地址单元和低 8 位寄存器相对应
mov/sub/add 有两个操作对象指令,jmp 只有一个
3.6 栈 先进后出
3. 7 CPU提供的栈机制
push ax 执行步骤: 1)SP=SP-2,SS:SP 指向当前栈顶前面的单元,以此为新的栈顶
2)将 ax 中的内容送入SS:SP 指向的内存单元处,SS:SP 此时指向新栈顶
将 10000H~1000FH 当作栈,SS=1000H,栈空间大小为16字节。任意时刻 SS:SP 指向栈顶,当栈中只有一个元素时,SS=1000H,SP=000EH。栈为空,就相当于栈中唯一的元素出栈,出栈后,SP=SP+2,SP 原来为 000EH,后 SP=0010H,所以栈为空时,SS=1000H,SP=10H
当栈为空时,也就不存在栈顶元素。SS:SP就只能指向最底部单元下面的单元,该单元的偏移地址为栈最底部的字单元的偏移地址+2,栈最底部字单元地址为1000:000E,所以栈空时,SP=0010H
pop ax步骤: 1 2 1) 将 SS:SP 指向的内存单元处的数据送入ax中 1) SP=SP+2,SS:SP指向当前栈下面单元,以此为栈顶
出栈后,SS:SP 指向新的栈顶 1000EH,pop 操作前的栈顶元素,1000CH 处的 2266H 依然存在。但已经不在栈中。再次执行 push 等入栈指令后,SS:SP 移至1000CH,并写入新的数据,将被覆盖
3.8 栈顶超界的问题 注意入栈出栈时超界问题
3.9 push、pop指令 1 2 push 寄存器 //将一个寄存器的数据入栈 pop 寄存器 //用一个寄存器接收出栈的数据
1 2 3 4 push 段寄存器 //将一个段寄存器中的数据入栈 pop 段寄存器 //用段寄存器接收数据 push 内存单元 //将一个内存单元处的字入栈 pop 内存单元 //用一个内存单元接收数据
1 2 3 4 5 e.g. mov ax,1000H mov ds,ax push [0] //将1000:0处的字压入栈中 pop [2] //出栈的数据放入1000:2处
指令执行时,CPU 要知道内存单元的地址,可以在 push、pop 指令中给出内存单元的偏移地址,段指令在执行时,CPU 从 ds 中获得
e.g
将 10000H~1000FH 这段空间当作栈,初始状态栈为空,将 AX,BX,DX 中的数据入栈
1 2 3 4 5 6 mov ax,1000H mov ss,ax //设置栈的段地址,SS=1000H,不能直接向段寄存器SS中送入数据,所以用ax中转 mov sp,0010H //设置栈顶的偏移地址,栈为空,所以sp=0010H push ax push bx push dx
将 10000H~1000FH 这段空间当作栈,初始状态是空的,AX=001AH,BH=001BH,将 AX,BX 的数据入栈,然后将 AX、BX 清零,从栈中恢复 AX、BX 原来的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 mov ax,1000H mov ss,ax mov sp,0010H //初始化栈 mov ax,001AH mov bx,001BH push ax push bx //ax,bx入栈 sub ax,ax //ax清零,也可以用mov ax,0 sub bx,bx pop bx //恢复数据,当前栈顶为bx中的内容001BH,ax的001AH在栈顶下面,所以先pop bx 再pop ax pop ax
3.10 栈段 一段内存当作栈,所以为栈段
将 10000H~1000FH 中的八个字,逆序复制到 20000H-2000FH 中
1 2 3 4 5 6 7 8 9 10 11 12 13 mov ax,1000H mov ds,ax mov ax,2000H mov ss,ax mov sp,10010H push [0] push [2] push [4] push [6] push [8] push [A] push [C] push [E]
将 10000H~1000FH 中的八个字,逆序复制到 20000H-2000FH 中
1 2 3 4 5 6 7 8 9 10 11 12 13 mov ax,2000H mov ds,ax mov ax,1000H mov ss,ax mov sp,0 pop [E] pop [C] pop [A] pop [8] pop [6] pop [4] pop [2] pop [0]
四、第一个程序 4.2 源程序 1. 伪指令 汇编语言中有汇编指令和伪指令。
汇编指令:有对应的机器码指令,可以被编译为机器指令,最终被 CPU 所执行。
伪指令:没有对应的机器指令,不被 CPU 执行。由编译器执行指令,进行相关编译工作。
1 2 3 4 5 6 7 8 9 10 assume cs:codesg //将代码段codesg和CPU的段寄存器cs联系 codesg segment //定义一个段,段名为codesg mov ax,0123H mov bx,0456H add ax,bx add ax,ax mov ax,4c00H int 21H codesg ends //段结束 end //程序结束
a. segment 和 ends 是成对使用的伪指令,segment 和 ends 定义一个段,格式为:
b. end 表示一个汇编程序结束
c. assume 说明某段寄存器和程序中的某一个用 segment…ends 定义的段相联
2. 源程序中的程序 3. 标号 如“codesg”,一个标号代表了一个地址,codesg 在 segment 前面,作为一个段名称,这个名称最终被编译、连接程序处理为一个段的段地址。
4. 程序的结构 编写运算2^3。
1 2 3 4 5 6 7 assume cs:abc //将abc和cs联系起来 abc segment //定义一个程序段 mov ax,2 add ax,ax add ax,ax abc ends end //程序结束
5. 程序的返回 一个程序结束后,将 CPU 的控制权交换给它得以运行的程序,这个过程称为程序返回
目的
相关指令
指令性质
指令执行者
通知编译器一个段的结束
段名 ends
伪指令
编译时编译器
通知编译器程序结束
end
伪指令
编译时由编译器
程序返回
mov ax,4cooH int 21H
伪指令
执行时由CPU
4.3 编辑源程序 DOS 中有一个程序 command.com,这个程序在 DOS 中称为命令解释器,也就是 DOS 系统的 shell
汇编程序从写出到执行过程
1 2 编程 -> 1.asm-> 编译 -> 1.obj -> 连接 -> 1.exe -> 加载 -> 内存中的程序 -> 运行 (Edit) (masm) (link) (command) (CPU)
五、[BX] 和 loop 指令 1.[bx] 和 [0] 类似,偏移地址为 0
2.[bx] 表示一个内存单元,偏移地址在 bx 中
3.loop 循环
4.”()“符号三种类型:**a)**寄存器名
**b)**段寄存器名
**c)**内存单元物理地址(一个20位数据)
e.g
1 2 3 4 5 6 7 8 ax内容位0010H,可以描述为:(ax)=0010H 2000:1000处的内容为0010H,描述为:(21000H)=0010H mov ax,[2] 描述为(ax)=((ds)*16+2) mov [2],ax 描述为((ds)*16+2)=ax add ax,2 描述为(ax)=(ax)+2 add ax,bx 描述为(ax)=(ax)+(bx) push ax 描述为 (sp)=(sp)-2 ((ss)*16+(sp))=(ax) pop ax 描述为(ax)=((ss)*16+(sp)) (sp)=(sp)+2
5.”(X)“中表示数据有两种类型:字节和字
1 2 3 4 比如(al)、(bl)、(cl)等得到的数据为字节 (ds)、(ax)、(bx)等得到的数据为字 (al)=(20000H),则(20000H)得到的数据为字节型 (ax)=(20000H),则(20000H)得到的为字型
6.约定符号 idata 表示常量
1 2 3 mov ax,[idata] 代表 mov ax,[1]、mov ax,[2]、mov ax,[3]等 mov bx,idata 代表mov bx,1、mov bx,2、mov bx,3等 mov ds,idata 代表mov ds,1、mov ds,2等,都是非法指令
5.1 [BX] 1 2 mov ax,[bx] bx中存放数据作为一个偏移地址EA,段地址SA默认在ds中,将ax中的数据送入内存SA:EA处,即:((ds)*16+(bx))=(ax)
e.g 写出程序执行后,21000H~21007H 单元中的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 mov ax,2000H mov ds,ax mov bx,1000H //ds=2000H,bx=1000H mov ax,[bx] //将2000:1000处的字型数据送入ax中。ax=00beH inc bx inc bx //bx=1002H mov [bx],ax //ds=2000H,bx=1002H,将ax内容送入2000:1002处。2000:1002内容为BE,2000:1003内容为00 inc bx inc bx //bx=1004H mov [bx],ax //2000:1004内容为BE,2000:1004内容为00 inc bx //bx=1005H mov [bx],al //ds=2000H,bx=1005H,将al中的数据送入2000:1005处。2000:1005单元的内容为BE inc bx mov [bx],al //bx=1006H,2000:1006单元的内容为BE
5.2 Loop指令
格式:loop 标号
CPU 执行指令时要进行两步操作:a. (cx)=(cx)-1 b. 判断 cx 中的值,不为零则转至标号处执行程序,如果为零则向下执行。
e,g 计算2^2,结果存在ax中
1 2 3 4 5 6 7 8 assume cs:code code sgement mov ax,2 add ax,ax mov ax,4c00h int 21h code ends end
e.g 计算2^12,结果存在 ax 中
1 2 3 4 5 6 7 8 9 10 11 12 13 assume cs:code code segment mov ax,2 mov cx,11 s: add ax,ax //用s标识了一个地址,地址处有一条指令:add ax,ax loop s //判断(cx)=(cx)-1,不为0转到s所标识的地址处执行,如果为0就执行下一条指令,下一条为mov ax,4c00h //执行loop s时,首先将(cx)-1,不为0就转到s处 mov ax,4c00h int 21h code ends end
cx 和 loop 指令配合实现循环3要点:
1)在 cx 中存放循环次数
2)loop 指令中的标号所标识地址要在前面
3)要循环执行的程序段,要写在标号和 loop 指令的中间
框架
1 2 3 4 mov cx,循环次数 s: 循环执行的程序段 loop s
e.g 用加法计算123*236,结果存在ax中
1 2 3 4 5 6 7 8 9 10 assume cs:code code segment mov ax,0 mov cx,236 s:add ax,123 loop s mov ax,4c00h int 21h code ends end
5.5 loop 和 [bx] 的联合应用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 assume cs:code code segment mov ax,0ffffh 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
5.6 段前缀 1 2 3 4 5 6 mov ax,ds:[bx] //将一个内存单元内容送入ax,这个内存单元长度为2字节,存放一个字,偏移地址在bx中,段地址在ds中 cs:[bx] ss:[bx] es:[bx] mov ax,ss:[0] //将一个内存单元内容送入ax,这个内存单元长度为2字节,存放一个字,偏移地址为0,段地址在ss中 cs:[0]
段前缀用于显示地指明内存单元的段地址,如”ds”,“cs”,“ss”,“es”等
5.7 一段安全的空间
直接向一段内存写入内容
这段内容不应存放系统或其他程序的数据或代码,否则写入操作可能引发错误
DOS 方式下,一般情况,0:200~0:2ff 空间中没有系统或其他程序的数据或代码
要直接向一段内存中写入内容时,使用 0:200~0:2ff 这段空间
5.8 段前缀的使用 e.g 将内存 ffff:0~fff:b 单元处的数据复制到 0:200-0:20b 单元中
0:200-0:20b 相当于 0020:0-0020:b 单元
初始化:X=0 循环12次:将ffff:X单元的数据送入 0020:X X=X+1
源始单元 ffff:X 和目标单元 0020:X 的偏移地址X是变量。用 bx 存放
将 0:200-0:20b 用 0020:0-0020:b 描述,使目标单元的偏移地址和源始单元的偏移地址从同一数值 0 开始
使用两个寄存器分别存放源始单元 ffff:X 和目标单元 0020:X 的段地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 assume cs:code code segment mov ax,0ffffh mov ds,ax //(ds)=0ffffh mov ax,0020h mov es,ax //(es)=0020h mov bx,0 //(bx)=0,此时ds:bx指向0fffh:0,es:bx指向0020h:0 mov cx,12 //(cx)=12 s: mov dl,[bx] //(dl)=((ds)*16+(bx)),将ffff:bx中的数据送入dl mov es:[bx],dl //(es)*16+(bx)=(dl),将dl中数据送入0020:bx inc bx //(bx)=(bx)+1 loop s mov ax,4c00h int 21h code ends end
六、包含多个段的程序 6.1 在代码块中使用数据 框架
1 2 3 4 5 6 7 8 9 10 11 assume cs:code code segment : 数据 : start: : 代码 : code ends end start
e.g. 计算以下八个数据的和,结果存在ax寄存器中:0123h、0456h、0789h、0abch、0defh、0fedh、0cbah、0987h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 assume cs:code code segment dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h //dw定义字型数据。即“define word” start: mov bx,0 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
6.2 在代码段中使用栈 利用栈,将数据0123h、0456h、0789h、0abch、0defh、0fedh、0cbah、0987h逆序存放,数据存放在cs:10-cs:2F单元中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 assume cs:codesg codesg segment dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 //用dw定义16个字型数据,在程序加载后,会取得16个字的内存空间,存放这16个数据。后面程序中将这段空间当作栈来使用 start: mov ax,cs mov ss,ax mov sp,30h //将设置栈顶ss:sp指向cs:30 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
1.e.g 下面的程序依次实现使用内存0:0-0:15单元中的内容改写程序中的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 assume cs:codesg codesg segment dw 0123h,0456h,0789h,0abch,0fedh,0cbah,0987h start: mov ax,0 mov ds,ax mov bx,0 //ds=0,bx=0 mov cx,8 s: mov ax,[bx] //将ds:0数据送入ax mov cs:[bx],ax //(cs)*16+(bx)=(ax),将ax数据送入cs:0处 add bx,2 //cs:2 loop s mov ax,4c00h int 21h codesg ends end start
用栈来进行数据传送
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 assume cs:codesg codesg segment dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 start: mov ax,cs mov ss,ax mov sp,24h mov ax,0 mov ds,ax mov bx,0 mov cx,8 s: push [bx] pop cs:[bx] add bx,2 loop s mov ax,4c00h int 21h codesg ends end start
6.3 将数据、代码、栈放入不同的段 逆序存放
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 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,0,0,0,0,0,0,0,0 stack ends code segment start: mov ax,stack mov ss,ax mov sp,20h //设置栈顶ss:sp指向stack:20 mov ax,data 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 //依次出栈到data段的0-15单元中 mov ax,4c00h int 21h code ends end start
七、定位内存地址的方法 7.1 and和or指令 1)and 指令:逻辑与,按位进行与运算(两个1才是1)
1 2 3 mov al,01100011B and al,00111011B 结果 al=00100011B
2)or 指令:逻辑或,按位进行或运算(有1就为1)
1 2 3 mov al,01100011B or al,00111011B 结果 al=01111011B
7.3 以字符形式给出的数据 1 2 3 4 5 6 7 8 9 10 11 12 assume cs:code,ds:data data segment db 'unIX' //相当于“db 75H,6EH,49H,58H”,db定义字节变量,类似dw db 'foRK' //相当于“db 66H,6FH,52H,4BH” data ends code segment start: mov al,'a' //mov al,61H mov bl,'b' mov ax,4c00h int 21h code ends end start
7.4 大小写转换 判断某一位置是 0 还是 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 assume cs:codesg,ds:datasg datasg segment db 'BaSiC' db 'iNfOrMaTiOn' datasg ends codesg segment start: mov ax,datasg mov ds,ax //设置ds指向datasg段 mov bx,0 //设置(bx)=0,ds:bx指向'BaSiC'的第一个字母 mov cx,5 //设置循环次数 s: mov al,[bx] //将ASCII码从ds:bx所指向的单元中取出 and al,11011111B //将al中的ASCII码的第五位为0,变为大写字母 mov [bx],al //转换后的ASCII码写回原单元 inc bx //ds:bx指向下一个字母 loop s mov bx,5 //(bx)=5,ds:bx指向'iNfOrMaTiOn'的第一个字母 mov cx,11 s0: mov al,[bx] or al,00100000B //将al中的ASCII码第5位置为1,变为小写字母 mov [bx],al inc bx loop s0 mov ax,4c00h int 21h codesg ends end start
7.5 [bx+idata] [bx+idata] 表示一个内存单元,偏移地址为 (bx) + idata ( bx中的数值加上 idata )
mov ax,[bx+200] 的含义:
将一个内存单元送入ax,内存单元长度为 2 个字节(字单元),存放一个字,偏移地址为 bx 中的数值加上200,段地址在ds中
(ax)=((ds)*16+(bx)+200)
mov ax,[200+bx]
mov ax,200[bx]
mov ax,[bx].200
1 2 3 4 5 6 7 8 9 10 11 2000:1000 BE 00 06 00 00 00 ... 写出下面的程序执行后,ax、bx、cx中的内容 mov ax,2000H mov ds,ax mov bx,1000H mov ax,[bx] //访问的段地址在ds中,(ds)=2000H;偏移地址在bx中,(bx)=1000H;指令执行后(ax)=00BEH mov cx,[bx+1] //偏移地址=(bx)+1=1001H;后(cx)=0600H add cx,[bx+2] //偏移地址=(bx)+2=1002H;后(cx)=0606H
7.6 用 [bx+idata] 的方式进行数组的处理 将 datasg 中定义的第一个字符串转化为大写,第二个字符串转化为小写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 assume cs:codesg,ds:datasg datasg segment db 'BaSiC' db 'MinIX' datasg ends codesg segment start: mov ax,datasg mov ds,ax mov bx,0 mov cx,5 s: mov al.0[bx] and al,11011111b mov 0[bx],al mov al,5[bx]([5+bx]) or al,00100000b mov 5[bx]([5+bx]),al inc bx loop s codesg ends end start
7.7 SI和DI si 和 di 是 8086CPU 中和 bx 功能相近的寄存器,不能分成两个 8 位寄存器来使用
下面三组实现同一功能
下面三组实现同一功能
1 2 mov si,0 mov ax,[si+123]
1 2 mov di,0 mov ax,[di+123]
e.g 用 si 和 di 实现将字符串’welcome to masm!’复制到它后面的数据中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 assume cs:codesg,ds:datasg datasg segment db 'welcome to masm!' db '................' datasg ends codesg segment start: mov ax,datasg mov ds,ax mov si,0 mov di,0 mov cx,8 s: mov ax,[si] mov [di],ax add si,2 add di,2 loop s mov ax,4c00h int 21h codesg ends end start //ds:si指向要复制的源始字符串,用ds:di指向复制的目的空间。用16位寄存器进行内存单元之间的数据传送,一次复制2个字节,循环8次
使用 [bx(si或di)+idata] 的方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 codesg segment start: mov ax,datasg mov ds,ax mov si,0 mov cx,8 s: mov ax,0[si] mov 16[si],ax add si,2 loop s mov ax,4c00h int 21h codesg ends end start
7.8 [bx+si] 和 [bx+di] [bx+si] 表示一个内存单元,偏移地址为 (bx)+(si) 即 bx 中的数值加上 si 的数值
1 2 3 4 5 6 mov ax,[bx+si] 将一个内存单元的内容送入ax,内存单元的长度为2字节(字单元),存放一个字,偏移地址为bx中的数值加上si中的数值,段地址在ds中 (ax)=((ds)*16+(bx)+(si)) 也可写为如下格式 mov ax,[bx][si]
e.g 2000:1000 BE 00 06 00 00 00 ….. ,写出ax、bx、cx中的内容
1 2 3 4 5 6 7 8 9 10 11 12 mov ax,2000H mov ds,ax mov bx,1000H mov si,0 mov ax,[bx+si] //段地址在ds中,(ds)=2000H;偏移地址=(bx)+(si)=1000H;指令执行后(ax)=00BEH inc si mov cx,[bx+si] //段地址在ds中,(ds)=2000H;偏移地址=(bx)+(si)=1001H;执行指令后(cx)=0600H inc si mov di,si add cx,[bx+di] //段地址在ds中,(ds)=2000H;偏移地址=(bx)+(di)=1002H;执行指令后(cx)=0606H
7.9 [bx+si+idata] 和 [bx+di+idata] [bx+si+idata] 表示一个单元,偏移地址为 (bx)+(si)+idata (即bx中数值加上si中的数值再加上idata)
1 2 3 4 5 6 7 8 9 10 mov ax,[bx+si+idata] 将内存单元送入ax,偏移地址为bx加上si数值再加上idata,段地址在ds中 (ax)=((ds)*16+(bx)+(si)+idata) 也有下面这些格式 mov ax,[bx+200+si] mov ax,[200+bx+si] mov ax,200[bx][si] mov ax,[bx].200[si] mov ax,[bx][si].200
e.g 2000:1000 BE 00 06 00 6A 22 ……写出ax、bx、cx内容
1 2 3 4 5 6 7 8 9 10 11 12 mov ax,2000H mov ds,ax mov bx,1000H mov si,0 mov ax,[bx+2+si] //段地址在ds中,(ds)=2000H,偏移地址=(bx)+(si)+2=1002H;(ax)=0006H inc si mov cx,[bx+2+si] //(ds)=2000H,偏移地址=(bx)+2+(si)=1003H;(cx)=6A00H inc si mov di,si mov bx,[bx+2+di] //偏移地址=(bx)+2+(di)=1004H;(bx)=226AH
7.10 不同的寻址方式的灵活运用
[idata] 用常量 表示地址,可以直接定位一个内存单元
[bx] 用 变量 来表示内存地址,可以间接定位一个内存单元
[bx+idata] 用 变量和常量 表示内存地址,可以在起始地址的基础上用变量间接定位一个内存单元
[bx+si] 用两个变量 表示地址
[bx+si+idata] 用两个变量和一个常量 表示地址
e.g 将datasg段中每个单词的头改为大写字母
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 assume cs:codesg,ds:datasg datasg segment db '1. file ' db '2. edit ' db '3. search ' db '4. view ' db '5. options ' db '6. help ' datasg ends codesg segment start: mov ax,datasg mov ds,ax mov bx,0 mov cx,6 s: mov al,[bx+3] and al,11011111b mov [bx+3],al add bx,16 loop s codesg ends end start
e.g 将 datasg 段中每个单词改为大写字母
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 assume cs:codesg,ds:datasg datasg segment db 'ibm ' db 'dec ' db 'dos ' db 'vax ' datasg ends codesg segment start: mov ax,datasg mov ds,ax mov bx,0 //R=第一行地址 mov cx,4 s0: mov si,0 //C=第一列地址 mov cx,3 s1: mov al,[bx+si] and al,11011111b //改变R行,C列的字母变为大写 mov [bx+si],al inc si //下一列地址 loop s add bx,16 //下一行地址 loop s0 codesg ends end start
改进程序
一般情况下,需要暂存数据的时候,应该使用栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 assume cs:codesg,ds:datasg,ss:stacksg datasg segment db 'ibm ' db 'dec ' db 'dos ' db 'vax ' datasg ends stacksg segment 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 mov cx,4 s0: push cx //将外层循环的cx值压栈 mov si,0 mov cx,3 //cx设置成内层循环的次数 s: mov al,[bx+si] add al,11011111b mov [bx+si],al inc si loop s add bx,16 pop cx //从栈顶弹出原cx的值,恢复cx loop s0 //外层循环的loop指令将cx中的值-1 mov ax,4c00H int 21H codesg ends end start
e.g 将 datasg 段每个单词前四个字母改为大写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 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,datasg mov ds,ax mov bx,0 mov cx,4 s0: mov si,0 mov cx,4 s: mov al,[bx+si+3] add al,11011111b mov [bx+si],al inc si loop s add bx,16 loop s0 mov ax,4c00H int 21H codesg ends end start
八、数据处理的两个基本问题 reg 寄存器的集合包括:ax、bx、cx、dx、ah、al、bh、bl、ch、cl、dh、dl、sp、bp、si、di
sreg 段寄存器集合包括:ds、ss、cs、es
8.1 bx、si、di 和 bp 1)在 8086CPU 中,只有这四个寄存器可以在“[……]”中进行内存单元的寻址
2)在[……]中,这4个寄存器可以单个出现,或只能以4种组合出现:==bx+si、bx+di、bp+si、bp+di ==
3)只要在[…]中使用寄存器bp,而指令中没有显性地给出段地址,段地址就默认在ss中 。
1 2 3 4 mov ax,[bp] //(ax)=((ss)*16+(bp)) mov ax,[bp+idata] //(ax)=((ss)*16+(bp)+idata) mov ax,[bp+si] //(ax)=((ss)*16+(bp)+(si)) mov ax,[bp+si+idata] //(ax)=((ss)*16+(bp)+(si)+idata)
8.3 数据位置的表达 1)立即数 (idata)
2)寄存器
3)段地址(SA)和偏移地址(EA)
存放段地址的寄存器可以是默认的,比如以下指令,段地址默认在 ds 中
1 2 3 4 5 mov ax,[0] mov ax,[di] mov ax,[bx+8] mov ax,[bx+si] mov ax,[bx+si+8]
段地址默认在ss中
1 2 3 4 mov ax,[bp] mov ax,[bp+8] mov ax,[bp+si] mov ax,[bp+si+8]
8.4 寻址方式
8.5 指令要处理得到数据有多长 1)通用寄存器指明要处理的数据的尺寸
2)没有寄存器名存在时,用操作符 X ptr 指明内存单元的长度,X 可以为 byte 或 word
3)push 不用指明访问字单元还是字节单元,push 指令只进行字操作
8.7 div指令 1)除数:有 8 位和 16 位,在一个 reg 或内存单元中
2)被除数:默认放在 AX 或 DX 和 AX 中,除数为 8 位,被除数则为 16 位,默认在 AX 中存放;除数为 16 位,被除数则为 32 位,在 DX 和 AX 中存放,DX 存放高16位,AX 存放低 16 位
3)结果:除数为 8 位,则 AL 存储除法操作的商,AH 存储除法操作的余数;
除数为16位,AX 存储商,DX 存储余数
用多种方法表示一个内存单元
1 2 3 div byte ptr ds:[0] //(al)=(ax)/((ds)*16+0)的商 //(ah)=(ax)/((ds)*16+0)的余数
1 2 3 div word ptr es:[0] //(ax)=[(dx)*10000H+(ax)]/((es)*16+0)的商 //(dx)=[(dx)*10000H+(ax)]/((es)*16+0)的余数
1 2 3 div byte ptr [bx+si+8] //(al)=(ax)/((ds)*16+(si)+8)的商 //(ah)=(ax)/((ds)*16+(si)+8)的余数
1 2 3 div word ptr [bx+si+8] //(ax)=[(dx)*10000H+(ax)]/((ds)+(si)+8)的商 //(dx)=[(dx)*10000H+(ax)]/((ds)+(si)+8)的余数
利用除法指令计算 100001/100
1 2 3 4 5 6 7 8 被除数100001大于65535,不能用ax寄存器存放,用dx和ax两个寄存器联合存放100001. 除数小于255,用8位寄存器存放 mov dx,1 mov ax,86A1H mov bx,100 div bx //(ax)=03E8H,(dx)=1
8.8 伪指令 dd dd 用来定义 dword(double word,双字)型整数
用 div 计算 data 段中第一个数据除以第二个数据后的结果,商存在第三个数据存储单元中
1 2 3 4 5 6 7 8 9 10 11 12 13 data segment dd 100001 dw 100 dw 0 mov ax,data mov ds,ax mov ax,ds:[0] //ds:0字单元中的低16位存储在ax中 mov dx,ds:[2] //ds:2中高16位存储到dx中 div word ptr ds:[4] //dx:ax中的32位数据除以ds:4字单元中的数据 mov ds:[6],ax //存储 data ends
8.9 dup 操作符,由编译器识别处理,用来进行数据的重复
1 2 3 4 5 6 7 8 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个字节,‘abcABCabcABCabcABC’,相当于db 'abcABCabcABCabcABC'
dup 使用格式
1 2 3 db 重复的次数 dup (重复的字节型数据) dw 重复的次数 dup (重复的字型数据) dd 重复的次数 dup (重复的双字型数据)
九、 转移指令的原理 可以修改 IP,或同时修改 CS 和 IP 的指令
8086CPU 转移行为分为两类:
段间转移分为:短转移和近转移
短转移 IP 修改范围为-128~127
近转移 IP 的修改范围为-32768~32767
8086CPU 转移指令:
无条件转移指令(如:jmp)
条件转移指令
循环指令(如:loop)
过程
中断
9.1 操作符 offset 操作符 offset 由编译器处理,取得标号的偏移地址
1 2 3 4 5 6 7 8 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 //start所标记的指令是代码段的第一条指令,偏移地址为0 //s所标记的指令为第二条,第一条指令长度为3个字节,则s的偏移地址为3
e.g 使程序在运行中将 s 处的一条指令复制到 s0 处
1 2 3 4 5 6 7 8 9 10 11 12 13 //将s处的指令复制到s0处就是将cs:offset s处数据复制到cs:offset s0 assume cs:codesg codesg segment s: mov ax,bx //mov ax,bx的机器码占2个字节 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 指令要给出两种信息:
转移的目的地址
转移的距离(段间转移、段内短转移、段内近转移)
9.3 根据位移进行转移的 jmp 指令
jmp short 标号(转到标号处执行指令) //段内短转移
CPU 在执行 jmp 指令时不需要转移的目的地址
“jmp short 标号”:功能:(IP)=(IP)+8 位位移
8位位移=标号处的地址-jmp 指令后的第一个字节的地址
short 指明此处的位移为8位位移
8位位移范围为-128~127,用补码表示
8位位移由编译程序在编译时算出
“jmp near ptr 标号”:(IP)=(IP)+16 位位移
16位位移=标号处的地址-jmp指令的第一个字节的地址
near ptr 指明此处的位移为16位位移,进行的是段内近转移
16位位移的范围为-32768~32767,用补码表示
16位位移由编译程序在编译时算出
9.4 转移的目的地址在指令中的 jmp 指令 “jmp far ptr 标号”实现段间转移,又称为远转移 **
(CS)=标号所在段的段地址;
(IP)=标号在段中的偏移地址
far ptr 指明了指令用标号的段地址和偏移地址修改 CS 和 IP
9.5 转移地址在寄存器中的 jmp 指令 格式:
功能:(IP)=(16位reg)
9.6 转移地址在内存中的 jmp 指令 1)jmp word ptr 内存单元地址(段内转移)
从内存单元地址处开始存放一个字,是转移的目的偏移地址
内存单元地址可用寻址方式的任一格式给出
mov ax,0123H
mov ds:[0],ax
jmp word ptr ds:[0]
//执行后(IP)=0123H
mov ax,0123H
mov [bx],ax
jmp word ptr [bx]
//执行后(IP)=0123H
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 2 )**jmp dword ptr 内存单元地址(段间转移)** - 从内存单元开始存放两个字,**高地址处的字是转移的目的段地址,低地址是偏移地址** - (CS )=(内存单元地址+2 ) (IP )=(内存单元地址) - ``` assembly mov ax ,0123H mov ds :[0 ],ax mov word ptr ds :[2 ],0 mov dword ptr ds :[0 ] //执行后(CS )=0 ,(IP )=0123H ,CS :IP 指向0000 :0123 mov ax ,0123H mov [bx ],ax mov word ptr [bx +2 ],0 jmp dword ptr [bx ] //执行后,(CS )=0 ,(IP )=0123H ,CS :IP 指向0000 :0123
e.g. 使 jmp 指令执行后,CS:IP 指向程序的第一条指令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 assume cs:code data segment dd 12345678H data ends code segment start: mov ax,data mov ds,ax mov bx,0 mov [bx],bx //或者 mov [bx],word ptr 0或者mov [bx],offset start //将IP设置为0 mov [bx+2],cs //或 mov [bx+2],cs 或 mov [bx+2],seg code //将cs段地址放入内存单元 jmp dword ptr ds:[0] code ends end start //jmp dword ptr ds:[0]为段间转移,(CS)=(内存单元地址+2),(IP)=(内存单元地址) //CS:IP指向第一条指令,第一条程序地址为cs:0,设置CS:IP指向cs:0
e.g 2000:1000 BE 00 06 00 00 00 ……
1 2 3 4 mov ax,2000H mov es,ax jmp dword ptr es:[1000H] //(CS)=0006H,(IP)=00BEH
9.7 jcxz 指令 jcxz 指令为有条件转移指令,都是短转移 ,在对应的机器码中包含转移的位移,而不是目的地址。IP修改范围为:-128~127
格式:
1 2 jcxz 标号(如果(cx)=0),转移到标号处执行 当(cx)=0时,(IP)=(IP)+8位位移
8位位移=标号处的地址 - jcxz 指令后的第一个字节的地址
8位位移的范围为-128~127,用补码表示
“ jcxz 标号 ”功能相当于:
1 2 if ((cx)==0) jmp short 标号
e.g 利用 jcxz 指令,实现在内存 2000H 段中查找第一个值为 0 的字节,找到后将它的偏移地址存储在 dx 中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 assume cs:code code segment start: mov ax,2000H mov ds,ax mov bx,0 s: mov ch,0 //将ch寄存器清零。ch寄存器被用作辅助寄存器,cx寄存器由ch和cl组成,用于循环计数 mov cl,[bx] //将bx指向的内存地址的内容送入cl中 jcxz ok //if(cx==0),就是ch和cl为0 inc bx jmp short s ok: mov dx,bx mov ax,4c00h int 21h code ends end start
9.8 loop 指令 loop 指令为循环指令,所有循环指令都是短转移
格式:
1 2 3 4 loop 标号 (cx)=(cx)-1,如果(cx)不为0,转移到标号处执行、 即: (cx)=(cx)-1 如果(cx)不为0,(IP)=(IP)+8位位移
8位位移=标号处地址 - loop 指令后的第一个字节的地址
8位位移的范围为-128~127,用补码表示
“ loop ”相当于:
1 2 3 (cx)-- if(cx)!=0) jmp short 标号
e.g 利用 loop 指令,实现在内存 2000H 段中查找第一个值为 0 的字节,将偏移地址存储在 dx 中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 assume cs:code code segment start: mov ax,2000H mov ds,ax mov bx,0 s: mov cl,[bx] mov ch,0 inc cx inc bx loop s ok: dec bx mov dx,bx //dec和inc相反,dec bx进行操作为:(bx)=(bx)-1 mov ax,4c00h int 21h code ends end start
十、CALL和RET指令 10.1 ret 和 retf ret 指令用栈中的数据,修改 IP 的内容,从而实现转移
retf 修改 CS 和 IP 的内容,实现转移
执行 ret 指令时 : 相当于进行:pop IP
1 2 (IP)=((ss)*16+(sp)) (sp)=(sp)+2
执行 retf 指令时:相当于进行:pop IP pop CS
1 2 3 4 (IP)=((ss)*16+(sp)) (sp)=(sp)+2 (CS)=((ss)*16+(sp)) (sp)=(sp)+2
ret 指令执行后,(IP)=0,CS:IP 指向代码段的第一条指令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 assume cs:code stack segment 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 mov bx,0 ret code ends end start
retf 指令执行后,CS:IP 指向代码段的第一条命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 assume cs:code stack segment 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 cs push ax mov bx,0 retf code ends end start
e.g 实现从内存 1000:0000 处开始执行指令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 assume cs:code stack segment db 16 dup (0) stack ends code segment start: mov ax,stack mov ss,ax mov sp,16 mov ax,1000h push ax mov ax,0 push ax retf code ends end start //先将段地址CS入栈,再将偏移地址IP入栈
10.3 根据位移进行转移的 call 指令 CPU 执行 call 指令时,进行:
call 标号(将当前 IP 压栈后,转到标号处执行指令)
执行 call 指令时,进行:
1 2 3 1) (sp)=(sp)-2 ((ss)*16+(sp))=(IP) 2) (IP)=(IP)+16位位移
16位位移 = 标号处的地址 - call 指令后的第一个字节的地址
16位位移的范围为-32768~32767
执行 “ call 标号 “时,相当于进行:
10.4 转移的目的地址在指令中的 call 指令 ” call far ptr 标号 “ 实现段间转移
执行时:
1 2 3 4 5 6 1) (sp)=(sp)-2 ((ss)*16+(sp))=(CS) (sp)=(sp)-2 ((ss)*16+(sp))=(IP) 2) (CS)=标号所在段的段地址 (IP)=标号在段中的偏移地址
执行” call far ptr 标号 “时,相当于:
1 2 3 push CS push IP jmp far ptr 标号
10.5 转移地址在寄存器中的 call 指令 格式:call 16位 reg
1 2 3 4 5 6 7 (sp)=(sp)-2 ((ss)*16+(sp))=(IP) (IP)=(16位reg) //相当于 push IP jmp 16位reg
10.6 转移地址在内存中的 call 指令 1) call word ptr 内存单元地址
相当于
1 2 push IP jmp word ptr 内存单元地址
1 2 3 4 5 mov sp,10h mov ax,0123h mov ds:[0],ax call word ptr ds:[0] //(IP)=0123H,(sp)=0EH
2) call dword ptr 内存单元地址
相当于
1 2 3 push CS push IP jmp dword ptr 内存单元地址
1 2 3 4 5 6 mov sp,10h mov ax,0123h mov ds:[0],ax mov word ptr ds:[2],0 call dword ptr ds:[0] //进行两次压栈操作,sp-2-2 //(CS)=0,(IP)=0123H,(sp)=0CH
e.g 下面程序执行后,ax 和 bx 值为多少
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 assume cs:code data segment dw 8 dup (0) data ends code segment start: mov ax,data //3字节 mov ss,ax //2字节 mov sp,16 //3字节 mov word ptr ss:[0],offset s //7字节 //(ss:[0]=1ah) mov ss:[2],cs //4字节 //(ss:[2])=cs call dword ptr ss:[0] //7字节 //cs入栈,ip=19h入栈,转到cs:1ah处执行指令,(ss:[4])=cs,(ss:[6])=ip nop s: mov ax,offset s //ax=1ah sub ax,ss:[0cH] //ax=1ah-(ss:[0ch])=1ah-19h=1 mov bx,cs //bx=cs=0c5bh sub bx,ss:[0eH] //bx=cs-cs=0 mov ax,4c00h int 21h code ends end start
10.7 call 和 ret 的配合使用 e.g bx 的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 assume cs:code code segment start: mov ax,1 mov cx,3 call s //IP指向下一条指令,并压栈,将IP的值改变为标号为s处的偏移地址 mov bx,ax mov ax,4c00h int 21h s: add ax,ax loop s //ax=8 ret //前压入的指令偏移地址送入IP中。CS:IP指向指令mov bx,ax,从此开始执行命令。bx=8 code ends end start
10.8 mul 指令
格式
内存单元可以用不同的寻址方式给出,比如
1 2 3 4 5 6 mul byte ptr ds:[0] //(ax)=(al)*((ds)*16+0) mul word ptr [bx+si+8] //(ax)=(ax)*((ds)*16+(bx)+(si)+8)结果的低16位 //(dx)=(ax)*((ds)*16+(bx)+(si)+8)结果的低16位
e.g 计算 100 * 10
1 2 3 4 mov al,100 mov bl,10 mul bl //(ax)=1000(03E8H)
计算 100 * 10000
1 2 3 4 mov ax,100 mov bx,10000 mul bx //(ax)=4240H,(dx)=000FH
10.10 参数和结果传递的问题 e.g 计算 data 段中第一组数据的 3 次方,结果保存在后面一组 dword 单元中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 assume cs:code data segment dw 1,2,3,4,5,6,7,8 dd 0,0,0,0,0,0,0,0 data ends code segment start: mov ax,data mov ds,ax mov si,0 //ds:si 指向第一组 word 单元 mov di,16 //ds:si 指向第二组 dword 单元 mov cx,8 s: mov bx,[si] call cube mov [di],ax mov [di].2,dx add si,2 //ds:si 指向下一个 word 单元 add di,4 //ds:si 指向下一个 dword 单元 loop s mov ax,4c00h int 21h cube: mov ax,bx mul bx mul bx ret code ends end start
十一、标志寄存器 标志寄存器 flag 按位起作用
11.1 ZF 标志 flag 的第 6 位是 ZF ,零标志位。如果执行相关指令后,结果为 0,zf = 1;不为 0,zf =0
11.2 PF标志 flag 第 2 位是 PF,奇偶标志位。记录相关指令执行后,结果的所有 bit 位中1的个数是否为偶数,如果是偶数,pf = 1,如果是奇数,pf = 0
1 2 3 4 5 6 7 8 mov al,1 add al,10 //00001011B,3个1,pf=0 mov al,1 or al,2 //执行后,结果为 00000011B,有 2 个1,pf=1 sub al,al // 00000000B,0个偶数,pf=1
11.3 SF 标志 flag 第7位,符号标志位。记录执行指令后,结果是否为负。结果为负,sf =1;如果非负,sf =0
1 2 3 4 5 6 7 8 sub al,al ZF=1 PF=1 SF=0 mov al,1 1 1 0 push ax 1 1 0 pop bx 1 1 0 //mov,push,pop等传送指令对标志寄存器没影响 add al,bl 0 0 0 add al,10 0 1 0 mul al 0 1 0
11.4 CF 标志 flag 第 0 位是 CF,进位标志位。一般情况下,在进行无符号运算时,记录了运算结果的最高有效位向跟高位的进位值,或从更高位的借位值
1 2 3 4 5 6 7 mov al,98H add al,al //(al)=30H,CF=1,CF记录了从最高有效位向跟高位的进位值 add al,al //(al)=60H,CF=0 mov al,97H sub al,98H //(al)=FFH,CF=1,CF记录了向更高位的借位值 sub al,al //(al)=0,CF=0
11.5 OF 标志 flag 第 11 位是 OF,溢出标志位。OF 记录有符号数运算的结果是否发生溢出。溢出 OF = 1,没有 OF = 0
1 2 3 4 5 6 7 mov al,0F0H add al,88H //执行后,CF=1,OF=1.无符号运算,0F0H+88H有进位,CF=1;有符号运算,0F0H+88H发生溢出,OF=1 mov al,0F0H add al,78H //CF=1,OF=0.无符号运算0F0H+78H有进位,CF=1;有符号运算,0F0H+78H不发生溢出,OF=0
e.g 下列每条指令执行后,ZF 、PF、SF、CF、OF等标志位的值
1 2 3 4 5 6 7 8 9 sub al,al //ZF=1,PF=1,SF=0,CF=0,OF=0 mov al,10H // 1 1 0 0 0 add al,90H // 0 1 1 0 0 mov al,80H // 0 1 1 0 0 add al,80H // 1 1 0 1 1 mov al,0FCH // 1 1 0 1 1 add al,05H // 0 0 0 1 0 mov al,7DH // 0 0 0 1 0 add al,0BH // 0 1 1 0 1
11.6 adc 指令 adc 为带进位加法指令
1 2 3 4 格式: adc 操作对象1,操作对象2 功能: 操作对象1 = 操作对象1 + 操作对象2 + CF //指令: adc ax,bx //(ax)=(ax)+(bx)+CF
e.g
1 2 3 4 5 mov ax,2 mov bx,1 sub bx,ax adc ax,1 //(ax)=4,(ax)+1+CF=4
1 2 3 4 mov ax,1 add ax,ax adc ax,3 //(ax)=2+3+0=5
1 2 3 4 mov al,98H add al,al adc al,3 //(al)=34H,(al)+3+CF=30H+3+1=34H
执行 adc 指令时,CF 的值如果是被 sub 指令设置的,那么就是借位值
add 指令设置时,是进位值
加法可以分两部进行:
低位相加,比如 0198H 和 0183H。低位相加得 1B
高位相加再加上低位相加的进位值。01+01+1=03
e.g 计算 1EF0001000H + 2010001EF0H,结果放在 ax(最高 16 位),bx(次高 16 位),cx(低 16 位)中
先将低 16 位相加,然后 CF 中记录本次相加的进位值
再将高 16 位和 CF 相加,然后 CF记录进位值
最后高 16 位和 CF相加,CF记录进位值
1 2 3 4 5 6 mov ax,001EH mov bx,0F000H mov cx,1000H add cx,1EF0H add bx,1000H add ax,0020H
11.7 sbb指令 sbb 是带借位减法指令,利用 CF 位上记录的借位值
1 2 3 4 sbb 操作对象1,操作对象2 操作对象1 = 操作对象1 - 操作对象2 - CF //sbb ax,bx //(ax)=(ax)-(bx)-CF
e.g 计算 003E1000H - 00202000H,结果放在 ax,bx 中
1 2 3 4 mov bx,1000H mov ax,003EH sub bx,2000H sbb ax,0020H
11.8 cmp 指令 cmp 为比较指令,相当于减法,但不保存结果。执行后会对标志寄存器产生影响。
1 2 3 4 格式:cmp 操作对象1,操作对象2 功能:计算操作对象1 - 操作对象2,不保存结果,仅仅根据计算结果对标志寄存器进行设置 //cmp ax,ax //结果为 0 ,指令执行后 zf=1,pf=1,sf=0,cf=0,of=0
e.g
1 2 3 4 mov ax,8 mov bx,3 cmp ax,bx //(ax)=8,zf=0,pf=1,sf=0,cf=0,of=0
对于 cmp ax,bx,有如下分析
if (ax==bx),(ax)-(bx)=0,zf=1
if (ax!=bx),(ax)-(bx)!=0,zf=0
if (ax<bx),(ax)-(bx)<0,产生借位,cf=1
if (ax>=bx),(ax)-(bx)>=0,cf=0
if (ax>bx),(ax)-(bx)>0,cf=0 或 zf=0
if (ax<=bx),(ax)-(bx)<=0,cf=1 或 zf=1
if:
zf=1,说明 (ax)=(bx)
zf=0,(ax)!=(bx)
cf=1,(ax)<(bx)
cf=0,(ax)>=(bx)
cf=0 且 zf=0,(ax)>(bx)
cf=1 或 zf=1,(ax)<=(bx)
1 2 3 4 5 6 7 8 9 对有符号数进行比较时 **对于cmp ah ,bh **``` assembly if (ah ==bh ),(ah )-(bh )=0 ,zf=1 if (ah !=bh ),(ah )-(bh )!=0 ,zf=0
如果 sf =1,of=0
of =0,没有溢出,逻辑上真正结果的正负 = 实际结果的正负
sf =1,实际结果为负数,所以逻辑上真正的结果为负,(ah)<(bh)
如果 sf =1,of =1
of =1,有溢出,逻辑上真正结果的正负与实际结果的正负不相等
sf =1,实际结果为负
如果因为溢出导致实际结果为负,那么逻辑上真正的结果为正(ah)>(bh)
如果 sf = 0,of = 1
sf=0,实际结果为非负,而 of =1 说明有溢出 ,结果为非 0 ,实际结果为正
如果因为溢出导致实际结果为非负,那么逻辑上真正结果为负,(ah)<(bh)
sf =0,of =0
没有溢出,实际结果为非负,(ah)>=(bh)
11.9 检测比较结果的条件转移指令 “ 转移 ”指它能修改 IP,“ 条件 ”指它可以根据某种条件,决定是否修改 IP
指令
含义
检测的相关标志位
je
等于则转移
zf = 1
jne
不等于就转移
zf = 0
jb
低于则转移
cf =1
jnb
不低于就转移
cf = 0
ja
高于就转移
cf = 0且 zf =0
jna
不高于就转移
cf =1 或 zf = 1
e.g 统计 data 段中数值小于 8 的字节的个数,用 ax 保存统计结果
1 2 3 4 5 6 7 8 9 10 11 mov ax,data mov ds,ax mov ax,0 mov bx,0 mov cx,8 s: cmp byte ptr [bx],8 jnb next inc ax next: inc bx loop s //(ax)=2
统计 F000:0 处 32 个字节中,大小在 [32,128] 的数据个数
1 2 3 4 5 6 7 8 9 10 11 12 13 mov ax,0f000h mov ds,ax mov bx,0 mov dx,0 mov cx,32 s: mov al,[bx] cmp al,32 ja s0 cmp al,128 ja s0 inc bx s0: inc bx loop s
11.10 DF 标志和串传送指令 flag 第 10 位,方向标志位。在串处理指令中,控制每次操作后 si ,di 的增减
df = 0 每次操作后 si、di 递增
df =1 每次操作后 si、di 递减
1 2 3 4 5 6 7 movsb 相当于: ((es)*16+(di))=((ds)*16)+si 如果 df=0: (si)=(si)+1 (di)=(di)+1 如果 df=1: (si)=(si)-1 (di)=(di)-1
1 2 movsw 将ds:si指向的内存单元中的字送入es:di中,根据标志寄存器df位的值,将si和di递增2或递减2
8086 CPU 提供两条指令对 df 位进行设置
1 2 cld 指令:将标志寄存器的df位置0 std 指令:将标志寄存器df位置1
e.g 用串传送指令,将 F000H 段中的最后 16 个字符复制到 data 段中
1 2 3 4 5 6 7 8 9 10 11 12 data segment db 16 dup (0) data ends mov ax,0f000h mov ds,ax mov si,0ffffh //ds:si 指向 f000:ffff mov ax,data mov es,ax mov di,15 //es:di 指向 data:000f mov cx,16 //(cx)=16,rep 循环16次 std //设置 df=1,逆向传送 rep movsb
11.11 push 和 popf pushf 是将标志寄存器的值压栈,popf 是出栈送入标志寄存器中
e.g (ax)=?
1 2 3 4 5 6 7 8 9 10 11 12 mov ax,0 push ax popf mov ax,0fff0h add ax,0010h pushf pop ax //0 0 0 0 of df if tf sf zf 0 af 0 pf 0 cf //0 0 0 0 0 0 * * 0 1 0 * 0 1 0 1 //ax=flag=000000**010*0101b and al,11000101B //al=01000101b=45h and ah,00001000B //ah=00000000b=0h //ax=45h
11.12 标志寄存器在 Debug 中的表示
标志
1
0
of
OV
NV
sf
NG
PL
zf
ZR
NZ
pf
PE
PO
cf
CY
NC
df
DN
UP
十二、内中断 12.1 内中断的产生 对于 8086CPU ,有以下情况会产生中断
除法错误,比如 div 指令产生的除法溢出
单步执行
into 指令
int 指令
中断源在 8086CPU 中的中断类型
除法错误:0
单步执行:1
into 指令:4
int 指令,格式为 int n,n 为字节型立即数,是提供给 CPU 的中断类型码
12.3 中断向量表 对于 8086PC 机,中断向量表指定放在内存地址 0 处。从内存 0000:0000 到 0000:03FF 的 1024 个单元中存放着中断向量表。
e.g
1 2 0000:0000 68 10 A7 00 8B 01 70 00-16 00 9D 03 8B 01 70 00 //3号中断源对应的中断处理程序的入口地址为: 0070:018B
一个表项存放一个中断向量,也就是一个中断处理程序的入口地址,入口地址包括段地址和偏移地址,一个表项占两个字,高地址存放段地址,低地址存放偏移地址
1 2 存储 N 号中断源对应的中断处理程序入口的偏移地址的内存单元的地址为:4N 段地址的内存单元地址为:4N+2
12.4 中断过程 8086CPU 中断过程
取得字段类型码 N
标志寄存器入栈,TF=0,IF=0
push CS
push IP
( IP ) = ( N * 4 ) , ( CS ) = ( N * 4 + 2 )
12.5 中断处理程序和 iret 指令 中断处理程序的编写方法
保存用到的寄存器
处理中断
恢复用到的寄存器
用 iret 指令返回
在中断过程中,寄存器入栈顺序是标志寄存器,CS,IP。iret 出栈顺序是 IP,CS,标志寄存器
12.9 do0 do0 程序的主要任务是显示字符串
e.g 显示“ overflow!”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 assume cs:code code segment start: mov ax,cs mov ds,ax mov si,offset do0 //设置 ds:si 指向源地址 mov ax,0 mov es,ax mov di,200h //设置 es:di 指向目的地址 mov cx,offset do0end-offset do0 //设置 cx 为传输长度 cld //传输方向为正 rep movsb 设置中断向量表 mov ax,4c0h int 21h do0: jmp short do0start //”overflow!”不是可执行代码,jmp 转移到正式 do0 程序。除法发生溢出时,CPU 执行0:200处的 jmp 指令,跳过后面字符串,转到正式 do0 程序执行 db "overflow!" do0start: mov ax,cs mov ds,ax mov si,202h //ds:si指向字符串,jmp 指令占两个字节,所以偏移地址为 202h mov ax,0b800h mov es,ax mov di,12*160+36*2 //es:di指向显存空间的中间位置 mov cx,9 //cx为字符串长度 s: mov al,[si] mov es:[di],al inc si add di,2 loop s mov ax,4c00h int 21h de0end:nop code:ends end start //将标号 do0 到标号 do0end 之间的内容送到 0000:0200处
12.10 设置中断向量 将 do0 的入口地址 0:200 写入中断向量表的 0 号表项中,使 do0 成为 0 号中断的中断处理程序
0 号表项的地址为 0:0,其中 0:0 字单元存放偏移地址,0:2字单元存放段地址
1 2 3 4 mov ax,0 mov es,ax mov word ptr es:[0*4],200h mov word ptr es:[0*4+2],0
12.11 单步中断 CPU 在执行完一条指令后,如果检测到标志寄存器的 TF 位为 1,则产生单步中断,引发中断过程。
取得中断类型码为 1
标志寄存器入栈,TF、IF 设置为 0
CS、IP 入栈
(IP)=(1*4),(CS)=(1*4+2)
十三、int 指令 13.1 int 指令
13.2 编写中断例程 安装中断 7ch 的中断例程
功能:将一个全是字母,以 0 结尾的字符串,转化为大写
参数:ds:si 指向字符串的首地址
eg. 将 data 段中的字符串转化为大写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 assume cs:code data segment db 'conversation',0 data ends code segment start: mov ax,data mov ds,ax mov si,0 int 7ch mov ax,4c00h int 21h code ends end start
安装程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 assume cs:code code segment start: mov ax,cs mov ds,ax mov si,offset capital cld rep movsb mov ax,0 mov es,ax mov word ptr es:[7ch*4],200h mov word ptr es:[7ch*4+2],0 mov ax,4c00h int 21h capital: push cx push si change: mov cl,[si] mov ch,0 jcxz ok and byte ptr [si],11011111b inc si jmp short change ok: pop si pop cx iret capitalend: nop code ends end start
13.3 对 int、iret 和栈的理解 e.g 在屏幕中显示 80 个 “ ! ”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 assume cs:code code segment start: mov ax,0b800h mov es,ax mov di,160*12 mov bx,offset s-offset se //从标号 se 到标号 s 的转移位移 mov cx,80 s: mov byte ptr es:[di],'!' add di,2 int 7ch //(cx)!=0,转移到标号 s 处 se:nop mov ax,4c0h int 21h code ends end start
13.4 BIOS 和 DOS 所提供的中断例程 在系统板的 ROM 位置存放一套程序,BIOS (基本输入输出系统),主要包含以下内容:
硬件系统的检测和初始化程序
外部中断和内部中断的中断例程
用于硬件设备进行 I/O 操作的中断例程
其他和硬件系统相关的中断例程
//e.g 下面程序在第2、4、6、8行显示 4 句英文诗
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 assume cs:code code segment s1: db 'Good,better,best,','$' s2: db 'Never let it rest,','$' s3: db 'Till good is better,','$' s4: db 'And better,best.','$' s: dw offset s1,offset s2,offset s3,offset s4 row: db 2,4,6,8 start:mov ax,cs mov ds,ax mov bx,offset s mov si,offset row mov cx,4 ok: mov bh,0 mov dh,
十四、端口 寄存器相同特点:
都和 CPU 总线相连,通过它们所在的芯片进行
CPU 对它们进行读或写的时候都通过控制线向它们所在的芯片发出端口号读写命令
CPU 可以直接读写以下地方的数据:
14.1 端口的读写 1)访问内存
1 mov ax,ds:[8] //假设执行前(ds)=0
CPU 通过地址线将地址信息 8 发出
CPU 通过控制线发出内存读命令,选中存储器芯片,通知他它要从中读取数据
存储器将 8 号单元中的数据通过数据线送入 CPU
2)访问端口
1 in al,60h //从 60h 号端口读入一个字节
CPU 通过地址线将地址信息 60h 发出
CPU 通过控制线发出端口命令,选中端口所在的芯片,通知他要从中读取数据
端口所在的芯片将 60h 端口中的数据通过数据线送入 CPU
在 in 和 out 指令中,只能用 ax 和 al 来存放从端口中读入的数据或要发送到端口的数据。访问 8 位端口时用 al ,访问 16 位用 ax
对0~255以内端口进行读写
1 2 in al,20h //从 20h 端口读入一个字节 out 20h,al //往 20h 端口写入一个字节
对 256~65535 的端口进行读写,端口号放在 dx 中
1 2 3 mov dx,3f8h //将端口号 3f8h 送入 dx in al,dx //读入 out dx,al //写入
14.2 CMOS RAM 芯片
包含一个实时钟和一个有 128 个存储单元的 RAM 存储器
128 个字节的 RAM 中,内部实时钟占用 0~0dh 单元来保存时间信息,其余大部分单元用于保存系统配置信息,供系统启动时 BIOS 程序读取
芯片内部有两个端口,存放要访问的 CMOS RAM 单元的地址;71h 为数据端口,存放从选定的 CMOS RAM 单元中读取的数据,或要写入到其中的数据。CPU 对 CMOS RAM 的读写分两步进行,读 CMOS RAM 的 2 号单元
将 2 送入端口 70h
从端口 70h 读出 2 号单元的内容
14.3 shl 和 shr 指令 shl 是逻辑左移指令
将一个寄存器或内存单元中的数据向左移位
将最后移出的一位写入 CF 中
最低位用 0 补充
指令:
1 2 3 mov al,01001000b shl al,1 //左移 1 位 //(al)=10010000b
如果移动位数大于 1 时,必须将移动位数放在 cl 中
1 2 3 4 mov al,01010001b mov cl,3 shl,al,cl //执行后 (al)=10001000b,最后移出的一位是 0,所以 CF=0
shr 是逻辑右移指令
向右移位
最后移出的一位写入 CF 中
最高位用 0 补充
指令:
1 2 3 mov al,10000001b shr al,1 //右移 //(al)=01000000b,CF =1
e.g 用加法和移位指令计算 (ax)=(ax)*10
1 2 3 4 5 6 7 8 9 10 11 12 assume cs:code code segment start: mov bx,ax shl,ax,1 mov cl,3 shl bx,cl //左移 3 位,(bx)=(ax)*8 add ax,bx //(ax)=(ax)*2+(ax)*8 mov ax,4c00h int 21h code ends end start
十五、外中断 15.2 外中断信息 1)可屏蔽中断
CPU 可以不响应的外中断,如果 IF = 0,CPU 可以不响应可屏蔽中断
sti ,设置 IF =1
cli ,设置 IF =0
几乎所有由外设引发的中断都是可屏蔽中断
2)不可屏蔽中断
对于 8086CPU ,不可屏蔽中断类型码固定为2,所以中断过程中,不需要取中断类型码,过程为:
1 2 3 标志寄存器入,IF=0,TF=0 CS、IP 入栈 (IP)=(8),(CS)=(0AH0)
指令系统总结 1. 数据传送指令 mov 、sub 、push、pop、pushf、popf、xchg等都是数据传送指令,实现寄存器和内存、寄存器和寄存器之间的单个数据传送
2. 算术运算指令 add、sub、adc、sbb、inc、dec、cmp、imul、idiv、aaa等,实现寄存器和内存中的数据的算数运算,执行结果影响寄存器的 sf、zf、of、cf、pf、af位
3. 逻辑指令 add、or、not、xor、test、shl、shr、sal、sar、rol、ror、rcl、rcr等,除了 not 外,执行结果都影响标志寄存器的相关标志位
4. 转移指令 可以修改 IP ,或者同时修改 CS 和 IP
1)无条件转移指令,比如,jmp
2)条件转移指令,比如,jcxz,je,jb,ja,jnb,jna等
3)循环指令,比如,loop
4)过程,比如,call、ret、retf
5)中断,比如,int、iret
5. 处理机控制指令 cld、std、cli、sti、nop、clc、cmc、stc、hlt、wait、esc、lock等
6. 串处理指令 movsb、movsw、cmps、scas、lods、stos等,可搭配 rep、repe、repne等使用
十六、直接定址表 16.2 在其他段中使用数据标号 1 2 3 4 5 data segment a db 1,2,3,4,5,6,7,8 b dw 0 c dd a,b data ends
标号 c 处存储的两个双字节型数据为标号 a 的偏移地址和段地址、标号 b 的偏移地址和段地址 ,相当于:
1 2 3 4 5 6 data segment a db 1,2,3,4,5,6,7,8 b dw 0 c dw offset a,seg a,offset b,seg b data ends //seg 操作符,功能为取得某一标号的段地址