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 = 1000h,sp=200h
- 执行 sp = sp - 2,于是ss:sp = 1000:1FE
- 执行 word ptr ss:[sp] = abc[0]注意小端规则:|  | 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 的堆栈操作
|  | ; ss = 1000h, sp = 200h
mov eax, 12345678h
push eax
pop ebx
 | 
 
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 调试中的表现
|  | 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,这样
 
|  | 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里的内容|  | 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 段表现
|  | 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 | 
|---|
|  | 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]
 |