.386
.model flat,stdcall
option casemap:none
定义程序使用的指令集、工作模式
相应的还有:.8086,.486,.586,.586p
内存模式有很多,如:
tiny small medium compact large huge flat
汇编经常遇到这样的代码:
mov ax,DATA
mov ds,ax
作用是给DS赋值。参考前面描述CPU结构的知识,知道不能直接写成
mov ds,DATA
但对于.model flat模式,MASM自动做了下面的定义:
ASSUME CS:flat, DS:flat, ES:flat, SS:flat, FS:ERROR,GS:ERROR
flat是内存平坦模式,意思是段寻址4G空间。
因此,CS,DS,ES,SS可以在程序中平坦使用。使用FS和GS则会报错。
.model 语句用于指定调用规则,即子程序的调用方式。就是说明了在调用API时的参数传递次序和堆栈平衡的方法。
option casemap:none 说明程序对大小写敏感。注意,Windows API是大小写敏感的,因此这里这么定义。
使用includelib 链接需要用到的lib库,如:
includelib user32.lib
对于所有要用到的库函数,在程序的开始部分必须预先声明。包括:
函数名称 PROTO[调用规则]:[第一个参数类型][,:后续参数类型]
全部声明所用到的Api函数显然很麻烦,因此可以使用include把声明包括进来,用法和C一样。
.data
数据段
.code
代码段,遇到end代码段结束。
.stack [堆栈大小]
定义堆栈段
有的变量一开始并不赋值,在程序运行过程中才赋值,因此放到:
.data?中。
如果定义一个缓冲区,则:
buffer byte 65536 dup(?)
定义固定变量,程序运行过程中不再修改:
.const
==========
一段Hello World例子代码
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.386
.model flat,stdcall
option casemap:none
MessageBoxA PROTO :dword , :dword, :dword, :dword
MessageBox equ <MessageBoxA>
includelib user32.lib
NULL equ 0
MB_OK equ 0
.stack 4096
.data
szTitle byte 'Hi!',0
szMsg byte 'Hello world!',0
.code
start:
invoke MessageBox, NULL, offset szMsg, offset szTitle, MB_OK
ret
end start
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
编译指令:
ml /coff hello.asm /link /subsystem:windows
1、 立即数寻址,操作数是常数
mov AL,0
mov EAX, 0fffffffH
2、 寄存器寻址,不和内存打交道。
MOV EAX, EBX
除了1、2两种外,其它寻址都要和内存打交道。
3、 直接寻址,给出操作数的内存地址。
定义变量dVar,寻址时
MOV EAX, dVar
MOV dVar, EBX
这相当于在C语言里用指针赋值一样
4、 寄存器间接寻址,地址在寄存器中。
MOV ESI, 00404011H
MOV EAX, [ESI]
5、 寄存器相对寻址,地址是寄存器和一个立即数相加得到的结果。
MOV ESI,00404011H
MOV EAX,[ESI + 4]
6、 基址变址寻址,地址是两个寄存器相加的结果。
MOV ESI, 0040200AH
MOV EBX,4
MOV EDI,[EBX + ESI]
7、 基址变址相对寻址,两个寄存器相加后再加一个立即数
MOV ESI, 0040200AH
MOV EBX,4
MOV EDI,[EBX + ESI + 4]
8、 基址变址比例相对寻址,变址寄存器需要乘以一个比例数。
MOV ESI, 0040200AH
MOV EBX,4
MOV EDI,[EBX*2 + ESI + 4]
段超越
为了确定内存中的地址,还需要借助段寄存器。
如果在基址寄存器中用了ESP(保存栈顶偏移)和EBP(栈数据访问),则段寄存器中的内存操作数在SS中,这是一种默认。
使用段超越前缀能够改变寻址方式中的默认使用的段寄存器。
MOV EAX, CS:[EDI] ;默认是DS
MOV EBX, ES:[ESP – 4] ;默认是SS
MOV DS: [EBP] ,ECX
CS段只能用来读取数据,因此只能作为源操作数的段前缀
MOV CS:[0040200AH] , EAX
Windows 32环境中,FS段保存系统使用的一些信息,如果要访问,必须明确指出段寄存器为FS。另外,Win32环境下,CS、DS、ES、SS在内存中指向同一个段,大小为4G,程序中一般不需要使用段超越前缀。
二进制:后跟b/B、0001b
八进制:后跟o、33o
十进制:后跟D、27d
十六进制:后跟H、2Ah
程序中可以指明默认使用16进制数
.radix 16
简单数据类型
类型 助记符 简写 占用字节数 范围
字节 BYTE DB 1 0~255
字 WORD DW
双字 DWORD DD
远字 FWORD DF
四字 QWORD DQ
十字 TBYTE DT
有符号字节 SBYTE
有符号字 SWORD
有符号双字 SDWORD
定义变量:
[变量名] 助记符 表达式,[,表达式]
表达式是 ? 则不进行初始化
数组定义
ch DWORD 50 DUP (0)
定义ch为数组,共50个元素,每个元素初始值为0。
DUP是重复数据操作符。可以嵌套
ch DWORD 3 DUP (2 DUP (-1,-2))
相当于char ch[3][2], ch[0] = {-1,-2},ch[1] = {-1,-2} 。。。。
PTR ,作用有二,
一是临时改变变量类型:
dVar DWORD 01020304H
MOV AX , WORD PTR dVar
MOV DX, WORD PTR dVar + 2
如果不用PTR就会报错,因为AX是16位,而dVar是双字类型。
MOV BYTE PTR dVar + 1 , 0FFH
MOV BYTE PTR dVar + 3 , 12H
将dVar的第一字节设置成0ffH,第三字节设置成12H。
作用二,指明操作传递的数据类型
有些语句能够知道传递的类型,比如
MOV AL , 0 ;传一个字节
MOV AX , [EBX] ;传一个字
但下面的语句就不知道了:
MOV [EBX] , 0
需要这样写:
MOV BYTE PTR [EBX] , 0
EQU,相当于C语言的#define
比如:
NULL EQU 0
MB_OK EQU 0
等于符号 =
为一个变量或者表达式定义一个等价符号名。
I = 100
DwFirst DWORD I
I = I + 1
DwSecond DWORD I
$ 当前地址计数器值
MOV EAX , $
EAX中是该指令所在的地址。
注意,程序中的每一行都有一个地址。
ORG 为程序的地址计数器赋值
aVar BYTE 01h
ORG $ + 10
bVar BYTE 02h
计数器当前值的基础上增加了10。
Offset 取地址
下面两条语句是等价的
dVar3 DWORD wVar2
dVar3 DWORD offset wVAr2
下面语句不等价
MOV EBX , dVar2
MOV EBX , offset dVar2
Type 返回变量占用字节数
Length 返回变量用DUP重复的数目,无重复返回1。
Size 返回type * length的值
算术操作符
+ - × / MOD
MOV EAX , 4 * 5
MOV EAX , OFFSET dVar2 – 10
Mov eax , 30 / 8
Mov eax , 30 mod 8
逻辑操作符
AND OR XOR NOT
关系操作符
EQ(等于) NE(不等于) LT(小于) LE(小于等于) GT(大于) GE(大于等于)
关系成立,结果为真,关系不成立,结果为假。
在实际寻址时,操作数类型很关键,比如:
MOV AL , -1
MOV AX , -1
MOV EAX , -1
同样是-1,但第一个-1 = 0FFH,第二个-1 = 0FFFFH,第三个-1 = 0FFFFFFFFH 。
数组元素的访问
可以这样定义变量
bVar byte 01h , 02h , 03h , 04h
wVar word 0101h , 0102h , 0103h , 0104h
dVar dword 01010101h , 01010102h , 01010103h , 01010104h
访问时
mov ebx , 2
mov al , bVar[ebx] ; al 中为03h
mov ax , wVar[ebx * 2] ; 加入比例因子,ax 中为0103h
mov eax , dVar[ebx * 4] ;