分配新的调用堆栈

(我认为这个问题很可能已经重复或者已经在这里回答了,但是由于“堆栈分配”和相关术语的干扰,寻找答案很难。)

我有一个玩具编译器,我一直在研究脚本语言。 为了能够在脚本正在进行时暂停执行并返回到主机程序,它有自己的堆栈:带有“堆栈指针”变量的简单内存块,使用正常的C代码操作递增对于那种事情等等。 到目前为止没有意思。

目前我编译为C.但我也有兴趣调查编译机器代码 – 同时保持二级堆栈和在预定义控制点返回主机程序的能力。

所以…我认为在我自己的代码中使用传统的堆栈寄存器不太可能是一个问题,我假设寄存器会发生什么,只要一切都在我完成时就恢复了(如果我做的话,请纠正我)在这一点上我错了。 但是 ……如果我希望脚本代码调用其他库代码,使用这个“虚拟堆栈”离开程序是否安全,或者是否必须为此目的返回原始堆栈?

像这一个和这一个的答案表明堆栈不是传统的内存块,但它依赖于特殊的,系统特定的行为来处理页面错误和诸如此类的东西。

所以:

  • 将堆栈指针移动到其他内存区域是否安全? 堆栈内存不是“特殊”? 我认为线程库必须做这样的事情,因为它们会创建更多的堆栈……
  • 假设使用堆栈寄存器和指令操作任何内存区域都是安全的,我可以认为没有理由调用任何具有已知调用深度的函数(即没有递归,没有函数指针)是一个问题,只要这样做金额在虚拟堆栈上可用。 对?
  • 无论如何,堆栈溢出在普通代码中显然是一个问题,但是对于这样的系统中的溢出会有任何额外的灾难性后果吗?

这显然不是必需的,因为简单地将指针返回到实际堆栈将是完全可用的,或者就此而言,首先不要滥用它们并且只是放置更少的寄存器,我可能不应该尝试这样做完全(尤其是因为显然不在我的深处)。 但无论如何我仍然很好奇。 想知道这些事情是如何运作的。

编辑:对不起,当然应该说。 我正在研究x86(我自己的机器为32位),Windows和Ubuntu。 没有异国情调。

所有这些答案都基于“通用处理器架构”,并且因为它涉及生成汇编代码,所以它必须是“特定于目标” – 如果您决定在处理器X上执行此操作,这对堆栈有一些奇怪的处理,下面是显然不值得在[替代纸张]上写的屏幕表面。 对于x86一般情况下,除非另有说明,否则以下内容为。

is it safe to move the stack pointers into some other area of memory? 

堆栈内存不是“特殊”? 我认为线程库必须做这样的事情,因为它们会创建更多的堆栈……

这样的记忆并不特别。 但是,这确实假设它不在x86架构上,其中堆栈段用于限制堆栈使用。 虽然这是可能的,但在实施中却很少见。 我知道几年前诺基亚有一个使用32位模式段的特殊操作系统。 就我现在所能想到的而言,这是我唯一接触到的那个使用堆栈段的x86分段模式描述。

假设使用堆栈寄存器和指令操作任何内存区域都是安全的,我可以认为没有理由调用任何具有已知调用深度的函数(即没有递归,没有函数指针)是一个问题,只要这样做金额在虚拟堆栈上可用。 对?

正确。 只要您不希望能够在不切换回原始堆栈的情况下返回其他function。 有限的递归水平也是可以接受的,只要堆栈足够深[有一些类型的问题,如果没有递归肯定很难解决 – 例如二叉树搜索]。

无论如何,堆栈溢出在普通代码中显然是一个问题,但是对于这样的系统中的溢出会有任何额外的灾难性后果吗?

事实上,如果你有点不走运,那将是一个难以破解的错误。

我建议您使用对VirtualProtect() (或Windows)或mprotect() (Linux等)的调用来将“堆栈末尾”标记为不可读和不可写,这样如果您的代码意外地从堆栈中走出,它会正常崩溃而不是其他一些更微妙的未定义行为[因为它不能保证下面的内存(较低的地址)不可用,所以你可以覆盖其他一些有用的东西,如果它离开堆栈,这将导致一些非常难以调试错误。

添加一些偶尔检查堆栈深度的代码(你知道堆栈的开始和结束位置,所以不应该很难检查特定的堆栈值是否在“超出范围”[如果你给自己一些“额外的缓冲区”空间“在堆栈的顶部和你保护的”我们死了“区域之间 – 如果它是一辆汽车在崩溃中它们会称之为”崩溃区域“。你也可以填充整个堆栈可识别的模式,并检查其中有多少是“未触动的”。

通常,在x86上,您可以使用现有堆栈而不会出现任何问题,只要:

  • 你不要溢出它
  • 你没有增加堆栈指针寄存器(使用popadd esp, positive_value / sub esp, negative_value )超出你的代码开始(如果你这样做,中断或异步回调(信号)或使用堆栈的任何其他活动将垃圾它的内容)
  • 您不会导致任何CPUexception(如果这样做,exception处理代码可能无法将堆栈展开到可以处理exception的最近点)

这同样适用于为临时堆栈使用不同的内存块并将esp指向其末尾。

exception处理和堆栈展开的问题与以下事实有关:编译的C和C ++代码包含一些与exception处理相关的数据结构,例如eip的范围以及到各自exception处理程序的链接(这告诉了最接近的exception的位置)处理程序适用于每一段代码)并且还有一些与调用函数的标识相关的信息(即返回地址在堆栈中的位置等),因此您可以冒泡出exception。 如果你只是将原始机器代码插入这个“框架”,你将无法正确扩展这些exception处理数据结构来覆盖它,如果出现问题,它们可能会出错(整个过程可能会崩溃或尽管您在生成的代码周围有exception处理程序,但是会被损坏。

所以,是的,如果你小心,你可以玩堆栈。

您可以将任何您喜欢的区域用于处理器的堆栈(模块化内存保护)。

基本上,您只需使用指向新区域的指针加载ESP寄存器(“MOV ESP,…”),但您设法分配它。

您必须拥有足够程序,以及它可能调用的任何内容(例如,Windows OS API),以及操作系统的任何有趣行为。 您可能能够确定代码需要多少空间; 一个好的编译器可以轻松做到这一点 确定Windows需要多少是更难的; 你可以随时分配“方式太多”,这是Windows程序倾向于做的事情。

如果您决定严格管理此空间,则可能必须切换堆栈以调用Windowsfunction。 这还不够; 你可能会被各种Windows惊喜所灼伤。 我在这里描述了其中一个Windows:避免在堆栈上推送完整的x86上下文 。 我有平庸的解决方案,但不是很好的解决方案。