为什么在函数堆栈上返回值不安全

时间:2011-03-03 19:52:39

标签: c++ stack return

我在阅读布鲁斯·埃克尔时遇到了以下段落。他试图解释为什么函数在堆栈上返回值不安全

  

现在想象一下普通函数试图在堆栈上返回值时会发生什么   。你可以触摸堆栈中返回地址之上的任何部分,因此函数必须将值推到返回地址之下。 但是当执行汇编语言返回时,堆栈指针必须指向返回地址(或取决于你的机器正下方),所以在RETURN之前,函数必须向上移动堆栈指针,从而清除所有局部变量 。如果你是试图在返回地址下面的堆栈上返回值, 此时你会变得容易受到攻击,因为中断可能会出现.ISR会将堆栈指针向下移动以保存其返回地址及其局部变量并覆盖您的返回值

您想帮助我理解粗体斜体文字吗?

4 个答案:

答案 0 :(得分:2)

他只是想解释为什么不应该返回指针或对局部变量的引用。因为它会在函数返回后立即消失!

在硬件级别上发生的事情并不重要,即使它可能解释为什么价值有时似乎仍然存在,有时也不存在。

答案 1 :(得分:2)

假设您的应用程序中某处有以下调用堆栈:

  1. 主要例程
  2. 功能1的本地变量
  3. 功能2的局部变量< - STACK POINTER
  4. 在这种情况下,main调用function1,function1调用function2。

    现在假设function2调用function3,并在堆栈上返回function3的返回值:

    1. 主要例程
    2. 功能1的本地变量
    3. 功能2的局部变量
    4. Function3的局部变量,包括返回值< - STACK POINTER
    5. Function3将返回值存储在堆栈上,然后返回。返回意味着再次减少堆栈指针,因此堆栈变为:

      1. 主要例程
      2. 功能1的本地变量
      3. 功能2的局部变量< - STACK POINTER
      4. 你看,function3的堆栈框架不再存在了。

        嗯,实际上我撒谎了一下。堆栈框架仍在那里:

        1. 主要例程
        2. 功能1的本地变量
        3. 功能2的局部变量< - STACK POINTER
        4. 功能3的局部变量,包括返回值
        5. 因此,仍然可以安全地访问堆栈以获取返回值。

          但是,如果有一个中断AFTER函数3已经返回,但是在函数2之前得到堆栈的返回值,我们得到这个:

          1. 主要例程
          2. 功能1的本地变量
          3. 功能2的局部变量
          4. 中断函数的局部变量< - STACK POINTER
          5. 现在堆栈框架真的被覆盖了,我们迫切需要的返回值已经消失了。

            这就是为什么在堆栈上返回返回值是不安全的。

            问题类似于这段简单的C代码中显示的问题:

            char *buf = (char *)malloc(100*sizeof(char *));
            strcpy (buf, "Hello World");
            free (buf);
            printf ("Buffer is %s\n",buf);
            

            大多数时候,用于buf的内存仍然会包含内容" Hello World"但是如果有人能够在调用free之后分配内存,那么它可能会出现可怕的错误,但是在调用printf之前。一个这样的例子是在多线程应用程序中(我们已经在内部遇到了这个问题),如下所示:

            THREAD 1:                                  THREAD 2:
            ---------                                  ---------
            char *buf = (char *)malloc(100);
            strcpy (buf, "Hello World");
            free (buf);
                                                       char *mybuf = (char *)malloc(100);
                                                       strcpy (mybuf, "This is my string");
            printf ("Buffer is %s\n",buf);
            

            printf是线程1现在可以打印" Hello World",或者它可以打印"这是我的字符串"。任何事情都可能发生。

答案 2 :(得分:1)

当您调用具有pass-by-stack参数的函数时,这些参数会被压入堆栈。当函数返回时,它正在使用的那个堆栈内存被释放。紧接着,访问那些堆栈值中的内容是不安全的,因为其他东西可能已经覆盖了它们。

假设我们在一个cpu上,堆栈指针保存在一个名为SP的寄存器中,它会“向上”增长。

  1. 您的代码正在进行并进入函数调用。此时,我们会说SP为100。
  2. 调用该函数,您的函数需要两个单字节参数。这两个字节的参数被压入堆栈,并且......这是重要的部分 - 您调用该函数的代码的地址(假设它是4字节)。现在SP是106.要返回的地址是SP = 100,你的两个字节是104和105.
  3. 假设该函数修改其中一个参数(SP = 105)作为返回修改值的方法
  4. 函数返回,堆栈快速回到原来的位置(SP = 100),然后继续。

  5. 在一个完美的世界中,系统中没有任何其他内容,你的程序可以完全控制CPU ......除非你做其他需要堆栈的事情,否则SP = 105的值将保留在那里“永远”。

  6. 然而,在中断的情况下,无法保证不会出现其他问题。假设硬件中断命中你的应用程序。这意味着立即跳转到中断服务程序,因此当中断命中时CPU被压入堆栈的当前地址(4个字节),现在SP为103.假设这个ISR调用其他子程序,这意味着更多返回地址被压入堆栈。所以现在SP是107 ...你原来的105值没有被覆盖。

  7. 最终这些ISR将返回,控制权返回到您的代码,SP再次为100 ......您的应用尝试检索SP = 105值,幸福地发现它已被ISR删除,现在你正在使用糟糕的数据。

答案 3 :(得分:1)

该段最重要的部分是:

  

如果您尝试返回值   返回地址(...)下方的堆栈

换句话说,不要将指针返回到仅在该函数范围内有效的数据。

在C标准化函数如何按值返回结构之前,这可能是您不得不担心的事情。现在,它是C99标准(6.8.6.4)的一部分,你不应该担心它。

现在,在C ++中完全支持按值返回。否则,许多STL实现细节将无法正常工作。