汇编学的时间好短,尽力速成一下程序设计,其他就靠背了
DOS功能调用
1 - 键盘输入
- 2 - 屏幕输出
- 3 - 辅助输入
- 4 - 辅助输出
- 5 - 打印器输出
- 6 - 直接控制台输入/输出
- 7 - 直接控制台输入,不回显
- 8 - 读取键盘不回显
- 9 - 显示字符串
- 0Ah -Buffered键盘输入
- 0Bh - 检查键盘输入
-4ch-结束正在运行的程序直接返回DOS
当作一个模块来做
mov ah, 2 ; 功能号 2 - 输出字符 mov dl, al int 21h ; 输出
1. mov ah, 2 设置ah寄存器为2,这对应DOS中断INT 21h的输出字符功能号。
2. mov dl, al 将al寄存器中的字符移动到dl寄存器中。DOS 21h号中断的输出字符功能需要将要输出的ASCII字符存放在dl寄存器中。
3. int 21h 触发DOS中断INT 21h,调用ah寄存器所指定的输出字符功能,将dl寄存器中的ASCII字符输出到屏幕上。
简单记一下就好
我们记住以上的模块就可以做相应的输入输出
PS:当我们输出字符串的时候,需要有$作为结尾,否则会超量输出
简单代码阅读
.486 DATA SEGMENT USE16 MESG1 db 'PLEASE INPUT YOUR USER: $' MESG2 db 'PLEASE INPUT YOUR PASSWORD: $' U db 15 db ? db 15 dup(?) PWD db 15 db ? db 15 dup(?) USER db '123' ULEN equ $-USER PASSWORD db '0' MLEN equ $-PASSWORD WELCOME db '-----------WELCOME!-----------$' ERROR db '------ERROR!------$' DATA ends CODE SEGMENT USE16 assume CS:CODE, DS:DATA START: mov ax, DATA mov ds, ax PRINT_USERNAME: mov ah, 9 mov dx, offset MESG1 int 21h GET_USERNAME: mov ah, 0Ah mov dx, offset U int 21h PRINT_NEWLINE: ;换行 mov ah, 2 mov dl, 0ah int 21h PRINT_PWD: mov ah, 9 mov dx, offset MESG2 int 21h GET_PASSWORD: mov ah, 0Ah mov dx, offset PWD int 21h CHECK_USER_PWD: ;检查用户名 mov bx, offset USER mov si, offset U+2 mov cx, 3 check_user: mov al, [bx] cmp al, [si] jne PRINT_ERROR inc bx inc si dec cx ; 减少循环计数器 jnz check_user ; 如果cx不为零,则继续循环 ;检查密码 mov bx, offset PASSWORD mov si, offset PWD+2 mov cx, 1 check_pwd: mov al, [bx] cmp al, [si] jne PRINT_ERROR inc bx inc si dec cx ; 减少循环计数器 jnz check_pwd ; 如果cx不为零,则继续循环 ;检查完成,用户名密码正确 jmp welcomeTo PRINT_ERROR: mov ah, 2 ; 功能号 2 - 输出字符 mov dl, 10 ; ASCII码值为10,表示换行 int 21h ; 输出换行字符 mov ah, 9 mov dx, offset ERROR int 21h mov ah, 2 ; 功能号 2 - 输出字符 mov dl, 10 ; ASCII码值为10,表示换行 int 21h ; 输出换行字符 jmp START welcomeTo: mov ah, 2 ; 功能号 2 - 输出字符 mov dl, 10 ; ASCII码值为10,表示换行 int 21h ; 输出换行字符 mov ah, 9 mov dx, offset WELCOME int 21h EXIT: mov ah, 4Ch int 21h CODE ends end START
以上这段代码没有使用ULEN和MLEN但进行了定义,这是对用户名长度和密码长度进行比较的,这段代码没有加入这部分
DATA SEGMENT USE16 ; 数据定义 DATA ends CODE SEGMENTassume CS:CODE, DS:DATA
; 代码主体 CODE ends end START
我们首先看一下以上这段代码的结构或者框架,如上
.486就不说了
然后就是
DATA SEGMENT ; 数据定义 DATA ends
这段数据定义后接着是代码段
CODE SEGMENT ; 代码主体 CODE ends
在代码段之后,我们就要指定整个程序的入口点
也就是
end START
从我刚才的描述可以发现,这里的START是指定的,它叫什么都无所谓,也可以叫常规的BEG,也可以叫abcd
因为我们迟早会指定一个标签
刚才的整段代码段分成下面的部分
; 打印用户名段 PRINT_USERNAME: ; 打印提示用户名的消息 ; 获取用户名段 GET_USERNAME: ; 调用DOS获取用户名存入缓冲区 ; 打印密码段 PRINT_PWD: ; 打印提示密码的消息 ; 获取密码段 GET_PASSWORD: ; 调用DOS获取密码存入缓冲区 ; 检查验证段 CHECK_USER_PWD: ; 检查用户名密码是否匹配 ; 输出结果段 PRINT_RESULT: ; 根据检查结果打印成功或失败消息 ; 结束段 EXIT: ; 程序退出前的收尾工作
当我们将这些分开之后,相关的功能就可以分开解决了
这些都是标签名,并不影响代码的运行,反而是大家都爱用的NEXT等等这些会可读性很差
初始化的问题
START: mov ax, DATA mov ds, ax
这里的就是将DATA段地址转入ax,再用ax初始化ds
不要直接转入ds
键盘输入与输出
阅读了我刚才的代码,可以发现我在DATA提供了
U db 15 db ? db 15 dup(?)
这是用以键盘输入的一个结构
mov ah, 0Ah mov dx, offset U int 21h
这里对U进行输入
但字符串是存储在U+2的
所以我们在使用的时候是用
mov si, offset U+2
输出就以之前说的就好
cmp的一些问题
首先cmp自己有特定的使用要求
mov al, [bx] cmp al, [si]
在比较的时候不要使用两个[偏移地址]进行比较
循环的一些小问题
1. 循环计数循环:mov cx, 5 ;循环次数 loop: ;循环体 dec cx jnz loop
2.利用cx寄存器作为计数器,执行循环体,dec指令递减cx,jnz判断cx是否为0,不为0则跳转继续循环。2. 标签循环:
begin: ;循环体 jmp begin ;无条件跳转到begin标签
3.通过无条件跳转begin实现循环,begin标签标记循环位置。3. 比较循环:
mov cx, 0 cmp_loop: cmp cx, 5 ;比较计数器cx和5 jl cmp_loop ;小于则跳转继续循环 ;循环体 inc cx ;计数器++
4.利用cmp和条件跳转jl实现计数器判断,控制循环执行。4. REP指令:
mov cx,5 ;循环次数 repeat: ;循环体 rep repeat ;重复执行repeat循环
REP前缀可以重复执行循环体,通过CX控制次数。
这里的标签依然是之前所说的,无所谓什么名字
甚至这里的循环都是通过指令实现的罢了并非坚固不可摧
剩下的就是一些简答输入输出问题
我们可以发现跟我们写一个普通程序一样,使用大脑的地方并不多
但剩下的地方需要你不断完善以合适用户真正的使用流程,这段代码比较用户名和密码的部分并不多
;检查用户名 mov bx, offset USER mov si, offset U+2 mov cx, 3 check_user: mov al, [bx] cmp al, [si] jne PRINT_ERROR inc bx inc si dec cx ; 减少循环计数器 jnz check_user ; 如果cx不为零,则继续循环 ;检查密码 mov bx, offset PASSWORD mov si, offset PWD+2 mov cx, 1;这里可以改成
MLEN
check_pwd: mov al, [bx] cmp al, [si] jne PRINT_ERROR inc bx inc si dec cx ; 减少循环计数器 jnz check_pwd ; 如果cx不为零,则继续循环 ;检查完成,用户名密码正确 jmp welcomeTo
就这些部分罢了,其他都是满足用户流程
第二段代码
.486 DATA SEGMENT USE16 BUF db 'ABC6DED55BV99CADE77ABD5AERS2AV1BA$' ; 保存字符串的缓冲区 count dw 0 ; 统计结果存放变量 DATA ENDS CODE SEGMENT USE16 ASSUME CS:CODE, DS:DATA START: MOV AX, DATA MOV DS, AX MOV DH, 0H MOV BX, OFFSET BUF CMP BYTE PTR [BX], 42H JB NEXT CMP BYTE PTR [BX], 45H JA NEXT INC DH ;首先判断第一个字符是否满足条件,满足了就DH加1 LAST: INC BX ;接着通过加BX继续判断后面的字符 CMP BYTE PTR [BX], 42H JB NEXT CMP BYTE PTR [BX], 45H JA NEXT INC DH NEXT: CMP BYTE PTR [BX], 24H ;判断'$',结尾 JNE LAST MOV CX,8 XUN: ROL DH,1 ;开始进行左移位,循环判断输出0、1 MOV DL, 30H JNC CON INC DL CON: MOV AH, 02H INT 21H LOOP XUN MOV AH, 4CH INT 21H CODE ENDS END START
简单解释几个代码
CMP BYTE PTR [BX], 42H JB NEXT
这是一段比较的代码,下面的NEXT可以做到循环判断,不过也可以让地址+1在直接跳转到自己的标签位置,不需要使用两个标签做循环
汇编的代码相当自由
XUN: ROL DH,1 ;开始进行左移位,循环判断输出0、1 MOV DL, 30H JNC CON INC DL CON: MOV AH, 02H INT 21H LOOP XUN MOV AH, 4CH INT 21H
这里的输出没有像我之前说的那么模块化,少用了寄存器,这样当然更有优势
可读性上和写起来都要多想一层
1. XUN标签处是循环体,ROL DH,1实现了DH寄存器左移1位的逻辑运算。
2. JNC CON判断左移后最高位是否为1,如果是则不跳转,否则跳转到CON。
3. INC DL指令将DL寄存器的值+1,这里用于统计左移中1的个数。
4. CON标签输出当前DH的最低位,MOV DL,30H将DL预置为0,然后判断左移后最高位,如果为1则DL加1,记录1的个数。
5. 调用INT 21H中断打印DL的值,0或1。
6. LOOP XUN实现循环控制。
7. 最后调用INT 21H和INT 4CH结束程序。
这篇文章只是简单写了几个小点,并且很多地方并没有写的很细,只是为了着半个学期的课程做个小努力
标签:语言,db,mov,汇编程序,cx,小点,offset,DATA,21h From: https://www.cnblogs.com/nish1hundun/p/17811406.html