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
assume cs:code,ds:data,ss:mystack
data segmentbuffer db 'Hello World!',10,13,'$'
data ends
mystack segment stackdb 32 dup(?)
mystack endscode segment
start:mov ax,datamov ds,axlea dx,buffermov ah,9int 21hmov ah,4chint 21h
code ends
end start
在星研IDE中编译连接运行会在屏幕打印helloworld
下面逐条语句分析程序作用
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]这样寻址
定义段
tasm要求一个程序必须要有代码段,数据段,堆栈段,即使用不到,不写,会给警告,不给通过编译
定义一个段的写法:
data segment...
data ends mystack segment stack;这里后面的stack是段属性,stack显示指出是一个堆栈段...
mystack ends
定义数据,这个数据是会出现在程序中的,要和"常数"区别
类比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也是写给程序员看的,列表列明的程序内容
包括行号,带机器码,带汇编指令
符号表,段信息
Turbo Assembler Version 4.1 11/19/22 00:01:35 Page 1
hw.asm12 assume cs:code,ds:data,ss:mystack34 0000 data segment5 0000 48 65 6C 6C 6F 20 57+ buffer db 'Hello World!',10,13,'$'6 6F 72 6C 64 21 0A 0D+7 248 000F data ends9 0000 mystack segment stack10 0000 20*(??) db 32 dup(?)11 0020 mystack ends1213 0000 code segment14 0000 start:15 0000 B8 0000s mov ax,data16 0003 8E D8 mov ds,ax17 0005 BA 0000r lea dx,buffer18 0008 B4 09 mov ah,919 000A CD 21 int 21h2021 000C B4 4C mov ah,4ch22 000E CD 21 int 21h23 0010 code ends24 end start
Turbo Assembler Version 4.1 11/19/22 00:01:35 Page 2
Symbol TableSymbol 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:0000Groups & Segments Bit Size Align Combine ClassCODE 16 0010 Para none
DATA 16 000F Para none
MYSTACK 16 0020 Para Stack
map文件也是给人看的,包括段的映射信息,段在源文件中的行号,程序入口点的位置
Start Stop Length Name Class00000H 0000EH 0000FH DATA 00010H 0002FH 00020H MYSTACK 00030H 0003FH 00010H CODE Line numbers for hw.obj(HW.ASM) segment CODE13 0003:0000 14 0003:0003 15 0003:0005 16 0003:000817 0003:000A 19 0003:000C 20 0003:000E
Program entry point at 0003:0000
coff可重定位目标模块
早期操作系统的目标模块,如今windows上仍在使用obj作为目标模块,但是结构已经发生了变化
编译生成的产物,和其他模块一起链接生成exe
coff可执行目标模块,如今windows上仍在使用exe作为可执行程序,但是结构已经发生了变化
链接的产物,可以在16位环境下直接执行
用010editor等二进制编辑器可以观察其结构
开头就是MZ魔数
推测是符号表文件
可能是dump object的缩写
整个程序翻译成的机器码,包括数据段,代码段,堆栈段

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 SEGMENThelloworld DB 'Hello,World!' ,0DH,0AH,24Hnext_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
| 功能 | 打印一个字符 |
|---|---|
| 调用约定 | al输入该字符 |
使用入口为2H的int 21软中断打印到屏幕
;调用约定:需要打印的字符->al
putchar procpush dxmov ah,02hmov dl,alint 21hpop dxret
putchar endp
| 功能 | 输入一个字符 |
|---|---|
| 调用约定 | al返回该字符 |
;调用约定:输入字符用al返回
getchar proc nearmov ah,01hmov al,0int 21Hretn
getchar endp
| 功能 | 打印字符串 |
|---|---|
| 调用约定 | AX存放字符串及地址,打印到碰到$字符结束 |
;调用约定:ax指定一个buffer的基地址,用于获取一个字符串
print proc nearpush bxpush cxpush dxmov dx,axmov ah,09Hmov al,00Hint 21Hpop dxpop cxpop bxret
print endp
| 功能 | 打印换行 |
|---|---|
| 调用约定 | 无参数无返回值 |
newline proc nearpush axlea ax,next_rowcall printpop axret
newline endp
| 功能 | 输入字符串 |
|---|---|
| 调用约定 | AX存放缓冲区地址,输入直到回车结束 |
;AX存放目的地址,结束时CX返回输入字符个数
input proc nearpush BX; push cx; push di; push ax; MOV CX, 0ffh; mov DI, ax; mov al,'$'; REPZ STOSB; pop axMOV DX,AXMOV BX,DXMOV byte ptr DS:[BX], 0ffh; mov [DX],BLmov ah,0ah ;将0ah放入ahmov al,0int 21h ;输入字符串功能调用pop BXret
input endp
| 功能 | 缓冲区初始化,即c语言的memset函数 |
|---|---|
| 调用约定 | ax存放基地址,cx存放长度,bl存放初始化字符 |
;调用约定:ax存放基地址,cx存放长度,bl存放目标字符
memset proc nearpush dimov di,axmemset_circle:cmp cx,0jz memset_finimov ds:[di],blinc didec cxjmp memset_circlememset_fini: pop diret
memset endp
| 功能 | 返回函数,将控制交给调用者 |
|---|---|
| 调用约定 | 无参数无返回值 |
exit proc nearmov ah,4Chmov al,0int 21h
exit endp
| 功能 | 打印一个字节的16进制表示 |
|---|---|
| 调用约定 | ax存放该字节 |
;16进制打印一个字节
hex PROC nearpush axpush dxmov dx,axmov ch, 4L1: mov cl, 4rol dx, clmov al,dland al,0FHadd al,30hcmp al,3ahjl printitadd al,7hprintit: call putchardec chjnz L1pop dxpop axret
hex ENDP
| 功能 | 打印一个字节的2进制表示 |
|---|---|
| 调用约定 | ax存放该字节 |
;二进制形式的输出,入口参数为ax
binary procpush bx ;数据入栈,保留数值push dxmov dh,0 ;用于计数,总共8次binaryagain: cmp dh,8je binaryover ;输出结束,到达函数末端rol ax,1 ;移动一位mov bx,ax ;数值保留and ax,0001h ;剥离出最后一位mov dl,al ;用于输出add dl,48mov ah,2int 21hinc dh ;计数加一mov ax,bx ;数值恢复jmp binaryagain ;再来一次binaryover: pop dx ;数据出栈,数值还原pop bxret
binary endp
| 功能 | 循环输入字符,打印ascii值 |
|---|---|
| 调用约定 | 无参数无返回值 |
circle proc nearrolling: lea ax,cmdpromptcall printlncall getcharcmp al,'q'jz finimov ah,0call newlinecall hexcall newlinejmp rollingfini: retncircle endp
BEGIN: MOV AX,DSEG;设置DS段寄存器,将DSEG作为数据段MOV DS,AXcall newline;遇事不决,换个行先lea AX,STUIDcall print;提示输入IDcall newlinelea AX,buffermov cx,0ffhmov bl,'$'call memset;初始化缓冲区memset(&buffer,0xFF,'$');lea AX,buffer;设置输入目标地址call input;输入call newlinelea ax,[buffer+2]call printcall newlinelea AX,STUNAMEcall printcall newlinelea AX,buffermov cx,0ffhmov bl,'$'call memsetlea AX,buffercall inputcall newlinelea ax,[buffer+2]call printcall newlinecall circlecall exit;归还控制
DSEG SEGMENThelloworld DB 'Hello,World!' ,0DH,0AH,24Hnext_row DB 0DH,0AH,24Hbuffer 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 ENDSSSEG SEGMENT PARA STACKDW 256 DUP(?)
SSEG ENDSCSEG SEGMENTASSUME CS:CSEG,DS:DSEG;调用约定:需要打印的字符->al
putchar procpush dxpush axmov ah,02hmov dl,alint 21hpop axpop dxret
putchar endp;调用约定:输入字符用al返回
getchar proc nearmov ah,01hmov al,0int 21Hretngetchar endp;16进制打印一个字节,调用约定:
hex PROC nearpush axpush dxmov dx,axmov ch, 4L1: mov cl, 4rol dx, clmov al,dland al,0FHadd al,30hcmp al,3ahjl printitadd al,7hprintit: call putchardec chjnz L1pop dxpop axret
hex ENDP;二进制形式的输出,入口参数为ax
binary procpush bx ;数据入栈,保留数值push dxmov dh,0 ;用于计数,总共16次binaryagain: cmp dh,8je binaryover ;输出结束,到达函数末端rol ax,1 ;移动一位mov bx,ax ;数值保留and ax,0001h ;剥离出最后一位mov dl,al ;用于输出add dl,48mov ah,2int 21hinc dh ;计数加一mov ax,bx ;数值恢复jmp binaryagain ;再来一次binaryover: pop dx ;数据出栈,数值还原pop bxret
binary endp;调用约定:ax指定一个buffer的基地址,用于获取一个字符串
print proc nearpush bxpush cxpush dxmov dx,axmov ah,09Hmov al,00Hint 21Hpop dxpop cxpop bxret
print endp;调用约定:ax指定一个buffer的基地址,用于获取一个字符串
println proc nearcall printmov al,0Dhcall putcharmov al,0DHcall putcharret
println endpnewline proc nearpush axlea ax,next_rowcall printpop axret
newline endp;AX存放目的地址,结束时CX返回输入字符个数
input proc nearpush BX; push cx; push di; push ax; MOV CX, 0ffh; mov DI, ax; mov al,'$'; REPZ STOSB; pop axMOV DX,AXMOV BX,DXMOV byte ptr DS:[BX], 0ffh; mov [DX],BLmov ah,0ah ;将0ah放入ahmov al,0int 21h ;输入字符串功能调用pop BXret
input endpcircle proc nearrolling: lea ax,cmdpromptcall printlncall getcharcmp al,'q'jz finimov ah,0call newlinecall hexcall newlinejmp rollingfini: retncircle endp;调用约定:ax存放基地址,cx存放长度,bl存放目标字符
memset proc nearpush dimov di,axmemset_circle:cmp cx,0jz memset_finimov ds:[di],blinc didec cxjmp memset_circlememset_fini: pop diret
memset endpexit proc nearmov ah,4Chmov al,0int 21h
exit endpBEGIN: MOV AX,DSEGMOV DS,AXcall newlinelea AX,STUIDcall printcall newlinelea AX,buffermov cx,0ffhmov bl,'$'call memsetlea AX,buffercall inputcall newlinelea ax,[buffer+2]call printcall newlinelea AX,STUNAMEcall printcall newlinelea AX,buffermov cx,0ffhmov bl,'$'call memsetlea AX,buffercall inputcall newlinelea ax,[buffer+2]call printcall newlinecall circlecall exit
CSEG ENDSEND BEGIN
上一篇:描写月亮的现代诗
下一篇:劳动应该用手和大脑来模仿