8086 asm文件语法-1

Posted 不会写代码的丝丽

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了8086 asm文件语法-1相关的知识,希望对你有一定的参考价值。

前置的一些编译链接命令

code_seg segment
LABLE1:
    mov ax,bx
LABLE2:
    mov cx,dx
code_seg ends

end LABLE1

我们在vs插件的dosbox会有内置编译器。

执行汇编:ml /c xxx.asm
执行链接: link xxxx.obj


多文件编译

第一个文件

;文件名myasm.asm

;导入的第二个文件的头文件,内部包含函数声明
include myasm2.inc

dseg segment

dseg ends

cseg segment
   assume  cs:cseg,ds:dseg,es:dseg
start:
    mov ax,dseg
    mov ds,ax

    ;调用其他文件的变量
    mov ax,g_w3
    ;调用其他文件的函数
    invoke MyFun

cseg ends
    end start

第二个文件

;myasm2.asm
;必须加上这个关键字否则无法在其他文件使用
public g_w3

asm2_dseg segment
   MyFun PROC far
     ret
   MyFun ENDP
   g_w3 word 2h

asm2_dseg ends


end

头文件

;防止重复导入
ifndef MY_ASM2
    MY_ASM2 equ 0
    MyFun proto far
    extern g_w3:word
    extern ExitProcess MACRO 
endif
编译语法
ml /c MYASM.ASM MYASM2.ASM
link MYASM.obj MYASM.obj

语法

段定义

段名字 segment 段定义开始
segment ends 结束段定义

code_seg segment
code_seg ends

重名段可以重复定义
如下面的代码:

myseg segment
 mov ax,bx
myseg ends

myseg segment
mov bx,ax
myseg ends

上面的代码会把段代码合并如下代码:

myseg segment
mov ax,bx
mov bx,ax
myseg ends

入口

end 标签

code_seg segment
Start:
mov ax,1234
mov ax,1234d
mov ax,1234D
mov ax,1234h
mov al,1001b
mov al,23o ;

code_seg ends

end Start 

常量定义

关键字说明示例
十进制mov ax,1234
D十进制mov ax,1234d
B二进制mov ax,1011b
O八进制mov ax,76o
H十六进制mov ax,76h
H十六进制mov ax,0abh

举例说明:

code_seg segment
mov ax,1234;十进制
mov ax,1234d;十进制
mov ax,1234D;十进制
mov ax,1234h;十六进制
mov al,1001b;二进制
mov al,23o ;八进制

code_seg ends

end LABLE1 

变量定义

关键字意义
db字节
dw
dd双字
dq8字节
dt10字节

来看一个小例子:

code_seg segment
val db 56h;在代码段定义了一个数据
Start:

mov ax,1234

code_seg ends

end Start 

data_seg segment
 g_db db 55h;
 g_dw dw 6655h;
 g_dd dd 77887788h
 g_dq dq 1122334455667788h
 g_dt dt 11223344556677889900h
 g_dw0 dw ?
data_seg ends


code_seg segment
Start:
;获取data_seg地址,方便查看
mov ax,data_seg

code_seg ends

end Start 

字符串

字符串可以以'(单引号)或者"(双引号)包裹
一般以$作为字符串的结束

data_seg segment
 g_db db "hello world$";
data_seg ends


code_seg segment
Start:
;获取data_seg地址,方便查看
mov ax,data_seg

code_seg ends

end Start 

数组

数组定义方式1:
变量名 类型 [数值1][,数值2][,数值3]

data_seg segment
;定义多个变量
 g_db db 1h,2h,3h,4h,5h
data_seg ends


code_seg segment
Start:
;获取data_seg地址,方便查看
mov ax,data_seg

code_seg ends

end Start 


数组定义方式2:
名字 类型 数量 dup(初始值)[,数量dup(初始值)][,值]

示例代码:
g_ary db 256 dup(0),128 dup(11h)
重复256个0和128个11

另一个示例

data_seg segment
 g_ary db 10 dup(0),128 dup(1)
data_seg ends


code_seg segment
Start:
;获取data_seg地址,方便查看
mov ax,data_seg

code_seg ends

end Start 

伪指令

部分指令是原始cpu没有的,但是为了简便开发汇编器提供一些简便的封装指令我们称为伪指令

关键字意义
seg取段基址
offset取段偏移
type取元素类型大小
length取元素个数
size取数据大小(length*type)

seg

data_seg segment
 g_val_1 db 10 
 g_val_2 db 10 

data_seg ends

code_seg segment
Start:
;获取g_val_1变量所在data_seg基地址
mov ax,seg g_val_1
;获取g_val_2变量所在data_seg基地址
mov ax,seg g_val_2

code_seg ends

end Start 

offset

data_seg segment
 g_val_1 db 10 
 g_val_2 db 10 

data_seg ends


code_seg segment
Start:
mov ax,offset g_val_1
mov ax,offset g_val_2

code_seg ends

end Start 

type

其本意返回类型变量的字节大小

data_seg segment
 g_val_1 db 10 
 g_val_2 dw 10 
 g_val_3 dw 23,46,78
 g_val_4 dw 10 dup(1)
data_seg ends


code_seg segment
Start:
mov ax,type g_val_1
mov ax,type g_val_2
mov ax,type g_val_3
mov ax,type g_val_4
code_seg ends

end Start 

length

获取数组类型的长度

data_seg segment
 g_val_1 db 10 
 g_val_2 dw 10 
 g_val_3 dw 23,46,78
 g_val_4 dw 10 dup(1)
data_seg ends


code_seg segment
Start:
mov ax,length g_val_1
mov ax,length g_val_2
mov ax,length g_val_3
mov ax,length g_val_4
code_seg ends

end Start 

size

assume

我们首先查看如下代码

data_seg segment
 g_dw dw 10 
data_seg ends

code_seg segment
Start:
mov ax,g_dw
code_seg ends

end Start 

一切看起来没有问题,我们编译下

因为编译器不知道g_dw在哪个段中,因此需要assume指令告诉编译器

data_seg segment
 g_dw dw 10 
data_seg ends

code_seg segment
Start:
;告诉编译器使用data_seg去寻找变量
assume ds:data_seg
mov ax,g_dw
code_seg ends

end Start 

assume不仅可以告诉编译器ds段基址,也可以指明ss段基址。

stack_seg segment
   mystask db 256 dup(0cch)
   g_dw2 dw 10 
stack_seg ends

code_seg segment
Start:
;告诉编译器使用data_seg去寻找变量
assume ss:stack_seg
mov ax,g_dw2
code_seg ends

end Start 

stack 标记

;自动根据stack_seg指定ss和sp数值
stack_seg segment stack
   mystask db 256 dup(0cch)
stack_seg ends

code_seg segment
Start:

code_seg ends

end Start 

DOS下的函数调用

dos下提供的功能调用表

操作系统提供了一些内置行为,通过中断实现,具体传给中断参数可通过上述表格查询。
比如我们需要像屏幕输出信息

上面的意思:传入AH=02h,dl传入输出到屏幕的字符

code_seg segment
Start:
;输出A到屏幕
mov dl,"A"
;调用输出到屏幕指令
mov ah,2
;陷入中断
int 21h 

;此处传参数表示结束程序并返回0
mov ax,4c00h
int 21h

code_seg ends

end Start

movsb

si(使用ds作为段基址)地址的内容拷贝到di(使用es作为段基址)地址,调用后sidi分别自增1

data_seg segment
    g_szSrc db "hello world$"
    g_szDst db 255 dup(00)
data_seg ends

code segment
START:
    assume ds:data_seg
    mov ax,data_seg
    ;放入ds到正确位置
    mov ds,ax

    mov ax,ds
    ;同上
    mov es,ax
    
    lea si,g_szSrc
    lea di,g_szDst

    ;拷贝h字符到es:di
    ;拷贝后di+=1,si+=1
    movsb 
    ;拷贝e字符到es:di
    ;拷贝后di+=1,si+=1
    movsb 
    ;拷贝l字符到es:di
    ;拷贝后di+=1,si+=1
    movsb 
    ;拷贝l字符到es:di
    ;拷贝后di+=1,si+=1
    movsb 
    ;拷贝o字符到es:di
    ;拷贝后di+=1,si+=1
    movsb 

code ends

end START

首先我们运行第一个movsb之前的内存状态图:


执行第一个movsb指令后我们查看对应的寄存器disi



后续步骤也是相同不在列出.

你可能看到上面的命令中sidi自增1,但如果你想每次调用movsb后自动减一也是允许的。
sidi执行加一或者减一是由DF标志位影响的.

8086中提供了两个相关指令让我们直接修改DF标志位

指令功能
STD (set df)让si和di自动减一
CLD(clear df)让si和di自动加一
data_seg segment
    g_szSrc db "hello world$"
    g_szDst db 255 dup(00)
data_seg ends

code segment
START:
    assume ds:data_seg
    mov ax,data_seg
    ;放入ds到正确位置
    mov ds,ax

    mov ax,ds
    ;同上
    mov es,ax
    
    lea si,g_szSrc
    lea di,g_szDst

    ;修改df标志位
    std

    ;拷贝h字符到es:di
    ;拷贝后di+=1,si+=1
    movsb 
    ;拷贝e字符到es:di
    ;拷贝后di+=1,si+=1
    movsb 
    ;拷贝l字符到es:di
    ;拷贝后di+=1,si+=1
    movsb 
    ;拷贝l字符到es:di
    ;拷贝后di+=1,si+=1
    movsb 
    ;拷贝o字符到es:di
    ;拷贝后di+=1,si+=1
    movsb 

code ends

end START

movsw

与movsb 功能相似,但是每次拷贝一个字,si和di加2或者减2

movs

其实本质会转化为movsw或movsb

movs byte ptr [di], byte ptr [si] ; 实际转化为movsb 
movs word ptr [di], word ptr [si] ; 实际转化为movsw

stosb

功能类似stosw,di自增1

stosw

将ax存储的数值拷贝ds:di ,之后di+=2

loadsb

类似loadsw ,但是si+=1

loadsw

将si地址的内容读入ax,然后si+=2

data_seg segment
    g_szSrc db "hello world$"
    g_szDst db 255 dup(00)
data_seg ends

code segment
START:
    assume ds:data_seg
    mov ax,data_seg
    ;放入ds到正确位置
     mov ds,ax



    lodsw
    lodsw
    lodsw
    lodsw
    

code ends

end START

cmpsb

与cmpsw大致相同只不过比较的是一个字节 ,运行后si和第自增1

cmpsw

si 地址内从减去di地址内容,不存储结果 但影响标志位,运行后si和di自增2



scasb

与scasw大致相同

scasw

ax减去di地址内容,不存储结果,但影响标志位,运行后di+=2

data_seg segment
    g_szSrc db "abcde$"
    g_szDst db 255 dup(00)
data_seg ends

code segment
START:
    assume ds:data_seg
    mov ax,data_seg
    ;放入ds到正确位置
    mov es,ax
    xor ax,ax
    ;由于小端模式注意反着写
    mov ax,'ba'
    scasw   
    mov ax,'dc'
    scasw
    mov ax,'e$'
    scasw

code ends

end START



重复前缀

一些指令前缀可以让指令无限的运行直到满足某一条件下才停止

rep

一般配合movs,stos,loads指令一起使用,重复条件cx!=0
我们看下如下代码,拷贝ds中的字符串到es中,每次执行rep指令时cx都会减少1

data_seg segment
    g_szSrc db "abcde$"
    g_szDst db 255 dup(00)
data_seg ends

code segment
START:
    assume ds:data_seg
    mov ax,data_seg
    ;放入ds到正确位置
     mov ds,ax
     xor ax,ax
 	;计算出重复的次数
    mov cx,g_szDst-g_szSrc
    rep movsb

code ends

end START

指令执行前:
cx=6 因此会执行6次 movsb指令

目标内存:

原内存

第一次执行后:

执行6次后:


repz/repe

配合指令:cmps,scas
重复条件:cx!=0zf==0

我们看一个例子

data_seg segment
    g_szSrc db "abcde$"
    g_szDst db 255 dup(00)
data_seg ends

code segment
START:
    assume ds:data_seg
    mov ax,data_seg
    mov ds,ax
    mov es,ax
    xor ax,ax
 	;得到字符串的长度
    mov cx,g_szDst-g_szSrc
    ;
    repz cmpsb

code ends

end START

repnz/repne

配合指令:cmps,scas
cx!=0zf ==1

data_seg segment
    g_szSrc db "abcde$"
    g_szDst db 255 dup(00)
data_seg ends

code segment
START:
    assume ds:data_seg
    mov ax,data_seg
    ;放入ds到正确位置
    mov ds,ax
    mov es,ax
    xor ax,ax
    mov cx,g_szDst-g_szSrc
	;由于字符串相等每次计算zf都会为0所以第一次比较就会结束
    repnz cmpsb

code ends

end START

以上是关于8086 asm文件语法-1的主要内容,如果未能解决你的问题,请参考以下文章

8086 asm文件语法-2

8086 asm文件语法-2

8086汇编 程序编译

在 DOSBox 中运行 ml program.asm 时 8086 程序没有输出

没有 div 的 ASM 8086 除法

8086asm中的struc类型数据无法打印