(我认为这个问题很可能是重复的,或者已经在这里得到了解答,但是由于“堆栈分配”和相关术语的干扰,搜索答案很难。)
我有一个玩具编译器,我一直在研究脚本语言。为了能够在脚本正在进行时暂停执行并返回到主机程序,它有自己的堆栈:带有“堆栈指针”变量的简单内存块,使用正常的C代码操作递增对于那种事情等等。到目前为止并不感兴趣。
目前我编译为C.但我也有兴趣调查编译机器代码 - 同时保持辅助堆栈和在预定义控制点返回主机程序的能力。
所以...我认为在我自己的代码中使用传统的堆栈寄存器不太可能是一个问题,我认为只要在完成后所有内容都被恢复,寄存器会发生自己的业务(做正确的)我,如果我在这一点上错了)。 但是 ...如果我希望脚本代码调用其他库代码,使用这个“虚拟堆栈”离开程序是否安全,或者是否必须将其返回给为此目的的原始堆栈?
像<{3}}和this one这样的答案表明堆栈不是传统的内存块,但它依赖于特殊的,系统特定的行为来处理页面错误等等。所以:
这显然不是必需的,因为简单地将指针返回到实际堆栈将是完全可用的,或者就此而言,首先不要滥用它们并且只是放置更少的寄存器,我可能不应该尝试完全这样做(尤其是因为显然不在我的深处)。但无论如何我仍然很好奇。想知道这些事情是如何运作的。
编辑:对不起当然应该说。我正在研究x86(我自己的机器为32位),Windows和Ubuntu。没有异国情调。
答案 0 :(得分:4)
所有这些答案都基于“通用处理器架构”,并且因为它涉及生成汇编代码,所以它必须是“特定于目标” - 如果您决定在处理器X上执行此操作,这对处理器堆栈有一些奇怪的处理,下面显然不值得在[替代纸张]上写的屏幕表面。对于x86一般情况下,除非另有说明,否则以下内容为。
is it safe to move the stack pointers into some other area of memory?
堆栈内存不是“特殊”?我认为线程库 必须做这样的事情,因为他们创造了更多的堆栈......
这样的记忆并不特别。然而,这确实假设它不在x86架构上,其中堆栈段用于限制堆栈使用。虽然这是可能的,但在实现中却很少见。我知道几年前诺基亚有一个使用32位模式段的特殊操作系统。就我现在所能想到的那样,这是我唯一接触到的那个使用堆栈段的x86分段模式描述。
假设使用堆栈操作任何内存区域都是安全的 寄存器和指令,我可以想到它没有理由 调用具有已知调用深度的任何函数的问题(即没有 递归,没有函数指针)只要该数量可用 在虚拟堆栈上。正确?
正确。只要您不希望能够在不切换回原始堆栈的情况下返回其他功能。有限的递归水平也是可以接受的,只要堆栈足够深[有某些类型的问题,如果没有递归就很难解决 - 例如二叉树搜索]。
无论如何,堆栈溢出显然是普通代码中的一个问题, 但是对于溢出来说会不会有任何额外的灾难性后果 这样的系统?
事实上,如果你有点不走运,这将是一个难以破解的错误。
我建议您使用对VirtualProtect()
(Windows)或mprotect()
(Linux等)的调用来将“堆栈末尾”标记为不可读且不可写,以便在您的代码意外行走时离开堆栈,它正常崩溃而不是其他一些更微妙的未定义行为[因为它不能保证下面的内存(较低的地址)不可用,所以你可以覆盖其他一些有用的东西,如果它从堆栈中消失,那会导致一些非常难以调试的错误]。
添加一些偶尔检查堆栈深度的代码(你知道堆栈的开始和结束位置,所以不应该很难检查特定的堆栈值是否“超出范围”[如果你给自己一些堆栈顶部和你保护的“我们已死”区域之间的“额外缓冲空间” - 如果它是撞车中的汽车就会称之为“崩溃区”。你也可以填满整个使用可识别的模式进行堆叠,并检查其中有多少是“未触及”。
答案 1 :(得分:2)
通常,在x86上,您可以使用现有堆栈而不会出现任何问题:
pop
或add esp, positive_value
/ sub esp, negative_value
)超出你的代码开头(如果你这样做,中断或异步回调)(信号)或使用堆栈的任何其他活动将废弃其内容)这同样适用于为临时堆栈使用不同的内存块并将esp
指向其末尾。
异常处理和堆栈展开的问题与以下事实有关:编译的C和C ++代码包含一些与异常处理相关的数据结构,例如eip
的范围以及指向各自异常处理程序的链接(这告诉最接近的异常处理程序对于每一段代码的位置)并且还有一些与调用函数的标识相关的信息(即返回地址在堆栈中的位置等),所以你可以冒泡例外。如果你只是将原始机器代码插入到这个&#34;框架中,你就不能正确地扩展这些异常处理数据结构来覆盖它,如果出现问题,他们可能会去非常错误(尽管您在生成的代码周围有异常处理程序,整个过程可能会崩溃或损坏。)
所以,是的,如果你小心,你可以玩堆栈。
答案 2 :(得分:2)
您可以将任何您喜欢的区域用于处理器的堆栈(模块化内存保护)。
基本上,您只需使用指向新区域的指针加载ESP寄存器(“MOV ESP,...”),但是您设法分配它。
你必须拥有足够的你的程序,以及它可能调用的任何程序(例如,Windows OS API),以及操作系统的任何有趣行为。您可能能够确定代码需要多少空间;一个好的编译器可以轻松做到这一点确定Windows需要多少是更难的;你可以随时分配“方式太多”,这是Windows程序倾向于做的事情。
如果您决定严格管理此空间,则可能需要切换堆栈以调用Windows功能。那还不够;你可能会被各种Windows惊喜所灼伤。我在这里描述其中一个Windows: avoid pushing full x86 context on stack。我有平庸的解决方案,但不是很好的解决方案。