按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
:00401035 E8D1FFFFFF call 0040100B
; 这里是invoke Sub3;1;2 — StdCall类型
:0040103A 6A02 push 00000002
:0040103C 6A01 push 00000001
:0040103E E8D5FFFFFF call 00401018
可以清楚地看到,在参数入栈顺序上,C类型和StdCall类型是先把右边的参数先压入堆栈,而PASCAL类型是先把左边的参数压入堆栈。在堆栈平衡上,C类型是在调用者在使用call指令完成后,自行用add esp;8指令把8个字节的参数空间清除,而PASCAL和StdCall的调用者则不管这个事情,堆栈平衡的事情是由子程序用ret 8来实现的,ret指令后面加一个操作数表示在ret后把堆栈指针esp加上操作数,完成的是同样的功能。
Win32约定的类型是StdCall,所以在程序中调用子程序或系统API后,不必自己来平衡堆栈,免去了很多麻烦。
存取参数和局部变量都是通过堆栈来定义的,所以参数的存取也是通过ebp做指针来完成的。在探讨局部变量的时候,已经就没有参数的情况下ebp指针和局部变量的对应关系做了分析,现在来分析一下ebp指针和参数之间的对应关系,注意,这里是以Win32中的StdCall为例,不同的语言类型,指针的顺序可能是不同的。
假定在一个子程序中有两个参数,主程序调用时在 push 第一个参数前的堆栈指针esp为X,那么压入两个参数后的esp为X…8,程序开始执行call指令,call指令把返回地址压入堆栈,这时候eps为X…C,接下去是子程序中用push ebp来保存ebp的值,esp变为X…10,再执行一句mov ebp;esp,就可以开始用ebp存取参数和局部变量了,图3。4说明了这个过程。
图3。4 ebp指针、参数和局部变量的关系
在源程序中,由于参数、局部变量和ebp的关系是由编译器自动维护的,所以读者不必关心它们的具体关系,但到了用Soft…ICE等工具来分析其他软件的时候,遇到调用子程序的时候一定要先看清楚它们之间的类型差别。
在子程序中使用参数,可以使用与存取局部变量同样的方法,因为这两者的构造原理几乎一模一样,所以,在子程序中有invoke语句时,如果要用到输入参数的地址当做invoke的参数,同样要遵循局部变量的使用方式,不能用offset伪操作符,只能用addr来完成。同样,所有对局部变量使用的限制几乎都可以适用于参数。
来源:电子工业出版社 作者:罗云彬 上一页 回书目 下一页
上一页 回书目 下一页
第3章 使用MASM
3。5 高 级 语 法(1)
以前高级语言和汇编的最大差别就是条件测试、分支和循环等高级语法。高级语言中,程序员可以方便地用类似于if,case,loop和while等语句来构成程序的结构流程,不仅条理清楚、一目了然,而且维护性相当好。而汇编程序员呢?只能在cmp指令后面绞尽脑汁地想究竟用几十种跳转语句中的哪一种,这里就能列出近三十个条件跳转指令来:ja,jae,jb,jbe,jc,je,jg,jge,jl,jle,jna,jnae,jnb,jnbe,jnc,jne,jng,jnge,jnl,jnle,jno,jnp,jns,jnz,jo,jp,jpe,jpo和jz等。虽然其中的很多指令我们一辈子也不会用到,但就是这些指令和一些loop,loopnz以及被loop涉及的ecx等寄存器纠缠在一起,使在汇编中书写结构清晰、可读性好的代码变得相当困难,这也是很多人视汇编为畏途的一个原因。
现在好了,MASM中新引入了一系列的伪指令,涉及条件测试、分支和循环语句,利用它们,汇编语言有了和高级语言一样的结构,配合对局部变量和调用参数等高级语言中常见元素的支持,为使用Win32汇编编写大规模的应用程序奠定了基础。
3。5。1 条件测试语句
在高级语言中,所有的分支和循环语句首先要涉及条件测试,也就是涉及一个表达式的结果是“真”还是“假”的问题,表达式中往往有用来做比较和计算的操作符,MASM也不例外,这就是条件测试语句。
MASM条件测试的基本表达式是:
寄存器或变量 操作符 操作数
两个以上的表达式可以用逻辑运算符连接:
(表达式1)逻辑运算符(表达式2) 逻辑运算符(表达式3)…
允许的操作符和逻辑运算符如表3。5所示。
表3。5 条件测试中的操作符
操作符和逻辑运算 操 作 用 途
等于
变量和操作数之间的比较
!=
不等于
变量和操作数之间的比较
》
大于
变量和操作数之间的比较
》=
大于等于
变量和操作数之间的比较
续表
操作符和逻辑运算 操 作 用 途
《
小于
变量和操作数之间的比较
=3)&&ebx ;y大于等于3且ebx为非零值
(z&1)||!eax ;z和1进行“与”操作后非零或eax取反后非零
;也就是说x的位0等于1或者eax为零
细心的读者一定会发现,MASM的条件测试采用的是和C语言相同的语法。如!和&是对变量的操作符(取反和“与”操作),||和&&是表达式结果之间的逻辑“与”和逻辑“或”,而、!=、》、《 等是比较符。同样,对于不含比较符的单个变量或寄存器,MASM也是将所有非零值认为是“真”,零值认为是“假”。
MASM的条件测试语句有几个限制,首先是表达式的左边只能是变量或寄存器,不能为常数;其次表达式的两边不能同时为变量,但可以同时是寄存器。这些限制来自于80x86的指令,因为条件测试伪操作符只是简单地把每个表达式翻译成cmp或test指令,80x86的指令集中没有cmp 0;eax之类的指令,同时也不允许直接操作两个内存中的数,所以对这两个限制是很好理解的。
除了这些和高级语言类似的条件测试伪操作,汇编语言还有特殊的要求,就是程序中常常要根据系统标志寄存器中的各种标志位来做条件跳转,这些在高级语言中是用不到的,所以又增加了以下一些标志位的状态指示,它们本身相当于一个表达式:
CARRY? 表示Carry位是否置位
OVERFLOW? 表示Overflow位是否置位
PARITY? 表示Parity位是否置位
SIGN? 表示Sign位是否置位
ZERO? 表示Zero位是否置位
要测试eax等于ebx同时Zero位置位,条件表达式可以写为:
(eaxebx) && ZERO?
要测试eax等于ebx同时Zero位清零,条件表达式可以写为:
(eaxebx) && ! ZERO?
和C语言的条件测试同样,MASM的条件测试伪指令并不会改变被测试的变量或寄存器的值,只是进行“测试”而已,到最后它会被编译器翻译成类似于cmp或test之类的比较或位测试指令。
3。5。2 分支语句
分支语句用来根据条件表达式测试的真假执行不同的代码模块,MASM中的分支语句的语法如下:
。if 条件表达式1
表达式1为“真”时执行的指令
'。elseif 条件表达式2'
表达式2为“真”时执行的指令
'。elseif 条件表达式3'
表达式3为“真”时执行的指令
…
'。else'
所有表达式为“否”时执行的指令
。endif
注意:关键字if/elseif/else/endif的前面有个小数点,如果不加小数点,就变成宏汇编中的条件汇编伪操作了,结果可是天差地别。
为了说明编译器究竟是如何处理这些伪指令的,先写一段如下的源代码:
。if eax && (ebx 》= dwX) || !(dwY != ecx)
mov esi;1
。elseif edx
mov esi;2
。elseif esi & 1
mov esi;3
。elseif ZERO? && CARRY?
mov esi;4
。endif
然后反汇编:
; 。if eax
:00401000 0BC0 or eax; eax
:00401002 7408 je 0040100C
; (ebx = dwX)
:00401004 3B1D00304000 cmp ebx; dword ptr '00403000'
:0040100A 7308 jnb 00401014
; (dwY != ecx)
:0040100C 390D04304000 cmp dword ptr '00403004'; ecx
:00401012 7507 jne 0040101B
:00401014 BE01000000 mov esi; 00000001
:00401019 EB23 jmp 0040103E
; elseif edx
:0040101B 0BD2 or edx; edx
:0040101D 7407 je 00401026
:0040101F BE02000000 mov esi; 00000002
:00401024 EB18 jmp 0040103E
; elseif esi & 1
:00401026 F7C601000000 test esi; 00000001
:0040102C 7407 je 00401035
:0040102E BE03000000 mov esi; 00000003
:00401033 EB09 jmp 0040103E
; ZERO?
:00401035 7507 jne 0040103E
; CARRY?
:00401037 7305 jnb 0040103E
:00401039 BE04000000 mov esi; 00000004
:0040103E …
来源:电子工业出版社 作者:罗云彬 上一页 回书目 下一页
上一页 回书目 下一页
第3章 使用MASM
3。5 高 级 语 法(2)
可以看到,MASM编译器对这些条件分支伪指令优化得相当好,看到这些反汇编后的指令,惟一的感觉是好像又回到了DOS汇编时代分支指令堆中,从这里可以发现,这些伪指令把汇编程序的可读性基本上提高到了高级语言的水平。
分析反汇编代码可以发现,在不同的条件满足之后,先是执行满足条件后需要执行的指令,如上面的mov esi,0001和mov esi;0002等指令,这些指令执行后,后面都有一句直接跳转的指令jmp 0040103E,0040103E地址对应整个条件分支结构的尾部,这意味着,由 。if/。elseif/。else/。endif条件分支伪指令构成的分支结构只能有一个条件被满足,也就是说,程序按照从上到下的各个条件表达式,顺序判断,当第一个条件表达式满足的时候,执行相应的代码,然后就忽略掉下面所有的其他条件表达式,即使后面有另一个满足条件时也是如此!
如果需要构成的分支结构对于所有的表达式为“真”都要执行相应的代码,可以利用多个 。if/。endif来完成,如下所示:
。if 表达式1
表达式1为“真”要执行的指令
。endif
。if 表达式2
表达式2为“真”要执行的指令
。endif
…
使用 。if/。else/。endif构成分支伪指令的时候,不要漏写前面的小数点,if/else/endif是宏汇编中条件汇编宏操作的伪操作指令,作用是根据条件决定在最后的可执行文件中包不包括某一段代码。比如在程序的调试阶段:
DEBUG equ 1
…
if DEBUG
invoke MessageBox;NULL;offset szText;offset szCaption;MB_OK
endif
该代码用来显示一个调试信息,当程序正式发行时,将第一句改为DEBUG equ 0,然后再编译,那么可执行文件中根本不会包括这段代码,这和。if/。else/。endif构成分支的伪指令完全是两回事情。
3。5。3 循环语句
循环是重复执行的一组指令,MASM的循环伪指令可以根据条件表达式的真假来控制循环是否继续,也可以在循环体中直接退出,使用循环的语法是:
。while 条件测试表达式
指令
'。break '。if 退出条件''
'ntinue'
。endw
或
。repeat
指令
'。break '。if 退出条件''
'ntinue'
。until 条件测试表达式 (或。untilcxz '条件测试表达式')
。while/。endw循环首先判断条件测试表达式,如果结果是“真”,则执行循环体内的指令,结束后再回到 。while处判断表达式,如此往复,一直到表达式结果为“假”为止。。while/。endw指令有可能一遍也不会执行到循环体内的指令,因为如果第一次判断表达式时就遇到结果为“假”的情况,那么就直接退出循环。
。repeat/。until循环首先执行一遍循环体内的指令,然后再判断条件测试表达式,如果结果为“真”的话,就退出循环,如果为“假”,则返回 。repeat处继续循环,可以看出,。repeat/。until不管表达式的值如何,至少会执行一遍循环体内的指令。
也可以把条件表达式直接设置为固定值,这样就可以构建一个无限循环,对于。while/。end直接使用TRUE,对 。repeat/。until直接使用FALSE来当表达式就是如此,这种情况下,可以使用 。break伪指令强制退出循环,如果 。break伪指令后面跟一个 。if测试伪指令的话,那么当退出条件为“真”时才执行 。break伪指令。
在循环体中也可以用 ntinue伪指令忽略以后的指令,遇到 ntinue伪指令时,不管下面还有没有其他循环体中的指令,都会直接回到循环头部开始执行。
同样,为了深入了解MASM编译器把循环伪指令变成了什么,下面对比一段源程序和反汇编后的代码。首先是源程序:
。while eax 》 1
mov esi;1
。break 。if ebx
ntinue
mov esi;2
。endw
。repeat
mov esi;1
。break 。if !ebx
ntinue
mov esi;2
。until eax 》 1
。repeat
mov esi;1
。break
。untilcxz
以下是反汇编后的代码:
; 。while 第一个循环开始
:00401000 EB10 jmp 00401012
:00401002 BE01000000 mov esi; 00000001
:00401007 0BDB or ebx; ebx
; 。break 。if ebx
:00401009 750C jne 00401017
; ntinue
:0040100B EB05 jmp 00401012
:0040100D BE02000000 mov esi; 00000002
; 。while eax 》 1
:00401012 83F801 cmp eax; 00000001
:00401015 77EB ja 00401002
; 。repeat 第二个循环开始
:00401017 BE01000000 mov esi; 00000001
; 。break 。if !ebx
:0040101C 0BDB or ebx; ebx
:0040101E 740C je 0040102C
; ntinue
:00401020 EB05 jmp 00401027
:00401022 BE02000000 mov esi; 00000002
; 。until eax 》 1
:00401027 83F801 cmp eax; 00000001
:0040102A 76EB jbe 00401017
; 。repeat 第三个循环开始
:0040102C BE01000000 mov esi; 00000001
; 。break
:00401031 EB02 jmp 00401035
; 。untilcxz
:00401033 E2F7 loop 0040102C ;注意这里是loop指令!
对比伪指令和翻译成的实际指令,可以对循环的伪指令有更好的理解:。break翻译成一个跳转指令跳到循环结束的地方,ntinue是一个无条件跳转指令跳到循环开始的地方,。while是先比较条件再执行循环体,而 。repeat是先执行循环体再比较条件的。
对指令的分析中可以发现,。while/。endw和 。repeat/。