一些汇编练习
一些汇编练习,代码可能有有些bug,不是太完美,更多请见
https://lartpang.github.io/myasm/
习题4:
- 从屏幕上输入大写字母,转换为小写字母并输出(生成.com文件)
要求:程序具有可读性、容错性
思路:
由于要生成.com文件,所以需要将文件代码设置到一个代码段里。本问题涉及到读入与显示,这里可以使用21h中断的1,2,9号子功能。
读入字符后,将al中存放的字符通过判断其是否为大写字母来保证程序的正确性。利用大小写ascii码上的对应关系,实现了大小写的转化。
代码如下:
- code
- cs:code, ds:code, ss:code
- org 0100h
- main proc near
- again:
- lea dx, str_dis
- mov ah, 9
- int 21h ;显示初始字符串,提示输入
- mov ah, 1
- int 21h ;获得输入字符,存到al里
- cmp al, ‘A‘ ;判断输入字符是否是A~Z的字符
- jb error
- cmp al, ‘Z‘
- ja error ;保证输入合法
- add al, 20h ;大写+20h=小写
- mov str_num, ax ;将ax保存
- lea dx, str_res
- mov ah, 9
- int 21h ;显示结果提示字符串
- mov ax, str_num ;将ax恢复
- mov dl, al
- mov ah, 2
- int 21h ;显示最终结果
- mov ax, 4c00h
- int 21h
- error:
- lea dx, str_err
- mov ah, 9
- int 21h
- jmp again
- main endp
- str_num dw 0000h
- str_dis db 0dh, 0ah, ‘Please input(A~Z):$‘
- str_res db 0dh, 0ah, ‘The result is:$‘
- str_err db 0dh, 0ah, ‘The data of input is invalid!$‘
- code ends
- end main
-
- 编写一子程序 asc2bin ,将 ASCII 转换为二进制数
要求:
输入参数:AL 中存放需要转换的 ASCII
输出参数:AL 中存放转换后的二进制数并返回
思路:
对于ascii码,先都当做数字来处理,即都减30h,在与9比大小,大于9的说明是10~15,再减7即可。
代码如下:
- asc2bin proc
- sub al, 30h ;先都当做表示数字的acsii
- cmp al, 9 ;与9比较,小于等于9的,直接跳转,大
- ;于9的,仍然需要处理
- jbe asc2bin_sub ;跳转子程序
- sub al, 7 ;继续减7
- asc2bin_sub:
- ret ;返回主程序
- asc2bin endp
- 内存中存放8个16位有符号数,求8个数值之和,并将结果存放在内存变量SUM中
注:程序中应用到字扩展为双字的指令CWD
思路:
通过使用循环加法,扩展双字的指令,转化为双字的计算加和。
代码如下:
- data
- buf dw -1, 2, -33, 44, -555, 666, -7777, 8888
- sum dd 0
- data ends
- ss_seg
- dw 100 dup(0)
- ss_seg ends
- code
- cs:code, ds:data, ss:ss_seg
- main proc far
- mov ax, data
- mov ds, ax ;指定数据位置
- lea bx, buf ;bx指向buf首地址
- mov cx, 8 ;指定循环次数
- w2d:
- mov ax, [bx]
- cwd ;有符号数字扩展为双字,默认使用{dx,ax}
- add word ptr sum, ax ;32 位数相加
- adc word ptr sum + 2, dx
- inc bx
- inc bx
- loop w2d ;循环 8 次
- mov ax, 4c00h
- int 21h
- main endp
- code ends
- end main
- 内存中存放 8 个 8 位有符号数,请按从大到小顺序排列
思路:
利用冒泡排序,设置两个循环,将相邻的数字进行比较,大的放在低位地址,小的放在高位地址。
对于循环的次数的设置这里有些需要注意的地方。一般而言,n个数的问题,外层循环需要n-1次,而内层循环也是需要n-1次比较,所以可以设置使用相同的循环计数器。只需要内层循环计数的时候将之前的压栈保存即可。
代码如下:
- ;低地址到高地址,大数到小数。
- data
- buf db -1, 2, -33, 44, -55, 66, -77, 88
- data ends
- stack
- dw 100 dup(0)
- stack ends
- code
- cs:code, ds:data, ss:stack
- main proc far
- ;将ds:bx指向存放数据的位置
- mov ax, data
- mov ds, ax
- mov cx, 7 ;主循环仅需要7次
- loop_main:
- lea bx, buf
- push cx ;cx=n, 子循环正好也要循环n次
- ;压栈保存外循环循环次数
- mov si, 0 ;标志是否有交换,当后面检测si==0时,说明
- ;内部已经排序完毕
- loop_sub:
- ;取得第一个数据到ax里,将之与第二个数据比较大小
- ;ax大等于[bx]的话,保留原样
- ;ax小的话,就得将两个数据进行交换位置
- mov al, [bx]
- cmp al, [bx+1]
- jge not_xchg ;>=不交换
- ;ax小,进行交换
- xchg al, [bx+1]
- mov [bx], al
- mov si, 1 ;标志有交换
- not_xchg:
- ;第一步交换完成,需要依次进行一遍
- inc bx
- loop loop_sub
- ;完毕后,只完成了初步的重排,这时数据段中的情况是,高地址存放最小的数
- ;还需要继续执行,之后每次都减少一次
- cmp si, 0
- je done_xchg
- ; 仍有交换,还需重复
- pop cx ;出栈,恢复cx
- loop loop_main
- done_xchg:
- mov ax, 4c00h
- int 21h
- main endp
- code ends
- end main
- 内存中有8个16位数,请编写程序将8个数倒序排放
例:定义内存中8个数 buf dw 100, 3, 1, 20, 40, -2, 7, 10
程序运行后,buf 开始应为: buf dw 10, 7, -2, 40, 20, 1, 3, 100
思路:
要实现倒序存放,利用栈的入栈出栈,或者反复循环交换。但是使用栈,更为便捷。
代码如下:
- data
- buf dw -1, 2, -33, 44, -555, 666, -7777, 8888
- data ends
- stack
- dw 100 dup(0)
- stack ends
- code
- cs:code, ds:data, ss:stack
- main proc far
- mov ax, data
- mov ds, ax
- lea bx, buf
- mov cx, 8
- stack_push:
- push [bx]
- add bx, 2
- loop stack_push
- lea bx, buf
- mov cx, 8
- stack_pop:
- pop [bx]
- add bx, 2
- loop stack_pop
- mov ax, 4c00h
- int 21h
- main endp
- code ends
- end main
- 从键盘输入 4 位十进制数,然后以 16 进制形式显示在屏幕上
例:键盘输入:1024 屏幕上应显示:0400H
要求:键盘输入和显示结果时均应有提示
思路:
输入四位十进制数,将ascii码利用中断功能,读入(此处会对输入字符做出合法性判断),转换为十进制数并以二进制的形式存在内存里,最后,转换为十六进制,并拆分各个数字,显示ascii码输出。具体流程详见注释。
代码如下:
- ;具体流程:ASCII码输入->十进制->十六进制->ASCII码显示
- ;故需要子函数完成进制转换、ascii转换
- data
- buf db 4 dup(0)
- var_10 dw 0
- str_input db 0dh, 0ah, ‘Please input four numbers(0-9):$‘
- str_error db 0dh, 0ah, ‘The input is error, please try again.$‘
- str_output db 0dh, 0ah, ‘The hex result is:$‘
- data ends
- stack
- dw 100 dup(0)
- stack ends
- code
- cs:code, ds:data, ss:stack
- main proc far
- mov ax, data
- mov ds, ax
- loop_again: ;输入有错误需要重新读取输入
- lea dx, str_input ;显示数据输入提示信息
- mov ah, 9
- int 21h
- lea bx, buf ;用bx来作为buf的指代
- mov cx, 4 ;循环输入4个数
- loop_input:
- mov ah, 1 ;输入数据
- int 21h
- cmp al, ‘0‘ ;判断输入字符是否为‘0’~‘9’
- jb error
- cmp al, ‘9‘
- ja error ;非法字符,进行错误处理
- ;直接输入一个字符,处理一个字符
- sub al, 30h ;ASCII转换为二进制,得到 0-9
- mov [bx], al ;存入缓冲区
- inc bx
- loop loop_input
- jmp input_valid ;数据输入正确后 ,跳转到后续处理
- error:
- lea dx, str_error ;显示错误提示信息
- mov ah, 9
- int 21h
- jmp loop_again ;跳转到重新输入
- input_valid: ;现在数据以二进制的形式存储
- mov ax, 0 ;以(((0*10+dl3)*10+dl2)*10+dl1)*10+dl0计算
- ;十进制数据结果存放到ax中
- mov dx, 0 ;因为ax可以满足存放四位十进制数的需求
- mov si, 10
- mov bx, 0
- mov cx, 4
- loop_mul10:
- mul si ;相乘后dx仍然保持 0
- mov dl, [bx] ;将buf数据取到dl中
- mov dh, 0
- add ax, dx
- inc bx
- loop loop_mul10 ;循环4次乘10
- mov var_10, ax ;得到的 4 位十进制数存放到 var_10 中
- ;var_10共16位,存放十进制数对应的二进制
- lea dx, str_output
- mov ah, 9
- int 21h ;显示输出提示符
- mov ch, 4 ;以16进制显示输入的数据,循环四次
- mov cl, 4
- loop_display:
- rol var_10, cl ;循环左移4位,因为要先显示高位
- mov ax, var_10
- and ax, 000fh ;仅保留最低四位
- call bin2asc ;二进制转换为 ASCII
- call pchar ;显示一个十六进制字符
- dec ch
- jnz loop_display
- mov al, ‘H‘ ;显示十六进制‘H’记号
- call pchar
- mov ax, 4c00h
- int 21h
- main endp
-
- ;功能: 将一个二进制数字转换为ASCII
- ;输入参数 : AL中存放二进制数(有效位只有低四位)
- ;输出参数 : AL中存放ASCII
- bin2asc proc
- and al, 0fh
- add al, 30h
- cmp al, 39h
- jbe done_b2a
- add al, 07h ;是A~Z
- done_b2a:
- ret ;返回
- bin2asc endp
-
- ;功能:显示单个字符
- ;输入参数:AL中存放ASCII
- ;输出参数:无
- pchar proc
- mov dl, al
- mov ah, 2
- int 21h
- ret ;返回
- pchar endp
- code ends
- end main
- 数据段从100H开始存放字符串str1,从200H开始存放str2,二者均以NULL字符为结束符,编写程序将str2拷贝到str1末尾,形成一个完整字符串
例:
ORG 100H
str1 db 0dh, 0ah, ‘Hello ’, 0
ORG 200H
str2 db ‘Automation!’, 0
程序运行后结果应为:
str1 db 0dh, 0ah, ‘Hello Automation!’, 0
思路:
利用repnz scasb
指令,将扫描的字符与al中预存的0比较,即查找str_first的结束位置,找到后,利用mov cx, count
& rep movsb
两条命令,以及count equ ($-str_second)
,将str_secon复制到str_first之后。
最后再利用lodsb,配合中断功能,显示最终的合并字符串。
代码如下:
- data
- ORG 100H
- str_first db 0dh, 0ah, ‘Hello ‘, 0
- ORG 200H
- str_second db ‘Automation! ‘, 0
- count equ ($-str_second)
- data ends
- ss_seg
- dw 100 dup(0)
- ss_seg ends
- code
- cs:code, ds:data, ss:ss_seg
- main proc far
- mov ax, data
- mov ds, ax
- mov es, ax
- lea di, str_first ;es:di 指向str_first首地址
- mov al, 0
- repnz scasb ;查找到str_first结束符NULL,最后di会多加一次
- dec di ;让es:di指向该位置
- lea si, str_second ;ds:si指向str_second首地址
- cld ;DF置零,右移
- mov cx, count
- rep movsb ;重复搬移
- lea si, str_first ;ds:si 指向拷贝后的 str_first 首地址
- display:
- lodsb ;显示拷贝后的 str_first 字符串
- cmp al, 0
- jz exit ;显示完毕
- call pchar
- jmp display
- exit:
- mov ax, 4c00h
- int 21h
- main endp
-
- ;功能:显示单个字符
- ;输入参数:AL中存放ASCII
- ;输出参数:无
- pchar proc
- mov dl, al
- mov ah, 2
- int 21h
- ret
- pchar endp
- code ends
- end main
- 以 10 进制形式显示 内存中一有符号字节数据
例:var db 0ffH
屏幕应显示:The result is: -1
代码如下:
思路:
主要是二进制转化为十进制后拆分成单个数字,在将之处理为ascii码。
首先需要与0比较,利用jg/jl等有符号数比较指令实现跳转处理。分别将第一个字符设置为对应的符号。
再将实际数值部分进行处理,转换为ascii码,进行显示。
代码如下:
- ;思路:二进制->十进制->ascii码
- data
- var_signed db 0ffH
- str_result db 0dh, 0ah, ‘The result is: ‘
- num_signed db 4 dup(‘ ‘);因为有符号字节数,最多就是三位十进制
- db ‘$‘
- data ends
- stack
- dw 100 dup(0)
- stack ends
- code
- cs:code, ds:data, ss:stack
- main proc far
- mov ax, data
- mov ds, ax
- mov num_signed, ‘+‘
- cmp var_signed, 0 ;判断 var_signed 是正数 ,还是负数
- jge is_pos ;>= 为正数
- mov num_signed, ‘-‘ ;< 为负数
- neg var_signed ;若 var_signed 为负,则补码的相反数
- is_pos:
- mov al, var_signed
- mov cx, 3
- mov dl, 10 ;进制
- lea bx, num_signed+3;倒着放数据
- again:
- mov ah, 0 ;高八位置零
- div dl
- add ah, 30h ;余数 ah
- mov [bx], ah
- dec bx
- loop again ;循环 3 次,分别得到百、十、个位
- done:
- lea dx, str_result ;显示 10 进制数
- mov ah, 9
- int 21h
- exit:
- mov ax, 4c00h
- int 21h
- main endp
- code ends
- end main
- 将一个16位的无符号数var,转换为非压缩格式BCD码,存放在内存中buf开始的单元中。(按高位在 前、低位在后的顺序存放)
思路:
利用二进制转换十进制:((0*2 + B15)*2 + B14)*2 + ? + )*2 + B0
在运算过程中利用非压缩格式bcd码调整指令aaa,处理为非压缩格式bcd码,使得最终结果即为非压缩格式bcd码。
代码如下:
- ;二进制数 0FFFH,十进制 4095,非压缩BCD 4 0 9 5 各一个字节的低四位
- ;二进制转换十进制:((0*2 + B15)*2 + B14)*2 + ? + )*2 + B0
- data
- buf db 4 dup(0)
- var dw 0FFFH
- data ends
- stack
- dw 100 dup(0)
- stack ends
- code
- cs:code, ds:data, ss:stack
- main proc far
- mov ax, data
- mov ds, ax
- mov cx, 16 ;要进行十六次移位,利用adc获得
- loop_wai:
- shl var, 1 ;得到 var 的 Bi 位
- mov bx, 3
- push cx
- mov cx, 4
- loop_nei:
- mov al, [bx] ;执行 buf*2 + Bi 操作
- adc al, al
- aaa ;非压缩格式 BCD 码调整
- mov [bx], al
- dec bx
- loop loop_nei ;内循环为 4 次
- pop cx
- loop loop_wai ;外循环为 16 次
- exit:
- mov ax, 4c00h
- int 21h
- main endp
- code ends
- end main
- 内存中有两个32位有符号数,请编写完整汇编语言程序,将二者相乘,结果保存在64位有符号数RESULT中
思路:
两个32位与符号数相乘,只要处理好符号位,就可以转化为无符号数相乘。
代码如下:
- data
- num_1 dd 80000002h
- num_2 dd 80005000h
- result dw 4 dup (?)
- data ends
- ss_seg
- dw 100 dup (0)
- ss_seg ends
- code
- cs:code, ds:data, ss:ss_seg
- main proc far
- mov ax, data
- mov ds, ax
- shl byte ptr [num_1+3], 1 ;处理符号位
- jc num_1_l0 ;num_1<0
- xor ax, ax
- shr byte ptr [num_1+3], 1 ;剥离符号位
- mov bh, 0h ;用bh存放符号位
- jmp next ;符号位被置零
- num_1_l0:;num_1<0
- mov bh, 1h ;用bh存放符号位
- next:
- shl byte ptr [num_2+3], 1 ;对num_2做相同处理
- jc num_2_l0
- xor ax, ax
- shr byte ptr [num_2+3], 1
- mov bl, 0h
- jmp continue
- num_2_l0:;num_2<0
- mov bl, 1h
- continue: ; 当做无符号数处理,计算相乘
- lea si, num_1
- lea di, num_2
- mov ax, [si]
- mov dx, [di]
- mul dx
- mov [result],ax
- mov [result+2],dx
- mov ax, [si+2]
- mov dx, [di]
- mul dx
- add [result+2],ax
- adc [result+4],dx
- mov ax, [si]
- mov dx, [di+2]
- mul dx
- add [result+2],ax
- adc [result+4],dx
- adc [result+6],0
- mov ax, [si+2]
- mov dx, [di+2]
- mul dx
- add [result+4],ax
- adc [result+6],dx
- sub bh, bl ;处理符号
- jnz bh_nz_bl ;num_1 num_2异号
- and [result+7], 01111111b ;同号就将结果最高位置0
- jmp exit
- bh_nz_bl:;不同号
- or [result+7], 10000000b ;异号就将结果最高位置1
- exit:
- mov ah, 4ch
- int 21h
- main endp
- code ends
- end main
- 将一个 8 位压缩 BCD 码转换为二进制数
思路:
对于四位压缩bcd码的过程比较简单,可以利用不断循环左移四位,将高位字节的两个bcd码置于字单元的最后位置,整体赋给寄存器,现在寄存器里存放的是一位压缩bcd码,乘以10再加上下一位如此获得的bcd码,反复如此,就可以获得四位的压缩bcd对应的十进制,存放到内存中,就变为了二进制。
而对于八位压缩bcd码,则可以拆成高四位对应的十进制,乘以10000,加上低四位对应的十进制数。这里就是这样处理的。
高四位低四位分别求出了对应的十进制,按上述规则相加,得到了最终结果。
代码如下:
- data
- BCD_num dd 12345678h
- bin_num db 4 dup(0)
- loop_num db 2
- data ends
- ss_seg
- dw 100 dup(0)
- ss_seg ends
- code
- cs:code, ds:data, ss:ss_seg
- main proc far
- mov ax, data
- mov ds, ax
- mov si, 10
- mov ax, 0
- mov dl, 2 ;分两段
- mov di, 2
- again_loop:
- mov cx, 0404h ;ch 乘法循环计数器; cl 移位计数器
- again_mul:
- mul si
- rol word ptr [BCD_num+di], cl
- mov bx, word ptr [BCD_num+di]
- and bx, 000fh
- add ax, bx
- dec ch
- jnz again_mul
- mov word ptr [bin_num+di], ax
- xor di, di
- xor ax, ax
- sub [loop_num], 1
- jnz again_loop
- ; 高四位对应的十进制乘以10,加上低四位对应的十进制
- mov ax, word ptr [bin_num+2]
- mov bx, 10000
- mul bx
- add ax, word ptr [bin_num]
- adc dx, 0
- mov word ptr [bin_num], ax
- mov word ptr [bin_num+2], dx
- mov ax, 4c00h
- int 21h
- main endp
- code ends
- end main
选作题:
- 内存中从str开始存放一字符串,结束符为NULL字符,请编写程序统计该字符串中单词的个数
例:str1 db 0dh, 0ah, ‘Hello world, welcome to DUT. CPU is central
processing unit!’, 0h
统计 ’….’ 中的单词个数,结果为10
思路:
如题所述,主要统计空格。通过扫描字符串,统计空格,但是有效的空格前一个字符是有效的字符,只有这样的空格才会统计。
代码如下:
- data
- jj string db 0dh, 0ah, ‘Hello world, welcome to DUT. CPU is central ‘
- db ‘processing unit!‘, 0h
- words dw 0
- data ends
- stack
- db 256 dup(0)
- stack ends
- code
- cs:code, ds:data, ss:stack
- main proc far
- mov ax, data
- mov ds, ax
- mov cx, 0 ;用 cx 存放单词数
- lea si, string
- mov bl, ‘ ‘ ;bl 总保存当前字符的前一个字符
- cld ;保证右移
- load_al:
- lodsb ;从ds:si中以字节为单位获取数据,al只判断0以及空格
- and al, al ;判断 al 是否为结束符 0,al=0 zf=1,al!=0,zf=0
- jz al_0
- cmp al, ‘ ‘ ;比较是否是空格,只对空格计数
- jnz bl_new
- al_douhao: ;处理重复计数:计数只是根据空格的个数来计算。
- ;重复计数主要是因为多个空格以及逗号空格连续所致。
- cmp bl, ‘ ‘ ;比较前一个字符是否为空格,如果是则此空格
- ;不能算一个单词,即为了防止多个空格的情况
- jz bl_new
- inc cx ;只有当前字符为 ‘ ‘或‘,‘ 而且前一个字符为有
- ;效字符时,才对单词数加 1
- jmp bl_new
- bl_new:
- mov bl, al ;进入这里表明此时 al 中内容不是 0 或者 ‘ ‘
- ;符号 ,保存 al 到 bl
- jmp load_al
- al_0: ;判断结束符前面是否有‘ ‘,删掉多余的计数
- cmp bl, ‘ ‘
- jz done
- inc cx ;若结束符前是一个有效字符,那么单词数应该加 1
- done:
- mov words, cx
- mov ax, 4c00h
- int 21h
- main endp
- code ends
- end main