3. 包含多个段的程序

110 阅读7分钟

包含多个段的程序

前面的程序中,都只含有一个代码段。那当我们需要空间去存放数据的时候,如何去获取一段安全的空间呢?

程序取得所需空间的方法有两种,一种是在加载程序的时候为程序分配,二是在执行的时候向系统申请。这里重点讲第一种。

1 在代码段中使用数据

考虑这样一个问题,编程计算以下8个数据的和,结果存在ax寄存器中:

1234H, 2342H, 0234H, 0345H, 0678H, 0a92H, abc1H, 1234H

当指定了要累加的数据的时候,我们固然可以一个一个把它们累加到ax寄存器中。但这样就没法用循环来累加了。如果我们可以把数据提前存储到一片地址连续的内存单元的时候,就非常方便了。那怎么去找到这段内存空间呢?

我们可以在程序中,定义我们希望处理的数据,这些数据就会被编译、链接程序作为程序的一部分写入到可执行文件中。 当可执行文件中的程序被加载入内存时,这些数据同时也被加载入内存中。

具体的做法看下面的程序:

 assume cs:code
     
 code segment
     
     dw 0123H, 0456H, 0789H, 0abcH, 0defH, 0fedH, 0cabH, 0987H
 ​
     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

程序中的第一行中的"dw"的含义是定义字型数据。在这里,我们定义了8个字型数据,它们所占内存空间大小为16字节。那这些数据实际在哪里呢?由于程序运行的时候CS中存放着代码段的段地址,所以我们可以从CS中得到它们的段地址。

但这样有一个问题,由于CS:IP指针指向程序的首地址,但前16个字节实际上都是数据,想要程序正常运行的话,debug模式下我们可以手动修改它的IP寄存器,那直接运行的时候怎么办呢?

image.png

image.png

可以看到,CS:10才能指向第一条汇编指令。

既然程序的入口处不是我们所希望执行的指令,如何让这个程序在编译、链接后可以直接在系统运行呢?

我们可以用如下代码实现:

 assume cs:code
 ​
 code segment
 ​
     dw 0123H, 1234H, 1234H, 1234H
 ​
     start:  mov bx, 0
             mov ax, 0
 ​
             mov cx, 4
     s:      add ax, cs:[bx]
             add bx, 2
             loop s
             
             mov ax, 4c00H
             int 21H
 ​
 code ends
 ​
 end start

程序的关键之处就是多加了一个标号start,并在结尾end语句后面指出程序的入口处。

伪指令end描述了程序的结束和程序的入口。在编译、链接后,由"end start"指明程序的入口,被转化为一个入口地址,地址存储在可执行文件的描述信息中。当程序被加载到内存后,加载者从程序的可执行文件的描述信息中读到程序的入口地址,设置CS:IP。

2 在代码段中使用栈

完成下面的程序,利用栈,将程序中定义的数据逆序存放。

 assume cs:code
 ​
 code segment
     dw 0123H, 0456H, 0789H, 0abcH, 0defH, 0fedH, 0cbaH, 0987H
     ?
 code ends
 ​
 end

程序运行时,定义的数据存放在CS:0~CS:f单元中,共8个字单元。依次入栈再出栈,即可实现逆序存放。那么我们如何获取一段空间来当栈呢?

实现方案如下:

 assume cs:code
 ​
 code segment
 ​
     dw 0123H, 1234H, 2453H, 5323H, 1934H, 4542H, 1353H, 9234H
     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
 ​
         mov bx, 0
         mov cx, 8
 s:      push cs:[bx]
         add bx, 2
         loop s
         
         mov bx, 0
         mov cx, 8
 s0:     pop cs:[bx]
         add bx, 2
         loop s0
 ​
     mov ax, 4c00H
     int 21H
 ​
 code ends
 ​
 end start

注意:

  • 不要忘记"end"语句后面的start符号
  • 在指定栈顶sp寄存器的值的时候,需要手动计算填入。

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

在前面的内容中,我们在程序中用到了数据和栈,将数据、栈和代码都放到了一个段里面。我们在编程的时候需要注意何处是数据,何处是栈,何处是代码。这样做显然有两个问题:

  1. 把它们放到一个段中显得程序混乱;
  2. 当数据、栈和代码需要的空间超过64KB,就不能放过一个段中。(受限于8086的寻址方式)

我们可以考虑用多个段来存放数据、代码和栈。

 assume cs:code, ds:data, ss:stack
 ​
 data segment
     
     dw 0123H, 0234H, 0345H, 0456H, 0567H, 0789H, 09abH, 0bcdH
     
 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
     
      mov bx, 0; ds:0指向data段中的第一个单元
      mov cx, 8
 ​
 s:   push [bx]
      add bx, 2
      loop s
 ​
      mov bx, 0
      mov cx, 8
 ​
 s0:  pop [bx]
      add bx, 2
      loop s0
 ​
      mov ax, 4c00H
      int 21H
 ​
 code ends
 ​
 end start

4. 实验五:编写、调试具有多个段的程序

  1. 将下面的程序编译、链接,用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
 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

可以发现,这两段代码中code、data、stack段的段地址有如下规律:如果code段的段地址为X,则data段的段地址为X-2,stack段的段地址为X-1。可以得出结论:*如果段中的数据占N个字节,则程序加载后,该段实际占有的空间为(N/16)16, 除式向上取整。

  1. 编写代码,将a段和b段中的数据依次相加,将结果存到c中。
 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, a
      mov ds, ax
      mov ax, b
      mov ss, ax
      mov ax, c
      mov es, ax
 ​
      mov bx, 0
      mov cx, 8
 ​
 s:   mov dx, ds:[bx]
      add dx, ss:[bx]
      mov es:[bx], dx
      inc bx
      loop s
 ​
     mov ax, 4c00H
     int 21
 ​
 code ends
 ​
 end start

需要注意的是:add OP1, OP2指令,两个操作对象不可以都是内存。