八喜电子书 > 经管其他电子书 > windows环境下32位汇编语言程序设计 >

第5部分

windows环境下32位汇编语言程序设计-第5部分

小说: windows环境下32位汇编语言程序设计 字数: 每页4000字

按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!



鲎纸诓皇�7吗,为什么是8呢?这是因为在80386处理器中,以dword为界对齐时存取内存速度最快,所以MASM宁可浪费一个字节,执行了这3句指令后,初始化完成,就可以进行正常的操作了,从指令中可以看出局部变量在堆栈中的位置排列,如表3。3所示。

表3。3  上例中局部变量排列的顺序

ebp偏移                 内    容
 
ebp+4
 由call推入的返回地址
 
Ebp
 push ebp指令推入的原ebp值,然后新的ebp=现在的esp
 
ebp…4
 第一个局部变量
 
ebp…6
 第二个局部变量
 
ebp…7
 第三个局部变量
 

在程序退出的时候,必须把正确的esp设置回去,否则,ret指令会从堆栈中取出错误的地址返回,看程序可以发现,ebp就是正确的esp值,因为子程序开始的时候已经有一句mov ebp;esp,所以要返回的时候只要先mov esp;ebp,然后再pop ebp,堆栈就是正确的了。

在80386指令集中有一条指令可以在一句中实现这个功能,就是leave指令,所以,编译器在ret指令之前只使用了一句leave指令。

明白了局部变量使用的原理,就很容易理解使用时的注意点:ebp寄存器是关键,它起到保存原始esp的作用,并随时用做存取局部变量的指针基址,所以在任何时刻,不要尝试把ebp用于别的用途,否则会带来意想不到的后果。

Win32汇编中局部变量的使用方法可以解释一个很有趣的现象:在DOS汇编的时候,如果在子程序中的push指令和pop指令不配对,那么返回的时候ret指令从堆栈里得到的肯定是错误的返回地址,程序也就死掉了。但在Win32汇编中,push指令和pop指令不配对可能在逻辑上产生错误,却不会影响子程序正常返回,原因就是在返回的时候esp不是靠相同数量的push和pop指令来保持一致的,而是靠leave指令从保存在ebp中的原始值中取回来的,也就是说,即使把esp改得一塌糊涂也不会影响到子程序的返回,当然,“窍门”就在ebp,把ebp改掉,程序就玩完了!

2。 局部变量的初始化值

显然,局部变量是无法在定义的时候指定初始化值的,因为local伪指令只是简单地把空间给留出来,那么开始使用时它里面是什么值呢?和全局变量不一样,局部变量的起始值是随机的,是其他子程序执行后在堆栈里留下的垃圾,所以,对局部变量的值一定要初始化,特别是定义为结构后当参数传递给API函数的时候。

 在API函数使用的大量数据结构中,往往用0做默认值,如果用局部变量定义数据结构,初始化时只定义了其中的一些字段,那么其余字段的当前值可以是编程者预想不到的数值,传给API函数后,执行的结果可能是意想不到的,这是初学者很容易忽略的一个问题。所以最好的办法是:在赋值前首先将整个数据结构填0,然后再初始化要用的字段,这样其余的字段就不必一个个地去填0了,RtlZeroMemory这个API函数就是实现填0的功能的。



 
来源:电子工业出版社 作者:罗云彬 上一页         回书目         下一页          
上一页         回书目         下一页          
  


第3章 使用MASM


3。3 标号、变量和数据结构(3)

    
3。3。4  数据结构

数据结构实际上是由多个字段组成的数据“样板”,相当于一种自定义的数据类型,数据结构中间的每一个字段可以是字节、字、双字、字符串或所有可能的数据类型。比如在API函数RegisterClass中要使用到一个叫做WNDCLASS的数据结构,Microsoft的手册上是如下定义的:

typedef struct _WNDCLASS {

    UINT         style;

    WNDPROC      lpfnWndProc;

    Int          cbClsExtra;

    int          cbWndExtra;

    HINSTANCE    hInstance;

    HICON        hIcon;

    HCURSOR      hCursor;

    HBRUSH       hbrBackground;

    LPCTSTR      lpszMenuName;

    LPCTSTR      lpszClassName;

} WNDCLASS; *PWNDCLASS;

注意,这是C语言格式的,这个数据结构包含了10个字段,字段的名称是style,lpfnWndProc和cbClsExtra等,前面的UINT和WNDPROC等是这些字段的类型,在汇编中,数据结构的写法如下:

结构名    struct

 

字段1    类型      ?

字段2    类型      ?

……

 

结构名    ends

上面的WNDCLASS结构定义用汇编的格式来表示就是:

WNDCLASS        struct

 

Style           DWORD      ?

LpfnWndProc     DWORD      ?

cbClsExtra      DWORD      ?

cbWndExtra      DWORD      ?

hInstance       DWORD      ?

hIcon           DWORD      ?

hCursor         DWORD      ?

hbrBackground   DWORD      ?

lpszMenuName    DWORD      ?

lpszClassName   DWORD      ?

 

WNDCLASS            ends

和大部分的常量一样,几乎所有API所涉及的数据结构在Windows。inc文件中都已经有定义了。要注意的是,定义了数据结构实际上只是定义了一个“样板”,上面的定义语句并不会在哪个段中产生数据,和Word中使用各种“信纸”与“文书”等模板类似,定义了数据结构以后就可以多次在源程序中用这个“样板”当做数据类型来定义数据,使用数据结构在数据段中定义数据的方法如下:

                。data?

stWndClass      WNDCLASS        

                  …

或者:

                  。data

stWndClass      WNDCLASS        

                  ……

这个例子定义了一个以WNDCLASS为结构的变量stWndClass,第一段的定义方法是未初始化的定义方法,第二段是在定义的同时指定结构中各字段的初始值,各字段的初始值用逗号隔开,在这个例子中10个字段的初始值都指定为1。

在汇编中,数据结构的引用方法有好几种,以上面的定义为例,如果要使用stWndClass中的lpfnWndProc字段,最直接的办法是:

mov     eax;stWndClass。lpfnWndProc

它表示把lpfnWndProc字段的值放入eax中去,假设stWndClass在内存中的地址是403000h,这句指令会被编译成mov eax;'403004h',因为lpfnWndProc是stWndClass中的第二个字段,第一个字段是dword,已经占用了4字节的空间。

在实际使用中,常常有使用指针存取数据结构的情况,如果使用esi寄存器做指针寻址,可以使用下列语句完成同样的功能:

mov     esi;offset stWndClass

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          变量名、数

返回目录 上一页 下一页 回到顶部 赞(1) 踩(1)

你可能喜欢的