包含多个段的程序
前面的程序中,都只含有一个代码段。那当我们需要空间去存放数据的时候,如何去获取一段安全的空间呢?
程序取得所需空间的方法有两种,一种是在加载程序的时候为程序分配,二是在执行的时候向系统申请。这里重点讲第一种。
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寄存器,那直接运行的时候怎么办呢?
可以看到,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 将数据、代码、栈放入不同的段
在前面的内容中,我们在程序中用到了数据和栈,将数据、栈和代码都放到了一个段里面。我们在编程的时候需要注意何处是数据,何处是栈,何处是代码。这样做显然有两个问题:
- 把它们放到一个段中显得程序混乱;
- 当数据、栈和代码需要的空间超过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. 实验五:编写、调试具有多个段的程序
- 将下面的程序编译、链接,用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, 除式向上取整。
- 编写代码,将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指令,两个操作对象不可以都是内存。