小白
此篇博客为学习总结,代码逻辑比较乱~
这是我的第一篇博客,肯定会有这样那样的问题,欢迎各位大佬评论区提建议。
程序功能
基本实现主流密码通行字功能,程序执行后,输出提示,请用户输入密码(通行字),按回车键表示键入结束,开始判断密码是否通过验证。只有当用户输入的密码和程序内置字符串完全相同时,显示欢迎界面,程序返回DOS, 否则请用户重新输入密码。
- 如果密码不通过,会清屏之后再给出提示,重新输入;
- 密码输入过程中,每位密码明文显示0.2秒之后变成星号,且程序在接收过程中不响应Ctrl_C;
- 提供密码长度检查,超过规定长度(16位),输入无效。;
- 支持移动光标到任意位置并修改密码(已添加边界检查,光标不会移动到非法位置),总体上实现主流的密码输入功能;
程序源码
该程序是课程的上机实验,所以整个程序是作为一个整体来写的。里面的子程序可能改变了某些寄存器的值,但这些寄存器对我这个程序而言是无关重要的,我就没有去保存/还原这些寄存器的值。所以,如果要移植某个子程序或者宏指令,请注意根据你自己的程序做好相应的保护。
显示单个字符
使用的是DOS 21H型中断,02H号功能。功能很简单,要注意的是,这个功能会破坏AL寄存器的值。
;显示一个字符
;参数 CHAR=显示字符的ASCII
;被改动的寄存器 AH,DL,破坏AL
DOSCH MACRO CHAR
MOV AH, 02H
MOV DL, CHAR
INT 21H
ENDM
无属性显示字符串
使用的是DOS 21H型中断,09H号功能。
入口参数:DS:DX=字符串首地址,字符串必须以’$’(ASCII码值为24H)为结束标志。
出口参数:无
;无属性显示字符串
;参数 POS=字符串名称
;被改的寄存器 AH,DX,破坏AL
DOSOU MACRO POS
MOV AH, 09H
MOV DX, OFFSET POS
INT 21H
ENDM
BIOS属性显示字符串
使用的是BIOS 10H型中断,13H号功能,该功能比较灵活。
入口参数:AL=0~3,BH=显示页号,BL=属性字(当AL=0、1时有效),CX=串长度,DX、DL=字符串显示的起始行、列号,ES:BP=待显示字符串首地址。
出口参数:无
该功能从屏幕当前光标所在位置开始显示字符串,遇到串结束符24H时停止,串结束符不显示。
说明:
- 该功能从屏幕指定位置开始显示一串彩色字符。
- 待显示的字符串要放在附加段,首地址偏移量写入BP寄存器。(提示:不要忘记初始化ES寄存器)
- AL=0表示:待显示的字符串中仅包含字符的ASCII码,串中各字符的属性由BL中的属性字决定,串显示结束后,光标返回调用前的位置。
- AL=1表示:光标停留在字符串的末尾,其余同AL=0。
- AL=2表示:待显示的字符串中包含有各个字符的ASCII码和属性字,格式为(ASCII码,属性,…,ASCII码,属性),串显示结束后,光标返回调用前的位置。
- AL=3表示:光标停留在字符串的末尾,其余同AL=2。
- 当AL选择2或者3时,CX中的串长度不包括个字符的属性字节。
;BIOS属性显示字符串
;参数ROW=行,COL=列,POS=字符串首地址,LEN=字符串长度,COLOR=属性
;被改的寄存器 AX,BX,CX,DX,BP,需要初始化ES
BIOSO MACRO ROW, COL, POS, LEN, COLOR
MOV AX, 1301H
MOV BH, 0
MOV BL, COLOR
MOV CX, LEN
MOV DH, ROW
MOV DL, COL
MOV BP, OFFSET POS
INT 10H
ENDM
附彩色编码表:
D7 D6 D5 D4 (背景) | 颜色 | D3 D2 D1 D0 (前景) | 颜色 | |
---|---|---|---|---|
0 0 0 0 | 黑 | 1 0 0 0 | 灰 | |
0 0 0 1 | 蓝 | 1 0 0 1 | 浅蓝 | |
0 0 1 0 | 绿 | 1 0 1 0 | 浅绿 | |
0 0 1 1 | 青 | 1 0 1 1 | 浅青 | |
0 1 0 0 | 红 | 1 1 0 0 | 浅红 | |
0 1 0 1 | 品红 | 1 1 0 1 | 浅品红 | |
0 1 1 0 | 棕 | 1 1 1 0 | 黄 | |
0 1 1 1 | 灰白 | 1 1 1 1 | 白 |
数据段
其中,PASS字段,9表示本地通行字长度,'B16080319’是本地通行字;
BUF字段是开辟的缓存区,用户输入的密码会存入这里,16表示用户输入密码的最大长度,0是用户输入的密码长度(随用户输入密码不断更改)。
DATA SEGMENT USE16
PASS DB 9,'B16080319'
STRA DB 'Logging...', 0DH, 0AH, '$'
STRB DB 'Pass: $'
STRC DB 'Error, please re-enter', 0DH, 0AH, '$'
STRW DB 'WELCOME...', '$'
BUF DB 16, 0
DB 20 DUP(?),'$'
TIME DW 20
DATA ENDS
程序主干
初始化程序,调用接收子程序(INPUT)接收密码,利用串比较指令比较接收的通行字是否和本地设定的通行字完全一样。是,则在屏幕中央卷屏,清理一块区域显示欢迎界面;否,则清屏,同时输出错误,提示用户重新输入。
其中,利用串比较指令比较通行字的时候,第一个字符是通行字长度(ASCII码值),如果长度都不一样就没有继续往下比较的必要了。
CODE SEGMENT USE16 ;代码段开始伪指令,结束伪指令在最后
ASSUME DS:DATA, CS:CODE, ES:DATA ;串比较指令要用到ES
BEG: MOV AX, DATA
MOV DS, AX
MOV ES, AX ;初始化ES
DOSOU STRA
NEXT: DOSOU STRB
MOV BX, OFFSET BUF ;传参,数据存储区格式为最大长度+实际长度+字符写入区
CALL INPUT
MOV CH, 00H
MOV CL, PASS
INC CX ;连带串长度一起比较
MOV SI, OFFSET BUF+1
MOV DI, OFFSET PASS
CLD
REPZ CMPSB
JZ WEL
MOV AX, 0003H ;同时清屏
INT 10H
BIOSO 0, 0, STRC, 24, 00001100B
JMP NEXT
WEL: MOV AX, 0603H
MOV BH, 01111001B
MOV CX, 0B21H
MOV DX, 0D2EH
INT 10H
BIOSO 0CH, 23H, STRW, 10, 01111001B
MOV AH, 4CH
INT 21H
接收用户输入的密码(INPUT)
程序首先判断接收的字符是否是功能键,如果是,则再接收一次,获取扩展码,根据具体方向(只响应左、右两个方向)移动光标位置;否则,判断是删除键还是回车键,或者是其他按键,做出对应处理。如果是删除键,则先调用子程序DELPW删除BUF缓存区中光标位置对应的字符,然后设置参数为0,调用子程序DEOCH删除最后一个星号;如果是回车键,则结束此次调用;如果是其他按键,则先调用子程序ENTPW在BUF缓存区中光标位置对应的位置插入获取的字符,然后设置参数为1,调用子程序DEOCH在光标位置显示获取的字符0.2秒后变为星号,并且在屏幕上密码输入区域末尾追加一个星号。
主要依赖于DOS 21H型中断,07H号功能(等待从键盘输入一个字符,但该输入 字符不显示在屏幕上,不响应Ctrl_C)。
入口参数:无
出口参数:AL=按键的ASCII码,若AL=0,需要再次调用该项功能才能在AL中得到该按键的扩展码。
常用功能键扩展码如下:
功能键 | 扩展码 |
---|---|
方向键:上 | 48H |
方向键:下 | 50H |
方向键:左 | 4BH |
方向键:右 | 4DH |
值得注意的是,删除键(backspace)和回车键(enter)并不是功能键,它们的ASCII码分别为08H和0DH。
;功能:输入密码,支持光标移动和修改,非明文
;传参BX为输入数据空间首地址,含限制字符串最大长度以及实际写入字符串长度,以回车换行键结束输入
;被改动的寄存器 AX,BX,CX, DX,SI, DI,BP其中BX是传参寄存器,BX和SI在子程序中贯穿全文,不可胡乱更改
INPUT PROC NEAR
MOV BYTE PTR [BX+1], 00H
MOV SI, 0
COM: MOV AX, 0700H
INT 21H
CMP AL, 0 ;功能键
JNZ JUDGE
MOV AX, 0700H
INT 21H
CMP AL, 4BH
JZ LEFT
CMP AL, 4DH
JNZ COM
MOV DH, 00H
MOV DL, [BX+1]
CMP SI, DX
JNB COM
DOSCH '*' ;相当于光标右移
INC SI
JMP COM
LEFT: CMP SI, 0
JLE COM
DOSCH 08H
DEC SI
JMP COM
JUDGE: CMP AL, 08H ;删除键
JNZ SAVE
CMP SI, 0
JLE COM ;已经在数据存储区首地址处,不响应删除键
CALL DELPW
DEC SI
DEC BYTE PTR [BX+1]
MOV DI, 0
CALL DOECH
JMP COM
SAVE: CMP AL, 0DH ;回车结束
JZ EOUT
MOV DH, 00H
MOV DL, [BX]
CMP SI, DX
JNB COM ;如果达到最大长度,输入无效
CALL ENTPW
INC SI
INC BYTE PTR [BX+1]
MOV DI, 1
CALL DOECH
JMP COM
EOUT: DOSCH 0AH
RET
INPUT ENDP
刷新屏幕密码输入区(DOECH)
根据参数,若实参为0,则先保存当前光标位置,将光标位置移到屏幕密码输入区末尾,然后删除屏幕密码输入区末尾的一个星号,最后再重置光标位置(在初始位置前移一位);若实参为1,则先在光标位置显示获取的字符0.2秒,保存光标位置,然后在屏幕密码输入区末尾追加一个星号,最后再重置光标位置(在初始位置后移一位)。
;传参DI, DI=0删除,DI!=0写入
;调用应该在密码已经存入之后,AL被破坏
;父程序中对调用和INC/DEC SI的次序无要求,但INC/DEC SI和[BX+1]应该同时在调用之前或之后
;父程序中应该提供是否需要删除的检查
DOECH PROC NEAR
CMP DI, 0
JZ DELCH
DOSCH AL
CALL WAITS
DOSCH 08H
DOSCH '*'
DOSCH 08H
DELCH: PUSH BX ;PROTECT
MOV DH, 00H
MOV DL, [BX+1]
SUB DX, SI
MOV BL, DL
MOV AH, 03H ;CL被破坏
MOV BH, 00H
INT 10H
PUSH DX ;PROTECT
ADD DL, BL
MOV AH, 02H
INT 10H
CMP DI, 0
JNZ ENTCH
DOSCH 08H
DOSCH 00H
POP DX
DEC DL
JMP RENEW
ENTCH: DOSCH '*'
POP DX
INC DL
;BH页数前面获取光标位置得到,且未改变,前面DOSCH内已经设置AH=02H,不用再设置
RENEW: INT 10H
POP BX
RET
DOECH ENDP
延时函数(WAITS)
延时函数用到了21H型中断,2C号DOS功能。该功能的作用是取时间,出口参数CH:CL=时:分,DH:DL=秒:1/100秒。
;延时函数
;内存数传参,参数TIME=延迟时间(TIME<=32767),单位1/100s
;被改动的寄存器 AX, CX, DX, BP
WAITS PROC
MOV BP, TIME
MOV AH, 2CH ;DH中存1s为单位,DL中为1/100s
INT 21H
GTTE: MOV AL, 100
MUL DH
MOV DH, 0
ADD AX, DX ;(AX)=(DH)*100+(DL)
PUSH AX
MOV AH, 2CH
INT 21H
POP CX
PUSH DX
MOV AL, 100
MUL DH
MOV DH, 0
ADD AX, DX
POP DX
SUB CX, AX
CMP CX, 0
JLE NOOU
SUB CX, 6000 ;分钟过0点纠正
NOOU: ADD BP, CX
CMP BP, 0
JG GTTE
RET
WAITS ENDP
上面的代码中,加入了时间是否过整分点的判断,其实,考虑到实际情况,这个操作是多余的(不考虑中断)。计算机的运算速度很快,每一趟执行所花费的时间不会超过1/100秒,所以,只需要判断当前时间和上一趟读取的时间是否相同就可以了,不同就给(BP)减去1 (TIME-1)。
实际上,延时功能还可以结合2CH及2DH号DOS功能(21H型中断)来实现,有兴趣的读者可以尝试一下。
- 中断类型:21H
- 功能号:2DH,设置时间
- 入口参数:CH:CL=时:分,DH:DL=秒:1/100秒
- 出口参数:AL=00H成功,AL=0FFH失败
在实现这个延时功能之前,我在(这里)找到15H型中断86H号功能(等待功能)实现延时的例子,但是运行时发现它除了已知的寄存器之外还会乱改其他东西,所以最终被我放弃了。有兴趣的同学可以研究一下,如果有了结果,希望可以在评论区留言告诉我答案。(谢谢)
删除密码缓存区指定位置的单个字符(DELPW)
和C/C++删除字符串中的某一字符一样,将该位置后面的所有字符向前移一位,覆盖掉该位置的字符。SI是要删除字符的位置,SI+1是光标的位置。
;无传参,删除密码中的单个字符, DEC 在调用该程序后
DELPW PROC NEAR
PUSH SI
MOV CH, 00H
MOV CL, [BX+1]
SUB CX, SI
CMP CX, 0
JLE RENEW
AGAIN: MOV DL, [BX+SI+2]
MOV [BX+SI+1], DL
INC SI
LOOP AGAIN
RENEW: POP SI
RET
DELPW ENDP
在密码缓存区指定位置插入单个字符(ENTPW)
先把插入位置后面的字符全部向后移一位,然后再将插入位置的存储单元赋值为接收到的ASCII码。其中SI是已完成写入的位置,SI+1是光标所在位置(即下一个要写入的位置)。
;无传参,密码中插入单个字符, INC 在调用该程序后
ENTPW PROC NEAR
PUSH SI
MOV CH, 00H
MOV CL, [BX+1]
SUB CX, SI
CMP CX, 0
JLE RENEW
MOV DH, 00H
MOV DL, [BX+1]
MOV SI, DX
AGAIN: MOV DL, [BX+SI+1]
MOV [BX+SI+2], DL
DEC SI
LOOP AGAIN
RENEW: INC SI ;需要把SI置到下一位写入
MOV [BX+SI+1], AL
POP SI
RET
ENTPW ENDP
当然,整个程序的最后,不要忘记代码段的结束伪指令和汇编结束伪指令
CODE ENDS
END BEG
程序运行结果
- 输入提示:
- 密码输入过程中,输入字符显示0.2秒之后变成星号:
- 如果密码错误,清屏并输出错误提示:
- 移到光标到任意位置(有边界检查)修改密码:
- 验证通过的欢迎界面: