分配一个新的调用堆栈

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

我有一个我一直在为脚本语言编写的玩具编译器。 为了能够在脚本执行过程中暂停执行并返回到主机程序,它有自己的堆栈:一个带有“堆栈指针”变量的简单内存块,使用正常的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分段模式描述的使用堆栈段的唯一联系。

    假设任何内存区域都可以安全地使用堆栈寄存器和指令进行操作,我可以不考虑为什么调用具有已知调用深度的任何函数(即没有递归,没有函数指针)会是个问题,只要金额在虚拟堆栈上可用。 对?

    正确。 只要你不希望能够返回到其他功能而不切换回原始堆栈。 有限的递归级别也是可以接受的,只要栈足够深[有某些类型的问题,如果没有递归,肯定难以解决 - 例如二叉树搜索]。

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

    事实上,如果你有点不幸,这将是一个棘手的错误。

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

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


    通常,在x86上,只要满足以下条件,就可以使用现有的堆栈:

  • 你不会溢出它
  • 你不会增加堆栈指针寄存器( popadd esp, positive_value / sub esp, negative_value )超出你的代码开始的位置(如果你这样做,中断或异步回调(信号)或任何其他活动使用堆栈将垃圾其内容)
  • 您不会导致任何CPU异常(如果这样做,则异常处理代码可能无法将堆栈展开到可处理异常的最近点)
  • 同样的情况适用于为临时堆栈使用不同的内存块,并将esp指向其末尾。

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

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


    你可以使用你喜欢的任何区域作为处理器的堆栈(模仿内存保护)。

    本质上,你只需用一个指向新区域的指针加载ESP寄存器(“MOV ESP,...”),然而你设法分配它。

    你必须有足够的资源用于你的程序,以及它可能调用的任何东西(例如,Windows OS API)以及操作系统所具有的任何有趣行为。 你可能会弄清楚代码需要多少空间; 一个好的编译器可以轻松地做到这一点 确定Windows需要多少是困难的; 你总是可以分配“方式太多”,这是Windows程序倾向于做的事情。

    如果您决定严格管理这个空间,您可能必须切换堆栈才能调用Windows函数。 这还不够; 你很可能会被各种Windows意外事件烧毁。 我在这里描述其中的一个Windows:避免在堆栈上推送完整的x86上下文。 我有平庸的解决方案,但没有很好的解决方案。

    链接地址: http://www.djcxy.com/p/71685.html

    上一篇: Allocating a new call stack

    下一篇: Tracking link in different browsers