CH3、Windows汇编基础
.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] ;