zoukankan      html  css  js  c++  java
  • 堆栈是个什么🐴

     1.call

      这个命令是访问子程序的一个汇编基本指令。也许你说,这个我早就知道了!别急请继续看完。

      call真正的意义是什么呢?我们可以这样来理解:1.向堆栈中压入下一行程序的地址;2.JMP到call的子程序地址处。例如:

    00401029    .  E8 DA240A00    call 004A3508

    0040102E    .  5A                      pop edx

    在执行了00401029以后,程序会将0040102E压入堆栈,然后JMP到004A3508地址处

      2.RET

      与call对应的就是RET了。对于RET我们可以这样来理解:1.将当前的ESP中指向的地址出栈;2.JMP到这个地址。


    创建堆栈框架

     

    总体看来,构建一个堆栈框架包含了以下几个步骤:
    • 如果要调用的函数有参数,将参数压入堆栈;
    • 用call指令调用子程序;
    • 此时子程序开始,将ebp寄存器压入栈:push ebp;
    • 将ebp的值设为esp(只是为了方便以后访问参数和局部变量):mov ebp, esp;
    • 若有局部变量,将esp的值减去相应的值。假设我们有3个DWORD类型的局部变量,则:sub esp, 12;
    • 若有需要保存的寄存器,将要保存的寄存器压栈。
    完成上述各个步骤后,堆栈的情况如下图:

     

    访问堆栈参数

     

    从上图可以看出来,若要访问第一个被压入的参数,假设将第一个参数的值放到eax中,我们可以使用:
    1. mov eax, [ebp + 8]  

    因为call指令会自动将返回地址压入堆栈,因此紧邻ebp上方的堆栈值不是参数,所以最近的参数地址是ebp + 8,而不是ebp + 4。访问其他参数类似,如[ebp + 12]等等。
     



     清理堆栈

     

    在运行完函数的基本代码后,返回前我们需要清理堆栈以使函数能够正确返回,并恢复保存的寄存器值,还要警惕内存泄露问题。
    清理堆栈的步骤如下:
    • 将之前保存的寄存器值以相反的顺序弹出堆栈;
    • 将esp的值设为ebp的值,以销毁局部变量:mov esp, ebp;
    • 将原ebp的值弹出堆栈:pop ebp;
    到了这里,esp的值已经指向了函数正确的返回地址。此时,若直接调用ret指令是可以达到返回调用函数的目的的。但我们可以发现之前被压入的参数并没有得到处理,仍然存在在堆栈中,当我们继续运行代码时,也没有人会为之前的函数”擦屁股“,即造成了内存泄露。解决这个问题有两个方法:
    • 一个简单的方法是在call指令后面紧跟一条add指令,将esp的值指向一个正确的地址。例如,如果我们之前压入了3个参数,那么应运行:add esp, 12;
    • 另一个更好的方法是是使用STDCALL调用规定,即修改子程序代码的ret指令。如对于上面的例子,应改写ret为:ret 12。
    显然第二个方法更符合我们的习惯,因为”自己的事情自己做“,谁的函数谁的参数自己处理。

    函数调用约定


    这里再补充一下各种调用规定的基本内容。
     

    _stdcall调用约定


    所有参数按照从右到左压入堆栈,由被调用的子程序清理堆栈
     

    _cdecl调用约定(The C default calling convention,C调用规定)


    参数也是从右到左压入堆栈,但由调用者清理堆栈。

    _fastcall调用约定


    顾名思义,_fastcall的目的主要是为了更快的调用函数。它主要依靠寄存器传递参数,剩下的参数依然按照从右到左的顺序压入堆栈,并由被调用的子程序清理堆栈。


    总结


    总结一下,汇编中一个子程序的代码长得像下面这个样子:
    1. push ebp                        ;保存ebp  
    2. mov ebp, esp                    ;将ebp设为当前esp值  
    3. sub esp, 4*局部变量个数         ;局部变量  
    4. push eax                        ;将寄存器压栈  
    5. push esi  
    6. ……  
    7. mov eax, [ebp + 8]          ;得到第一个参数  
    8. mov edi, [ebp + 12]         ;得到第二个参数  
    9. ……  
    10. pop esi                         ;恢复寄存器值  
    11. pop eax  
    12. mov esp, ebp                    ;销毁局部变量  
    13. pop ebp                         ;恢复ebp的值  
    14. ret 4*被压入参数个数  

    对于 c 语言,函数有好几种调用规则。最常见的是两种,cdecl 方式和 stdcall 方式。

    Cdecl 方式
    (1)使用堆栈传递参数
    (2)主程序按从右向左的顺序将参数逐个压栈,最后一个参数先入栈。每一个参数压栈一次。
    (3)在子程序中,使用 [EBP+X] 的方式来访问参数。X=8 代表第 1 个参数;X=12 代表第二个参数,依次类推
    (4)子程序用 RET 指令返回。
    (5)由主程序执行“ADD ESP, n”指令调整 ESP,达到堆栈平衡。
    (6)一般返回值放在 EAX 中

    Stdcall 方式
    与Cdecl的不同是,堆栈的平衡不是由主程序完成,而是由子程序通过调用“RET n”指令主动平衡。

  • 相关阅读:
    阶段1 语言基础+高级_1-2 -面向对象和封装_2面向对象思想的举例
    阶段1 语言基础+高级_1-2 -面向对象和封装_1面向对象思想的概述
    2-3 【初识组件】顶部 TabBar
    2-2 工程源码文件结构
    Fragment状态保存
    【51单片机】六种亮灯方式
    Hadoop自学笔记(二)HDFS简单介绍
    lvs 负载均衡环境搭建
    [学习笔记—Objective-C]《Objective-C-基础教程 第2版》第十一章 属性
    说说nio----1
  • 原文地址:https://www.cnblogs.com/rookieDanny/p/8511503.html
Copyright © 2011-2022 走看看