刚开始学习微机原理课程的时候,对汇编语言编程曾经有种无从下手的感觉。经过一个学期的练习,对汇编语言熟悉了不少,想要整理一篇文章来帮助一下刚入门的汇编小白。
一、编程环境
简单先提一句编程环境的选择,我比较建议VScode,安装相对比较方便,而且功能比较齐全,可以tab补全、代码折叠,也可以方便地查看寄存器内容。而且,VScode软件的启动速度挺快的,所占用的存储空间也很能接受,并不是很笨重的软件!
安装:VScode直接去官网下载就行了https://code.visualstudio.com/,然后安装VScode里面的MASM/TASM插件即可,安装过程可以参考其他文章,这里不再赘述。

二、helloworld示例及运行
新建一个文件,选择assembly(DOS)语言,然后把下面这段代码复制过去:
TITLE HelloWorld
DATA SEGMENT
DispTEXT DB 10,'HelloWorld','$'
DATA ENDS
STACKS SEGMENT
DW 80 DUP(?) ;duplicate 80 times,prepare 80Byte for stacks
STACKS ENDS
.486
CODE SEGMENT USE16
ASSUME CS:CODE,DS:DATA,SS:STACKS
BEG:
;Initialization
MOV AX,DATA
MOV DS,AX
;where you can add your codes
MOV AH,09H
LEA DX,DispTEXT
INT 21H
;Ending
MOV AX,4C00H
INT 21H
CODE ENDS
END BEG
在VScode里面应该是这样的:

然后右键选择“运行当前程序(汇编+链接+运行)”,即可运行程序
运行结果如下图所示:
注:一般而言完整的程序有三个段:数据段、堆栈段和代码段。
数据段中定义变量;
堆栈段中定义堆栈空间(使用“过程”,或者主动PUSH和POP都需要一定堆栈空间)
代码段书写代码:
开头要初始化数据区(标准格式,一般都要写的);
结尾必须以MOV AX,4C00H INT 21H结束(先这样写就行了,后面会慢慢明白意思的);
中间就可以写代码啦,代码较长时建议使用模块化程序设计(利用过程PROC以及宏MACRO,两者区别后文会阐述)。
最前面的TITLE为程序标题(可写可不写,为程序的说明)。
注释用英文分号;开头。
HelloWorld程序用到的为字符串的显示,其中DispTEXT变量定义一开始的“10”为换行符的ASCII码(不使用回车的ASCII码,原因是汇编语言中回车会使字符从一行的最开始进行显示,而非换行显示,会出现覆盖);字符串必须以$结尾,否则程序不知道显示到哪里结束;使用INT21H(DOS)的09号功能,显示以$为结尾的字符串,字符串的首地址预先存在DX寄存器中。
(更多常用DOS功能后面不咕咕咕的话可能会出一期)
三、一些排错(如果上一步正确运行了可以不看)
- 假如你使用的环境不支持32位寄存器,可以把代码中的“.486”和“USE16”同时删除。 后果是程序中不允许出现EAX、EBX、ECX、EDX等32位寄存器,否则会报错。
- doxbox和jsbox的切换:在左侧 “扩展”-MASM/TASM的“扩展设置”中切换。
一般而言,doxbox和jsbox没有太大的区别,我个人觉得jsbox的显示界面(左右分屏显示)比较舒服,而dosbox一般以弹窗的形式显示运行结果。
(下图为jsbox的运行界面:
注:jsbox对于INT15H的86H延时功能好像不支持,如果运行结果有问题可以尝试换成dosbox运行。
MOV AH,86H
MOV CX,200
MOV DX,0 ;CX,DX:延迟时间(微秒)
INT 15H
四、查看寄存器的debug方法
作为初学者,一般需要通过查看寄存器的方法了解汇编语言的运作方式,偶尔也会通过查看寄存器进行debug。
右键选择“运行当前程序(汇编+链接+调试)”,即可进入调试模式。


选择“View-CPU”即可查看寄存器,如下图所示:

一般用 (Fn+)F8进行调试(会跳过过程)
如果希望进入“过程”进入单步调试,可以使用 (Fn+)F7
结束本次调试 (Fn+)F9
注:假如鼠标被限制在了调试区域无法移动,可以按一下“Windows”按钮就可以让鼠标出来~
五、过程PROC的使用
将HelloWorld程序的主程序代码用“过程”包装后的代码如下:
TITLE HelloWorld(withPROC)
DATA SEGMENT
DispTEXT DB 10,'HelloWorld','$'
DATA ENDS
STACKS SEGMENT
DW 80 DUP(?) ;duplicate 80 times,prepare 80Byte for stacks
STACKS ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA,SS:STACKS
BEG:
;Initialization
MOV AX,DATA
MOV DS,AX
;where you can add your codes
CALL A10DISP ;call A10DISP to display "HelloWorld"
;Ending
MOV AX,4C00H
INT 21H
;define a process
A10DISP PROC NEAR
MOV AH,09H
LEA DX,DispTEXT
INT 21H
RET
A10DISP ENDP
CODE ENDS
END BEG
注:
- PROC中的代码必须以RET(return)结尾,否则程序运行时无法正常结束、调用多个过程时无法正常返回;
- 主程序中通过CALL来调用过程;
- 过程的调用需要用到堆栈,所以堆栈错误时(如过程中PUSH了3次,却只POP了2次)会引起过程的调用错误,表现就是“程序跑飞了”,出现各种奇怪的跳转错误;
- 过程的命名一般以A10xxx,B10xxx等命名,便于分类整理;
- 一般单个程序的过程用NEAR就足够了,多个文件之间互相调用时才需要用到FAR。
六、宏MACRO的使用
将HelloWorld程序的主程序代码用“宏”包装后的代码如下:
TITLE HelloWorld(withMACRO)
;define a macro
DISPhw MACRO
MOV AH,09H
LEA DX,DispTEXT
INT 21H
ENDM
DATA SEGMENT
DispTEXT DB 10,'HelloWorld','$'
DATA ENDS
STACKS SEGMENT
DW 80 DUP(?) ;duplicate 80 times,prepare 80Byte for stacks
STACKS ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA,SS:STACKS
BEG:
;Initialization
MOV AX,DATA
MOV DS,AX
;where you can add your codes
DISPhw
;Ending
MOV AX,4C00H
INT 21H
CODE ENDS
END BEG
注:
- 宏的定义一般放在程序的最开始,每个宏的定义以ENDM结束;
- 主程序中直接通过宏的名称来调用宏;
宏和过程的区别:
宏 | 过程 |
---|---|
程序汇编时完成展开 | CALL在程序执行时完成调用 |
直接传递和接受参数 | 通过“寄存器”、“存储单元”堆栈传递参数 |
简化源程序,不能简化目标代码,增加内存空间 | 缩短目标代码,节省内存空间 |
不增加执行时间 | 保护恢复现场,增加时间开销 |
因此,在子程序本身较短,参数较多的情况下,用宏指令更有效;
一般而言,可执行文件被调用时,以过程的方式调用。
模块化程序设计时,也建议以过程的方式调用。
所以可知,HelloWorld程序比较合适的包装方式为“过程”而非“宏”。
七、完整程序示例
简单的冒泡排序(降序)并显示
TITLE BUBBLESORT
;==============================================DATAsegment============================================================================================
DATA SEGMENT
numbers DB 78,23,-12,0,-2,99,-11,-67,9,56,44 ;11 numbers
DATA ENDS
;=================================================STACKS SEGMENT=======================================================================
STACKS SEGMENT
DW 128 DUP(?) ;duplicate 128 times,准备128Byte的空间
STACKS ENDS
;===================================================CODE SEGMENT=======================================================================
.486
CODE SEGMENT USE16
ASSUME CS:CODE,DS:DATA
BEG:
;Initialization
MOV AX,DATA
MOV DS,AX
A10MAIN PROC NEAR
CALL A11BUBBLESORT
CALL A12DISPLAY
A10end:
MOV AX,4C00H
INT 21H
A10MAIN ENDP
;===========================================PART1.BUBBLESORT=================================================================================
A11BUBBLESORT PROC NEAR
MOV CX,10 ;记录外循环的次数
L1:
MOV DI,CX ;记录当前内循环的次数
MOV BX,0 ; 记录当前比较的数字的位置
L2:
MOV AL,numbers[BX]
CMP AL,numbers[BX+1]
JGE NEXT1
XCHG AL,numbers[BX+1]
MOV numbers[BX],AL
NEXT1:
INC BX
DEC DI
CMP DI,0
JNE L2
LOOP L1
RET
A11BUBBLESORT ENDP
;===========================================PART2.DISPLAY=================================================================================
A12DISPLAY PROC NEAR
MOV CX,11
MOV SI,0
Ldisp:
MOV BX,10
PUSH BX
MOV AX,0
MOV AL,numbers[SI]
TEST AL,10000000B
JZ LTRANSFORM ;非负数
PUSH AX
MOV AH,02H
MOV DL,'-'
INT 21H
POP AX
NEG AL
LTRANSFORM: ;转换数字
MOV DX,0
DIV BX ;DX,AX/BX = 商:AX,余数:DX
PUSH DX
CMP AX,0
JNE LTRANSFORM
DISP:
POP DX
CMP DX,BX
JE NEXTNUM ;NEXT NUMBER
MOV AH,02H
ADD DL,30H
INT 21H
JMP DISP
NEXTNUM:
CMP SI,9
JA NEXT3
MOV AH,02H
MOV DL,','
INT 21H
NEXT3:
INC SI
LOOP Ldisp
RET
A12DISPLAY ENDP
CODE ENDS
END BEG