包含多个段的程序
为什么需要多个段
在代码段中放数据或栈存在隐患:程序加载后 CS:IP 可能执行到数据区。安全且清晰的方式是用多个段分别存放代码、数据和栈。
8086 CPU 下,单个段不能超过 64KB。
在代码段中使用数据(简单方式)
用 dw(define word)定义字型数据,并用 end start 指明程序入口,避免执行数据区:
asm
assume cs:code
code segment
; 数据区(位于代码段前部,程序从 start 处开始执行,不会执行到这里)
dw 0123h, 0456h, 0789h, 0abch, 0defh, 0fedh, 0cbah, 0987h
start:
mov bx, 0
mov ax, 0
mov cx, 8
s:
add ax, cs:[bx] ; 从代码段读取数据(cs 指向本段)
add bx, 2 ; 每次移动 2 字节(字型数据)
loop s
mov ax, 4c00h
int 21h
code ends
end start ; 程序从 start 标号处开始执行在代码段中使用栈
用 dw 0, 0, ... 预留空间作为栈区,再用 SS:SP 指向它:
asm
assume cs:codesg
codesg segment
dw 0123h, 0456h, 0789h, 0abch, 0defh, 0fedh, 0cbah, 0987h ; 8 个字(数据区)
dw 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ; 16 个字(栈区)
start:
mov ax, cs
mov ss, ax
mov sp, 30h ; 栈顶指向代码段偏移 30H(数据+栈区共 48 字节 = 0x30)
mov bx, 0
mov cx, 8
s0: push cs:[bx] ; 将 8 个数据依次入栈
add bx, 2
loop s0
mov bx, 0
s1: pop cs:[bx] ; 依次出栈,写回数据区(逆序)
add bx, 2
loop s1
mov ax, 4c00h
int 21h
codesg ends
end start将数据、代码、栈放入不同的段(推荐方式)
asm
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 ; 32 字节栈空间
stack ends
code segment
start:
mov ax, stack
mov ss, ax
mov sp, 20h ; 初始化栈顶(栈空,SP 指向栈底+1)
mov ax, data
mov ds, ax ; DS 指向 data 段(assume 不自动设置寄存器!)
mov bx, 0
mov cx, 8
s0: push ds:[bx] ; 8 个数据入栈
add bx, 2
loop s0
mov bx, 0
s1: pop ds:[bx] ; 出栈,逆序写回 data 段
add bx, 2
loop s1
mov ax, 4c00h
int 21h
code ends
end start注意:
assume cs:code, ds:data只是告知编译器段名和寄存器的对应关系,不会自动设置 DS/SS。程序员必须在代码中手动mov ds, ax来初始化 DS。CS 是个例外:CPU 加载程序时会自动设置 CS 指向代码段。
段地址的相对关系
程序加载后,各段的段地址由操作系统分配。设 code 段地址为 X:
- data 段(在 code 前面)地址为
X - N(N 取决于 data 和 stack 的大小) - 段的大小以 16 字节 为最小单位(不足 16 字节的段会补齐到 16 字节边界)
实验结论(示例):
cs = 076C code 段
ss = 076B stack 段(紧接在 code 前一段)
ds = 076A data 段(最靠前)代码段、数据段、栈段的本质
这三种段的划分完全是程序员的安排。同一段内存可以既作为代码段,又作为数据段,还可以作为栈段——完全由 CS:IP、DS、SS:SP 的设置决定,CPU 本身不区分。