汇编学习笔记(4)-伪指令(MASM)
前言
编写汇编代码的时候会使用到两种语句,一种就是前面介绍的汇编指令又CPU提供功能支持,另一种呢叫做伪指令,伪指令是由汇编的编译器提供支持。所以伪指令的运行结果都必须实在编译的时候就能确定的,下面介绍的就是伪指令了。
注意接下来介绍的伪指令都是基于MASM汇编编译器,比较常用的还有NASM 它的语法以后有机会介绍
数值表达式
数值表达式不是汇编指令,表达式的值是在汇编代码的汇编过程中就由汇编编译器计算出结果而写到二进制程序中了,并不是在程序运行的过程中才计算的
(1) 常数表达式
常数就是直接的数字,直接写数字默认表示10进制数,也可以用符号指定为其他进制
D = 十进制 ; MOV AL, 1234D
B = 二进制 ; MOV AL, 0101B
H = 十六进制 ; MOV AL, 0FFFFH , 注意常数必须是数字开头不能是字母开头,如果16进制数第一个数字是字母的话就要补一个0在前面
Q = 八进制 ; MOV AL, 123Q
因为在程序中字母也是数字,所以其实也可以直接将字母或者字符串当成数字,比如
MOV AL, 'a'
MOV AX,"ab" ; 双引号和单引号都是可以使用的
(2) 算数运算符
就是简单的 正(+) 负(-) 加(+) 减(-) 乘(*) 除(/) 模(mod)
mov ax, 100+200
mov ax, 100/2
(3) 关系运算符
等于(EQ) 不等(NE) 大于(GT) 小于(LT) 大于等于(GE) 小于等于(LE)
如果等式成立则实际的值为0FFFF就是补码表示的-1, 如果关系不成立那么结果就是0
mov ax, 123 gt 234
mov ax, 1234+5 lt 1235
(4) 逻辑运算
与(and) 或(or) 非(not) 异或(xor) 左位移(shl) 右位移(shr)
mov ax, 1 shl 3
mov al, 3 and 47
(5) 其他操作符
HIGH LOW WIDTH MASK
HIGH 表示取数据的高八位
LOW 表示取数据的低八位
地址表达式
地址表达式所表示的是存储器操作数的地址。前面介绍的各种寻址方式实际上也是地址表达式。
一般格式是
mov ax, varw+4
注意 varw 变量或者标号的地址,这个是在编译的时候就知道的,所以varw+4 计算出来的就是varw 地址偏移4的地址,所以AX的值不是 varw+4的值,而是varw+4 内存地址处的值,所以AX的值是不确定的。
当然还有其他的写法
[varw+bx]
varw[bx]
varw[bx+di]
varw[bx][di]
变量和标号
(1) 数据定义语句
[变量名] 变量类型 表达式[, 表达式]
其中变量类型有
DB 字节
DW 字
DD 双字
所以数据定义就可以是这样的
a db 1,2,3,4,5
db 7,8,9,10
b dw 123,123,123
c dd 123,?,?,?
汇编中的数据定义意义其实就是C中的数组指针。这些数据都是顺序存放在内存中的,所以虽然第二行的数据没有定义名字,但是因为他紧跟在a 数组之后 b数组之前,所以使用a+5,或者 b-1 也是可以访问的,只是需要注意的是数据类型和 大端小端的问题。
后面跟实际数据的就表示定义的时候就直接初始化了数据,如果只是想留着位置,并不需要初始化的话也可以使用? 表示,?的意思就是只留位置不初始化内容
前面也说过,字符其实也可以表示为数字,所以也之后直接定义字符数组,下面的两种方式都是可行的
str db “hello”
str db "h","e","l","l","0"
(2) 循环定义 DUP
数字表达式 DUP(数据[, 数据,数据...])
有时候定义数组只是想预留一些空间,可能需要预留100个字节那么使用
buff db ?,?,?,?.......?
这种方式要打100个? 这样显然很没效率所以有个伪指令可以帮忙
buff db 100 dup(?); 定义100个DB 数据不用初始化
也可以这样
buff db 5 dup("ab") = buff db 'a','b','a','b','a','b','a','b','a','b'
dup 也是可以嵌套的比如
buff db 2 dup(1,2,5 dup(?)) = buff db 1,2,?,?,?,?,?,1,2,?,?,?,?,?
(3) this
this 类型
类型可以是byte word dword this返回下一个将分配的地址的
my_byte equ this byte ; equ是等效定义符号,下面或解释
my_word dw 8 dup(?)
这样就是定义了一个 byte的地址指针指向了一个 word数组等效的C语言就是
short[8] my_word[];
byte* my_byte = (byte*)my_word;
(4) 等价语句 EQU
符号名 EQU 表达式
说白了 EQU 就类似C的 #define 语句
用法可以是这样的
COUNT EQU 100 ; #define COUNT 100
BUFF_EN EQU 4*COUNT ; #define BUFF_EN 4*COUNT
HELLO EQU "how are you" ; #define HELLO "how are you"
MOVE EQU MOV ;#define MOVE MOV
VARW EQU THIS WORD ; 这个就和#dfine不一样了意义上面说过
VARB DB 2 DUP(?)
(5) 等于语句 =
符号名 = 数值表达式
其实就是专用的 EQU,功能和EQU是一样的,只不过试用范围比EQU小,只能用在数值表达式上
x = 10 ; x equ 10
y =20 +50*60 ; y equ 20+50*60
(6) 定义符号名语句
符号名 LABEL 类型
类型可以是 BYTE WORD DWORD NEAR FAR
他的作用其实应该是包含上面 this 指令中的用法即
my_byte equ this byte
my_word dw 8 dup(?)
换成LABEL的写法就是
my_byte label byte
my_word dw 8 dup(?)
同时LABLE 还比this强大就是还能作用在标号上
quit label far
exit: mov ah,4ch
这样的用法就 mov ah,4ch这条指令同时拥有 exit的短调指令和quit的长跳指令了。
(7) 变量和标号的属性
变量和标号的属性无非就是对应的类型 地址 大小等,对应的操作符
LENGTH SIZE OFFSET SEG TYPE PTR
seg 返回变量或标号的段地址 假设 VAR 的地址是 100+2 那么mov al, seg var ; al=100
offset 返回变量或标号的偏移地址 假设 VAR 的地址是 100+2 那么mov al, offset var ; al=2
type 返回变量或标号的类型 字节=1 字=2 双字=3 近=-1 远=-2,变量类型就是实际占用的字节数
length 返回的是DUP定义的长度 DUP将在下面讲到, 注意这里返回的是个数,如果数组没用DUP那么总是返回1 如果嵌套了就返回最外层的
size 返回DUP定义的实际占用字节数
ptr 强制类型转化 就是C语言中的(int)A;的作用 用法就是 类型 ptr 变量/数据地址 类型可以是 byte word dword near far
例如:
mov word ptr [si],1
jmp far ptr ok
优先级
1. 各种括号,LENGTH SIZE WIDTH MASK
2. PTR OFFSER SEG TYPE THIS 冒号
3. * / shl shr
4. HIGH LOW
5. + -
6. eq ne lt le gt ge
7. not
8. and
9. or, xor
10. short
程序组织伪指令
这些指令是为了方便汇编程序的结构组织的
(1) 段定义语句
为了与存储器的分段结构相对应,所以汇编指令也提供了对应的段的组织方式
段的开始
段名 SEGMENT [定位类型] [组合类型] [‘类别’]
。。
段的结束
段名 ENDS
例子
;数据段
DSEG SEGMENT
MESS DB 'HELLO'
DSEG ENDS
;代码段
CSEG SEGMENT
MOV AX, DSEG
...
...
CSEG ENDS
(2) 段使用设定语句
ASSUME 段寄存器名: 段名 [, 段寄存器名: 段名, 段寄存器名: 段名 ..]
设定了段之后汇编程序需要知道各自段对应是用来干嘛的,并设置对应的段寄存器。ASSUME就是这个作用
ASSUME CS: CSEG,DS:DSEG
ASSUME 是伪指令所以汇编编译器其实是将其转换成了对应的汇编指令,所以ASSUME可以出现在代码段的任何位置。随时进行切换
这里有个需要注意的是,如下代码
DSGE1 SEGMENT
.....
DSGE ENDS
DSEG2 SEGMENT
......
DSGE2 ENDS
CSEG SEGMENT
ASSUME CS:CSEG DS:DSEG1 ES:DSEG2
MOV AX,DSEG1 ; 由于此时数据段就是DSEG1 所以指令就是直接翻译的
MOV AX,DSEG2 ; 由于此时数据段是DSEG1,所以实际语句会被翻译成 MOV AX, ES:DSEG2
(3) ORG 指令
ORG 数值表达式
汇编编译器在汇编的时候使用汇编地址计算器来计算每条指令的偏移地址,而ORG指令就是用于手动修改当前地址的
比如
test segment
org 100h
begin: mov ax,1234h
test ends
mov ax,1234h 是段内的第一条语句偏移应该是0 ,但是由于ORG 100H的缘故,实际的偏移地址就变成了100H
(4) 当前地址 $
$表示当前指令的第一个字节的地址
org $+8 表示表示地址计算器从此处开始向后空8个字节出来
他也可以用在其他指令中,比如
jmp $+ 6 ; 转跳到本条指令之后6个字节处,注意计算地址是JMP指令的开始位置不是结束位置,所以这6个自己包含了JMP本身的长度
ARRAY 1,2,$+4; 这里的% 表示的是ARRAY的地址不是 $ +4的地址