一日速成汇编

一日速成汇编

110814869_p0 .jpg

环境搭建

本文使用VSCODE 环境

在拓展商城中选择 MASM/TASM

点开拓展选项

我的设置是这样的

image-20231014162336765.png

在asm文件界面右键可以调用

image-20231014162416224.png
点运行和调试都是可以的

从代码开始讲起

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 的 代码 在 SEGMENTENDS 中间

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控制字符,它们在文本处理和排版中起着不同的作用,有以下区别:

  1. 回车(0x0D)
    • 回车字符通常用于将光标(或打印头)移动到行的开头,以便在同一行上继续文本输入或输出。
    • 在许多操作系统中,回车通常与换行一起使用,形成回车换行(CRLF)序列,表示新行的开始。例如,Windows操作系统使用CRLF作为文本文件中行的分隔符。
    • 在某些系统中,仅使用回车,而没有换行,可能会导致文本在同一行上连续输出,而不换行。
  2. 换行(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)作为中继

  1. MOV AX,DSEG
    • 这行代码的目的是将 DSEG 数据段的地址(偏移地址)加载到通用寄存器 AX 中。注意,这里的 DSEG 是一个标签或标识符,用于指示数据段的位置。
    • 通用寄存器 AX 可以直接用于存储16位地址。
  2. MOV DS,AX
    • 一旦 AX 寄存器中包含了数据段地址,这行代码将 AX 寄存器的内容复制到数据段寄存器 DS,以确保 DS 寄存器指向了正确的数据段。
    • 数据段寄存器 DS 用于确定数据操作的默认段,所以将 DS 设置为正确的段非常重要,以便程序可以正确访问和操作数据。

MS-DOS[段调用的前置知识]

MS-DOS功能调用的编号通常由 AH 寄存器中的值确定。不同的编号对应不同的功能或服务。具体的功能调用编号会根据所需的操作而有所不同,但以下是一些常见的MS-DOS功能调用编号的示例:

  1. 文件操作
    • AH = 3DH:打开文件
    • AH = 3FH:创建文件
    • AH = 3EH:关闭文件
    • AH = 3FH:写入文件
    • AH = 3FH:读取文件
    • AH = 10H:删除文件
    • 等等...
  2. 目录操作
    • AH = 1AH:创建子目录
    • AH = 2FH:读取目录条目
    • AH = 39H:删除目录
    • AH = 2AH:设置当前目录
    • 等等...
  3. 屏幕和键盘操作
    • AH = 02H:在屏幕上显示字符
    • AH = 06H:从键盘获取字符
    • AH = 0AH:从键盘获取字符并将其存储在缓冲区
    • AH = 09H:在屏幕上显示字符串
    • 等等...
  4. 程序控制
    • 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

输出

image-20231014162248345.png

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 指向栈顶

要在栈上分配局部变量或参数可以使用指令如 PUSHPOP

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

函数

函数的定义

PROCENDP vs. 标签:是声明函数的两种方式

codeMyFunction:
    ; 函数内的操作
    ...
    RET ; 返回
MyFunction PROC
; 这里编写函数的代码
RET ; 返回

函数的调用

CALL MyFunction ; 调用函数

段内调用和段间调用

  • NEARNEAR 是 "近调用" ,意味着它在同一个代码段内,与调用者的代码处于相同的段内
  • FARFAR是 "远调用" ,这意味着它可能位于不同的代码段内,与调用者的代码处于不同的段内

声明的时候可以手动指定,如果不指定默认为 NEAR

PROC A:CALL FAR PTR B
RET
ENDP

PROC B FAR:...
RET ENDP

PROC的本质是:入栈程序出口指针,RET时从回到出口指针的位置

  1. 第一个出栈元素会是一个偏移地址
  2. 如果最后SP的指针位置不对,就无法正确RET

所以可以用寄存器BP保护SP,使用BP进行数据的读取

x+y

  1. 在堆栈段push任意两个长度为1 word的数据
  2. 使用子程序,将这两个数据的和存储于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号:字符串输出

字符串要以'\0'是一种字符串的终止符)
程序会将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 中国大陆许可协议进行许可。

posted @   liangqianxing  阅读(226)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起