2. 第一个程序、BX和loop指令

252 阅读7分钟

第一个程序、BX和loop指令

一个汇编源程序需要经过编译、链接生成可执行文件。可执行文件包括两个部分:1)程序和数据。2)相关的描述信息(如程序有多大、占用多少内存空间等)。

1 源程序

先给出一个简单的范例:

 assume cs:codesg
 ​
 codesg segment
 ​
         mov ax, 0123H
         mov bx, 1456H
         add ax, bx
         add ax, ax
         
         mov ax, 4c00H
         int 21H
         
 codesg ends
 ​
 end

在汇编语言源程序中,包含两种指令。一种是汇编指令,可以被编译为机器指令;另外一种是伪指令,由编译器执行。上述代码中出现了三种伪指令。

segment 和 ends是一对成对使用的伪指令,它的功能是定义一个段。一个段必须有一个名称来标识,使用格式为:

 段名 segment
 ​
 段名 ends

一个汇编程序由多个段组成,这些段被用来存放代码、数据或当作栈来使用。同时一个有意义的汇编程序中至少要有一个段,这个段用来存放代码。

end是一个汇编程序的结束标记,编译器在编译汇编程序的过程中,如果碰到了指令end,就结束对源程序的编译。

注意:end是标记整个程序的结束;而ends是标记一个段的结束,并且与segment成对使用。

assume指令可以把段寄存器和一个具体的段相联系。

2 程序返回

我们的程序经编译、链接后转变为机器码,存储在可执行文件上。那么它是如何运行的呢?

一个单任务DOS系统为例:

一个程序P2在可执行文件中,则必须由一个正在运行的程序P1,将P2从可执行文件中加载如内存后,将CPU的控制权交给P2,P2得以运行。P2开始运行后,P1暂停运行。而当P2运行完毕后,将CPU的控制权交还给P1。

 mov ax, 4c00H
 int 21H

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

3 实验三

重点理解一下实验的第三个小问。

程序在被加载到内存后,DS中存放着程序所在内存区的段地址,这个内存区的偏移地址为0,则程序所在的内存区的地址为DS:0

这个内存区的前256个字节用来存放的是PSP,是DOS用来和程序进行通信的

所以CS指向的地址应该比DS指向的地址大10H。

4 [bx]和内存单元的描述

[bx]和[0]类似,都是代表内存单元。只不过[0]代表偏移地址是0,[bx]代表偏移地址是寄存器bx中的值。

  • 在汇编源程序中当直接给出数字时,不能是字母开头。所以类似A023H的数字必须在前面加一个0,即0A023。
  • 形如 mov ax, [0] 这样的汇编语句,本意是地址为ds:0的数据送入寄存器ax,但是debug程序和汇编解释器对它的解释不一致。汇编解释器对上面的语句解释为: mov ax, 0 ,想要执行我们的预期结果需要改用如下形式:mov ax, ds:[0]或者是 mov bx, 0 mov ax, [bx]

定义一个描述性的符号:“()”,它表示一个寄存器或内存单元中的内容。如:

(ax)表示寄存器ax中的内容、(al)表示寄存器al中的内容。

(20000H)表示内存20000H单元的内容。那么((ds)*16 + (bx))表示:ds中的ADR1作为段地址,bx中的ADR2作为偏移地址,即ADR1:ADR2单元的内容。

注意:"( )"中的元素可以有3种类型:**1)寄存器名;2)段寄存器名;3)内存单元的物理地址(一个20位的数据)

5 Loop指令

loop指令的格式是:loop 标号,CPU指令loop指令的时候,要进行两部操作,1) (cx) = (cx) - 1, 2)判断cx中的值,不为零则转至标号处执行程序,如果为零则向下执行。

也就是说,cx的值影响loop指令的执行结果,cx中存放的是循环次数

如编程计算2^12的值:

 assume cs:codesg
 ​
 codesg segment
     mov ax, 2
     mov cx, 11
 s:  add ax, ax
     loop s
     
     mov ax, 4c00H
     int 21H
 code ends
 ​
 end

计算ffff:0006单元中的数乘以3,结果存储在dx中。

 assume cs:code
 ​
 code segment
     mov ax, 0ffffH
     mov ds, ax
     mov cx, 3
     
     mov dx, 0
     mov ah, 0
     mov al, ds:[0006H]
 s:  add dx, ax
     loop s
     
     mov ax, 4c00H
     int 21H
 code ends
 ​
 end

这道题需要注意以下几点:

  • 循环结构不要冗余,比如取数这个操作不要放到循环里。
  • 注意ffff:6是一个字节单元,而ax是16位寄存器,数据长度不一样需要适配。
  • 有的题目要注意数据溢出的可能。

6 在debug中跟踪用loop指令实现的循环程序

在debug中通过"u cs: 偏移地址"可以查看内存中汇编指令,可以发现loop指令其实就是通过改变IP寄存器的内容来实现循环。

具体的调试不难,此时引入两个新的debug命令。

  • g 偏移地址,可以避免t命令一条一条的执行,直接执行到指定地址。如"g 0012", CS:0012之前的程序段被执行
  • 遇到loop指令用p命令,可以直接一次性执行循环体。

7 loop和[bx]的联合应用

考虑这样一个问题,计算ffff:0~ffff:b单元中数据的和,结果存储在dx中。

注意细节如下:

  • 运算后的结果是否会超过dx的存储范围?
  • 能否把ffff:0~ffff:b中的数据直接累加到dx中?
  • 我们能否将ffff:0~ffff:b中的数据累加到dl中,并设置(dh) = 0,从而实现累加到dx中?
 assume cs:code
 ​
 code segment
     mov ax, 0ffffH
     mov ds, ax
     mov cx, 0bH
     
     mov dx, 0
     mov bx, 0
     mov ah, 0
 s:  mov al, [bx]
     add dx, ax
     inc bx
     loop s
 code ends
 ​
 end

8 段前缀的使用

指令" mov ax, [bx]"中默认的段地址寄存器是ds,但实际上我们可以在访问内存单元的指令中显示的给出内存单元的段地址所在的寄存器。

我们考虑一个问题,将内存ffff:0ffff:b单元中的数据复制到0:2000:20b中。分析过程如下:

  • 0:2000:20b单元等同于0020:00020:b单元,它们描述的是同一段内存空间。
 assume cs:code
 ​
 code segment
 ​
     mov ax, 0ffffH
     mov ds, ax
     mov ax, 0020H
     mov es, ax
     
     mov bx, 0
     mov cx, 12
 s:  mov dl, [bx]
     mov es:[bx], dl
     inc bx
     loop s
 ​
     mov ax, 4c00H
     int 21H
 ​
 code ends
 ​
 end

9 实验四

  1. 编程,像内存0:2000:23F依次传送数据063(3FH)。
 assume cs:code
 ​
 code segment
 ​
     mov ax, 0020H
     mov ds, ax
     
     mov cx, 3FH
     mov bx, 0
 s:  mov [bx], bx
     inc bx
     loop s
     
     mov ax, 4c00H
     int 21H
 ​
 code ends
 ​
 end
  1. 下面的程序的功能是将"mov ax, 4c00H"之前的指令复制到内存0:200处,补全程序上机调试。跟踪运行结果。
assume cs:code

code segment
	
	mov ax, cs
	mov ds, ax
	mov ax, 20H
	mov es, ax
	mov bx, 0
	mov cx, 17H
	
s:	mov al, [bx]
	mov es:[bx], al
	inc bx
	loop s
	
	mov ax, 4c00H
	int 21H

code ends

end

寄存器cx的值需要debug去查看前面的指令占多少字节。