使用前关于堆栈的假设

Posted

技术标签:

【中文标题】使用前关于堆栈的假设【英文标题】:Assumptions about the stack before usage 【发布时间】:2021-01-05 22:39:52 【问题描述】:

堆栈在使用前是否需要“清除”?换句话说,在使用堆栈之前,是否可以安全地假设它完全为零,或者它是垃圾并且它可以是任何东西?例如,假设我将一个三字节值 AA AA AA 移动到 %rbp-4

 ---------------------- %rsp / %rbp

 ---------------------- -1
            AA
 ---------------------- -2
            AA
 ---------------------- -3
            AA
 ---------------------- -4

如果我将四个字节移动到一个寄存器中,例如:

movl -4(%rbp), %eax

我首先需要清除堆栈上的一个未使用字节,还是我们确定堆栈已经被清除?

【问题讨论】:

【参考方案1】:

这取决于。堆栈上的内存是为程序的执行分配的,在此之前谁知道还有什么用。如果使用该内存的进程先前将其归零,或者如果您的操作系统在重新分配内存之前将其归零,那么是的,您将在未初始化的字节中归零。但是,我对您的特定设置一无所知,我会说这是极不可能的,您不应该指望它。这些字节几乎肯定包含上次使用时留下的任何垃圾。

许多现代语言不允许程序员读取他们自己没有初始化的内存位置,以避免未定义的行为。即使在像 C/C++ 这样不会自动禁止这种情况的旧语言中,强烈鼓励您不要对新分配的堆栈/堆内存进行任何假设。

【讨论】:

【参考方案2】:

除了在 static 可执行文件中的_start 之外,对未写入的堆栈空间做出任何假设都是不安全的(否则动态链接器又名 ELF 解释器可能会在您的 _start 运行之前弄脏堆栈空间.)

在 Linux 下,在此过程中尚未触及的新堆栈内存将被清零,就像其他匿名页面(BSS、mmap(MAP_ANONYMOUS) 等)这样可以避免泄漏内核或其他用户数据.利用 BSS 的这种已知状态或从 mmap 新分配的内存是正常的(例如,这就是为什么惰性 calloc 对于大型分配比 malloc + memset 更便宜 - 没有页面错误。)但这不正常对于堆栈空间,因为大多数函数都应该工作,无论它们是否正在重用一些早期函数使用的堆栈空间,或者它们是否碰巧是在此过程中第一次接触这些字节。

所以基本上作为在 Linux 等已知操作系统下的玩具程序的肮脏黑客,特别是对于代码高尔夫,当然,假设一个零堆栈。例如https://codegolf.stackexchange.com/questions/133618/extreme-fibonacci/135618#135618 假定堆栈为零,并且没有信号处理程序,因此它可以使用 ESP 下方近 4kiB 的红色区域,使用 pop 循环数组以及其他节省空间但在实践中仍然有效的脏黑客我的桌面上的已知条件。

任何其他时间(例如您可能想从其他任何地方调用的函数),不可以。

堆栈内存如何归零?当函数返回时,没有什么会浪费时间将堆栈内存归零;通常这会浪费时间而没有任何好处,因为没有任何东西会读取那些零。

大多数时候,为本地和函数 args 保留的大部分空间都是用代码想要存储在那里的任何数据编写的。如果这恰好是一个需要进行零初始化的数组(例如,一个计数器数组),则由您来实现。

请注意,如果您执行print some_func(123) 之类的操作,即使使用调试器也会弄脏红色区域下方的空间。 GDB 将使用您进程的堆栈空间来调用该函数并获取返回值。

【讨论】:

【参考方案3】:

...在使用堆栈之前,假设它完全为零是否安全...

在几乎所有用例中,堆栈在您使用之前就已经“使用”过。

示例 C 代码:

void someFunction(void)

    someOtherFunction();
    functionIHaveWritten();

假设堆栈指针的初始值为rsp=0x10F000

现在函数someOtherFunction() 将使用堆栈,将堆栈指针更改为另一个值(比如说rsp=0x10EF00)并将一些数据(通常是局部变量)存储到0x10EF000x10F000 之间的内存区域。

当函数someOtherFunction()返回时,它会将栈指针恢复为rsp=0x10F000,但不会清除栈上被函数覆盖的字节。

清除没有任何意义 - 这会花费时间,而且大多数函数不需要清除堆栈。

所以当你写的函数——functionIHaveWritten()——被调用时,栈中包含了函数someOtherFunction()写的“垃圾”。

堆栈在使用前是否需要“清除”?

如果要确保堆栈上的所有字节都初始化为零:是。

但是,在大多数情况下,您不需要这样做。

【讨论】:

以上是关于使用前关于堆栈的假设的主要内容,如果未能解决你的问题,请参考以下文章

堆栈 pop push

尝试在java中使用正则表达式时堆栈溢出

堆栈和堆内存的大小[重复]

在 python 中使用链表实现堆栈。 pop 方法的问题和关于可变性的问题

C# 中是不是可以使用基于堆栈的数组?

关于使用Spring Boot Microservices设置ELK堆栈