8086 masm6.11(1981,1988)宏魔法(dosbox下)
Thursday, June 8, 2023
本文共2299字
5分钟阅读时长
⚠️本文是作者P3troL1er原创,首发于https://peterliuzhi.top/posts/8086-masm6.1119811988%E5%AE%8F%E9%AD%94%E6%B3%95dosbox%E4%B8%8B/。商业转载请联系作者获得授权,非商业转载请注明出处!
People usually compare the computer to the head of the human being. I would say that hardware is the bone of the head, the skull. The semiconductor is the brain within the head. The software is the wisdom. And data is the knowledge.
— Masayoshi Son
引入
最近在写汇编语言大作业,因为老师讲了一些汇编宏的用法,让我大为新奇,于是在大作业里面普遍用到了宏,在这里记录一下
另外,教学中用的masm好像比较老旧,很多语法都支持不了(是masm自己的原因吗,好像不支持leave指令,不过我用宏自己定义了一个),很多网上搜到的高级宏用法都用不了,所以这里分享的内容其实离我的想象还有一段距离,也算是一种遗憾吧
宏的基本知识
无参数宏
类似于C语言中的宏常量的真子集(不包括宏定义的常量数字等),如下定义:
会直接把宏名替换为里面的内容,由此可以实现leave指令:
leave macro
mov sp, bp
pop bp
endm
同时也可以定义一下进入函数时的准备工作:
intofunc macro
push bp
mov bp, sp
endm
有参数宏
类似于C语言中的宏函数:
<宏名> macro <参数1>, <参数2>, <参数3>, ...
...
endm
类似于C语言中:
#define <宏名>(<参数1>, <参数2>, <参数3>, ...) ...
不过在我所使用的这个版本的masm中好像不支持变参宏
<>操作符
把尖括号内的看作一个整体把一个完整的实参括起来,成为一个新的实参,比如:
其中把offset mydata作为一个整体传给了mymacro
在C语言中没有对应的操作符,硬说只能是括号
&操作符
表示拼接,意同C语言中的##
,例如:
shift_var marco r_m,direct,count
mov cl,count
s&direct r_m,cl
endm
其中把s和direct所代表的文本拼接在了一起
%操作符
告诉宏汇编程序获取表达式的值,而不是获取表达式文本本身,一般用于给宏传递实参(宏调用),但有些情况不能用,要确保%后面是一个编译期表达式
!操作符、;;注解符
!表示后面的操作符不是操作符而是普通的字符
;;表示宏中的注解,宏不会把这段注解展开(只使用;,预处理后的代码中会多出注解)
文本替换宏
在新版的masm中好像有TEXTEQU指令,但是在我的masm中只有EQU指令
它可以实现C语言中的宏常量变量,也可以简单地用于替换一段文本
call_print EQU <func1 print>
STACK_SIZE EQU 1024
local伪指令
在宏定义中使用变量名和标号,为了避免在宏展开时产生多个相同的变量名或标号(比如有时候会在宏中定义循环和跳转)
宏展开时,local伪指令指定的变量、标号自动生成格式为??ⅩⅩⅩⅩ
的符号,其中后四位顺序使用0000~FFFF的十六进制数字。
delay macro reg ;; reg为16位寄存器
local lop
mov cx,reg
lop: nop
loop lop
endm
宏嵌套
可以使用一个宏生成另一个宏的定义,这样就可以定义一个生成器,然后通过给这个宏传不同的参数,从而批量生成宏
重复汇编
可以使用rept指令来重复生成一段汇编,将重复语句序列重复汇编,表达式的值为重复汇编的次数。也可以重复生成一段宏。
【例】用重复汇编可以在9*9个字节存储单元中存放一个乘法九九表的数值:
n=0
rept 9
n=n+1
m=0
rept 9
m=m+1
db n*m
endm
endm
irp指令将重复语句序列重复汇编,次数由实参个数所决定
irp 形参,<实参1,实参2,……>
...
endm
pushreg macro text
irp reg,<text>
push reg
endm
endm
irpc指令将重复语句序列重复汇编,次数由字符串中字符个数决定
条件汇编
宏汇编提供了十种条件宏汇编伪操作指令:if、ife、if1、if2、ifdef、ifndef、ifb、ifnb、ifidn、ifdif
if: 当表达式的值不为零,则条件为真;
ife:如果表达式的值为零,则条件为真;
if1: 如果是第一遍扫描,则条件为真;
if2:如果是第二遍扫描,则条件为真;
ifdef:表达式为符号。如果该符号已经定义,则条件为真。
ifndef:表达式为符号。如果符号没有定义,则条件为真。
ifb <count>:如果尖括号中为空,则条件为真。
ifnb <count>:如果尖括号中不为空,则条件为真。
ifidn <char1>, <char2>:如果两个字符串相同则条件为真
ifidf <char1>, <char2>:如果两个字符串不同则条件为真。
使用语法:
ifx ×× 表达式
...
else
...
endif
类似于C语言中的#if
、#endif
、#ifdef
、#ifndef
等
宏魔法实例
x86风格函数传参
pusharg macro val
mov ax, val
push ax
endm
类C函数原型声明
func0 macro funcname
call funcname
endm
func1 macro funcname, v1
mov ax, v1
push ax
; pusharg v1
call funcname
add sp, 2
endm
func2 macro funcname, v1, v2
mov ax, v2
push ax
mov ax, v1
push ax
call funcname
add sp, 4
endm
func3 macro funcname, v1, v2, v3
mov ax, v3
push ax
mov ax, v2
push ax
mov ax, v1
push ax
call funcname
add sp, 6
endm
call_print EQU <func1 print>
call_puts EQU <func1 puts>
call_putchar EQU <func1 putchar>
call_read EQU <func3 read>
call_strlen EQU <func1 strlen>
call_strcmp EQU <func2 strcmp>
call_itoa EQU <func2 itoa>
call_atoi EQU <func1 atoi>
call_getchar EQU <func0 getchar>
call_getint EQU <func0 getint>
call_memset EQU <func3 memset>
call_memcpy EQU <func3 memcpy>
call_swap EQU <func2 swap>
call_gets macro v1, v2, v3
mov ax, v3
push ax
mov ax, v2
push ax
mov ax, v1
push ax
call read
add sp, 6
mov byte ptr ds:[di], 0
endm
使用的时候的示例:
call_print <offset change_index_st>
call_gets <offset search_key_st>, <offset buffer>, STU_NAME_SIZE
在函数中取形参
这里我使用的是x86风格传参,因此参数都保存在栈中
arg macro i
arg&i macro reg
mov reg, [bp+2*(i+1)]
endm
endm
; 声明arg1~arg7
n=0
rept 7
n=n+1
arg %n
endm
使用的时候,比如read有三个参数:
; read(read_instruction, read_storage, read_size)
read:
intofunc
arg1 ax
...
arg2 di
...
arg3 cx
...
leave
ret
而且只要不更改bp下面(地址上是上面)的栈,在函数任意位置都能取参
进入函数与退出函数
上面已经说过了
intofunc macro
push bp
mov bp, sp
endm
leave macro
mov sp, bp
pop bp
endm
初始化程序与结束程序
这个比较简单,就普通的替换
prog_initailize macro
mov ax, data
mov ds, ax
mov ax, 0B800H
mov es, ax
;; mov ax, stack
;; mov ss, ax
;; mov sp, STACK_SIZE
mov bp, sp
xor di, di
xor si, si
xor ax, ax
xor bx, bx
xor cx, cx
xor dx, dx
endm
prog_doom macro
mov ax, 4c00h
int 21h
endm
在全局变量保存寄存器
有时候我们不想用复杂的栈操作,在保证函数内嵌套调用的函数不会破坏全局变量的前提下,可以在全局变量暂存寄存器。对每个寄存器都有一个暂时的存储空间:
save macro reg
mov storage_®, reg
endm
load macro reg
mov reg, storage_®
endm
然后在数据段声明:
storage_di dw 0
storage_si dw 0
storage_bx dw 0
storage_ax dw 0
storage_cx dw 0
storage_dx dw 0
只要数据段的相应标号存在,save和load就能起效
宏常量声明
很简单的类似#define的声明
STACK_SIZE EQU 1024
STU_NAME_SIZE EQU 20h
STU_NUMBER_SIZE EQU 15
STU_COUNT EQU 10
CONTINUE_COUNT EQU 3
SCORE_COUNT EQU 2
扫码阅读此文章
点击按钮复制分享信息
点击订阅