汇编程序语言设计的一些小点

发布时间 2023-11-05 22:42:42作者: 逆世混沌

汇编学的时间好短,尽力速成一下程序设计,其他就靠背了

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 SEGMENT
  assume 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结束程序。

 

 

这篇文章只是简单写了几个小点,并且很多地方并没有写的很细,只是为了着半个学期的课程做个小努力