欢迎光临
我们一直在努力

8088单板机监控程序解读(一)

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 返回。

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

    赞(0)
    未经允许不得转载:171主机测评 » 8088单板机监控程序解读(一)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址