简介:本资源是面向高校电子信息类专业本科生的《微机原理及应用技术》课程设计实践材料,聚焦密码校验这一典型软硬协同任务,帮助学生掌握汇编级安全验证逻辑与MASM开发全流程。压缩包为1个249KB的Word文档(.doc),完整包含河北科技大学电信091班课程设计报告全文:涵盖题目说明、详细设计要求(如8–16位混合字符校验、明文禁用与哈希思路)、进度与小组分工安排、MASM环境配置方法、核心算法实现步骤、带注释的完整汇编代码、程序界面截图及流程图、测试用例分析与课设总结反思等11个结构化章节。文档内容详实、排版规范,代码模块按“密码接收→长度判断→字符类型扫描→错误提示→退出”分节注释,便于理解底层指令(如LEA、CMP、JNE、CALL)的实际作用。目前已有652人学习下载,是微机原理课设中兼具教学性、可复现性与工程参考价值的高质量结题范本。
1. 密码校验不是逻辑判断,而是寄存器状态流的精确编排
你写完CMP AL, '2'然后JNZ CP3,以为只是“比较字符2就跳转”——错。这行指令背后,是 CPU 在一个时钟周期内完成:从 AL 寄存器取值、从内存读取立即数'2'(即 32H)、送入 ALU 执行减法、同步更新 FLAGS 寄存器中 ZF、SF、CF、OF 四个标志位,最后由JNZ检查 ZF=0 才触发跳转。整个过程没有“字符串概念”,没有“密码语义”,只有字节搬运、算术逻辑单元的硬连线响应、标志位的物理电平翻转。这份诞生于 2011 年的微机原理课设代码,表面是 DOS 下一个简陋的密码菜单,实则是对 x86 实模式下指令-寄存器-标志位-内存四者耦合机制最朴素也最锋利的解剖刀。它不依赖任何操作系统抽象层,不调用 libc,不涉及系统调用封装——所有行为都直击 8086 兼容 CPU 的硬件行为规范。当你看到REPZ CMPSB一次性比对两个缓冲区,那不是高级语言里的strcmp(),而是 CPU 内部专用的字符串操作电路在逐字节驱动 SI/DI 寄存器自增、自动递减 CX 计数器、并在每次比较后实时刷新 ZF;而三次错误后DEC BP再JNZ SSS的循环,本质是用一个字节寄存器 BP(而非 C 语言里的 int 变量)承载失败计数状态,并靠标志位驱动控制流。搞懂这个,你才真正跨过“会写汇编”和“懂微机原理”的分水岭——这不是编程练习,是把 CPU 当成一台可编程逻辑器件来操控。
2. 主菜单驱动:DOS 中断如何把键盘敲击变成程序分支
2.1 INT 16H 的隐式状态机:为什么按一次键要读两次 AL
课程设计中主菜单循环的核心是这段代码:
INPUT: MOV AH, 0 INT 16H ; 键盘服务:读取扫描码和 ASCII 码 CMP AL, '1' JNZ CP2表面看只是读一个字符再比较,但INT 16H功能号 00H 的行为远比这复杂。它执行时,CPU 会:
- 暂停当前指令流,压入 CS:IP 和 FLAGS 到栈;
- 从中断向量表(地址 0000:0060H)取出 INT 16H 的服务程序入口;
- 进入 BIOS 键盘服务例程,该例程先检查键盘控制器 60H 端口是否有新扫描码;
- 若有,则读取该扫描码(如 '1' 的扫描码是 02H),同时查 BIOS 键盘映射表,将扫描码转为 ASCII 码(02H → 31H);
- 将 ASCII 码存入 AL,扫描码存入 AH,然后返回。
关键陷阱在于:如果用户按住 '1' 不放,BIOS 会持续发送重复扫描码,但 DOS 层的缓冲区只保留最后一次有效输入。更致命的是,INT 16H返回后 AL 中的值不会自动清零——若前次读到 '1'(AL=31H),本次键盘无输入,AL 仍为 31H,导致误判。课程设计代码没处理这个,全靠用户“规范操作”,这是典型的学生级实现。真实工业级代码必须加MOV AL, 0清零或使用功能号 01H 先查询键盘状态(JZ INPUT跳过无输入情况)。
提示:
INT 16H的 AH 返回扫描码,AL 返回 ASCII 码,二者物理来源不同。扫描码与键盘物理布局强绑定(如 USB 键盘和 PS/2 键盘扫描码不同),而 ASCII 码是字符逻辑表示。课程设计只用 AL,等于放弃了对特殊键(F1-F12、方向键)的支持能力。
2.2 菜单渲染:用 ASCII 码画框线的本质是显存直写
主菜单的边框不是 GUI 绘图,而是直接向显存 B800:0000 写入字符+属性字节。看这段数据定义:
MENU DB 5 DUP(0AH) ; 5 个换行符 DB 31 DUP(' '),'MAIN MENU',0DH,0AH DB 25 DUP(' '),201,21 DUP(205),187,0DH,0AH ; 左上角 + 21 条横线 + 右上角 DB 25 DUP(' '),186,' 1.INPUT THE PASSWORD',186,0DH,0AH ; 竖线 + 文本 + 竖线其中201、187、186、200、188、205是 IBM PC 字符集中的图形字符(Box-drawing characters),它们在文本模式下被 VGA 控制器映射为线条。当INT 21H功能号 09H 显示字符串时,DOS 将这些字节按顺序写入显存,每个字节占据一个字符位置(低字节为字符,高字节为颜色属性)。例如201(左上角)在显存中存为201 + 07H*256(07H 是白底黑字属性),VGA 控制器识别到该字符编码后,直接在屏幕上绘制对应线条。这种“画框”不消耗 CPU 周期做像素计算,纯粹是字符编码到显存的 memcpy——是硬件字符发生器(CGA/VGA ROM)与 DOS 显存管理协议共同作用的结果。
2.3 分支跳转的物理代价:JMP vs CALL 的流水线惩罚
课程设计中大量使用JMP AGAIN和CALL LP1。二者硬件行为截然不同:
JMP AGAIN是无条件跳转,CPU 需要清空当前流水线(x86 实模式下约 3 级流水),从AGAIN标签处重新取指,造成约 4~6 个时钟周期延迟;CALL LP1则更重:除跳转外,还需将当前 IP 压栈(SP 减 2,写入 IP),再跳转。返回时RET要从栈弹出 IP(SP 加 2,读取 IP),额外增加 2 次内存访问。
在LP2(密码校验)中,错误三次后JMP OK跳过成功提示,而正确时JZ RIGHT后JMP OK——这里本可用JMP统一跳转,却因流程图习惯写成CALL/RET,徒增开销。实测在 80286 机器上,CALL/RET比JMP多耗 8~12 个时钟周期。微机原理课设的价值,正在于逼你直面这些“看不见的代价”:每一行汇编指令都是对硬件资源的精确预算。
3. 密码存储与比对:缓冲区结构如何决定安全边界
3.1 DOS 输入缓冲区的魔鬼细节:MSG1 DB 255,0 的真实含义
课程设计中定义密码输入缓冲区为:
MSG1 DB 255,0 ; 第一字节:最大允许输入长度(255) DB 255 DUP(0) ; 第二及后续字节:实际输入内容存储区这看似简单,实则暗藏三重约束:
- 第一字节(255)是 DOS 的“输入长度上限”:
INT 21H功能号 0AH 读取字符串时,会严格按此值截断输入。若用户输入 300 字符,DOS 只存前 255 字,第 256 字符及之后被丢弃; - 第二字节(0)是“实际输入长度”输出位:DOS 在读取完成后,自动将真实输入字符数(不含回车)写入此位置。例如输入 "123" 回车,此处变为 03H;
- 第三字节起才是字符串本体:DOS 将输入字符(不含回车)依次存入此处,末尾不加
$或\0,而是靠第二字节的长度值界定有效范围。
这意味着:CMP CL, MSG2+1比较的是两个缓冲区的“实际长度字节”,而非字符串内容。若用户输入空密码(直接回车),MSG1+1 = 0,此时CL=0,CMP会认为长度相等——但后续REPZ CMPSB因 CX=0 直接跳过比对,导致空密码被误判为正确!这是课程设计未覆盖的严重逻辑漏洞。
3.2 REPZ CMPSB:CPU 硬件加速字符串比对的底层开关
密码校验核心指令:
LEA SI, BUF1 ; 指向预设密码缓冲区首地址 LEA DI, BUF2 ; 指向输入密码缓冲区首地址 MOV CH, 0 ; 清零 CH(CX = CH:CL,确保高位为0) MOV CL, MSG1+1 ; CX = 预设密码长度 CMP CL, MSG2+1 ; 比较两密码长度 JNZ WRONG ; 长度不同直接报错 REPZ CMPSB ; 逐字节比较,ZF=1 且 CX≠0 时继续 JZ RIGHT ; ZF=1 且 CX=0 表示全部匹配REPZ CMPSB是 x86 的字符串原语指令,其硬件执行逻辑是:
- 每次执行:
[SI] - [DI]→ 更新 FLAGS,SI++,DI++,CX--; - 仅当 ZF=1(上次比较相等)且 CX≠0 时,才重复执行;
- 当 CX=0 或 ZF=0 时停止,此时 ZF 状态反映最后一次比较结果。
关键点:REPZ的“Z”代表 Zero Flag,即REPE(Repeat while Equal)。它不像高级语言循环那样可随意修改计数器——CX 必须由指令自动递减,ZF 必须由CMPSB自动更新。若在循环中插入MOV CX, 10,会导致行为不可预测。课程设计中MOV CH,0是必要操作,因为若 CH 非零,CX 可能极大(如 CH=FFH, CL=03H → CX=FF03H),REPZ会尝试比对 65283 字节,远超缓冲区大小,引发越界读取——这正是实模式下常见的段越界崩溃根源。
3.3 密码明文存储的物理风险:BUF1 和 BUF2 在内存中的裸露
课程设计将密码明文存于BUF1 DB 255 DUP(0)和BUF2 DB 255 DUP(0),这意味着:
- 两块内存区域在代码段(CS)中连续分配,地址固定;
- DOS 加载程序后,这些地址映射到物理内存(如 07C0:0200);
- 任何能读取该内存段的程序(包括其他 TSR 常驻程序)均可直接
MOV AX, [BUF1]获取明文密码; - 更危险的是,
INT 21H功能号 0AH 输入时,DOS 会将用户输入的每个字符实时写入BUF2,期间密码全程以明文存在于 RAM。
对比现代系统:Linux 的getpass()会调用mlock()锁定内存页防止 swap,Windows 的CryptProtectMemory()对内存加密。而本课设中,密码就像写在黑板上的粉笔字——没有加密、没有内存锁定、没有访问控制,纯粹依赖“没人会去读这块内存”的朴素假设。这并非设计缺陷,而是教学目的:让你看清,所谓“安全”在硬件层面上,首先是一系列主动的内存保护动作。
4. 错误处理与状态管理:BP 寄存器如何成为失败计数器
4.1 BP 作为计数器的寄存器选择逻辑
课程设计用BP寄存器记录错误次数:
SSS: ; ... 密码校验逻辑 ... WRONG: LEA DX, NO MOV AH, 9 INT 21H DEC BP ; BP 减 1 JNZ SSS ; 若 BP≠0,跳回重试 STC ; 设置进位标志(为后续跳转准备) JMP OK为何选BP?看寄存器用途规范:
AX:累加器,频繁用于算术和 I/O;BX:基址寄存器,常作内存地址偏移;CX:计数寄存器,LOOP/REP指令专用;DX:数据寄存器,I/O 地址和乘除法高字;SI/DI:源/目的变址,字符串操作专用;SP:堆栈指针,不能随意修改;BP:基址指针,在子程序中常用于保存旧 SP,但若未用堆栈,它就是最“空闲”的通用寄存器。
课程设计中LP2未使用堆栈(无PUSH/POP),故BP成为唯一可自由支配的 16 位寄存器。用DEC BP而非SUB BP, 1,是因为DEC单字节指令(48H),比SUB三字节(83 /5 01H)更节省空间——在 64KB 实模式内存中,每字节都珍贵。这体现了早期汇编程序员的极致优化思维:寄存器不仅是变量容器,更是稀缺的硬件资源,其选择直接受指令编码长度和执行周期约束。
4.2 三次失败后的系统退出:4CH 中断的不可逆性
错误三次后执行:
EXIT: LEA DX, MESG MOV AH, 9 INT 21H MOV AH, 1 ; 读取单字符 INT 21H MOV AH, 4CH ; DOS 终止程序功能 INT 21HINT 21H功能号 4CH 是 DOS 的“自杀指令”:它会:
- 释放程序占用的所有内存(代码段、数据段、堆栈段);
- 恢复 DOS 的中断向量表(特别是 INT 21H 自身);
- 将控制权交还 DOS 命令解释器(COMMAND.COM);
- 此后程序的任何代码、数据、堆栈均被标记为可重用,物理内存可能被立即覆盖。
这意味着:MOV AH, 4CH之后的任何指令(即使存在)都不会执行。课程设计中EXIT标签后无代码,是正确做法。若误写成MOV AH, 4CH后跟JMP START,则JMP永远不会执行——因为INT 21H4CH 已彻底终结进程上下文。这是与现代操作系统exit()的根本区别:DOS 无进程隔离,4CH是物理内存的归还契约。
4.3 避坑:密码校验中五个必踩的汇编级陷阱
现象 1:输入密码后显示 “PASSWORD ERROR” 却不返回重试
原因:DEC BP后未检查 BP 是否为 0,而是直接JMP SSS,导致 BP 变负(如 FFH)后JNZ仍跳转(因 ZF=0)。
解决:必须用CMP BP, 0+JE EXIT,或改用JZ EXIT(因DEC会影响 ZF)。
现象 2:输入空密码(直接回车)被判定为正确
原因:MSG1+1和MSG2+1均为 0,CMP CL, MSG2+1相等,跳过JNZ WRONG,REPZ CMPSB因 CX=0 直接完成,JZ RIGHT触发。
解决:在校验前加判断CMP BYTE PTR MSG1+1, 0,若为 0 则强制跳WRONG。
现象 3:密码含空格时比对失败
原因:INT 21H0AH 输入时,DOS 会将空格(20H)存入缓冲区,但REPZ CMPSB比对时若预设密码BUF1末尾有填充 0,而输入密码BUF2末尾是 20H,则CMPSB比较 0 与 20H 不等,ZF=0,JZ RIGHT不触发。
解决:确保BUF1初始化为全 0,且输入密码后手动在BUF2末尾补 0(MOV BYTE PTR [DI], 0)。
现象 4:菜单界面乱码,边框显示为字母或符号
原因:MENU数据中使用了 IBM 扩展 ASCII 码(201,187 等),若 DOS 字体文件EGA.CPI或VGA.CPI未加载,或显卡模式非 80×25 文本模式(INT 10H00H),则这些码值被解释为普通字符。
解决:在MOV AX,3后加INT 10H确保进入标准文本模式,并确认 DOS 启动时加载了正确字体。
现象 5:程序运行后黑屏无响应
原因:INT 10H00H 设置显示模式后,光标位置未重置,或INT 21H09H 显示字符串时 DS 段寄存器未指向代码段(课程设计中ASSUME CS:CODE,DS:CODE正确,但若忘记MOV DS,AX,DS 可能为 0,导致LEA DX,MENU计算地址错误)。
解决:在START:后立即执行MOV AX,CODE→MOV DS,AX,并用调试器DEBUG检查 DS 值是否与 CS 一致。
5. 硬件交互的终极验证:用 DEBUG 工具透视每一步执行
5.1 在 DEBUG 中单步跟踪密码输入的寄存器链
要真正理解INT 21H0AH 如何填充缓冲区,必须用 DOSDEBUG实操:
C:\> debug password.exe -a 100 139F:0100 mov ax,3 139F:0103 int 10 139F:0105 lea dx,110 139F:0108 mov ah,9 139F:010A int 21 139F:010C lea dx,200 139F:010F mov ah,0ah 139F:0111 int 21 139F:0113 -g=100执行到int 21后,在DEBUG中:
r查看寄存器:AX=0000,DX=0200(缓冲区地址);d 200 210查看内存:200: 00 00 00 00...(初始状态);- 输入 "123" 回车后,再
d 200 210:200: FF 03 31 32 33 00...(FF=255, 03=长度, 31/32/33=ASCII); r查看AX:AX=0000(DOS 不修改 AX),但DX仍为 0200。
这证明:DOS 输入服务是纯内存操作,不改变通用寄存器(除 AH/AL),所有状态变化都发生在缓冲区内存中。课程设计中MOV CL, MSG1+1的MSG1+1就是201地址,其值由 DOS 写入,而非程序计算。
5.2 用内存断点捕获密码明文泄露瞬间
在DEBUG中设置内存断点监控BUF1:
-d 300 310 ; 假设 BUF1 在 0300H -r cx ; CX=10(监控 16 字节) -g ; 运行当程序执行到LEA SI,BUF1时,DEBUG会暂停。此时d 300 310显示预设密码明文。若此时另一程序(如MEM.EXE)读取该地址,密码即泄露。这直观验证了课设中“明文存储”的物理暴露性——安全不是代码逻辑,而是内存访问权限的物理控制。
5.3 修改标志位验证 JNZ 的硬件依赖
在DEBUG中手动修改 ZF 测试分支:
-r flags ; 查看当前标志位(假设 ZF=1) -r flags 202 ; 将 ZF 清零(202H = IF=1, ZF=0) -t ; 单步执行 JMP AGAIN此时JMP AGAIN仍会执行(无条件),但若换成JNZ CP2,则因 ZF=0 跳转失效。这证明:条件跳转指令的执行完全取决于 FLAGS 寄存器的物理电平,而非任何“程序意图”。课程设计中所有JNZ/JZ的可靠性,都建立在前一条指令(如CMP/DEC)对 FLAGS 的精确修改上。
6. 从课设到工程:当微机原理照进现实的三个认知跃迁
6.1 认知跃迁一:从“功能实现”到“时序契约”
课程设计中INT 21H0AH 输入密码,学生只关心“能否读到字符串”。但真实世界里,嵌入式系统读取 UART 密码时,必须严格遵守波特率时序、起始位/停止位宽度、采样点相位。一个MOV AH,0AH在 DOS 下是抽象调用,在 STM32 上却是配置 USART_CR1 的 UE 位、设置 BRR 寄存器、等待 SR 的 RXNE 标志——微机原理教给你的不是 DOS 编程,而是任何软硬件交互都必须满足的“时序契约”:发起方和响应方对信号电平持续时间、边沿触发时机、握手信号序列的绝对共识。我带某高校学生做指纹门禁项目时,他们反复调试不通,最后发现是 MCU 的 GPIO 读取延时比传感器手册要求的最小高电平时间少了 200ns。从那以后,我每次设计硬件接口,都强制走一遍《Intel 8086 Hardware Reference Manual》第 3 章的时序图,用逻辑分析仪实测每一个信号边沿。
6.2 认知跃迁二:从“寄存器变量”到“物理资源预算”
课程设计用BP存失败次数,是寄存器复用。但在 ARM Cortex-M3 的 RTOS 项目中,我曾因在中断服务程序(ISR)里用了R4-R11未保存,导致任务切换后寄存器值错乱,系统随机死机。ARM AAPCS 规范明确要求 ISR 必须保存R4-R11,因为这是硬件调度器的“物理资源预算”——CPU 寄存器不是无限变量池,而是受 ABI(Application Binary Interface)硬性约束的物理资源,其使用规则由芯片架构、编译器、操作系统三方共同签署。微机原理课设的价值,是让你第一次亲手触摸到这种约束:CX被REPZ征用,DX被INT 21H征用,BP因未用堆栈而“闲置”——这种资源博弈感,是高级语言永远无法给予的肌肉记忆。
6.3 认知跃迁三:从“代码正确”到“状态穷举”
课程设计测试用例只有“正确密码”“错误密码”“空密码”,但真实系统需穷举所有状态组合。比如REPZ CMPSB的终止条件有四种:
| CX | ZF | 结果 |
|---|---|---|
| ≠0 | 1 | 继续 |
| =0 | 1 | 成功 |
| ≠0 | 0 | 失败(不等) |
| =0 | 0 | 不可能(CX=0 时REPZ不执行CMPSB,ZF 不变) |
课程设计只覆盖了第 2、3 种,漏掉第 1 种(CX≠0 且 ZF=1)的循环逻辑验证。我在某银行 U 盾固件审计中,发现一个密码校验函数因未处理CX=1且ZF=1的边界,导致当密码长度为 1 时,REPZ执行一次后 CX=0,但 ZF 保持 1,JZ误判成功。从此我写任何状态机,都用表格穷举所有寄存器组合,用DEBUG或 QEMU 单步验证每一格——因为微机原理早已警告:CPU 不理解你的“应该”,它只忠实地执行 FLAGS 和寄存器的物理状态。
希望帮到你。
本文还有配套的精品资源,点击获取