汇编语言(七):控制转移类指令(转移指令、循环指令、子程序指令、中断指令)、最后一次作业

作为汇编语言的课程笔记,方便之后的复习与查阅

本篇为课程第八次课内容

目录

  • 控制转移类指令
    • 无条件转移指令JMP
      • 段内转移、直接寻址
      • 段内转移、间接寻址
      • 条件转移指令
        • 判断单个标志位状态
        • 比较无符号数高低
        • 比较有符号数大小
    • 循环指令(loop)
    • 子程序指令
    • 中断指令
      • 系统功能调用
      • 字符输出的功能调用
      • 字符串输出的功能调用
      • 字符输入的功能调用
      • 字符串输入的功能调用
      • DOS 文件操作
        • 建立文件
        • 打开文件
        • 关闭文件
        • 读取文件
        • 写文件
      • 练习:文件写入 + 字符串加密解密

控制转移类指令

重点掌握:

  • JMP/Jcc/LOOP
  • CALL/RET
  • INT n 常用系统功能调用

一般了解:

  • LOOPZ/LOOPNZ

无条件转移指令JMP

JMP label			;程序转向label标号指定的地址

操作数label是要转移到的目标地址

JMP指令分成4种类型:

  • 段内转移、直接寻址
  • 段内转移、间接寻址
  • 段间转移、直接寻址
  • 段间转移、间接寻址

因为现在的硬件条件比较好,所以主要了解段内转移的两种即可

段内转移、直接寻址

转移地址和立即数一样,直接在指令的机器代码中,就是直接寻址方式(用标号表达,标号在编译的时候,会转换成直接的地址)

JMP label			;IP←IP+位移量 实际上是相对寻址

位移量是紧接着JMP指令后的那条指令的偏移地址,到目标指令的偏移地址的地址位移。当向地址增大方向转移时,位移量为正;向地址减小方向转移时,位移量为负

jmp again				;转移到again处继续执行
	……
again:	dec cx			;标号again的指令
	……
	jmp output			;转向output
	……
output:	mov result,al	;标号output的指令

段内转移、间接寻址

转移地址在寄存器或主存单元中,就是通过寄存器或存储器的间接寻址方式

JMP r16/m16 			;IP←r16/m16

将一个16位寄存器或主存字单元内容送入IP寄存器,作为新的指令指针,但不修改CS寄存器的内容

jmp ax						;IP←AX
jmp word ptr [2000h]		;IP←[2000h]

条件转移指令

Jcc label		;条件满足,发生转移:IP←IP+8位位移量
  				;条件不满足,顺序执行

指定的条件cc如果成立,程序转移到由标号label指定的目标地址去执行指令;条件不成立,则程序将顺序执行下一条指令

操作数label是采用短转移,称为相对寻址方式

Jcc指令不影响标志,但要利用标志。根据利用的标志位不同,16条指令分成3种情况:

  • 判断单个标志位状态
  • 比较无符号数高低
  • 比较有符号数大小

判断单个标志位状态

  • JZ/JEJNZ/JNE:利用零标志ZF,判断结果是否为零(或相等)
  • JSJNS:利用符号标志SF,判断结果是正是负
  • JOJNO:利用溢出标志OF,判断结果是否产生溢出
  • JP/JPEJNP/JPO:利用奇偶标志PF,判断结果中“1”的个数是偶是奇
  • JC/JB/JNAEJNC/JNB/JAE:利用进位标志CF,判断结果是否进位或借位

:如果al最高位为1,ah值设置为0ffh,否则ah值为0

test al,80h				;测试最高位
jz next0				;D7=0(ZF=1),转移
mov ah,0ffh				;D7=1,顺序执行
jmp done				;无条件转向

next0:	mov ah,0
done:	...

:计算|X-Y|。X和Y为存放于X单元和Y单元的16位操作数,结果存入result

mov ax,X
sub ax,Y
jns nonneg
neg ax
	
nonneg:	mov result,ax

:计算X-Y,X和Y为存放于X单元和Y单元的16位操作数。若溢出,则转移到overflow处理

mov ax,X
sub ax,Y
jo overflow

...	;无溢出,结果正确
overflow:	...	;有溢出处理

:设字符的ASCII码在AL寄存器中,将字符加上奇校验位,在字符ASCII码中为“1”的个数已为奇数时,令其最高位为“0”;否则令最高位为“1”

and al,7fh			;最高位置“0”,同时判断“1”的个数
jnp next			;个数已为奇数,则转向next
or al,80h			;否则,最高位置“1”
next:

:记录BX中1的个数

xor al,al					;AL=0,CF=0
again:	test bx,ffffh 		;等价于 cmp bx,0 		判断bx中是否有1
		je next				;如果没1,则跳转到next
		shl bx,1
		jnc again
		inc al
		jmp again
next:	...					;AL保存1的个数
xor al,al					;AL=0,CF=0
again:	cmp bx,0
		jz next
		shl bx,1			;也可使用 shr bx,1
		adc al,0			;adc:带CF的加法
		jmp again
next:	...					;AL保存1的个数

比较无符号数高低

  • 无符号数的大小用(Above)(Below)表示
  • 利用CF确定高低、利用ZF标志确定相等(Equal)

两数的高低分成4种关系:

  • <:JBJNAE):CF=1
  • >=:JNBJAE): CF=0
  • <=:JBEJNA): CF=1或ZF=1
  • >:JNBEJA ):CF=0且 ZF=0

AX,BX中为无符号数,编程将较大者存于AX中,较小者存于BX

cmp ax,bx			;比较ax和bx
jnb next			;若ax≥bx,转移
xchg ax,bx			;若ax<bx,交换
next:	...

比较有符号数大小

  • 有符号数的(Greater)(Less)
  • 需要组合OFSF标志,并利用ZF标志确定相等(Equal)

两数的大小分成4种关系:

  • <:JLJNGE):(SFOF)
  • >=:JNLJGE):(SF=OF)
  • <=:JLEJNG):(SFOFZF=1)
  • >:JNLEJG ):(SF=OFZF=0)

:设AX,BX中为有符号数,编程将较大者存于AX中,较小者存于BX

	cmp ax,bx					;比较ax和bx
	jnl next	          		;若ax≥bx,转移
	xchg ax,bx					;若ax<bx,交换
next:	...

循环指令(loop)

LOOP label			;CX←CX-1,
    				;CX≠0,循环到标号label
LOOPZ label			;CX←CX-1,
    				;CX≠0且ZF=1,循环到标号label
LOOPNZ label		;CX←CX-1,
    				;CX≠0且ZF=0,循环到标号label

循环指令默认利用CX计数器,方便实现计数循环的程序结构

:记录空格个数

	mov cx,count			;设置循环次数
	mov si,offset string
	xor bx,bx				;bx=0,记录空格数
	mov al,20h				;20h为空格的ascii码,用来和每个字符进行比较
again:	cmp al,es:[si]
	jnz next				;ZF=0非空格,转移
	inc bx					;ZF=1是空格,个数加1
next:	inc si
		loop again 			;等价于dec cx , jnz again
							;字符个数减1,不为0继续循环

子程序指令

子程序是完成特定功能的一段程序。当主程序(调用程序)需要执行这个功能时,采用CALL调用指令转移到该子程序的起始处执行。当运行完子程序功能后,采用RET返回指令回到主程序继续执行

CALL指令分成4种类型(类似JMP

CALL label		;段内调用、直接寻址
CALL r16/m16	;段内调用、间接寻址

CALL指令需要保存返回地址:

  • 段内调用——入栈偏移地址IP
    SPSP-2,SS:[SP]←IP

RET指令需要弹出CALL指令压入堆栈的返回地址:

  • 段内返回——出栈偏移地址IP
    IPSS:[SP], SPSP+2

;主程序
	mov al,0fh			;提供参数AL
	call htoasc			;调用子程序
	...
;子程序:将AL低4位的一位16进制数转换成ASCII码
Htoasc  proc			;Htosac为子程序名称,可以随意命名		proc代表一个子程序
	and al,0fh			;只取al的低4位
	or al,30h			;al高4位变成3		0~9转成ascii码只要加上30h
	cmp al,39h			;是0~9,还是0Ah~0Fh
	jbe htoend
	add al,7			;是0Ah~0Fh,加上7		如果是字母的话则再加上7
htoend:    ret			;子程序最后要用ret返回
Htoasc endp				;endp代表子程序结束

中断指令

INT i8  

DOS系统调用表

系统功能调用

  • 21H号中断是DOS提供给用户的用于调用系统功能的中断,它有近百个功能供用户选择使用,主要包括设备管理、目录管理和文件管理三个方面的功能
  • ROM-BIOS也以中断服务程序的形式,向程序员提供系统的基本输入输出程序

功能调用的步骤:

  • AH寄存器中设置系统功能调用号
  • 在指定寄存器中设置入口参数
  • 执行指令INT 21H(或ROM-BIOS的中断向量号)实现中断服务程序的功能调用
  • 根据出口参数分析功能调用执行情况

字符输出的功能调用

DOS功能调用INT 21H

  • 功能号:AH02H
  • 入口参数:DL=字符的ASCII码
  • 功能:在显示器当前光标位置显示给定的字符,光标右移一个字符位置。如按Ctrl-Break或Ctrl-C则退出

:在当前显示器光标位置显示一个问号

mov ah,02h	
mov dl,'?'	
int 21h	

进行字符输出时,当输出响铃字符(07H)以及退格(08H)、回车(0DH)和换行(0AH)字符时,该功能调用可以自动识别并能进行相应处理

字符串输出的功能调用

DOS功能调用INT 21H

  • 功能号:AH09H
  • 入口参数:
    DX=欲显示字符串在主存中的首地址
    字符串应以$(24H)结束
  • 功能:在显示器输出指定的字符串
    可以输出回车(0DH)和换行(0AH)字符产生回车和换行的作用

字符输入的功能调用

DOS功能调用INT 21H

  • 功能号:AH01H
  • 出口参数:AL=字符的ASCII码
  • 功能:获得按键的ASCII代码值
    调用此功能时,若无键按下,则会一直等待,直到按键后才读取该键值

:判断按键

getkey:	mov ah,01h		;功能号:ah←01h
		int 21h			;功能调用
		cmp al,’Y’		;处理出口参数al
		je yeskey		;是“Y”
		cmp al,’N’
		je nokey		;是“N”
		jne getkey
	...
yeskey:	...
nokey:	...

字符串输入的功能调用

DOS功能调用INT 21H

  • 功能号:AH0AH
  • 入口参数:DS:DX=缓冲区首地址(关键就是要定义好缓冲区)
  • 执行该功能调用时,用户按键,最后用回车确认
    本调用可执行全部标准键盘编辑命令;用户按回车键结束输入,如按Ctrl+Break或Ctrl+C则中止

缓冲区的定义

  • 第1字节事先填入最多欲接收的字符个数(包括回车字符,可以是1~255)
  • 第2字节将存放实际输入的字符个数(不包括回车符)
  • 第3字节开始将存放输入的字符串(回车符也会放入缓冲区)
  • 实际输入的字符数多于定义数时,多出的字符丢掉,且响铃
  • 扩展ASCII码(如功能键等)占两个字节,第1个为0

:输入字符串

buffer	db 81 			;定义缓冲区
						;第1个字节填入可能输入的最大字符数
		db 0			;存放实际输入的字符数
		db 81 dup(0)	;存放输入的字符串
		...
	
mov dx,offset buffer
mov ah,0ah
int 21h

DOS 文件操作

建立文件

  • 调用号: 3CH
  • 参数: DX=文件要存放的路径的字符串的地址,该字符串结束用0表示
    CX=文件属性 0 :一般 1:只读 2:隐蔽 4:系统
  • 返回: 成功:AX=文件代号,错误:AX=错误码

打开文件

打开文件了之后一定要关闭

  • 调用号: 3DH
  • 参数: DX=文件路径的字符串的地址,该字符串结束用0表示
    AL:为打开方式 =0 读 =1 写 =3 读/写
  • 返回: 成功:AX=文件代号
    错误:AX=错误码

关闭文件

  • 调用号: 3EH
  • 参数: BX=文件代号
  • 返回: 错误:AX=错误码

读取文件

  • 调用号: 3FH
  • 参数: BX=文件代号
    DX=数据缓冲区地址
    CX=读取的字节数
  • 返回:读成功: AX=实际读入的字节数
    AX=0 已到文件尾
    读出错:AX=错误码

写文件

  • 调用号: 40H
  • 参数: BX=文件代号
    DX=数据缓冲区地址
    CX=写入的字节数
  • 返回:读成功: AX=实际写入的字节数
    读出错:AX=错误码

练习:文件写入 + 字符串加密解密

这个练习部分主要是记录两个作业题

  1. 要求是将用户输入的内容写入文本文件data.txt中
.model tiny
.code
.startup
    mov dx, offset hello_str
    call print_str

    mov ah, 0ah ; 读取输入
    mov dx, offset buffer
    int 21h

    mov ah, 41h ; 删除文件
    mov dx, offset file_path
    int 21h

    mov ah, 3ch ; 创建文件
    mov dx, offset file_path
    mov cx, 00h
    int 21h

    mov bx, ax ; 写入文件
    mov ah, 40h 
    mov cx, 00h
    mov cl, byte ptr [buffer+1] ; 注意这里前后位数要一致
    mov dx, offset buffer+2
    int 21h
 
    mov ah, 3eh ; 关闭文件
    int 21h

    mov dx, offset end_str
    call print_str

    .exit 0

    print_str proc
        mov ah, 09h  
        int 21h
        ret
    print_str endp

    buffer_size equ 101 ; 最多支持输入100个字符
    buffer  db buffer_size ;定义缓冲区
            db 0 ; 存放实际输入的字符数
            db buffer_size dup(?) ; 存放输入的字符串

    file_path db 'data.txt', 0
    hello_str db 'Please input message(no more than 100 characters):', 0dh, 0ah, '$'
    end_str db 'message saved successfully!', 0dh, 0ah, '$'
end
  1. 进行字符串的加密和解密

我采用的加密方法是先将输入的字符串进行倒序,然后奇数字节和偶数字节分别于不同的8位密钥进行异或操作,最后再将每个字节的前4位和后4位交换位置

最后程序的效果如下:
汇编语言(七):控制转移类指令(转移指令、循环指令、子程序指令、中断指令)、最后一次作业_第1张图片
首先会输出一行菜单,之后输入1表示进行加密,输入2表示进行解密,输入3表示退出程序。
如果选择加密模式,则要在之后输入不超过100个字符的字符串进行加密,输入完毕之后程序会输出加密完的字符串,同时在当前目录下生成一个encrypt.txt文件
如果选择解密模式,则要先确保当前目录下有encrypt.txt文件,之后输入需要解密的字节数,比如上例中要解密的字节数就为14,之后程序会根据encrypt.txt文件和字节数,将解密结果输出到屏幕上

总的来说思路挺简单,但是刚开始写汇编,各种错误还是不少,真正完成这个程序还是花了点时间的

.model tiny
.code
.startup
again:
    mov dx, offset hello_str
    call print_str

    call read_input
    
    mov al, byte ptr [buffer+2]
    cmp al, 31h
    jz encryption ; 加密
    cmp al, 32h
    jz decryption ; 解密
    cmp al, 33h
    jz over ; 结束程序

    mov dx, offset err_str ; 输入错误
    call print_str
    jmp again

    encryption:
        mov dx, offset encryption_str 
        call print_str
        call read_input

        xor ax, ax
        mov al, byte ptr [buffer+1] ; 保存字符串长度
        mov word ptr [str_len], ax

        ; 将字符串逆序存放到output中
        call reverse_str

        ; 异或 奇数byte与偶数byte分别与不同的key进行异或
        ; 每个字节异或之后,前后半个字节交换位置
        mov cx, word ptr [str_len]
        xor_encryption:
            mov bx, offset output-1 ; bx中保存要处理的字节地址
            add bx, cx
            mov ax, cx
            mov dl, 02h
            div dl
            cmp ah, 0 
            jnz odd_xor ; 奇数字节跳转
            mov dl, byte ptr [even_key]
            xor dl, byte ptr [bx]
            mov byte ptr [bx], dl
            jmp exchange
            odd_xor:
                mov dl, byte ptr [odd_key]
                xor dl, byte ptr [bx]
                mov byte ptr [bx], dl
            exchange:
                ; 将前后半字节交换位置
                mov dl, cl
                mov cl, 4
                ror byte ptr [bx], cl
                mov cl, dl ; 注意这里cx还要用做循环,值一定要还原回来
            loop xor_encryption

        jmp create_encryption_file

    decryption:
        mov dx, offset decryption_str 
        call print_str
        call read_input ; 读入需要解密几个字节

        cmp byte ptr [buffer+3], 0dh 
        jnz two_bits    ; 输入2位数则跳转
        xor cx, cx
        mov cl, byte ptr [buffer+2] ; cl中存需要解密几个字节
        sub cl, 30h
        jmp save_str_len

        two_bits:
            xor cx, cx
            mov cl, byte ptr [buffer+2] ; 十位
            sub cl, 30h
            mov al, cl
            mov dh, 10
            mul dh
            mov cl, byte ptr [buffer+3] ; 个位
            sub cl, 30h
            add cl, al
        save_str_len:
            mov word ptr str_len, cx ; 保存字符串长度

        mov ah, 3dh ; 打开文件
        mov dx, offset encryption_file
        mov al, 00h
        int 21h

        ; 读取文件
        mov bx, ax
        mov ah, 3fh 
        mov dx, offset buffer+2
        int 21h

        mov byte ptr [buffer+1], al 
    
        mov ah, 3eh ; 关闭文件
        int 21h

        ; 前后半个字节交换位置
        ; 然后 异或 奇数byte与偶数byte分别与不同的key进行异或
        mov cx, word ptr [str_len]
        xor_decryption:
            mov bx, offset buffer+1 ; bx中保存要处理的字节地址
            add bx, cx

            ; 将前后半字节交换位置
            mov dl, cl
            mov cl, 4
            ror byte ptr [bx], cl
            mov cl, dl ; 注意这里cx还要用做循环,值一定要还原回来

            ; 异或
            mov ax, cx
            mov dl, 02h
            div dl
            cmp ah, 0 
            jnz odd_dexor ; 奇数字节跳转
            mov dl, byte ptr [even_key]
            xor dl, byte ptr [bx]
            mov byte ptr [bx], dl
            loop xor_decryption
            odd_dexor:
                mov dl, byte ptr [odd_key]
                xor dl, byte ptr [bx]
                mov byte ptr [bx], dl
                loop xor_decryption
        
        ; 将字符串逆序存放到output中
        call reverse_str

        ; 显示解密后的字符串
        mov dx, offset over_str
        call print_str
        mov dx, offset output
        call print_str

        jmp again

    create_encryption_file:
        mov ah, 41h ; 删除文件
        mov dx, offset encryption_file
        int 21h

        mov ah, 3ch ; 创建文件
        mov dx, offset encryption_file
        mov cx, 00h
        int 21h

        mov bx, ax ; 写入文件
        mov ah, 40h 
        mov cx, word ptr [str_len] ; 注意这里前后位数要一致
        mov dx, offset output
        int 21h
    
        mov ah, 3eh ; 关闭文件
        int 21h

        ; 显示加密后的字符串
        mov dx, offset over_str
        call print_str
        mov si, offset output
        add si, word ptr [str_len]
        mov byte ptr [si], 24h ; 注意: 如果要显示的话,最后还要加上美元符
        mov dx, offset output
        call print_str

        jmp again

    over:
        .exit 0

    read_input proc
        mov ah, 0ah ; 读取输入
        mov dx, offset buffer
        int 21h
        ret
    read_input endp

    print_str proc
        mov ah, 09h  
        int 21h
        ret
    print_str endp

    reverse_str proc
        mov cx, word ptr [str_len]
        mov si, offset buffer+1
        add si, word ptr [str_len] ; si保存buffer中最后一个字符的地址
        mov di, offset output ; di保存output的首地址
        reverse:
            mov al, byte ptr [si]
            mov byte ptr [di], al
            dec si
            inc di
            loop reverse
        ret
    reverse_str endp

    buffer_size equ 101 ; 最多支持输入100个字符
    buffer  db buffer_size ;定义缓冲区
            db 0 ; 存放实际输入的字符数
            db buffer_size dup(?) ; 存放输入的字符串
    output  db buffer_size dup(?)

    str_len     dw ?
    odd_key     db 2ch
    even_key    db 44h

    encryption_file db 'encrypt.txt', 0
    decryption_file db 'decrypt.txt', 0
    hello_str   db  0dh, 0ah, 0dh, 0ah, 'Input 1--encryption 2--decryption 3--exit', 0dh, 0ah, '$'
    encryption_str db 0dh, 0ah, 'Please input data to encrypt(no more than 100 characters):', 0dh, 0ah, '$'
    decryption_str db 0dh, 0ah, 'Please input string length(no more than 100 characters):', 0dh, 0ah, '$'
    end_str db 0dh, 0ah, 'message saved successfully!', 0dh, 0ah, '$'
    err_str db 0dh, 0ah, 'Input err!', 0dh, 0ah, '$'
    over_str db 0dh, 0ah, 'Encrypted / Decrepted string:', 0dh, 0ah, '$'
end

你可能感兴趣的:(汇编语言)