1. 完整压栈过程(从中断触发到当前)
第一阶段:硬件自动压栈(中断响应时)
当INT 1指令执行或单步中断触发时,CPU 自动按顺序压入:
| 顺序 | 压入内容 | 大小 | SP 变化 |
|---|---|---|---|
| 1 | FLAGS | 2 字节 | SP ← SP - 2 |
| 2 | CS | 2 字节 | SP ← SP - 2 |
| 3 | IP | 2 字节 | SP ← SP - 2 |
此时,IP在栈顶,SP指向IP的高字节(8086 小端模式)。
第二阶段:你的代码手动压栈(保存现场)
你的代码继续压入:
push ax ; 顺序 4 push bx ; 顺序 5 push cx ; 顺序 6 push dx ; 顺序 7 push si ; 顺序 8 push di ; 顺序 9 push ds ; 顺序 10 push es ; 顺序 112. 完整的堆栈内存布局
假设中断发生前的初始SP值为SP_initial,经过以上操作后:
内存地址(低地址在上,向下增长) ═══════════════════════════════════════════════════════ [SP_initial - 22] ← 当前 SP 指向这里 +-----------------+ | ES 低字节 | ← 最后压入(顺序 11) +-----------------+ | ES 高字节 | +-----------------+ | DS 低字节 | ← 顺序 10 +-----------------+ | DS 高字节 | +-----------------+ | DI 低字节 | ← 顺序 9 +-----------------+ | DI 高字节 | +-----------------+ | SI 低字节 | ← 顺序 8 +-----------------+ | SI 高字节 | +-----------------+ | DX 低字节 | ← 顺序 7 +-----------------+ | DX 高字节 | +-----------------+ | CX 低字节 | ← 顺序 6 +-----------------+ | CX 高字节 | +-----------------+ | BX 低字节 | ← 顺序 5 +-----------------+ | BX 高字节 | +-----------------+ | AX 低字节 | ← 顺序 4 +-----------------+ | AX 高字节 | +-----------------+ | IP 低字节 | ← 硬件自动压入(顺序 3) +-----------------+ | IP 高字节 | +-----------------+ | CS 低字节 | ← 硬件自动压入(顺序 2) +-----------------+ | CS 高字节 | +-----------------+ | FLAGS 低字节 | ← 硬件自动压入(顺序 1) +-----------------+ | FLAGS 高字节 | +-----------------+ [SP_initial] ← 原始栈顶 +-----------------+ | ... | +-----------------+3. FLAGS 在堆栈中的位置
重要答案:FLAGS位于堆栈中的偏移 +20处(相对于当前SP)。
3. FLAGS 在堆栈中的位置
重要答案:FLAGS位于堆栈中的偏移 +20处(相对于当前SP)。
相对于当前 SP 的偏移量计算
| 内容 | 相对于当前 SP 的偏移 | 字节数 |
|---|---|---|
| 当前 SP 指向的位置 | 0 | - |
| ES | +2 | 2 |
| DS | +4 | 2 |
| DI | +6 | 2 |
| SI | +8 | 2 |
| DX | +10 | 2 |
| CX | +12 | 2 |
| BX | +14 | 2 |
| AX | +16 | 2 |
| IP | +18 | 2 |
| CS | +20 | 2 |
| FLAGS | +22 | 2 |
注意:堆栈是向下增长的,当前 SP 指向最低地址(栈顶)。FLAGS 在高地址处(更靠近原始栈顶)。
4. 如何访问堆栈中的 FLAGS
方法 1:通过 BP 访问(推荐)
int1_handler proc push bp ; 先保存 BP mov bp, sp ; BP 指向当前栈顶 ; 现在可以通过 [BP+偏移] 访问 ; [BP+24] 是 FLAGS(因为又多压了一个 BP) ; [BP+22] 是 CS ; [BP+20] 是 IP ; [BP+4] 是 AX 等方法 2:直接计算偏移(不推荐,容易出错)
; 假设当前 SP 指向 ES 的高字节 ; FLAGS 在 [SP+22] mov ax, [sp+22] ; 读取 FLAGS5. 完整的恢复过程(与压栈顺序相反)
中断返回前,必须按相反顺序恢复:
int1_handler proc ; ... 中断处理代码 ... ; 恢复现场(顺序相反) pop es ; 先弹出最后压入的 pop ds pop di pop si pop dx pop cx pop bx pop ax iret ; 自动弹出 IP → CS → FLAGS6. 示意图:SP 变化轨迹
中断前: SP = 1000H ↓ 硬件压 FLAGS: SP = 0FFEH (1000-2) 硬件压 CS: SP = 0FFCH 硬件压 IP: SP = 0FFAH 压 AX: SP = 0FF8H 压 BX: SP = 0FF6H 压 CX: SP = 0FF4H 压 DX: SP = 0FF2H 压 SI: SP = 0FF0H 压 DI: SP = 0FEEH 压 DS: SP = 0FECH 压 ES: SP = 0FEAH ← 当前 SP此时FLAGS在[SP+22]=[0FEAH+16H]=[1000H-2]=[0FFEH],验证正确。
. 关键总结
| 问题 | 答案 |
|---|---|
| FLAGS 在堆栈中的位置 | 当前 SP + 22(字节偏移) |
| 为什么是 +22? | 因为压了 10 个用户寄存器(20 字节)+ IP(2) + CS(2) = 24 字节,FLAGS 在第 25-26 字节,从 0 计数偏移 22 |
| 如何访问 FLAGS? | mov ax, [bp+24](如果用 BP 帧指针) |
| 为什么要保存这么多寄存器? | 中断处理不能破坏任何寄存器,必须完整保存和恢复 |
| 8088 有 PUSHA 吗? | 没有,PUSHA 是 80186+ 才有的 |