在主程序中用call指令来调用子程序。

Win32汇编中的子程序也采用堆栈来传递参数,这样就可以用invoke伪指令来进行调用和语法检查工作。

一. 子程序的定义
子程序的定义方式如下所示。
子程序名  proc [距离][语言类型][可视区域][USES 寄存器列表][,参数:类型]...[VARARG]
         local 局部变量列表
         指令
子程序名  endp

proc和endp伪指令定义了子程序开始和结束的位置, proc后面跟的参数是子程序的属性和输入参数。子程序的属性有:
1.距离——可以是NEAR,FAR,NEAR16,NEAR32,FAR16或FAR32,Win32中只有一个平坦的段,无所谓距离,所以对距离的定义往往忽略。
2.语言类型——表示参数的使用方式和堆栈平衡的方式,可以是StdCall,C,SysCall,BASIC、FORTRAN和PASCAL,如果忽略,则使用程序头部 .model定义的值。
3.可视区域——可以是PRIVATE,PUBLIC和EXPORT。PRIVATE表示子程序只对本模块可见;PUBLIC表示对所有的模块可见(在最后编译链接完成的 .exe文件中);EXPORT表示是导出的函数,当编写DLL的时候要将某个函数导出的时候可以这样使用。默认的设置是PUBLIC。
4.   USES寄存器列表——表示由编译器在子程序指令开始前自动安排push这些寄存器的指令,并且在ret前自动安排pop指令,用于保存执行环境,但笔者认为不如自己在开头和结尾用pushad和popad指令一次保存和恢复所有寄存器来得方便。
5.   参数和类型——参数指参数的名称,在定义参数名的时候不能跟全局变量和子程序中的局部变量重名。对于类型,由于Win32中的参数类型只有32位(dword)一种类型,所以可以省略。在参数定义的最后还可以跟VARARG,表示在已确定的参数后还可以跟多个数量不确定的参数,在Win32汇编中惟一使用VARARG的API就是wsprintf,类似于C语言中的printf,其参数的个数取决于要显示的字符串中指定的变量个数。

完成了定义之后,可以用invoke伪指令来调用子程序.

如果调用在定义之前, 则报这个错误, 声明一下即可(使用proto声明).
error A2006: undefined symbol : _ProcWinMain
在写源程序的时候有意识地把子程序的位置提到invoke语句的前面,省略掉proto语句,可以简化程序和避免出错。

二.参数传递和堆栈平衡
在调用子程序时,参数的传递是通过堆栈进行的,也就是说,调用者把要传递给子程序的参数压入堆栈,子程序在堆栈中取出相应的值再使用,比如,如果要调用:
SubRouting(Var1, Var2, Var3)

经过编译后的最终代码可能是(注意只是“可能”):
push  Var3
push  Var2
push  Var1
call  SubRouting
add   esp,12

也就是说,调用者首先把参数压入堆栈,然后调用子程序,在完成后,由于堆栈中先前压入的数不再有用,调用者或者被调用者必须有一方把堆栈指针修正到调用前的状态,即堆栈的平衡。参数是最右边的先入堆栈还是最左边的先入堆栈、还有由调用者还是被调用者来修正堆栈都必须有个约定,不然就会产生错误的结果,这就是在上述文字中使用“可能”这两个字的原因。各种语言中调用子程序的约定是不同的,所以在proc以及proto语句的语言属性中确定语言类型后,编译器才可能将invoke伪指令翻译成正确的样子,不同语言的不同点如表3.4所示。

表3.4  不同语言调用方式的差别
     C  SysCall     StdCall  BASIC FORTRAN  PASCAL
最先入栈参数  右  右   右   左  左   左 
清除堆栈者   调用者 子程序  子程序  子程序 子程序  子程序 
允许使用VARARG  是  是   是   否  否   否
注:VARARG 表示参数的个数可以是不确定的,如wsprintf函数,本表中特殊的地方是StdCall 的堆栈清除平时是由子程序完成的,但使用VARARG 时是由调用者清除的。

为了了解编译器对不同类型子程序的处理方式,先来看一段源程序:
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Sub1    proc         C _Var1,_Var2
        mov          eax,_Var1
        mov          ebx,_Var2
        ret
Sub1    endp

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Sub2    proc         PASCAL _Var1,_Var2
        mov          eax,_Var1
        mov          ebx,_Var2
        ret
Sub2    endp

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Sub3    proc         _Var1,_Var2
        mov          eax,_Var1
        mov          ebx,_Var2
        ret
b3  endp

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
        …
        invoke       Sub1,1,2
        invoke       Sub2,1,2
        invoke       Sub3,1,2

编译后再进行反汇编,看编译器是如何转换处理不同类型的子程序的:

; 这里是Sub1 - C类型
:00401000 55                    push ebp
:00401001 8BEC                  mov ebp, esp
:00401003 8B4508                mov eax, dword ptr [ebp+08]
:00401006 8B5D0C                mov ebx, dword ptr [ebp+0C]
:00401009 C9                    leave
:0040100A C3                    ret

; 这里是Sub2 - PASCAL类型
:0040100B 55                    push ebp
:0040100C 8BEC                  mov ebp, esp
:0040100E 8B450C                mov eax, dword ptr [ebp+0C]
:00401011 8B5D08                mov ebx, dword ptr [ebp+08]
:00401014 C9                    leave
:00401015 C20800                ret 0008

; 这里是Sub3 — StdCall类型
:00401018 55                    push ebp
:00401019 8BEC                  mov ebp, esp
:0040101B 8B4508                mov eax, dword ptr [ebp+08]
:0040101E 8B5D0C                mov ebx, dword ptr [ebp+0C]
:00401021 C9                    leave
:00401022 C20800                ret 0008
        …

; 这里是invoke Sub1,1,2 — C类型
:00401025 6A02                  push 00000002
:00401027 6A01                  push 00000001
:00401029 E8D2FFFFFF            call 00401000
:0040102E 83C408                add esp, 00000008

; 这里是invoke Sub2,1,2 — PASCAL类型
:00401031 6A01                  push 00000001
:00401033 6A02                  push 00000002
: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指令把返回地址压入堆栈,这时候esp为X-C,接下去是子程序中用push ebp来保存ebp的值,esp变为X-10,再执行一句mov ebp,esp,就可以开始用ebp存取参数和局部变量了,图3.4说明了这个过程。


图3.4  ebp指针、参数和局部变量的关系

在源程序中,由于参数、局部变量和ebp的关系是由编译器自动维护的,所以读者不必关心它们的具体关系,但到了用Soft-ICE等工具来分析其他软件的时候,遇到调用子程序的时候一定要先看清楚它们之间的类型差别。

在子程序中使用参数,可以使用与存取局部变量同样的方法,因为这两者的构造原理几乎一模一样,所以,在子程序中有invoke语句时,如果要用到输入参数的地址当做invoke的参数,同样要遵循局部变量的使用方式,不能用offset伪操作符,只能用addr来完成。同样,所有对局部变量使用的限制几乎都可以适用于参数。

  1. .386
  2. .model flat,stdcall
  3. option casemap:none
  4. ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  5. ; Include 文件定义
  6. ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  7. include     windows.inc
  8. include     user32.inc
  9. includelib  user32.lib
  10. include     kernel32.inc
  11. includelib  kernel32.lib
  12. LPVAR   typedef  PTR DWORD ;定义一个指针类型数据LPVAR
  13. sum proto a:LPVAR,b:LPVAR ;声明,这里可省略
  14. .const
  15. szText db 'hello world!',0
  16. szCaption db 'hello',0
  17. .code
  18. sum proc a:LPVAR,b:LPVAR
  19. pushad
  20. invoke MessageBox,NULL,a,b,0
  21. popad
  22. ret
  23. sum endp
  24. start:
  25. ;invoke MessageBox,NULL,offset szText,offset szCaption,0
  26. invoke sum,offset szText,offset szCaption ;调用子程序呢
  27. invoke ExitProcess,0
  28. end start
		.386
.model flat,stdcall
option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
LPVAR typedef PTR DWORD ;定义一个指针类型数据LPVAR
sum proto a:LPVAR,b:LPVAR ;声明,这里可省略 .const
szText db 'hello world!',0
szCaption db 'hello',0
.code
sum proc a:LPVAR,b:LPVAR
pushad
invoke MessageBox,NULL,a,b,0
popad
ret
sum endp
start:
;invoke MessageBox,NULL,offset szText,offset szCaption,0
invoke sum,offset szText,offset szCaption ;调用子程序呢
invoke ExitProcess,0
end start

jpg改rar

罗云彬win32汇编教程笔记 子函数的声明, 定义与调用的更多相关文章

  1. 《c程序设计语言》读书笔记--子函数原型和声明的形参

    #include <stdio.h> #define Num 20 int power(int base,int n) { int p = 1; int i; for(i = 0;i &l ...

  2. Python 学习 第七篇:函数1(定义、调用和变量的作用域)

    函数是把一些语句集合在一起的程序结构,用于把复杂的流程细分成不同的组件,能够减少代码的冗余.代码的复用和修改代码的代价. 函数可以0个.1个或多个参数,向函数传递参数,可以控制函数的流程.函数还可以返 ...

  3. Python函数的基本定义和调用以及内置函数

    首先我们要了解Python函数的基本定义: 函数是什么? 函数是可以实现一些特定功能的小方法或是小程序.在Python中有很多内建函数,当然随着学习的深入,你也可以学会创建对自己有用的函数.简单的理解 ...

  4. python函数知识一 函数初始、定义与调用、返回值、参数和函数的好处+菜中菜

    第四章 函数 1.函数初识: def :关键字 -- 定义 函数名:和变量的定义方式一样 (): 用于参数传递,: 形参:函数的定义中()内的是形参 实参:调用的()内是实参 传参:调用时将实参传递给 ...

  5. python 函数的参数定义及调用

    参数定义:1. 位置参数:    这是熟悉的标准化参数,位置参数必须在调用函数中定义的准确顺序来传递,在没有默认参数的情况下,传入参数    的精确数目必须和声明的数目一致. def foo(who, ...

  6. Python3基础 def 函数要先定义再调用

             Python : 3.7.3          OS : Ubuntu 18.04.2 LTS         IDE : pycharm-community-2019.1.3    ...

  7. 汇编学习笔记(11)int指令和端口

    格式 int指令也是一种内中断指令,int指令的格式为int n,n是中断类型码.也就是说,使用int指令可以调用任意的中断例程,例如我们可以显示的调用0号中断例程,还记得在汇编学习笔记(10)中我们 ...

  8. matlab 子函数的使用

    本文参考了该篇博客:http://www.cnblogs.com/MarshallL/p/4048846.html 对其进行学习,为我所用吧. 一. 在matlab的函数定义中,如果函数如果函数较长或 ...

  9. Python基础--函数的定义和调用

    一.函数的作用: 提高代码的可读性,减少代码的冗余,方便调用和修改,组织结构清晰 二.函数的定义:函数遵循先定义后调用的原则 1.无参函数 def funcname(): #def 是关键字,后跟函数 ...

随机推荐

  1. egret学习记录

    最近h5小游戏比较流行,本来我是做cocos2dx的,一开始想用它的js版. 可惜看着js真是头大.于是选择了egret,egret采用typescript,学过面向对象的,上手还是比较快的,而且ap ...

  2. [sz,rz]使用sz/rz在两台Linux设备之间传输数据

    转自:https://superuser.com/questions/604055/using-rz-and-sz-under-linux-shell zsend #!/bin/sh DEV=/dev ...

  3. 判断DataTable某字段是否包含某值

    // <summary> /// 判断DataTale中判断某个字段中包含某个数据 /// </summary> /// <param name="dt&quo ...

  4. 修改jdk

    (一)修改jdk的path: (二)修改eclipse里面的jre环境 (三)修改具体项目的jre环境 build path -> config build path (四)修改服务运行环境

  5. 数据处理包plyr和dplyr包的整理

    以下内容主要参照 Introducing dplyr 和 dplyr 包自带的简介 (Introduction to dplyr), 复制了原文对应代码, 并夹杂了个人理解和观点 (多附于括号内). ...

  6. EXTJS入门教程及其框架搭建

    EXTJS是一个兼容AJAX的前台WEB UI的框架,在普通的HTML文件的 BODY 元素中无须写任何HTML代码,就能产生相应的表格等元素. 首先是为每一个页面定义一个类,再以EXTJS的规范格式 ...

  7. 【转】【Android】Android不同版本下Notification创建方法

    使用 new Notification(int icon, CharSequence tickerText, long when)构造函数时,Eclipse却提示:" The constru ...

  8. e672. 缩放,剪取,移动,翻转缓冲图像

    AffineTransform tx = new AffineTransform(); tx.scale(scalex, scaley); tx.shear(shiftx, shifty); tx.t ...

  9. ANSI 标准是为了确保 C++ 的便携性

    ANSI 标准ANSI 标准是为了确保 C++ 的便携性 —— 您所编写的代码在 Mac.UNIX.Windows.Alpha 计算机上都能通过编译. 由于 ANSI 标准已稳定使用了很长的时间,所有 ...

  10. Blend for Visual Studio 2013

    软件开发中为了使设计师和程序员“并行”工作并直接参与到程序的开发中来. 1.在网络程序开发团队中,草图设计后,设计师们可以使用HTML.CSS.JavaScript直接生成UI,程序员则在这个UI产生 ...