一日速成汇编
一日速成汇编
环境搭建
本文使用VSCODE 环境
在拓展商城中选择 MASM/TASM
点开拓展选项
我的设置是这样的
在asm文件界面右键可以调用
点运行和调试都是可以的
从代码开始讲起
Hello world?
DSEG SEGMENT ;datasegment,datasg,data...
MESS DB 'Hello, World!',0DH,0AH,24H
DSEG ENDS
CSEG SEGMENT
ASSUME CS:CSEG, DS:DSEG
BEGIN:
MOV AX,DSEG ;INT MAIN()
MOV DS,AX
MOV DX,OFFSET MESS
MOV AH,9
INT 21H
MOV AH,4CH
INT 21H
CSEG ENDS
END BEGIN
这段代码运行的结果是Hello, World!
如何运行得来呢?
注释语法
汇编语言的注释通常以分号 ;
开始,并在同一行的代码之后
; 这是一个示例注释
MOV AX, 1234h ; 将值1234H移动到AX寄存器
SEGMENT 语法
SEGMENT 相当于 函数 , 类似于 python
中的 def()
类型
可以将其作为函数来理解
SEGMENT 的 代码 在 SEGMENT
和 ENDS
中间
XXX SEGMENT ; XXX 代表 段名
;DO SOMETHING
XXX ENDS
字符串声明 语法
怎么存储
Hello world
?
声明变量
汇编语言中变量的声明一般是
变量名 变量类型 初始值
例如
MESS DB 'Hello, World!',0DH,0AH,24H
就等同于 C++中的
string MESS="Hello, World!\n";
回车,换行
后面的0DH,0AH
代表着回车 和 换行
回车和换行是两个ASCII控制字符,它们在文本处理和排版中起着不同的作用,有以下区别:
- 回车(0x0D):
- 回车字符通常用于将光标(或打印头)移动到行的开头,以便在同一行上继续文本输入或输出。
- 在许多操作系统中,回车通常与换行一起使用,形成回车换行(CRLF)序列,表示新行的开始。例如,Windows操作系统使用CRLF作为文本文件中行的分隔符。
- 在某些系统中,仅使用回车,而没有换行,可能会导致文本在同一行上连续输出,而不换行。
- 换行(0x0A):
- 换行字符通常用于将光标(或打印头)向下移动到下一行,以便在新行上继续文本输入或输出。
- 换行字符通常与回车一起使用,以便在文本文件中表示新行的开始,如CRLF序列所示。
- 在类Unix操作系统中,通常只使用换行(LF)来表示新行的开始,而不使用回车。
终止
24H
在汇编语言中,字符串通常会以特殊的方式终止,以便在处理字符串时能够知道字符串的结束位置。在ASCII编码中,十六进制值 24H
对应字符 $
,通常用于表示字符串的终止符。
主函数?
在汇编程序中有很多SEGMENT段
那么哪里是程序运行的入口呢?
答案是标签(label)
在上面的程序中
入口点被定义为 BEGIN
标签
在程序开始执行时,操作系统或汇编器将控制权传递给这个入口点。
CSEG SEGMENT
ASSUME CS:CSEG, DS:DSEG
BEGIN: ;没错 ,就是我
MOV AX,DSEG ;INT MAIN()
MOV DS,AX
MOV DX,OFFSET MESS
MOV AH,9
INT 21H
MOV AH,4CH
INT 21H
CSEG ENDS
END BEGIN ;出口
ASSUME
ASSUME
是一个汇编语言中的指令,用于建立寄存器和段寄存器之间的关联,以告诉汇编器如何解释寄存器中的值。具体来说:
ASSUME
是一个伪指令,用于建立寄存器和段寄存器之间的关联。
常用寄存器的种类
- AX:累加寄存器(Accumulator)。
- BX:基址寄存器(Base Register)。
- CX:计数寄存器(Count Register)。
- DX:数据寄存器(Data Register)。
这些寄存器通常用于执行通用计算操作,存储临时数据和地址。
SEGMENT的调用
我们将Hello world
存储在了 DSEG 段寄存器中
我们如何在段中 调用其他段呢
我们可以用上面的ASSUME
将其他段和寄存器相关联
例如
ASSUME CS:CSEG, DS:DSEG
就将 DSEG 段和 DS 寄存器相关联了
段 寄存到寄存器
MOV AX,DSEG ;INT MAIN()
MOV DS,AX
DS
寄存器不支持直接的立即数赋值,因此需要使用通用寄存器(如 AX
)作为中继
MOV AX,DSEG
:- 这行代码的目的是将
DSEG
数据段的地址(偏移地址)加载到通用寄存器AX
中。注意,这里的DSEG
是一个标签或标识符,用于指示数据段的位置。 - 通用寄存器
AX
可以直接用于存储16位地址。
- 这行代码的目的是将
MOV DS,AX
:- 一旦
AX
寄存器中包含了数据段地址,这行代码将AX
寄存器的内容复制到数据段寄存器DS
,以确保DS
寄存器指向了正确的数据段。 - 数据段寄存器
DS
用于确定数据操作的默认段,所以将DS
设置为正确的段非常重要,以便程序可以正确访问和操作数据。
- 一旦
MS-DOS[段调用的前置知识]
MS-DOS功能调用的编号通常由 AH
寄存器中的值确定。不同的编号对应不同的功能或服务。具体的功能调用编号会根据所需的操作而有所不同,但以下是一些常见的MS-DOS功能调用编号的示例:
- 文件操作:
AH = 3DH
:打开文件AH = 3FH
:创建文件AH = 3EH
:关闭文件AH = 3FH
:写入文件AH = 3FH
:读取文件AH = 10H
:删除文件- 等等...
- 目录操作:
AH = 1AH
:创建子目录AH = 2FH
:读取目录条目AH = 39H
:删除目录AH = 2AH
:设置当前目录- 等等...
- 屏幕和键盘操作:
AH = 02H
:在屏幕上显示字符AH = 06H
:从键盘获取字符AH = 0AH
:从键盘获取字符并将其存储在缓冲区AH = 09H
:在屏幕上显示字符串- 等等...
- 程序控制:
AH = 00H
:程序结束AH = 4CH
:程序正常退出AH = 31H
:获取程序返回码- 等等...
OFFSET
具体来说,OFFSET
是一个操作符,用于获取标签或变量的内存偏移地址,而不是它的实际值。偏移地址是变量相对于其所在段的起始地址的偏移量。这个偏移地址可以用于访问内存中的数据。
调用段内的内容
我们可以执行下面的代码将 所需要输出的字符串地址偏移量寄存到DX
中
MOV DX,OFFSET MESS
然后设置 AH
寄存器的值 来进行相应的操作
INT 21H
:
- 这行代码是一个中断调用指令。它会触发MS-DOS中断21h,这是一个用于执行各种MS-DOS功能的中断服务例程。具体来说,
INT 21H
用于执行字符串输出操作。 - 在执行时,MS-DOS会查看
AH
寄存器中的值(即9
),以确定要执行的操作。在这种情况下,9
表示要执行字符串输出操作。然后,MS-DOS会查找其他寄存器或内存中的参数来执行相应的输出操作,通常是在DS:DX
段地址偏移指针中指定要输出的字符串
由此便可以输出字符串
程序与输出
DSEG SEGMENT ;datasegment,datasg,data...
MESS DB 'Hello, World!',0DH,0AH,24H
MESS1 DB 'TEST, World!',0DH,0AH,24H
DSEG ENDS
CSEG SEGMENT
ASSUME CS:CSEG, DS:DSEG
BEGIN:
MOV AX,DSEG ;INT MAIN()
MOV DS,AX
MOV DX,OFFSET MESS
MOV AH,9
INT 21H
MOV DX,OFFSET MESS1
MOV AH,9
INT 21H
MOV AH,4CH
INT 21H
CSEG ENDS
END BEGIN
输出
Jump? goto!
我们在学C++的时候,goto可谓是一个大杀器,但是在没有一定水平的时候很难掌握
现在汇编将 Jump 定义为了 goto
于是我们可以愉快的实现跳转的功能
运行下面的程序 我们可以发现JUMP
功能发挥了作用
没有执行TEST,World!
的输出
DSEG SEGMENT ;datasegment,datasg,data...
MESS DB 'Hello, World!',0DH,0AH,24H
MESS1 DB 'TEST, World!',0DH,0AH,24H
DSEG ENDS
CSEG SEGMENT
ASSUME CS:CSEG, DS:DSEG
BEGIN:
MOV AX,DSEG ;INT MAIN()
MOV DS,AX
MOV DX,OFFSET MESS
MOV AH,9
INT 21H
MOV DX,OFFSET MESS1
MOV AX,0
CMP AX,0
JMP DONE
MOV AH,9
INT 21H
DONE:
MOV AH,4CH
INT 21H
CSEG ENDS
END BEGIN
选择语句
汇编语言本身没有高级编程语言中的直接的"if"和"else"语句。在汇编中,条件判断和分支通常使用条件跳转指令(例如,JE、JNE、JG、JL等)来实现。
Jump if *
汇编将 if 封装在了Jump里
大致分为以下几类
指令 | 含义 | 检查条件 |
---|---|---|
JE / JZ | 跳转条件相等(Jump if Equal / Jump if Zero) | 检查"零标志"(ZF)位,如果为1(表示比较的操作数相等),则跳转。 |
JNE / JNZ | 跳转条件不相等(Jump if Not Equal / Jump if Not Zero) | 检查"零标志"(ZF)位,如果为0(表示比较的操作数不相等),则跳转。 |
JG / JNLE | 跳转条件大于(Jump if Greater / Jump if Not Less or Equal) | 检查"零标志"(ZF)位和"溢出标志"(OF)位,如果ZF为0且OF为0,则跳转。 |
JGE / JNL | 跳转条件大于等于(Jump if Greater or Equal / Jump if Not Less) | 检查"零标志"(ZF)位和"溢出标志"(OF)位,如果ZF为0或OF为0,则跳转。 |
JL / JNGE | 跳转条件小于(Jump if Less / Jump if Not Greater or Equal) | 检查"溢出标志"(OF)位,如果OF为1,则跳转。 |
JLE / JNG | 跳转条件小于等于(Jump if Less or Equal / Jump if Not Greater) | 检查"零标志"(ZF)位和"溢出标志"(OF)位,如果ZF为1或OF为1,则跳转。 |
JC | 跳转条件进位(Jump if Carry) | 检查"进位标志"(CF)位,如果为1,则跳转。通常用于无符号数的比较。 |
JNC | 跳转条件不进位(Jump if No Carry) | 检查"进位标志"(CF)位,如果为0,则跳转。 |
例如让我们运行以下的程序,同样可以实现JUMP
的功能
DSEG SEGMENT ;datasegment,datasg,data...
MESS DB 'Hello, World!',0DH,0AH,24H
MESS1 DB 'TEST, World!',0DH,0AH,24H
DSEG ENDS
CSEG SEGMENT
ASSUME CS:CSEG, DS:DSEG
BEGIN:
MOV AX,DSEG ;INT MAIN()
MOV DS,AX
MOV DX,OFFSET MESS
MOV AH,9
INT 21H
MOV DX,OFFSET MESS1
MOV AX,0
CMP AX,0
JE DONE
MOV AH,9
INT 21H
DONE:
MOV AH,4CH
INT 21H
CSEG ENDS
END BEGIN
循环
在汇编中 用 LOOP
实现循环的功能
LOOP
指令将 CX 寄存器的值减1,然后检查 CX 寄存器的值是否为零,如果不为零,就跳转到指定的标签(通常是循环的开始标签),从而实现循环。
用法为
LOOP Tag
堆和栈
栈(Stack):
栈是一种先入后出的数据结构
在汇编中,通过栈指针来管理栈,SP 指向栈顶
要在栈上分配局部变量或参数可以使用指令如 PUSH
和 POP
:
PUSH AX ; 将AX寄存器中的值压入栈
POP BX ; 弹出栈顶的值到BX寄存器
可以使用 SUB
指令来为局部变量分配空间,并使用 ADD
指令释放这些空间。例如:
SUB SP, 2 ; 为两个字节的局部变量分配空间
ADD SP, 2 ; 释放局部变量的空间
堆(Heap):
在汇编中,堆通常是通过系统调用或库函数来分配和释放的,具体方式取决于操作系统和编程语言。例如,对于DOS平台,你可以使用INT 21h
来进行堆内存的分配和释放。
以下是一个简单的示例,使用DOS中的INT 21h
来分配内存:
MOV AH, 48h ; 功能号48h表示分配内存
MOV BX, 1000h ; 要分配的内存大小,例如1000h字节
INT 21h ; 执行INT 21h中断来分配内存,BX中将包含分配到的内存段地址
释放堆内存可以使用AH=49h
的INT 21h功能来释放内存,通常需要传递段地址
MOV AH, 49h ; 功能号49h表示释放内存
MOV ES, BX ; 将BX中的段地址加载到ES寄存器
INT 21h ; 执行INT 21h中断来释放内存
那怎么将数据传输到堆中呢
我们从代码开始理解
下面是一段将数据传输到堆中的代码
ORG 100h ; 设置代码的起始地址为100h,这是DOS可执行文件的标准入口点
; 分配堆内存
MOV AH, 48h ; 功能号48h表示分配内存
MOV BX, 100h ; 要分配的内存大小,例如100h字节
INT 21h ; 执行INT 21h中断来分配内存,BX中将包含分配到的内存段地址
; 将数据复制到堆内存,使用ES:BX指向堆内存
MOV AX, 1234h ; 要插入的数据
MOV ES, BX ; 将分配到的堆内存段地址加载到ES
MOV [ES:BX], AX ; 将数据复制到堆内存
; 释放内存(可选)
MOV AH, 49h ; 功能号49h表示释放内存
INT 21h ; 执行INT 21h中断来释放内存
INT 20h ; 终止程序
RET
函数
函数的定义
PROC
和 ENDP
vs. 标签:是声明函数的两种方式
codeMyFunction:
; 函数内的操作
...
RET ; 返回
MyFunction PROC
; 这里编写函数的代码
RET ; 返回
函数的调用
CALL MyFunction ; 调用函数
段内调用和段间调用
NEAR
:NEAR
是 "近调用" ,意味着它在同一个代码段内,与调用者的代码处于相同的段内FAR
:FAR
是 "远调用" ,这意味着它可能位于不同的代码段内,与调用者的代码处于不同的段内
声明的时候可以手动指定,如果不指定默认为 NEAR
PROC A:CALL FAR PTR B
RET
ENDP
PROC B FAR:...
RET ENDP
PROC的本质是:入栈程序出口指针,RET时从回到出口指针的位置
- 第一个出栈元素会是一个偏移地址
- 如果最后SP的指针位置不对,就无法正确RET
所以可以用寄存器BP保护SP,使用BP进行数据的读取
x+y
- 在堆栈段push任意两个长度为1 word的数据
- 使用子程序,将这两个数据的和存储于AX
SUM PROC ;取两个栈顶元素求和储存到AX中
MOV BP,SP
MOV AX,[BP+2]
ADD AX,[BP+4]
RET
SUM ENDP
*Macro(宏定义)
PROC的使用有调用开销(程序的中断 跳转 继续),而MACRO没有
MACRO相当于写代码的人把重复写代码的过程交给了汇编器,相比子程序来说,是通过多占程序的内存来提高运行速度(对机器来说,每调用一次Macro,就是把这段指令重复了一次)
NM MACRO R1,R2...(参数)
...
END M
NM MACRO AX,BX...(寄存器取值)
输入/输出
键盘输入
1号:单个字符输入
MOV AH,1
INT 21H
(内容会保存在AL)
10号:从键盘输入字符串
内存里需要划分三个部分:
1.一个字节存放最大长度(你写,溢出会被裁掉)
2.一个字节存放实际长度(指令运行完CPU会写)
3.一些字节用来存字符串
DATA SEGMENT
MAXLENGTH DB 100 ;一个字节,用它存最大的长度
ACTUALLENGTH DB ? ;一个字节,用它存实际的长度,在指令执行后会被填写
STRING DB 100 DUP(?) ;用来存字符串
DATA ENDS
STACK SEGMENT
STACK ENDS
CODE SEGMENT
ASSUME DS:DATA,SS:STACK,CS:CODE
MAIN:
MOV AX,DATA
MOV DS,AX
MOV DX,OFFSET MAXLENGTH ;把需要用到的内存块(三个部分)的地址存入DX
MOV AH,10
INT 21H
MOV AH,4CH
INT 21H
CODE ENDS
END MAIN
显示器输出
2号:单个字符输出
MOV DL,'A'
MOV AH,2
INT 21H
9号:字符串输出
字符串要以'
程序会将DS:DX地址开始输出字符到'$'结尾
MOV DX,OFFSET STRING
MOV AH,9
INT 21H
小练习
分支/循环/子程序+输入输出
用户输入一个单词,程序将所有大写转换为小写并输出到显示器
(注:'a'='A'+20H)
code
;转换大小写
dseg segment
MaxLength DB 100
ActualLength DB ?
STRING DB 100 DUP(?)
ChangeRow DB 0DH,0AH,'$'
dseg ends
sseg segment
sseg ends
cseg segment
assume cs:cseg,ds:dseg,ss:sseg ;装载
MAIN:
mov ax,dseg
mov ds,ax
INPUT:
MOV DX,OFFSET MaxLength
MOV AH,10
INT 21H
;大小写转换
MOV BX,OFFSET STRING
MOV CH,0
MOV CL,ActualLength ;CX=ACTUALLENGTH
PROCESS:
MOV AL,[BX]
CALL TurnSmall
MOV [BX],AL
INC BX
LOOP PROCESS
ADDtail:
MOV BX,OFFSET STRING
MOV DH,0
MOV DL,ActualLength
ADD BX,DX
MOV AL,24H ;'$'
MOV [BX],AL
OUTPUT: MOV DX,OFFSET ChangeRow
MOV AH,9
INT 21H
MOV DX,OFFSET STRING
MOV AH,9
INT 21H
END: MOV AH,4CH
INT 21H
TurnSmall PROC
;AL中的字符作大小写转换
CMP AL,'a'
JGE DONE
;小写=大写+20H
ADD AL,20H
DONE:
RET
TurnSmall ENDP
CSEG ENDS
END MAIN
本文作者:liangqianxing
本文链接:https://www.cnblogs.com/liangqianxing/p/17765161.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步