MASM中标号和变量的命名规范

开心编程网   2010年01月26日 10:56   评论»  

MASM中标号和变量的命名规范

MASM中标号和变量的命名规范是相同的,如下:

1)        可以用字母、数字、下划线及符号@、$和?。

2)        第一个符号不能是数字。

3)        长度不能超过240个字符。

4)        不能使用指令名等关键字。

5)        在作用域内必须是唯一的。

全局变量

全局变量的作用域是整个程序

Win32汇编的全部变量定义在.data或.data?段内,这两个段都是可写的。可以同时定义变量的类型和长度。

全局变量的定义格式如下:

变量名    类型       初始值1,初始值2,……

变量名    类型       重复数量 dup (初始值1,初始值2,……)

MASM支持的变量类型如下表:

名称 表示方式 缩写 长度(字节)
字节 Byte db 1
word dw 2
双字(double word) dword dd 4
三字(far word) fword df 6
四字(quad word) qword dq 8
10字节BCD码(ten byte) tbyte dt 10
有符号字节(sign byte) sbyte   1
有符号字(sign word) sword   2
有符号双字(sign dword) sdword   4
单精度浮点数 Real4   4
双精度浮点数 Real8   8
10字节浮点数 Real10   10

注意:只有定义全局变量的时候,类型才可以用缩写。

在byte类型变量的定义中,可以用引号定义字符串和数值定义的方法混用。

例如:szText        db           ‘Hello,world!’,0dh,0ah,’Hello again’,0dh,0ah,0

全局变量的初始化:

全局变量在定义中既可以指定初值,也可以只用问号预留空间。

全局变量定义在.data?段中时,只能用问号预留空间,因为.data?段不能指定初始值。

定义时用问号指定的全局变量的初始值是0。

局部变量

局部变量的好处是使程序的模块结构更加分明。

局部变量的缺点是因为空间是临时分配的,所以无法定义含有初始化值的变量,对局部变量的初始化一般在子程序中由指令完成。

局部变量的作用域是单个子程序。

局部变量定义在堆栈中。

局部变量的定义格式如下:

local       变量名1[[重复数量]][:类型],变量名2[[重复数量]][:类型] ……

local是MASM提供的伪指令,用于支持局部变量的定义。有了local伪指令降低不少难度。

定义局部变量需注意以下几点:

a)         local伪指令必须紧接在子程序定义的伪指令proc后、其它指令开始之前,因为局部变量的数目必须在子程序开始的时候就确定下来;

b)        定义局部变量时数据类型不能用缩写。如果要定义数据结构,可以用数据结构的名称当作类型;

c)        Win32汇编中,参数的默认类型是dword,如果定义dword类型的局部变量,类型可以省略;

d)        当定义数组类型的局部变量时,重复数量可以用“[]”括起来,不能使用定义全局变量的dup伪指令。

e)         局部变量不能和已定义的全局变量同名。

f)         局部变量的作用域是当前的子程序,所以在不同的子程序中可以有同名的局部变量。

局部变量的初始化:

局部变量无法在定义的时候指定初始化值,因为local伪指令只是为局部变量留出空间。

局部变量的初始值是随机的,所以,对局部变量的值一定要初始化。

一般在子程序中使用指令来初始化局部变量。

使用局部变量时的注意点:

a)         ebp寄存器是关键,它起到保存原始esp寄存器值的作用;

b)        另外,ebp寄存器随时用做存取局部变量的指针基址,所以绝不能把ebp寄存器用于别的用途;

c)        ebp寄存器的值绝对不能被改变,把ebp寄存器的值改掉,程序就玩完;

数据结构

数据结构相当于一种自定义的数据类型,类似C语言中的struct定义。

汇编中,数据结构的定义方法如下:

结构名    struct

字段1     类型       ?

字段2     类型       ?

……

结构名    ends

定义数据结构并不会在某个段中产生数据,只有使用数据结构在数据段中定义数据后,才会产生数据。

使用数据结构在数据段中定义数据的两种方法如下:

第一种定义方法是未初始化的定义方法:

                     .data?

stWndClass    WNDCLASS         <>

……

第二种定义方法是定义的同时指定结构中个字段的初始值:

                     .data

stWndClass    WNDCLASS         <1,1,1,1,1,1,1,1,1,1>

……

汇编中,对数据结构变量的几种引用方法如下:

a)         最直接的方法:

              mov eax,stWndClass.lpfnWndProc

              如果stWndClass结构变量在内存中的起始地址是403000h,那么这句指令会被编译成mov eax,[403004h]

b)        在实际使用中,常有使用指针存取数据结构变量的情况:

              如果使用esi寄存器做指针寻址

              mov esi,offset stWndClass

              mov eax,[esi + WNDCLASS.lpfnWndProc]

              第二句指令将被编译成mov eax,[esi+4]

c)        使用assume伪指令把寄存器预先定义为结构指针,在进行操作:

              mov       esi,offset stWndClass

              assume esi:ptr WNDCLASS

              mov        eax,[esi].lpfnWndClass

              ……

              assume esi:nothing

              编译后产生同样的代码,不过程序的可读性比较好。

              注意:在不使用esi寄存器做指针的时候要用assume esi:nothing取消定义。

结构的嵌套定义如下:

NEW_WNDCLASS      struct

dwOption                     dword            ?

oldWndClass                 WNDCLASS <>

NEW_WNDCLASS       ends

引用嵌套的oldWndClass结构变量的lpfnWndProc字段的方法:

assume esi:ptr NEW_WNDCLASS

mov       eax,[esi].oldWndClass.lpfnWndProc

……

assume esi:nothing

windows.inc文件定义了大部分Win32 API所涉及的常量和数据结构。

以不同的类型访问变量

MASM中以不同的类型访问不会对变量造成影响。而C语言中的数据类型强制转换过程中,数据的内容已经发生变化。

MASM中,如果要用指定类型之外的长度访问变量,必须显式地指出要访问的长度,这样,编译器忽略语法上的长度校验,仅使用变量的地址。

访问变量是显式地指出要访问长度的方法是:

类型       ptr   变量名

例如:

mov       ax,word ptr szBuffer

mov       eax,dword ptr szBuffer

类型可以设置为byte、word、dword、fword、qword、real8和real10。

类型必须和操作的寄存器长度匹配,否则无法通过编译。

需要注意的是:

指定类型的访问变量并不会去检测长度是否溢出。

变量的尺寸和数量

sizeof伪操作符可以取得变量、数据类型或数据结构以字节为单位的长度(尺寸)。

格式:

sizeof     变量、数据类型或数据结构名

lengthof伪操作符可以取得变量、数据类型或数据结构中数据的项数(数量)

格式:

length      变量、数据类型或数据结构名

对字符串使用sizeof伪操作符,取得的长度包括结束符0。

需要注意的是:

sizeof伪操作符和length伪操作符取得的数值是编译期产生的,由编译器直接替换到指令中去。所以,在反汇编得到的代码中没有sizeof或lengthof,而只有它们取得的数值。

取得字符串长度的一种特殊情况:

如果szHello的定义分成两行:

szHello db    ‘Hello’,0dh,0ah

              db    ‘World’,0

sizeof szHello得到的数值是7而不是13。

这种定义方式实质为越界使用字符串变量。

MASM中的变量定义只认一行,后一行db ‘World’,0实际上是另一个没有名称的数据定义。

要取得这种字符串的长度时,千万不能用sizeof伪指令,最好是在程序中用lstrlen函数去计算。

获取变量地址

获取全局变量地址和获取局部变量地址的操作是不同的。

因为全局变量定义在数据段中,而局部变量在堆栈中。全局变量的地址可以在编译期确定,而局部变量的地址只能在运行期确定。

全局变量的地址在编译期已经由编译器确定了。

获取全局变量的地址使用offset伪操作符,这个操作在编译期而不是运行期完成。

mov       寄存器,offset 变量名

不可能用offset伪操作符来获取局部变量地址的原因是:

局部变量是用ebp来做指针访问的,由于ebp的值随着程序的执行环境不同可能是不同的,所以局部变量的地址值在编译期也是不确定的。

获取局部变量的地址使用lea指令

lea指令是80386处理器指令集中的一条指令。

lea eax,[ebp-4]

在invoke伪指令的参数中用到某个局部变量的地址,使用MASM提供的伪操作符addr。

格式为:

addr        局部变量名和全局变量名

addr伪操作符即可用于局部变量,也可用于全局变量

使用addr伪操作符需要注意以下几点:

a)         对局部变量取地址的时候,addr伪操作符只能用在invoke的参数中,不能用在如下的mov指令中。

mov eax,addr 局部变量名           ;这是错误的用法

因为在这句mov指令中,编译器无法把addr伪操作符替换成lea指令。

b)        当在invoke中使用addr伪操作符时,在addr伪操作符的左边不能使用eax寄存器,否则eax寄存器的值会被覆盖掉,当然eax寄存器用在addr伪操作符的右边的参数中是可以的。

MASM对于这种情况会报编译期错误。

条件测试语句

MASM的条件测试的语法和C语言相同。

同样,对于不含比较符的单个变量或寄存器,MASM也是将所有非零值认为是“真”,零值认为是“假”。

与C语言的条件测试相同,MASM的条件测试伪操作符并不会改变被测试的变量或寄存器的值。

MASM的条件测试伪操作符经过编译器编译会翻译成类似cmp或test之类的比较或位测试的指令。

MASM条件测试的基本表达式如下:

寄存器或变量       操作符    操作数

两个以上的表达式可以用逻辑运算符连接:

(表达式1)逻辑运算符(表达式2)逻辑运算符(表达式3)…

条件测试中的操作符和逻辑运算符如下表

操作符和逻辑运算符 操作 用途
== 等于 变量和操作数之间的比较
!= 不等于 变量和操作数之间的比较
> 大于 变量和操作数之间的比较
>= 大于等于 变量和操作数之间的比较
< 小于 变量和操作数之间的比较
<= 小于等于 变量和操作数之间的比较
& 位测试 将变量和操作数做“与”操作
! 逻辑取反 对变量取反或对表达式的结果取反
&& 逻辑与 对两个表达式的结果进行逻辑“与”操作
|| 逻辑或 对两个表达式的结果进行逻辑“或”操作

MASM的条件测试语句有如下几点限制:

a)         表达式的左边只能是变量或寄存器,不能为常数;

b)        表达式的两边不能同时为变量,但可以同时为寄存器;

这些限制来自于80×86的指令。

分支语句

MASM中的分支伪指令的语法如下:

.if 条件表达式1

       表达式1为“真”时执行的指令

[.elseif 条件表达式2]

       表达式2为“真”时执行的指令

[.elseif 条件表达式3]

       表达式3为“真”时执行的指令

[.else]

       所有表达式为“否”时执行的指令

.endif

注意:

关键字if/elseif/else/endif的前面有个小数点,如果不加小数点,就变成宏汇编中的条件汇编伪操作。功能完全不一样。

if/else/endif是宏汇编中条件汇编宏操作的伪操作指令,作用是根据条件决定在最后的可执行文件中包不包括某一段代码。

由.if/.elseif/.else/.endif条件分支伪指令构成的分支结构只能有一个条件被满足。

如果需要构成的分支结构对于所有的表达式为“真”都要执行相应的代码,可以利用多个.if/endif来完成,如下:

.if    表达式1

       表达式1为“真”要执行的指令

.endif

.if    表达式2

       表达式2为“真”要执行的指令

.endif

循环语句

循环语句的语法如下:

.while 条件测试表达式

       指令

       [.break[.if 退出条件]]          ;如果.break伪指令后面跟一个.if测试伪指令的话,那么当退出条件为“真”时才执行.break伪指令。

       [.continue]

.endw

.repeat

       指令

       [.break[.if 退出条件]]          ;如果.break伪指令后面跟一个.if测试伪指令的话,那么当退出条件为“真”时才执行.break伪指令。

       [.continue]

.until 条件测试表达式(或.untilcxz [条件测试表达式])

其中,.while/.break/.continue/.endw/.repeat/.until/.untilcxz都是伪指令。

循环体中可以使用.break伪指令强制退出循环

循环体中可以使用.continue伪指令忽略以后的指令。

.while/.endw和.repeat/.until的区别如下:

a)         前者可能一次也不会执行循环体内的指令,而后者至少会执行一次循环体内的指令。

b)        前者当判断条件为FALSE时退出循环,而后者当判断条件为TRUE时退出循环。

MASM的条件测试总是把操作数当作无符号数看待。

这就是说,在分支和循环的伪指令反汇编后可以发现,在使用>,>=,<和<=比较符时,MASM的伪指令总是将比较以后的跳转指令使用为jb和jnb等无符号数比较跳转的指令。

所以,如果程序中需要构造有符号数的比较分支或循环结构,那么必须另外用jl和jg等有符号数比较跳转的指令来完成,使用条件测试配合分支或循环伪指令可能会得到错误的结果

欢迎您发表评论:

赞助商链接

最新新闻动态

友情链接

关于站点 - 联系我们 - 网站大事 - 友情链接 - 免责声明 - 意见反馈 - 网站投稿 - 站点地图
版权所有开心编程网禁止转载! Copyright © 2009-2010 All Rights Reserved. Email:hbhgfzk@126.com