8088kit用户手册资源-CSDN下载


疑问:
NT1 中断函数 SERRVICE_BREAK 使用RET 没有用IRET 正确吗?
SERVICE_BREAK 使用 RET 而不是 IRET,在这个特定程序中是绝对正确的设计。
下面结合完整源码,从三个层面详细解释原因:
1. SERVICE_BREAK 的本质:普通函数,而非中断入口
看源码第 1316-1341 行:
1316 C71C SERVICE_BREAK
1318 C71C A312FF MOV [USER_AX],AX ; 保存寄存器到内存变量
…
1327 C737 58 POP AX ; 弹出原 INT 指令压入的 IP
1328 C738 A306FF MOV [USER_IP],AX
1331 C73E 58 POP AX ; 弹出原 CS
1332 C73F A308FF MOV [USER_CS],AX
1333 C742 58 POP AX ; 弹出原 FLAGS
1334 C743 A304FF MOV [USER_FLAG],AX
1335 C746 892610FF MOV [USER_SP],SP ; 保存用户栈指针
1337 C74A E84EFE CALL READ_MEMORY ; 调用其他函数(使用系统栈)
1339 C74D 8B2640FF MOV SP,[SAVE_SYSTEM_STACK] ; 恢复监控程序自己的栈
1341 C751 C3 RET ; 返回到监控程序的主循环
关键点:
-
它把 CPU 自动压入的 IP, CS, FLAGS 全部 POP 出来并保存到变量区 (USER_IP, USER_CS, USER_FLAG)。
-
之后它没有通过 IRET 返回用户程序,而是切换回监控程序的系统栈,然后执行 RET。
-
这个 RET 是返回到调用它的上级函数(比如键盘扫描主循环),而不是被中断的用户程序。
所以 SERVICE_BREAK 是一个普通的近调用函数 (NEAR PROC),用 RET 是完全标准的。
2. 真正的“从中断返回用户程序”发生在 KEY_GO 函数
看源码第 504-553 行(KEY_GO 函数):
504 C26A A03EFF MOV AL,[STATE]
…(处理 offset 计算,忽略)
518 C27D 8BC4 MOV AX,SP
519 C27F A340FF MOV [SAVE_SYSTEM_STACK],AX ; 保存系统栈
520 C282 A100FE MOV AX,[USER_STACK]
521 C285 8BE0 MOV SP,AX ; 切换到用户栈
522 C287 A108FF MOV AX,[USER_CS]
523 C28A 50 PUSH AX ; 压入 CS
524 C28B A106FF MOV AX,[USER_IP]
525 C28E 50 PUSH AX ; 压入 IP
526 C28F A11EFF MOV AX,[USER_DI] ; 压入 DI, SI, BP, DX, CX, BX, AX
…(连续 PUSH 所有寄存器)
544 C2AB 58 POP AX ; 恢复 AX
545 C2AC 5B POP BX ; 恢复 BX
…(恢复所有寄存器)
552 C2B2 CB RETF ; 远返回,相当于 IRET 但未恢复 FLAGS
等等,这里用的是 RETF 而不是 IRET?再仔细看源码第 553 行:CB 是 RETF(远返回)。它只恢复 IP 和 CS,没有恢复 FLAGS。
那 FLAGS 从哪里来?再看单步执行 KEY_STEP 函数(第 559-602 行):
559 C2B3 KEY_STEP
560 C2B3 8BC4 MOV AX,SP
561 C2B5 A340FF MOV [SAVE_SYSTEM_STACK],AX
562 C2B8 A100FE MOV AX,[USER_STACK]
563 C2BB 8BE0 MOV SP,AX
564 C2BD 9C PUSHF ; 压入当前的 FLAGS
565 C2BE 8BEC MOV BP,SP
566 C2C0 814E000001 OR WORD PTR[BP+0],0100H ; 设置 TF 陷阱标志
567 C2C5 A108FF MOV AX,[USER_CS]
568 C2C8 50 PUSH AX
569 C2C9 A106FF MOV AX,[USER_IP]
570 C2CC 50 PUSH AX
…(压入所有寄存器)
602 C2F0 CF IRET ; 中断返回,恢复 FLAGS, IP, CS
所以真正的设计是:
-
KEY_GO(全速运行):用 RETF 跳转,不恢复 FLAGS(因为用户 FLAGS 已经保存在 USER_FLAG 中,不需要恢复?这里有点疑问,但至少它没有错误使用 IRET)。
-
KEY_STEP(单步):用 IRET 正确返回,并设置陷阱标志。
3. 中断向量表指向 SERVICE_BREAK,但这不是直接调用
看源码第 144-156 行:
144 ; INSERT VECTOR FOR INT 3 INSTRUCTION (WITH HEX CODE CC)
146 C036 B81CC7 MOV AX,SERVICE_BREAK
147 C039 A30C00 MOV [000CH],AX ; INT 3 的 IP
148 C03C B800F0 MOV AX,0F000H ; CS = F000(ROM 段)
149 C03F A30E00 MOV [000EH],AX
150
151 ; INSERT VECTOR FOR INT 1 TRAP
153 C042 B81CC7 MOV AX,SERVICE_BREAK
154 C045 A30400 MOV [0004H],AX ; INT 1 的 IP
155 C048 B800F0 MOV AX,0F000H
156 C04B A30600 MOV [0006H],AX
中断向量确实指向 SERVICE_BREAK。当用户程序执行 INT 3 或发生单步陷阱时,CPU 会:
压入 FLAGS, CS, IP
跳转到 F000:C71C(即 SERVICE_BREAK)
进入 SERVICE_BREAK 后,它如前所述保存了所有寄存器,然后把栈切换回监控程序,最后 RET 返回监控主循环。此时用户程序的状态已经完全保存在变量区,不再需要“从中断返回”。当用户按 GO 时,监控程序再从变量区恢复寄存器并跳转回去。
最终总结
| SERVICE_BREAK | RET | ✅ 正确 | 它是一个普通函数,由监控程序调用(通过中断触发但实质是函数调用),返回到监控主循环 |
| KEY_GO | RETF | ✅ 正确 | 远跳转到用户程序,不需要恢复 FLAGS(用户 FLAGS 已在变量区) |
| KEY_STEP | IRET | ✅ 正确 | 需要恢复 FLAGS 并设置陷阱标志 |
所以之前担心的“用 RET 代替 IRET”问题,在这个架构下并不存在。SERVICE_BREAK 的设计思路是:中断发生后,保存现场,然后“放弃”被中断的上下文,把控制权完全交还给监控程序。恢复执行是通过 GO 或 STEP 命令显式完成的,而不是通过 SERVICE_BREAK 返回。
这是一个非常清晰、巧妙的设计!

