作为汇编语言的课程笔记,方便之后的复习与查阅
本篇为课程第八次课内容
重点掌握:
JMP
/Jcc
/LOOP
CALL
/RET
INT n
常用系统功能调用一般了解:
LOOPZ
/LOOPNZ
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
/JE
和JNZ
/JNE
:利用零标志ZF
,判断结果是否为零(或相等)JS
和JNS
:利用符号标志SF
,判断结果是正是负JO
和JNO
:利用溢出标志OF
,判断结果是否产生溢出JP
/JPE
和JNP
/JPO
:利用奇偶标志PF
,判断结果中“1”的个数是偶是奇JC
/JB
/JNAE
和JNC
/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的个数
CF
确定高低、利用ZF
标志确定相等(Equal)两数的高低分成4种关系:
JB
(JNAE
):CF
=1JNB
(JAE
): CF
=0JBE
(JNA
): CF
=1或ZF
=1JNBE
(JA
):CF
=0且 ZF
=0例:AX
,BX
中为无符号数,编程将较大者存于AX
中,较小者存于BX
中
cmp ax,bx ;比较ax和bx
jnb next ;若ax≥bx,转移
xchg ax,bx ;若ax<bx,交换
next: ...
OF
、SF
标志,并利用ZF
标志确定相等(Equal)两数的大小分成4种关系:
JL
(JNGE
):(SF
≠OF
)JNL
(JGE
):(SF
=OF
)JLE
(JNG
):(SF
≠OF
或 ZF
=1)JNLE
(JG
):(SF
=OF
且ZF
=0)例:设AX
,BX
中为有符号数,编程将较大者存于AX
中,较小者存于BX
中
cmp ax,bx ;比较ax和bx
jnl next ;若ax≥bx,转移
xchg ax,bx ;若ax<bx,交换
next: ...
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
SP
←SP
-2,SS
:[SP
]←IP
RET
指令需要弹出CALL
指令压入堆栈的返回地址:
IP
IP
←SS
:[SP
], SP
←SP
+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提供给用户的用于调用系统功能的中断,它有近百个功能供用户选择使用,主要包括设备管理、目录管理和文件管理三个方面的功能功能调用的步骤:
AH
寄存器中设置系统功能调用号入口参数
INT 21H
(或ROM-BIOS的中断向量号)实现中断服务程序的功能调用出口参数
分析功能调用执行情况DOS功能调用INT 21H
AH
=02H
DL
=字符的ASCII码例:在当前显示器光标位置显示一个问号
mov ah,02h
mov dl,'?'
int 21h
进行字符输出时,当输出响铃字符(07H)以及退格(08H)、回车(0DH)和换行(0AH)字符时,该功能调用可以自动识别并能进行相应处理
DOS功能调用INT 21H
AH
=09H
DX
=欲显示字符串在主存中的首地址$
(24H)结束DOS功能调用INT 21H
AH
=01H
AL
=字符的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
AH
=0AH
DS:DX
=缓冲区首地址(关键就是要定义好缓冲区)缓冲区的定义:
例:输入字符串
buffer db 81 ;定义缓冲区
;第1个字节填入可能输入的最大字符数
db 0 ;存放实际输入的字符数
db 81 dup(0) ;存放输入的字符串
...
mov dx,offset buffer
mov ah,0ah
int 21h
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
=错误码这个练习部分主要是记录两个作业题
.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
我采用的加密方法是先将输入的字符串进行倒序,然后奇数字节和偶数字节分别于不同的8位密钥进行异或操作,最后再将每个字节的前4位和后4位交换位置
最后程序的效果如下:
首先会输出一行菜单,之后输入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