【问题标题】:why its not safe to return value on function stack为什么在函数堆栈上返回值不安全
【发布时间】:2011-07-08 07:57:30
【问题描述】:

我在阅读 bruce eckel 时遇到了以下段落。他试图解释为什么函数在堆栈上返回值是不安全的

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

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

【问题讨论】:

  • 这确实描述了一个糟糕的调用约定,所以我在整个段落中看不到重点。但可能我不知道它的上下文......你最终可以使用堆栈来返回值的调用约定,但调用者应该为它保留堆栈空间!
  • 报价中缺少一条重要信息。作者很可能在谈论返回一个 referencepointer 到堆栈中的元素,而不是返回堆栈中的实际元素(这将创建一个副本)

标签: c++ stack return


【解决方案1】:

该段最重要的部分是:

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

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

在 C 标准化函数如何按值返回结构之前,这可能是您必须担心的问题。现在,它是 C99 标准 (6.8.6.4) 的一部分,您不必担心。

C++ 完全支持按值返回有一段时间了。否则,许多 STL 实现细节将无法正常工作。

【讨论】:

    【解决方案2】:

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

    1. 主程序
    2. Function1 的局部变量
    3. Function2 的局部变量

    本例中main调用function1,function1调用function2。

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

    1. 主程序
    2. Function1 的局部变量
    3. Function2 的局部变量
    4. Function3的局部变量,包括返回值

    Function3 将返回值存入栈中,然后返回。返回的意思是,栈指针再次递减,所以栈变成了这样:

    1. 主程序
    2. Function1 的局部变量
    3. Function2 的局部变量

    你看,function3的栈帧已经不在了。

    嗯,其实我撒了一点谎。栈帧还在:

    1. 主程序
    2. Function1 的局部变量
    3. Function2 的局部变量
    4. Function3 的局部变量,包括返回值

    所以仍然访问堆栈以获取返回值似乎是安全的。

    但是,如果在function3返回之后有一个中断,但是在function2从堆栈中获取返回值之前,我们得到这个:

    1. 主程序
    2. Function1 的局部变量
    3. Function2 的局部变量
    4. 中断函数的局部变量

    而现在栈帧真的被覆盖了,我们急需的返回值也不见了。

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

    这个问题与这段简单的 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 is Thread 1 现在可以打印“Hello World”,或者它可以打印“This is my string”。任何事情都有可能发生。

    【讨论】:

      【解决方案3】:

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

      假设我们在一个 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. 1234563您正在处理错误的数据。

      【讨论】:

        【解决方案4】:

        他只是想解释为什么你不应该返回一个指针或对局部变量的引用。因为函数一返回就消失了!

        在硬件级别发生的确切情况并不那么重要,即使它可以解释为什么该值有时似乎仍然存在而有时不存在。

        【讨论】:

        • :-)。当你写一整本书时,你需要很多字。 IMO 这个解释并不是那么好,因为它包含了很多硬件细节。关于中断处理程序的部分也有点过时,因为它可能使用自己的堆栈而不是用户模式堆栈。如果我继续在这里,我将同样深入不必要的细节。 :-)
        • 我希望他能提供更多示例或图形表示,而不是签署摇篮曲
        猜你喜欢
        • 2023-03-13
        • 2014-09-06
        • 1970-01-01
        • 2021-04-15
        • 1970-01-01
        • 2012-03-04
        • 1970-01-01
        • 2021-10-30
        • 1970-01-01
        相关资源
        最近更新 更多