友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
windows环境下32位汇编语言程序设计-第6部分
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!
mov eax;'esi + WNDCLASS。lpfnWndProc'
注意:第二句是'esi + WNDCLASS。lpfnWndProc'而不是'esi + stWndClass。lpfnWndProc',因为前者会被编译成mov eax;'esi+4',而后者会被编译成mov eax;'esi+403004h',后者的结果显然是错误的!如果要对一个数据结构中的大量字段进行操作,这种写法显然比较烦琐,MASM还有一个用法,可以用assume伪指令把寄存器预先定义为结构指针,再进行操作:
mov esi;offset stWndClass
assume esi:ptr WNDCLASS
mov eax;'esi'。lpfnWndProc
…
assume esi:nothing
这样,使用寄存器也可以用逗点引用字段名,程序的可读性比较好。这样的写法在最后编译成可执行程序的时候产生同样的代码。注意:在不再使用esi寄存器做指针的时候要用assume esi:nothing取消定义。
结构的定义也可以嵌套,如果要定义一个新的NEW_WNDCLASS结构,里面包含一个老的WNDCLASS结构和一个新的dwOption字段,那么可以如下定义:
NEW_WNDCLASS struct
DwOption dword ?
OldWndClass WNDCLASS
NEW_WNDCLASS ends
假设现在esi是指向一个NEW_WNDCLASS的指针,那么引用里面嵌套的oldWndClass中的lpfnWndProc字段时,就可以用下面的语句:
mov eax;'esi'。oldWndClass。lpfnWndProc
结构的嵌套在Windows的数据定义中也常有,比如在第13章13。3节中使用的DEBUG_EVENT结构中竟然使用了4层数据结构的嵌套。 熟练掌握数据结构的使用对Win32汇编编程是很重要的!
来源:电子工业出版社 作者:罗云彬 上一页 回书目 下一页
上一页 回书目 下一页
第3章 使用MASM
3。3 标号、变量和数据结构(4)
3。3。5 变量的使用
1。 以不同的类型访问变量
这个话题有点像C语言中的数据类型强制转换,C语言中的类型转换指的是把一个变量的内容转换成另外一种类型,转换过程中,数据的内容已经发生了变化,如把浮点数转换成整数后,小数点后的内容就丢失了。在MASM中以不同的类型访问不会对变量造成影响。
举一个简单的例子,先以db方式定义一个缓冲区:
szBuffer db 1024 dup (?)
然后从其他地方取得了数据,但数据的格式是以字方式组织的,要处理数据,最有效的方法是两个字节两个字节地处理,但如果在程序中把szBuffer的值放入ax:
mov ax;szBuffer
编译器会报一个错:
error A2070: invalid instruction operands
意思是无效的指令操作,为什么呢?因为szBuffer是用db定义的,而ax的尺寸是一个word,等于两个字节,尺寸不符合。MASM中,如果要用指定类型之外的长度访问变量,必须显式地指出要访问的长度,这样,编译器忽略语法上的长度检验,仅使用变量的地址。使用的方法是:
类型 ptr 变量名
类型可以是byte,word,dword,fword,qword,real8和real10。如:
mov ax;word ptr szBuffer
mov eax;dword ptr szBuffer
上述语句能通过编译,当然,类型必须和操作的寄存器长度匹配。在这里要注意的是,指定类型的参数访问并不会去检测长度是否溢出,看下面一段代码:
。data
bTest1 db 12h
wTest2 dw 1234h
dwTest3 dd 12345678h
…
de
…
mov al;bTest1
mov ax;word ptr bTest1
mov eax;dword ptr bTest1
…
上面的程序片断,每一句执行后寄存器中的值是什么呢,mov al;bTest1这一句很显然使al等于12h,下面的两句呢,ax和eax难道等于0012h和00000012h吗?实际运行结果很“奇怪”,竟然是3412h和78123412h,为什么呢?先来看反汇编的内容:
;。data段中的变量
:00403000 12 34 12 78 56 34 12 。。。
│ │ │
│ │ └─→ dwTest3
│ └──────→ wTest2
└─────────→ bTest1
;de段中的代码
:00401000 A000304000 mov al; byte ptr '00403000'
:00401005 66A100304000 mov ax; word ptr '00403000'
:0040100B A100304000 mov eax; dword ptr '00403000'
。data段中的变量是按顺序从低地址往高地址排列的,对于超过一个字节的数据,80386处理器的数据排列方式是低位数据在低地址,所以wTest2的1234h在内存中的排列是34h 12h,因为34h是低位。同样,dwTest3在内存中以78h 56h 34h 12h从低地址往高地址存放,在执行指令mov ax;word ptr bTest1的时候,是从bTest1的地址403000h处取一个字,其长度已经超过了bTest1的范围并落到了wTest2中,从内存中看,是取了bTest1的数据12h和wTest2的低位34h,在这两个字节中,12h位于低地址,所以ax中的数值是3412h。同样道理,看另一条指令:
mov eax;dword ptr bTest1
这条指令取了bTest1,wTest2的全部和dwTest3的最低位78h,在内存中的排列是12h 34h 12h 78h,所以eax等于78123412h。
这个例子说明了汇编中用ptr强制覆盖变量长度的时候,实质上是只用了变量的地址而禁止编译器进行检验,编译器并不会考虑定界的问题,程序员在使用的时候必须对内存中的数据排列有个全局概念,以免越界存取到意料之外的数据。
如果程序员的本意是类似于C语言的强制类型转换,想把bTest1的一个字节扩展到一个字或一个双字再放到ax或eax中,高位保持0而不是越界存取到其他的变量,可以用80386的扩展指令来实现。80386处理器提供的movzx指令可以实现这个功能,例如:
movzx ax;bTest1 ;例1
movzx eax;bTest1 ;例2
movzx eax;cl ;例3
movzx eax;ax ;例4
● 例1把单字节变量bTest1的值扩展到16位放入ax中。
● 例2把单字节变量bTest1的值扩展到32位放入eax中。
● 例3把cl中的8位值扩展到32位放入eax中。
● 例4把ax中的16位值扩展到32位放入eax中。
用movzx指令进行数据长度扩展是Win32汇编中经常用到的技巧。
2。 变量的尺寸和数量
在源程序中用到变量的尺寸和数量的时候,可以用sizeof和lengthof伪指令来实现,格式是:
sizeof 变量名、数据类型或数据结构名
lengthof 变量名、数据类型或数据结构名
sizeof伪指令可以取得变量、数据类型或数据结构以字节为单位的长度,lengthof可以取得变量中数据的项数。假如定义了以下数据:
stWndClass WNDCLASS
szHello db 'Hello;world!';0
dwTest dd 1;2;3;4
…
de
…
mov eax;sizeof stWndClass
mov ebx;sizeof WNDCLASS
mov ecx;sizeof szHello
mov edx;sizeof dword
mov esi;sizeof dwTest
执行后eax的值是stWndClass结构的长度40,ebx同样是40,ecx的值是13,就是“Hello;world!”字符串的长度加上一个字节的0结束符,edx的值是一个双字的长度:4,而esi则等于4个双字的长度16。
如果把所有的sizeof换成lengthof,那么eax会等于1,因为只定义了1项WNDCLASS,而ecx同样等于13,esi则等于4,而lengthof WNDCLASS和lengthof dword是非法的用法,编译程序会报错。
要注意的是,sizeof和lengthof的数值是编译时候产生的,由编译器传递到指令中去,上边的指令最后产生的代码就是:
mov eax;40
mov ebx;40
mov ecx;13
mov edx;4
mov esi;16
如果为了把Hello和World分两行定义,szHello是这样定义的:
szHello db 'Hello';0dh;0ah
db 'World';0
那么sizeof szHello是多少呢?注意!是7而不是13,MASM中的变量定义只认一行,后一行db 'World';0实际上是另一个没有名称的数据定义,编译器认为sizeof szHello是第一行字符的数量。虽然把szHello的地址当参数传给MessageBox等函数显示时会把两行都显示出来,但严格地说这是越界使用变量。虽然在实际的应用中这样定义长字符串的用法很普遍,因为如果要显示一屏幕帮助,一行是不够的,但要注意的是:要用到这种字符串的长度时,千万不要用sizeof去表示,最好是在程序中用lstrlen函数去计算。
来源:电子工业出版社 作者:罗云彬 上一页 回书目 下一页
上一页 回书目 下一页
第3章 使用MASM
3。3 标号、变量和数据结构(5)
3。 获取变量地址
获取变量地址的操作对于全局变量和局部变量是不同的。
对于全局变量,它的地址在编译的时候已经由编译器确定了,它的用法大家都不陌生:
mov 寄存器;offset 变量名
其中offset是取变量地址的伪操作符,和sizeof伪操作符一样,它仅把变量的地址代到指令中去,这个操作是在编译时而不是在运行时完成的。
对于局部变量,它是用ebp来做指针操作的,假设ebp的值是40100h,那么局部变量1的地址是ebp…4即400FCh,由于ebp的值随着程序的执行环境不同可能是不同的,所以局部变量的地址值在编译的时候也是不确定的,不可能用offset伪操作符来获取它的地址。
80386处理器中有一条指令用来取指针的地址,就是lea指令,如:
lea eax;'ebp…4'
该指令可以在运行时按照ebp的值实际计算出地址放到eax中。
如果要在invoke伪指令的参数中用到一个局部变量的地址,该怎么办呢?参数中是不可能写入lea指令的,用offset又是不对的。MASM对此有一个专用的伪操作符addr,其格式为:
addr 局部变量名和全局变量名
当addr后跟全局变量名的时候,用法和offset是相同的;当addr后面跟局部变量名的时候,编译器会自动用lea指令先把地址取到eax中,然后用eax来代替变量地址使用。注意addr伪操作符只能在invoke的参数中使用,不能用在类似于下列的场合:
mov eax;addr 局部变量名 ;注意:错误用法
假设在一个子程序中有如下invoke指令:
invoke Test;eax;addr szHello
其中Test是一个需要两个参数的子程序,szHello是一个局部变量,会发生什么结果呢?编译器会把invoke伪指令和addr翻译成下面这个模样:
lea eax;'ebp…4'
push eax ;参数2:addr szHello
push eax ;参数1:eax
call Test
发现了什么?到push第一个参数eax之前,eax的值已经被lea eax;'ebp…4'指令覆盖了!也就是说,要用到的eax的值不再有效,所以,当在invoke中使用addr伪操作符时,注意在它的前面不能用eax,否则eax的值会被覆盖掉,当然eax在addr的后面的参数中用是可以的。幸亏MASM编译器对这种情况有如下错误提示:
error A2133: register value overwritten by INVOKE
否则,不知道又会引出多少莫名其妙的错误!
来源:电子工业出版社 作者:罗云彬 上一页 回书目 下一页
上一页 回书目 下一页
第3章 使用MASM
3。4 使用子程序
当程序中相同功能的一段代码用得比较频繁时,可以将它分�
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!