Skip to content

05 段和堆栈

1 堆栈段的定义#

定义和简单操作#

example: 段的定义
data segment
    abc dw 1234h, 5678h
data ends

code segment
assume cs:code, ds:data, ss:stk
main:
    mov ax, data
    mov ds, ax
    push abc[0]  ; 相当于 push 1234h
    pop abc[2]
    mov ah, 4C
    int 21h
code ends
end main

stk segment stack  ; 定义堆栈段,堆栈段只能定义一个
    a db 200h dup('S')  ; 或写成 dw 100h dup(0),这里设置成 S 方便调试的时候发现
stk ends  ; 程序刚开始运行时,ss = stk,sp = 200h

Attention

堆栈段的定义必须要加 stack 修饰

push 操作拆解

  • 假设此时 ss = 1000hsp=200h
  • 执行 sp = sp - 2,于是 ss:sp = 1000:1FE
  • 执行 word ptr ss:[sp] = abc[0] 注意小端规则:
    1
    2
    3
    1000:1FE 34h  <ss:sp
    1000:1FE 12h
    1000:200 ???
    

pop 操作拆解

  • 假设刚刚执行了上面的 push
  • 执行 abc[2] = word ptr ss:[sp]
  • 执行 sp = sp + 2

补充

  • 无法进行 8 位的栈操作
  • 如果没有定义堆栈段?
    • ds = es = 568D, ss = 569D, sp = 0000,也就是 ss 的初始值等于首段的段地址
    • 如果有 push,那么 sp = sp - 2 = FFFE
  • 堆栈只能有一个
dword 的堆栈操作
1
2
3
4
; ss = 1000h, sp = 200h
mov eax, 12345678h
push eax
pop ebx

操作拆解

  • sp = sp - 4
  • dword ptr ss:[sp] = 12345678h

    1
    2
    3
    4
    1000:1FC  78h  <ss:sp
    1000:1FD  56h
    1000:1FE  34h
    1000:1FF  12h
    

  • ebx = dword ptr ss:[sp]

  • sp = sp + 4

1.1 如果没有定义堆栈段?#

进行一次 push 后
569D:0000           data; len=10h
569D:0010=569E:0000 code; len=20h
569D:0030=56A0:0000 后续段
......
569D:FFFE           <ss:sp
569D:FFFF
......
......
9000:FFFF

A000:0000 ~ A000:FFFF  ; 显卡地址
B000:0000 ~ B000:7FFF  ; 显卡地址
B800:0000 ~ B800:7FFF  ; 显卡地址
C000:0000 ~ F000:FFFF  ; ROM 映射,只读

Important

  • DOS 系统是单任务的,所以一整块内存都是可用的,一直到 9000:FFFF 都是这个程序可用的空间
  • A000:0000 前面一共有 640k,其中包含了 DOS 系统的内存

1.2 调试中的表现#

1
2
3
569D:0000  34 12 00 00 00 00 00 00  <data segment: 569D:0000 - 569D:0004
569D:0008  00 00 00 00 00 00 00 00
568D:0010  B8 9D 56 8E D8 FF 36 00  <code segment: 569E:0000 -

此时 ds 569D cs 569E

data 段之后应该就是 code 段吗?

并不是,只有能够整除 0010 的地址才能作为段地址的开始,所以 569D:0010 才是 code 段的开始

段首地址的偏移地址不应该是 0000

可以让 code 的首地址偏移为 0000,只需要让 cs = 569E 即可,这就相当于 569D:0010 == 569E:0000,这样

1
2
3
ds:0220 53 53 53 53 53 53 53 53
ds:0228 53 53 53 53 53 53 00 00
ds:0230 ?? ?? ?? ?? ?? ?? ?? ??  <ss:sp = 56A0:01FE = 569D:0230 = ds:0230 = 56A0:

为什么最后面有的 S 被覆盖掉了?

  • td 在载入的时候可能会调用程序里定义的堆栈,于是就会出现乱码
  • 不仅如此,如果程序里使用了 pop,调试的时候也可能被同时使用同一个堆栈的其他程序覆盖掉(如调试器),堆栈指针前方的内容是不安全的

3 寄存器初始化赋值和 psp#

程序载入内存时,dos 会对以下寄存器做初始化赋值

ss:sp cs:ip ds es
ss=stk, sp=堆栈长度 cs=code, ip=offset main ds=psp es=psp

psp

  • psp 是程序段前缀 (program segment prefix),是一块长度为 100h 字节的内存,并且一定位于程序首段的前方
  • psp 是操作系统定义的,储存了与当前进程相关的一些信息,例如命令行参数
  • 初始都赋值成 psp,方便访问 psp 里的内容
    1
    2
    3
    4
    5
    6
    mov ax, data
    mov ds, ax
    ; 等价于
    mov ax, ds
    add ax, 10h
    mov ax, ds 
    

Attention

ip 的位置由 end X 决定,ip 会被赋值成 X label 的位移地址,所以 end 其实指定了程序的入口

3.1 调试观察 psp 段表现#

ss 123 456 abc
ds:0080  0C 20 31 32 33 20 34 35  ? 123 45
ds:0088  36 20 61 62 63 0D 00 FF  6 abc???

内容解读

第一个 0C 是参数长度,也就是 12,即字符串 " 123 456 abc"

data1 segment
abc db 1, 2, 3
end data1

data2 segment
xyz db 4, 5, 6
end data2

code segment
assume cs:code, ds:data1, es:data2
main:
    mov ax, data1
    mov ds, ax
    mov ax, data2
    mov ds, ax
    mov ah, abc[1]  ; -> mov ah, ds:[abc+1] -> mov ah, ds:[0+1] -> mov ah, ds:[1]
    mov xyz[1], ah

ds = 569D, es = 569E, cs = 569F

memory
1
2
3
ds:0000  01 02 03 00 00 00 00 00  <ds
ds:0008  00 00 00 00 00 00 00 00
ds:0010  04 05 06 00 00 00 00 00  <es

4 段地址的隐含和覆盖#

4.1 段地址的隐含#

段地址的隐含
; 隐含了 ds:
mov ax, [bx]
mov ax, [si]
mov ax, [di+2]
mov ax, [bx+di+2]
mov ax, [1000h]

; 隐含了 ss:
; 只要 [] 中有 bp,默认的段地址就是 ss
mov ax, [bp]
mov ax, [bp+2]
mov ax, [bp+si+2]

堆栈段的引用

  • ss:[sp] 是语法错误的,因为 sp 不能放在方括号里
  • 使用 bp 来进行直接的堆栈段的引用

4.2 段覆盖#

就是在操作数前添加一个段前缀来改变默认的 ds

segment override
mov bx, sp
mov ax, ss:[bx]

Comments