8088单板机监控程序解读(一)
2026/6/6 17:22:32 网站建设 项目流程

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 行:CBRETF(远返回)。它只恢复IPCS没有恢复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 会:

  1. 压入FLAGS, CS, IP

  2. 跳转到F000:C71C(即SERVICE_BREAK

进入SERVICE_BREAK后,它如前所述保存了所有寄存器,然后把栈切换回监控程序,最后RET返回监控主循环。此时用户程序的状态已经完全保存在变量区,不再需要“从中断返回”。当用户按GO时,监控程序再从变量区恢复寄存器并跳转回去。

最终总结

函数使用的返回指令是否正确原因
SERVICE_BREAKRET✅ 正确它是一个普通函数,由监控程序调用(通过中断触发但实质是函数调用),返回到监控主循环
KEY_GORETF✅ 正确远跳转到用户程序,不需要恢复 FLAGS(用户 FLAGS 已在变量区)
KEY_STEPIRET✅ 正确需要恢复 FLAGS 并设置陷阱标志

所以之前担心的“用 RET 代替 IRET”问题,在这个架构下并不存在SERVICE_BREAK的设计思路是:中断发生后,保存现场,然后“放弃”被中断的上下文,把控制权完全交还给监控程序。恢复执行是通过GOSTEP命令显式完成的,而不是通过SERVICE_BREAK返回。

这是一个非常清晰、巧妙的设计!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询