zoukankan      html  css  js  c++  java
  • Intel汇编程序设计-高级过程(上)

    第八章 高级过程

    8.1 简介

    本章主要讲:

    堆栈框架

    变量作用域和生存期

    对战参数的类型

    通过传递值或者传递引用来传递参数

    在堆栈上创建和初始化局部变量

    递归

    编写多模块程序

    内存模型和语言关键字

    注意关键词:

    子过程=函数=方法(因不同语言导致名字不统一)

    8.2堆栈框架(很重要)

        堆栈框架(stack frame)也称活动记录(activation record),它是为传递的参数、子例程的返回地址、局部变量和保存的寄存器保留的堆栈空间。堆栈框架是按一下步骤创建的:

    1.如果有传递的参数,则压入堆栈。

    2.子历程被调用,字垒成的返回地址压入堆栈。

    3.子例程开始时,EBP被压入堆栈。

    4.EBP设为ESP的值,从这时开始,EBP就被座位寻址所有子例程参数的基址指针使用了。

    5.如果任何寄存器需要保存,则亚茹堆栈。

        堆栈框架的结构手程序的内存模式及参数传递约定直接影响。

    8.2.1 堆栈参数

        有两种基本类型的子例程参数:寄存器参数和堆栈参数。Irvine32Irvine16库使用寄存器参数,本节讲述如何声明和使用堆栈参数。

        被调用的子例程访问调用子例程时亚茹堆栈的参数。使用寄存器参数可以优化程序的执行速度,但是遗憾的是,这样可能会造成代码的混乱,因为有些寄存器在装入参数之前必须首先保存。例如,调用DumpMem时就是这种情况:


    Pushad

    Mov esi,OFFSET array           ;起始偏移地址

    Mov ecx,LENGTHOF array       ;大小

    Mov ebx,TYPE array            ;双字格式

    Call DumpMem                ;显示内存内容 

    Popad

     

        另外一种更灵活的方式是堆栈参数,在调用子例程之前,参数首先压入堆栈。例如,假设DumpMem使用堆栈参数,那么可以使用下面的代码进行调用:

    Push TYPE array

    Push LENGTHOF array

    Push PFFSET array

    Call DumpMem

        在进行子例程调用时在堆栈上压入了两类参数:

          值参数(变量和常量的值)

          引用参数(地址)

     

    堆栈参数的访问(C/C++

        在调用函数时,C/C++程序使用标准的方法初始化和访问参数。C/C++中的函数以序言(prologue)开始,序言部分的代码保存了EBP寄存器,并使EBP指向当时堆栈的顶部,函数还有可能把一些寄存器压栈,这些寄存器的值将在函数返回的时候恢复。函数以收尾(epilogue)代码结束,在这部分代码中,EBP寄存器被恢复,RET指令从函数返回。

    例子AddTwo

    C:

    Int AddTwo(int x ,int y){

        Return x + y;

    }

    对应汇编:

     

    AddTwo PROC

    Push ebp

    Mov ebp,esp          ;堆栈框架的基址

    Mov eax,[ebp+12]     ;第二个参数

    Mov eax,[ebp+8]      ;第一个参数

    Pop  ebp       

    Ret

    AddTwo ENDP


    自己用vs2012看了下反汇编(DeBug模式)

    调用部分:

     

    函数部分


    堆栈的清理

        在子例程返回时,必须要有某种方法清除堆栈上的参数,否则就会导致内存泄漏以及堆栈的破坏。假设main中调用AddTwo的语句如下:

    Push 5

    Push 5

    Call AddTwo

    下面是从调用返回后堆栈的示意图:

     

        如果没有清理,那么函数结束的时候就会从栈里拿出来一个地址,然后跳转过去。那么上面就直接跳转到存储5的地 址了,这样就发生问题了。

       对于这个问题,一种简单的解决方法是在CALL指令后使用一条ADD指令给ESP加上一个值,以使ESP指向正确的返回地址:

    Example1 PROC

        Push  5

        Push  6

        Call  AddTwo

        Add  esp,8

        Ret

    Example1 ENDP

        这实际上也是C++使用的一种方法。

        STDCALL调用约定(Calling Convention:处理堆栈清理问题的另一种方法是使用STDCALL调用约定,可以在AddTwo过程中的RET指令后提供一个整数参数以修复ESP的值,这个整数值必须等于堆栈参数小号的堆栈空间字节数。

        大体是下面这样的姿势:

    AddTwo PROC

    Push  ebp

    Mov  ebp,esp

    Mov  eax,[ebp+12]

    Add  eax,[ebp+8]

    Pop ebp

    Ret 8

    AddTwo ENDP

        这样一来,上面堆栈清理问题就简化了:谁应该对清理堆栈负责?是调用子例程的代码,还是子例程本身?这两种方式都有各自的优缺点:STDCALL减少了为子例程调用生成代码数量(只有一条指令)并且能够确保调用者永远不会忘记清理堆栈;另一方面,C调用约定允许子例程生命可变数目的参数,由调用者决定要传递多少参数。例子之一是printf函数,这种类型的,清理堆栈的职责职能留给调用者了。

        通过堆栈传递8位和16位的参数

        在保护模式下传递参数时,最好使用32位的操作数,虽然可以砸IDUI站上压入16位的操作数,但这样会似的ESP无法对其在双字地址边界上,由此可能会导致发生页故障,程序的性能也能会降低。因此在传递8位或16位对扎你参数时,应把它扩展到32位在压栈。

    So需要把一些小宽度参数扩展成32位的:movzx eax,word1

    那如果是大于32位的怎么办?:这个我们可以分开传递,先传32位,再传32...

     

    USER操作符对堆栈的影响

    之前应该说过USER,它可以帮助保存和恢复一些寄存器的值。例如:

    MySub1 PROC USES ecx ,edx

    Ret

    MySub1 ENDP

    下面是汇编时产生的代码:

    Push ecx

    Push edx

    Pop  edx

    Pop  ecx

    Ret


        假设在MySub2中把USES和堆栈参数一起使用,我们预期第一个参数在堆栈位置EBP+8处:

    MySub2 PROC USES ecx ,edx

    Push ebp

    Mov ebp,esp

    Mov eax,[ebp+8]

    Pop ebp

    Ret 4

    MySub2 ENDP

    下面是生成的汇编代码
    push ecx

    Push edx

    Push ebp

    Mov ebp,esp

    Mov eax,dword ptr[ebp+8]  ;错误的位置!

    Pop ebp

    Pop edx

    Pop ecx

    Ret 4

  • 相关阅读:
    R语言 主成分分析
    主成分分析(PCA)及其在R里的实现
    UML类图几种关系的总结
    微信Android客户端架构演进之路
    Android单元测试实践
    Android studio 快捷键(Mac)
    Android 启动模式及常用的Intent的Flag
    linux常用命令 (mac ),积少成多
    Android Studio IDE 简单学习和介绍
    轻量级分布式 RPC 框架
  • 原文地址:https://www.cnblogs.com/csnd/p/12062144.html
Copyright © 2011-2022 走看看