西电计组II 实验1
文章目录
8086汇编 IO操作
环境搭建
win32平台上仍然保留了MS-DOS的解释器,可以模拟16位环境(NTVDM.EXE),但是win64就白搭了
机房里的电脑是win7 32位操作系统,可以在cmd终端上直接运行ms-dos应用程序
但是我自己的win11 64位,尝试终端运行ms-dos程序会报告不兼容.
但是在win64上也有方法,要么是用星研电子IDE(实际上使用的是TASM编译器+dosbox模拟16位dos环境)
要么是建立一个虚拟硬盘,在其前512字节的主引导扇区中写入编译好的机器码,然后用bochs或者vmware等虚拟机软件运行
…方法有的是…
在我的电脑上,采用vscode编写汇编代码+星研IDE编译链接运行
8086汇编 helloworld
要了解8086汇编语言的基本结构,最快的办法是学习如何打印helloworld
assume cs:code,ds:data,ss:mystack
data segment
buffer db 'Hello World!',10,13,'$'
data ends
mystack segment stack
db 32 dup(?)
mystack ends
code segment
start:
mov ax,data
mov ds,ax
lea dx,buffer
mov ah,9
int 21h
mov ah,4ch
int 21h
code ends
end start
在星研IDE中编译连接运行会在屏幕打印helloworld
下面逐条语句分析程序作用
assume
assume cs:code,ds:data,ss:mystack
assume是tasm的伪指令,不会产生任何机器码,其作用是,将段寄存器和我们定义的段关联,以后在ds段中寻址的时候,就是在data中寻址
这既是给程序员看的,也是给tasm编译器看的,但不是给机器看的
即使在这里将ds和data段关联,我们在代码段的一开始还是要lea ax,data然后mov ds,ax,这样设置代码段的指向.只有这样才会生成相关机器代码,机器才会知道ds段在哪
给tasm编译器看,也就是说当使用mov [di],ax这种没有显示指明内存地址的段寄存器时,tasm知道我们实际上在使用data段作为ds段寄存器,然后ds:[di]这样寻址
segment
定义段
tasm要求一个程序必须要有代码段,数据段,堆栈段,即使用不到,不写,会给警告,不给通过编译
定义一个段的写法:
data segment
...
data ends
mystack segment stack;这里后面的stack是段属性,stack显示指出是一个堆栈段
...
mystack ends
db
定义数据,这个数据是会出现在程序中的,要和"常数"区别
类比c语言,db定义的是变量,"常数"是宏
buffer db 'Hello World!',10,13,'$'
标号 db 数据1,数据2,…
其中db是定义字节的意思,意味着后面的数据1,数据2等都视为字节
如果是dw则后面的操作数视为字
编译链接
只需要在星研IDE中傻瓜一样点一下编译链接即可,同目录下加上asm共有7个同名文件
PS C:\Users\Administrator\Desktop\helloworld> ls | findstr "hw"
-a--- 2022/11/19 0:01 292 hw.asm
-a--- 2022/11/19 0:01 68 hw.dob
-a--- 2022/11/19 0:01 1221 hw.exe
-a--- 2022/11/19 0:01 1418 hw.lst
-a--- 2022/11/19 0:01 383 hw.map
-a--- 2022/11/19 0:01 414 hw.obj
-a--- 2022/11/19 0:01 222 hw.sym
其中asm自然是源文件
lst
lst也是写给程序员看的,列表列明的程序内容
包括行号,带机器码,带汇编指令
符号表,段信息
Turbo Assembler Version 4.1 11/19/22 00:01:35 Page 1
hw.asm
1
2 assume cs:code,ds:data,ss:mystack
3
4 0000 data segment
5 0000 48 65 6C 6C 6F 20 57+ buffer db 'Hello World!',10,13,'$'
6 6F 72 6C 64 21 0A 0D+
7 24
8 000F data ends
9 0000 mystack segment stack
10 0000 20*(??) db 32 dup(?)
11 0020 mystack ends
12
13 0000 code segment
14 0000 start:
15 0000 B8 0000s mov ax,data
16 0003 8E D8 mov ds,ax
17 0005 BA 0000r lea dx,buffer
18 0008 B4 09 mov ah,9
19 000A CD 21 int 21h
20
21 000C B4 4C mov ah,4ch
22 000E CD 21 int 21h
23 0010 code ends
24 end start
Turbo Assembler Version 4.1 11/19/22 00:01:35 Page 2
Symbol Table
Symbol Name Type Value
??DATE Text "11/19/22"
??FILENAME Text "hw "
??TIME Text "00:01:35"
??VERSION Number 040A
@CPU Text 0101H
@CURSEG Text CODE
@FILENAME Text HW
@WORDSIZE Text 2
BUFFER Byte DATA:0000
START Near CODE:0000
Groups & Segments Bit Size Align Combine Class
CODE 16 0010 Para none
DATA 16 000F Para none
MYSTACK 16 0020 Para Stack
map
map文件也是给人看的,包括段的映射信息,段在源文件中的行号,程序入口点的位置
Start Stop Length Name Class
00000H 0000EH 0000FH DATA
00010H 0002FH 00020H MYSTACK
00030H 0003FH 00010H CODE
Line numbers for hw.obj(HW.ASM) segment CODE
13 0003:0000 14 0003:0003 15 0003:0005 16 0003:0008
17 0003:000A 19 0003:000C 20 0003:000E
Program entry point at 0003:0000
obj
coff可重定位目标模块
早期操作系统的目标模块,如今windows上仍在使用obj作为目标模块,但是结构已经发生了变化
编译生成的产物,和其他模块一起链接生成exe
exe
coff可执行目标模块,如今windows上仍在使用exe作为可执行程序,但是结构已经发生了变化
链接的产物,可以在16位环境下直接执行
用010editor等二进制编辑器可以观察其结构
开头就是MZ魔数
sym
推测是符号表文件
dob
可能是dump object的缩写
整个程序翻译成的机器码,包括数据段,代码段,堆栈段
int 21H 软件中断
int 21h是dos系统提供的基本的io操作
调用约定是AH中存放入口参数,如果有其他参数,可能使用DL等寄存器,结果如果有返回值,则通常放到AL寄存器
本次实验要用到的功能具体如下
AH 值 | 功 能 | 调 用 参 数 | 结 果 |
---|---|---|---|
1 | 键盘输入并回显 | AL=输出字符 | |
2 | 显示单个字符(带Ctrl+Break检查) | DL=输出字符 | 光标在字符后面 |
6 | 显示单个字符(无Ctrl+Break检查) | DL=输出字符 | 光标在字符后面 |
8 | 从键盘上读一个字符 | AL=字符的ASCII码 | |
9 | 显示字符串 | DS:DX=串地址,‘$’为结束字符 | 光标跟在串后面 |
4CH | 返回DOS系统 | AL=返回码 |
程序设计
要求
要求是输入学号姓名,回车后回显学号姓名
然后输入一个字符,打印其ascii码值
全局变量
DSEG SEGMENT
helloworld DB 'Hello,World!' ,0DH,0AH,24H
next_row DB 0DH,0AH,24H;打印换行使用
buffer DB 0100H dup('$');输入缓冲区
STUID DB 'please input your id >','$';提示输入学生姓名时打印
STUNAME DB 'please input your name >','$'
cmdprompt DB 'please input an alpha to get its ascii code, q to quit >','$';提示输入字符时打印
DSEG ENDS
函数设计
putchar
功能 | 打印一个字符 |
---|---|
调用约定 | al输入该字符 |
使用入口为2H的int 21软中断打印到屏幕
;调用约定:需要打印的字符->al
putchar proc
push dx
mov ah,02h
mov dl,al
int 21h
pop dx
ret
putchar endp
getchar
功能 | 输入一个字符 |
---|---|
调用约定 | al返回该字符 |
;调用约定:输入字符用al返回
getchar proc near
mov ah,01h
mov al,0
int 21H
retn
getchar endp
功能 | 打印字符串 |
---|---|
调用约定 | AX存放字符串及地址,打印到碰到$ 字符结束 |
;调用约定:ax指定一个buffer的基地址,用于获取一个字符串
print proc near
push bx
push cx
push dx
mov dx,ax
mov ah,09H
mov al,00H
int 21H
pop dx
pop cx
pop bx
ret
print endp
newline
功能 | 打印换行 |
---|---|
调用约定 | 无参数无返回值 |
newline proc near
push ax
lea ax,next_row
call print
pop ax
ret
newline endp
input
功能 | 输入字符串 |
---|---|
调用约定 | AX存放缓冲区地址,输入直到回车结束 |
;AX存放目的地址,结束时CX返回输入字符个数
input proc near
push BX
; push cx
; push di
; push ax
; MOV CX, 0ffh
; mov DI, ax
; mov al,'$'
; REPZ STOSB
; pop ax
MOV DX,AX
MOV BX,DX
MOV byte ptr DS:[BX], 0ffh
; mov [DX],BL
mov ah,0ah ;将0ah放入ah
mov al,0
int 21h ;输入字符串功能调用
pop BX
ret
input endp
memset
功能 | 缓冲区初始化,即c语言的memset函数 |
---|---|
调用约定 | ax存放基地址,cx存放长度,bl存放初始化字符 |
;调用约定:ax存放基地址,cx存放长度,bl存放目标字符
memset proc near
push di
mov di,ax
memset_circle:
cmp cx,0
jz memset_fini
mov ds:[di],bl
inc di
dec cx
jmp memset_circle
memset_fini:
pop di
ret
memset endp
exit
功能 | 返回函数,将控制交给调用者 |
---|---|
调用约定 | 无参数无返回值 |
exit proc near
mov ah,4Ch
mov al,0
int 21h
exit endp
hex
功能 | 打印一个字节的16进制表示 |
---|---|
调用约定 | ax存放该字节 |
;16进制打印一个字节
hex PROC near
push ax
push dx
mov dx,ax
mov ch, 4
L1:
mov cl, 4
rol dx, cl
mov al,dl
and al,0FH
add al,30h
cmp al,3ah
jl printit
add al,7h
printit:
call putchar
dec ch
jnz L1
pop dx
pop ax
ret
hex ENDP
binary
功能 | 打印一个字节的2进制表示 |
---|---|
调用约定 | ax存放该字节 |
;二进制形式的输出,入口参数为ax
binary proc
push bx ;数据入栈,保留数值
push dx
mov dh,0 ;用于计数,总共8次
binaryagain:
cmp dh,8
je binaryover ;输出结束,到达函数末端
rol ax,1 ;移动一位
mov bx,ax ;数值保留
and ax,0001h ;剥离出最后一位
mov dl,al ;用于输出
add dl,48
mov ah,2
int 21h
inc dh ;计数加一
mov ax,bx ;数值恢复
jmp binaryagain ;再来一次
binaryover:
pop dx ;数据出栈,数值还原
pop bx
ret
binary endp
circle
功能 | 循环输入字符,打印ascii值 |
---|---|
调用约定 | 无参数无返回值 |
circle proc near
rolling:
lea ax,cmdprompt
call println
call getchar
cmp al,'q'
jz fini
mov ah,0
call newline
call hex
call newline
jmp rolling
fini:
retn
circle endp
程序入口
BEGIN:
MOV AX,DSEG;设置DS段寄存器,将DSEG作为数据段
MOV DS,AX
call newline;遇事不决,换个行先
lea AX,STUID
call print;提示输入ID
call newline
lea AX,buffer
mov cx,0ffh
mov bl,'$'
call memset;初始化缓冲区memset(&buffer,0xFF,'$');
lea AX,buffer;设置输入目标地址
call input;输入
call newline
lea ax,[buffer+2]
call print
call newline
lea AX,STUNAME
call print
call newline
lea AX,buffer
mov cx,0ffh
mov bl,'$'
call memset
lea AX,buffer
call input
call newline
lea ax,[buffer+2]
call print
call newline
call circle
call exit;归还控制
完整代码
DSEG SEGMENT
helloworld DB 'Hello,World!' ,0DH,0AH,24H
next_row DB 0DH,0AH,24H
buffer DB 0100H dup('$')
STUID DB 'please input your id >','$'
STUNAME DB 'please input your name >','$'
cmdprompt DB 'please input an alpha to get its ascii code, q to quit >','$'
DSEG ENDS
SSEG SEGMENT PARA STACK
DW 256 DUP(?)
SSEG ENDS
CSEG SEGMENT
ASSUME CS:CSEG,DS:DSEG
;调用约定:需要打印的字符->al
putchar proc
push dx
push ax
mov ah,02h
mov dl,al
int 21h
pop ax
pop dx
ret
putchar endp
;调用约定:输入字符用al返回
getchar proc near
mov ah,01h
mov al,0
int 21H
retn
getchar endp
;16进制打印一个字节,调用约定:
hex PROC near
push ax
push dx
mov dx,ax
mov ch, 4
L1:
mov cl, 4
rol dx, cl
mov al,dl
and al,0FH
add al,30h
cmp al,3ah
jl printit
add al,7h
printit:
call putchar
dec ch
jnz L1
pop dx
pop ax
ret
hex ENDP
;二进制形式的输出,入口参数为ax
binary proc
push bx ;数据入栈,保留数值
push dx
mov dh,0 ;用于计数,总共16次
binaryagain:
cmp dh,8
je binaryover ;输出结束,到达函数末端
rol ax,1 ;移动一位
mov bx,ax ;数值保留
and ax,0001h ;剥离出最后一位
mov dl,al ;用于输出
add dl,48
mov ah,2
int 21h
inc dh ;计数加一
mov ax,bx ;数值恢复
jmp binaryagain ;再来一次
binaryover:
pop dx ;数据出栈,数值还原
pop bx
ret
binary endp
;调用约定:ax指定一个buffer的基地址,用于获取一个字符串
print proc near
push bx
push cx
push dx
mov dx,ax
mov ah,09H
mov al,00H
int 21H
pop dx
pop cx
pop bx
ret
print endp
;调用约定:ax指定一个buffer的基地址,用于获取一个字符串
println proc near
call print
mov al,0Dh
call putchar
mov al,0DH
call putchar
ret
println endp
newline proc near
push ax
lea ax,next_row
call print
pop ax
ret
newline endp
;AX存放目的地址,结束时CX返回输入字符个数
input proc near
push BX
; push cx
; push di
; push ax
; MOV CX, 0ffh
; mov DI, ax
; mov al,'$'
; REPZ STOSB
; pop ax
MOV DX,AX
MOV BX,DX
MOV byte ptr DS:[BX], 0ffh
; mov [DX],BL
mov ah,0ah ;将0ah放入ah
mov al,0
int 21h ;输入字符串功能调用
pop BX
ret
input endp
circle proc near
rolling:
lea ax,cmdprompt
call println
call getchar
cmp al,'q'
jz fini
mov ah,0
call newline
call hex
call newline
jmp rolling
fini:
retn
circle endp
;调用约定:ax存放基地址,cx存放长度,bl存放目标字符
memset proc near
push di
mov di,ax
memset_circle:
cmp cx,0
jz memset_fini
mov ds:[di],bl
inc di
dec cx
jmp memset_circle
memset_fini:
pop di
ret
memset endp
exit proc near
mov ah,4Ch
mov al,0
int 21h
exit endp
BEGIN:
MOV AX,DSEG
MOV DS,AX
call newline
lea AX,STUID
call print
call newline
lea AX,buffer
mov cx,0ffh
mov bl,'$'
call memset
lea AX,buffer
call input
call newline
lea ax,[buffer+2]
call print
call newline
lea AX,STUNAME
call print
call newline
lea AX,buffer
mov cx,0ffh
mov bl,'$'
call memset
lea AX,buffer
call input
call newline
lea ax,[buffer+2]
call print
call newline
call circle
call exit
CSEG ENDS
END BEGIN