一、操作符offset

操作符offset在汇编语言中是由编译器处理的符号,它的功能是取得标号的偏移地址。比如下面的程序

  1. assume cs:codesg
  2. codesg segment
  3. start:mov ax,offset start ; 相当于mov ax,0
  4. s:mov ax,offset s ; 相当于mov ax,3
  5. codesg ends
  6. end start

在上面的程序中,offset操作符取得了标号starts的偏移地址0和2,所以指令mov ax,offset start相当于指令mov ax,0,因为start是代码段中的标号,他所标记的指令是代码段中的第一条指令,偏移地址位0

mov ax,offset s相当于指令mov ax,3,因为s是代码段中的标号,它所标记的指令是代码段中的第二条指令,第一条指令长度为3个字节,则s的偏移地址为3.

问题:有如下程序段,填写两条指令,使该程序在运行中将s处的一条指令复制到s0

§ 第9章 offset - 图1

分析

  1. ss0处的指令所在的内存单元的地址是多少?cs:offset scs:offset s0
  2. s处的指令复制到s0处,就是将cs:offset s处的数据复制到cs:offset s0
  3. 段地址已知在cs中,偏移地址offset soffset s0已经送入sidi
  4. 要复制的数据有多长?mov ax,bx指令的长度为两个字节,即1个字

程序如下所示

  1. assume cs:codesg
  2. codesg segment
  3. s: mov ax,bx ; mov ax,bx的机器码占两个字节
  4. mov si,offset s
  5. mov di,offset s0
  6. mov ax,cs:[si] ; 使用ax表示字节的长度为1个字
  7. mov cs:[di],ax
  8. s0:nop ; nop的机器码占一个字节
  9. nop
  10. codesg ends

§ 第9章 offset - 图2

二、jmp指令

jmp为无条件转移指令,可以只修改IP,也可以同时修改CS和IP。

jmp指令要给出两种信息:

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

不同的给出目的地址的方法,和不同的转移位置,对应有不同格式的jmp指令。下面的几节内容中,我们以给出目的地址的不同方法为主线,讲解jmp指令的主要应用格式和CPU执行转移指令的基本原理。

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

jmp short 标号(转到标号处执行指令)

这种格式的jmp指令实现的是段内段转移,它对IP的修改范围为 § 第9章 offset - 图3,也就是说,它向前转移时可以最多越过128个字节,向后转移时最多越过127个字节。

jmp指令中的short符号,说明指令进行的是段转移。jmp指令中的标号是代码段中的标号,指明了指令要转移的目的地,转移指令结束后,CS:IP应该指向标号处的指令。

3.1 程序9.1

比如下面的程序

  1. assume cs:codesg
  2. codesg segment
  3. start:mov ax,0
  4. jmp short s
  5. add ax,1
  6. s:inc ax
  7. codesg ends
  8. end start

上面的程序执行后,ax中的值为1,因为执行了jmp short s后,越过了add ax,1,IP指向了标号s处的inc ax。也就是或,程序只进行了一次ax加1操作。

汇编指令jmp short s对应的机器指令应该是什么样的呢?我们先看一下别的汇编指令和其对应的机器指令。

汇编指令 机器指令
mov ax,0123h B8 23 01
mov ax,ds:[0123h] Al 23 01
push ds:[0123h] FF 36 23 01

可以看到,在一般的汇编指令中,汇编指令中的idata(立即数),不论它是代表一个数据还是内存单元的偏移地址,都会在对应的机器指令中出现,因为CPU执行的是机器指令,他必须要处理这些数据或地址。

现在将上述程序9.1翻译为机器码,结果如下图所示

§ 第9章 offset - 图4

对照汇编源程序,我们能够看到,INC AX对应的偏移地址为8,而jmp short s也确实是jmp 0008跳转到了INC AX对应的偏移地址处,一切似乎很合理。但是§ 第9章 offset - 图5,当我们查看jmp short s对应的机器码时,却有了一些问题。

§ 第9章 offset - 图6

jmp 0008所对应的机器码为EB03。注意,这个机器码不包含转移的目的地址,这就表示CPU在执行EB 03时,不知道转移的目的地址。指令中有地址,机器码没有转移地址❗️ ,而机器码才是CPU工作需要的指令,没有这个指令,CPU根据什么转移呢❓

3.2 程序9.2

把上面的程序9.1修改一下,改写为下面这种

  1. assume cs:codesg
  2. codesg segment
  3. start:mov ax,0
  4. mov bx,0
  5. jmp short s
  6. add ax,1
  7. s:inc ax
  8. codesg ends
  9. end start

§ 第9章 offset - 图7

比较一下程序9.1和程序9.2的Debug查看的结果。两个程序中的jmp指令都要使IP指向inc ax指令,但是程序1的inc ax指令的偏移地址为8,而程序2的inc ax的偏移地址为000BH

而且两个程序中的jmp指令所对应的机器码都是EB 03,这就说明了CPU在执行**jmp**指令的时候是不需要转移目的地址的✔️。否则如果需要的话,转移地址就会出现不同的->对应的机器码就会不一样,但是这里都是EB 03。

§ 第9章 offset - 图8

CPU不是神仙,它只能处理你提供给它的东西,jmp指令的机器码中不包含转移目的地址,CPU如何知道IP应该改为多少呢?

所以,在jmp指令的机器码中,一定包含了某种信息,使得CPU可以将它当作修改IP的依据,现在我们来找一下这个依据㊙️。

9.3 怎么找到的转移地址?

现在,先简单的回忆一下CPU执行指令的过程(需要更多回忆,回到2-10)

  1. 从CS:IP指向内存单元读取指令,读取的指令进行指令缓冲器
  2. § 第9章 offset - 图9%3D(IP)%2B%E6%89%80%E8%AF%BB%E5%8F%96%E6%8C%87%E4%BB%A4%E7%9A%84%E9%95%BF%E5%BA%A6%7D#card=math&code=%5Ctext%7B%28IP%29%3D%28IP%29%2B%E6%89%80%E8%AF%BB%E5%8F%96%E6%8C%87%E4%BB%A4%E7%9A%84%E9%95%BF%E5%BA%A6%7D&id=tkCdH),从而读取下一条指令
  3. 执行指令,转到1,重复这个过程

上面的程序是根据机器码EB 03来进行跳转的,它怎么跳转的呢,CPU在执行EB 03的时候是根据什么修改的IP,使其指向目标指令的呢⁉️

👉答案就是:根据指令码中的03。注意,如果转移的目的地址是程序9.2中的CS:000B,而CPU执行EB 03时,当前的(IP)=000H,如果将当前的§ 第9章 offset - 图10,使得(IP)=000BH,CS:IP就可指向目标指令。指令EB 03并没有告诉CPU转移的目标地址,却告诉了CPU要转移的距离,即向后移动3个字节

  1. assume cs:codesg
  2. codesg segment
  3. start:mov ax,0
  4. mov bx,0
  5. jmp short s
  6. add ax,1
  7. add ax,1
  8. add ax,1
  9. s:inc ax
  10. codesg ends
  11. end start

§ 第9章 offset - 图11

现在的指令就变成了EB 09了,因为它要往后跳转9个字节。

9.4 总结

原来,在jmp short 标号指令所对应的机器码中,并不包含转移目的地址,而包含的是转移的位移。这个位移,是编译器根据汇编指令中的标号计算出来的,计算方法如下图所示

§ 第9章 offset - 图12

9.4.1 jmp short 标号

实际上,jmp short 标号的功能:(IP)=(IP)+8位位移

  1. 8位位移 = 标号处的地址 - jmp指令后的第一个字节的地址
  2. short指令此处的位移为8位位移
  3. 8位位移的范围为§ 第9章 offset - 图13,用补码表示
  4. 8位位移由编译程序在编译时算出

就像上面的程序找到位置一样

§ 第9章 offset - 图14

9.4.2 jmp near ptr 标号

jmp near ptr 标号是和jmp short 标号功能相近的指令格式,实现的是段内近转移:(IP) = (IP) + 16位位移

  1. 16位位移 = 标号处的地址 - jmp指令后的第一个字节的地址
  2. near ptr指令此处的位移为16位位移
  3. 16位位移的范围为§ 第9章 offset - 图15,用补码表示,进行的是段内近转移
  4. 16位位移由编译程序在编译时算出

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

前面的jmp指令,其对应的机器指令没有转移目的地址,而是相对于当前IP的转移位移。

jmp far ptr 标号实现的是段间位移,又称远位移📗。功能如下:

  1. (CS) = 标号所在段的段地址
  2. (IP) = 标号所在段的偏移地址
  3. far ptr 指明了指令用标号的段地址和偏移地址修改CSIP

看下面的程序

  1. assume cs:codesg
  2. codesg segment
  3. start:mov ax,0
  4. mov bx,0
  5. jmp far ptr s
  6. db 256 dup (0) ; 这里使用段间转移
  7. s:add ax,1
  8. inc ax
  9. codesg ends
  10. end start

翻译成机器码以后,可以看到 EA 010B 6A07是直接跳转到s对应的 段地址:偏移地址

§ 第9章 offset - 图16

五、转移地址在寄存器中的jmp指令

指令格式:jmp 16位reg

功能:(IP)=(16位reg)

这种格式之前已经看过了,这里就不用再看了

六、转移地址在内存中的jmp指令

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

6.1 jmp word ptr 内存单元地址(段内地址)

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

内存单元地址可用寻址方式的任意一种格式给出,比如下面的指令

  1. mov ax,0123H
  2. mov ds:[0],ax
  3. jmp word ptr ds:[0]

指行后,§ 第9章 offset - 图17%3D0123H%7D#card=math&code=%5Ctext%7B%28IP%29%3D0123H%7D&id=I1CwL)

  1. mov ax,0123H
  2. mov [bx],ax
  3. jmp word ptr [bX]

指行后,§ 第9章 offset - 图18%3D0123H%7D#card=math&code=%5Ctext%7B%28IP%29%3D0123H%7D&id=n4U8Q)

6.2 jmp dword ptr 内存单元地址(段间转移)

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

  • § 第9章 offset - 图19%20%3D%20(%E5%86%85%E5%AD%98%E5%8D%95%E5%85%83%E5%9C%B0%E5%9D%80%2B2)%7D#card=math&code=%5Ctext%7B%28CS%29%20%3D%20%28%E5%86%85%E5%AD%98%E5%8D%95%E5%85%83%E5%9C%B0%E5%9D%80%2B2%29%7D&id=Wjei4)
  • § 第9章 offset - 图20%20%3D%20(%E5%86%85%E5%AD%98%E5%8D%95%E5%85%83%E5%9C%B0%E5%9D%80)%7D#card=math&code=%5Ctext%7B%28IP%29%20%3D%20%28%E5%86%85%E5%AD%98%E5%8D%95%E5%85%83%E5%9C%B0%E5%9D%80%29%7D&id=r8kA2)

内存单元地址可用寻址方式的任一格式给出,比如下面的指令

  1. mov ax,0123H
  2. mov ds:[0],ax
  3. mov word ptr ds:[2],0
  4. jmp dword ptr ds:[0]

执行后,§ 第9章 offset - 图21%EF%BC%8C(IP)%3D0123H%7D#card=math&code=%5Ctext%7B%28CS%3D0%29%EF%BC%8C%28IP%29%3D0123H%7D&id=w6z2F),CS:IP指向0000:0123

§ 第9章 offset - 图22

又比如下面的指令

  1. mov ax,0123H
  2. mov [bx],ax
  3. mov word ptr [bx+2],0
  4. jmp dword ptr [bx]

执行后,§ 第9章 offset - 图23%EF%BC%8C(IP)%3D0123H%7D#card=math&code=%5Ctext%7B%28CS%3D0%29%EF%BC%8C%28IP%29%3D0123H%7D&id=RIlwq),CS:IP指向0000:0123

七、jcxz指令

jcxz指令为有条件转移指令,所有的有条件转移指令都是短转移,在对应的机器码中包含转移的位移,而不是目的地址。对IP的修改范围都为:§ 第9章 offset - 图24

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

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

  • 8位位移 = 标号处的地址 - jcxz指令后的第一个字节的地址
  • 8位位移的范围为§ 第9章 offset - 图25,用补码表示
  • 8位位移由编译程序在编译时算出

§ 第9章 offset - 图26%7D%5Cne0#card=math&code=%5Ctext%7B%28cx%29%7D%5Cne0&id=PErDn)时,什么也不做(程序向下执行)

jcxz的功能中就可以看出,jcxz 标号的功能相当于

  1. // C语言和汇编语言进行的综合描述,语法当然不对,但是能理解意思
  2. if ((cx)==0)
  3. jmp short 标号;

八、loop指令

loop指令为循环指令,所有的循环指令都是短转移,在对应的机器码中包含转移的位移,而不是目的地址。对IP的修改范围都为§ 第9章 offset - 图27

指令格式:loop 标号(cx)=(cx)-1,如果§ 第9章 offset - 图28,转移到标号处执行)

操作:

  1. § 第9章 offset - 图29%3D(cx)-1%7D#card=math&code=%5Ctext%7B%28cx%29%3D%28cx%29-1%7D&id=lCqOh)
  2. 如果§ 第9章 offset - 图30%7D%5Cne%200%2C%20%5Ctext%7B(IP)%3D(IP)%2B8%7D#card=math&code=%5Ctext%7B%28cx%29%7D%5Cne%200%2C%20%5Ctext%7B%28IP%29%3D%28IP%29%2B8%7D&id=xfdEV)位位移
  • 8位位移 = 标号处的地址 - loop指令后的第一个字节的地址
  • 8位位移的范围为§ 第9章 offset - 图31,用补码表示
  • 8位位移由编译程序在编译时算出

如果(cx)=0,什么也不做(程序向下执行)。我们从loop的功能中可以看出,loop 标号的功能相当于:

  1. (cx)--;
  2. if ((cx)!=0)
  3. jmp short 标号;

九、根据位移进行转移的意义

前面学习到了

  1. jmp short 标号
  2. jmp near ptr 标号
  3. jcxz 标号
  4. loop 标号

这几种汇编指令,它们对IP的修改是根据转移目的地址和转移起始地址之间的位移来进行的。在他们对应的机器码中不包含转移的目的地址,而包含的的是目的地址的位移。

这种设计,方便了程序段在内存中的浮动装配。例如:

汇编指令 机器代码
mov cx,6 B9 06 00
mov ax,10h B9 10 00
s: add ax,ax 01 C0
loop s E2 FC

这段程序装在内存中的不同位置都可以正确执行,因为loop s在执行时只涉及s的位移(-4,迁移4个字节,补码表示为FCH),而不是s的地址。

§ 第9章 offset - 图32

十、编译器对转移位移超界的检测

注意§ 第9章 offset - 图33,根据位移进行转移的指令,它们的转移范围受到转移位移的限制,如果在源程序中出现了转移范围超界的问题,在编译的时候,编译器将报错。

比如,下面的程序将引起编译错误:

  1. assume cs:code
  2. code segment
  3. start:jmp short s
  4. db 128 dup (0)
  5. s:mov ax,0ffffh
  6. code ends
  7. end start

jmp short s的转移范围是§ 第9章 offset - 图34,IP最多向后移动127个字节。

注意:我们在第2章中使用到的jmp 2000:0100这样的转移指令,是在Debug中使用的汇编指令,汇编编译器并不认识。如果在源程序中使用,编译时也会报错。

检测

检测点9.1

检测1

程序如下

  1. assume cs:code
  2. data segment
  3. db 1 dup (0) ; 开创
  4. data ends
  5. code segment
  6. start:mov ax,data
  7. mov ds,ax
  8. mov bx,0
  9. jmp word ptr [bx+1]
  10. code ends
  11. end start

若要使程序中的jmp指令执行后,CS:IP指向程序的第一条指令,在data段中应定义哪些数据?

解答:

如果我们只开辟一个空间db 1 dup(0),编译后发现DS=075A,所以程序的入口在076A处。并且从076A:0 ~ 076A:F都是0,从076A:10处开始才是程序。

只需要将[bx+1]对应的内存单元里的值为0即可,所以答案可以为db 1 dup(0) 一直到db 16 dup(0)

§ 第9章 offset - 图35

我在这里迷糊了好久,§ 第9章 offset - 图36这个规律为什么在这里不适用了?其实,这个规律在不指定CS的地址还是可以使用的,因为cs:codecs指针和code的段地址联合起来了,所以CS=076B.

但是,我们说的jmp word ptr [bx+1][bx+1]是整个段的。

§ 第9章 offset - 图37

检测2

程序如下

  1. assume cs:code
  2. data segment
  3. dd 12345678H
  4. data ends
  5. code segment
  6. start:mov ax,data
  7. mov ds,ax
  8. mov bx,0
  9. mov [bx],bx
  10. mov [bx+2],cs
  11. jmp dword ptr ds:[0]
  12. code ends
  13. end start

补全程序,使得jmp指令执行后,CS:IP指向程序的第一条指令

解析:要想使得最后跳转到指令的第一条,就要满足:§ 第9章 offset - 图38

所以就将现在的值直接赋给上面开辟的空间的值即可。

  • db 1:数据为01H,占1个字节
  • dw 1:数据为0001H,占2个字节
  • dd 1:数据为00000001H,占4个字节

§ 第9章 offset - 图39

检测3

用Debug查看内存,结果如下

§ 第9章 offset - 图40

则此时,CPU执行指令

  1. mov ax,2000H
  2. mov es,ax
  3. jmp dword ptr es:[1000H]

后,§ 第9章 offset - 图41%3D00BE%2C%20(IP)%3D0006%7D#card=math&code=%5Ctext%7B%28CS%29%3D00BE%2C%20%28IP%29%3D0006%7D&id=E49Y5)

这个题很简单的,但是涉及到数据的存放问题,例如dd 12345678H,它在内存中的存放为

78 56 34 12(先低后高)

所以CS对应的是高位置:1234

  1. IP对应的是低位值:5678

检测点9.2

补全指令,利用jcxz指令,是现在内存2000H段中查找第一个值为0的字节,找到后,将它的偏移地址存储在dx中。

  1. assume cs:code
  2. code segment
  3. start:mov ax,2000H
  4. mov ds,ax
  5. mov bx,0
  6. s:mov cl, [bx]
  7. mov ch,0
  8. jcxz ok ; 将段中的数当作cx作为判断条件,很巧妙的一个思想
  9. inc bx
  10. jmp short s
  11. ok:mov dx,bx
  12. mov ax,4c00h
  13. int 21h
  14. code ends
  15. end start

检测点9.3

补全编程,利用loop指令,实现在内存2000H 段中查找第一个值为0的字节,找到后,将它的偏移地址存储在dx中

  1. assume cs:code
  2. code segment
  3. start:mov ax,2000H
  4. mov ds,ax
  5. mov bx,0
  6. s: mov cl,[bx]
  7. mov ch,0
  8. inc cx ; 因为loop s有--cx这个动作,所以找到了第一个值为0,--0=-1,此时不会退出loop s
  9. inc bx
  10. loop s
  11. ok: dec bx ; dec指令功能和inc相反,dec bx进行的操作为(bx)=(bx)-1
  12. mov dx,bx
  13. mov ax,4c00h
  14. int 21h
  15. code ends
  16. end start

实验8 分析一个奇怪的程序

分析下面的程序,在运行前思考:这个程序可以正确返回吗?

运行后再思考:为什么会是这种结果?

通过这个程序加深对相关内容的理解

  1. assume cs:codesg
  2. codesg segment
  3. mov ax,4c00h
  4. int 21h
  5. start: mov ax,0
  6. s: nop
  7. nop
  8. mov di,offset s
  9. mov si,offset s2
  10. mov ax,cs:[si]
  11. mov cs:[di],ax
  12. s0: jmp short s
  13. s1: mov ax,0
  14. int 21h
  15. mov ax,0
  16. s2: jmp short s1
  17. nop
  18. codesg ends
  19. end start

这个很奇怪的程序先拿过来编译一下看看它在内存中长什么样子。

§ 第9章 offset - 图42

之后开始进行单步调试:当调试到s0: jmp short s这一句的时候异变突起:

§ 第9章 offset - 图43

原来的jmp 0008变成了jmp 0000从而跳转到了程序的最开头完成了整个程序,怎么回事?

分析:现在开始追根溯源看看到底是哪里发生了奇妙的反应

JMP 0000对应的机器码是EBF6,F6对应的位移正好是8,而IP=0008,意思就是IP向前移动8位从而到达了程序的开头.

跟着程序走一遍,发现程序走到这里的时候会造成一个神奇的效果发生

§ 第9章 offset - 图44

s段处开辟的两个字节会被jmp short s1对应的机器码给填充

§ 第9章 offset - 图45

jmp指令本质上是往前进行跳转,并且跳转的距离由目的距离和源距离差决定,因为§ 第9章 offset - 图46的距离大小正好和程序开头到s的距离一样,所以就可以直接跳转到开头。

§ 第9章 offset - 图47

实验9 根据材料编程

这个编程任务必须在进行下面的课程之前独立完成,因为后面的课程中,需要通过这个实验而获得的编程经验。

编程:在屏幕中间分别显示绿色、绿底红色、白底蓝色的字符串welcome to masm!

编程所需的知识通过阅读、分析下面的材料获得:

80x25彩色字符模式显示缓冲区(以下简称为显示缓冲区)的结构:

  • 内存地址空间中,D8000H~BFFFFH共32KB的空间,为82x25彩色字符模式的显示缓冲区。向这个地址空间写入数据,写入的内容将立即出现在显示器§ 第9章 offset - 图48
  • 在80x25彩色字符模式下,显示器可以显示25行,每行80个字符,每个字符有256种属性(背景色、前景色、闪烁、高亮等组合信息)§ 第9章 offset - 图49

这样,一个字符在显示缓冲区就要占2个字节,分别存放字符的ASCII码和属性。80x25模式下,一屏的内容在显示缓冲区共占4000个字节。

§ 第9章 offset - 图50

显示缓冲区分8页,每页§ 第9章 offset - 图51,显示器可以显示任意一页的内容,一般情况下,显示第0页的内容。也就是说通常情况下,§ 第9章 offset - 图52 中的4000个字节的内容将出现在显示器上。

在一页显示缓冲区中:

  1. 偏移000~09F对应显示器上的第1行(80个字符占160个字节)
  2. 偏移0A0~13F对应显示器上的第2行
  3. 偏移140~1DF对应显示器上的第3行
  4. …….
  5. 依次类推可知,偏移F00~F9F对应显示器上的第25行

§ 第9章 offset - 图53

在一行中,一个字符占2个字节的存储空间(一个字),低位字节存储字符的ASCII码,高位字节存储字符的属性。一共有80个字符,占160个字节。

即在一行中:

  1. 00~01单元对应显示器上的第1列
  2. 02~03单元对应显示器上的第2列
  3. 04~05单元对应显示器上的第3列
  4. ……
  5. 依此类推,可知,9E~9F单元对应显示器上的第80列。

显示缓冲区里的内容为:

§ 第9章 offset - 图54

可以看出,在显示缓冲区中,偶地址存放字符,奇地址存放字符的颜色属性。

一个在屏幕上的字符,具有前景(字符色)和背景色(底色)两种颜色,字符还可以以高亮度和闪烁的方式显示。前景色、背景色、闪烁、高亮等信息被记录在属性字节中。

属性字节的格式:

§ 第9章 offset - 图55

可以按位设置属性字节,从而配出各种不同的前景色和背景色,比如:

  • 红底绿字,属性字节为:01000010B
  • 红底闪烁绿字,属性字节为:11000010B
  • 红底高亮绿字,属性字节为:01001010B
  • 黑底白字,属性字节为:00000111B
  • 白底蓝字,属性字节为:01110001B

例如:在显示器的0行0列显示红底高亮闪烁绿字的字符串’ABCDEF’(红底高亮闪烁绿字,属性字节为:11001010B,CAH),显示缓冲区里的内容为:

§ 第9章 offset - 图56

注意:闪烁的效果必须在全屏DOS方式下才能看到

  1. assume cs:code
  2. data segment
  3. db 'welcome to masm!',0
  4. data ends
  5. code segment
  6. start:
  7. ;dx存放的是行列信息
  8. mov dh,8
  9. mov dl,3
  10. mov cl,2
  11. mov ax,data
  12. mov ds,ax
  13. mov si,0
  14. call show_str
  15. mov ax,4c00h
  16. int 21h
  17. show_str:
  18. ;
  19. ;入栈储存数据tt
  20. ;
  21. push dx
  22. push cx
  23. push si
  24. ;
  25. ;第八行定位【460-4ff
  26. ; 7*a0=460
  27. mov bl,dh ;行号赋值8
  28. dec bl ;行号自减一
  29. mov al,160 ;A0h【每一行的自增值】
  30. mul bl ;将blal相乘结果放到ax
  31. mov bx,ax ;结果赋值
  32. ;
  33. ;第三列定位【04-05
  34. ;
  35. add dl,dl ;列号自增
  36. ;
  37. ;行列号拼接形成偏移地址
  38. ;
  39. add bl,dl
  40. ;
  41. ;设定显示位置
  42. ;
  43. mov ax,0b800h
  44. mov es,ax
  45. mov al,cl ;颜色赋值【后面要用cx来结束循环所以这里要把cx的数据先保存起来】
  46. mov di,0 ;数据初始化
  47. s:
  48. mov ch,0
  49. mov cl,ds:[si]
  50. jcxz ok
  51. ;将数据赋给cx,当cx0的时候(到字符串尾)就会自动退出
  52. mov es:[bx+di],cl
  53. mov es:[bx+di+1],al;颜色赋值【颜色设定在高位】
  54. add di,2 ;es段每次跳两个字节
  55. inc si ;ds段每次跳一个
  56. loop s
  57. ok:
  58. ;
  59. ;数据恢复
  60. ;
  61. pop dx
  62. pop cx
  63. pop si
  64. ret
  65. code ends
  66. end start

程序来源:《汇编语言(王爽)第三版》实验【未完待续】 - laolao - 博客园 (cnblogs.com)