操作系统如何检测堆栈溢出?

6
在许多操作系统中,堆栈和堆从进程的虚拟地址空间的相反侧开始,并朝向彼此增长。这使得堆栈可以尽可能地扩展而不会碰到堆。
假设我有一个导致堆栈溢出的程序。我的理解是,这将导致堆栈无法控制地向堆增长,并最终撞到它。这个理解正确吗?如果是的话,操作系统如何检测到堆栈溢出发生?似乎操作系统无法检测到程序试图使用为堆分配的虚拟内存作为堆栈的一部分,因为它们将在连续的内存区域中。
我知道这是特定于操作系统的,但对于任何操作系统中发生这种情况的机制的见解肯定会很有帮助。这已经困扰我一段时间了,我似乎找不到任何好的解释。
4个回答

7
操作系统会为栈分配一些空间。当进程访问未分配的栈部分时,处理器会引发页面错误,并被操作系统捕获。如果操作系统认为扩展栈仍然是合理的,它会简单地为其分配新的空间并将控制权返回给进程。如果不合理,则会引发堆栈溢出异常。

1
@CAFxX- 这看起来很合理,但是作为后续,假设我有一个堆栈帧非常巨大的程序(比如说,大于虚拟内存的一页)。那么,我的程序是否可以分配大量页面用于其堆栈,然后“跳过”堆栈后面的内存并进入堆的内存? - templatetypedef
操作系统已经有相应的检查措施。当我说如果不合理就会引发堆栈溢出时,我的意思是如果堆栈增长太多(比如达到堆的范围),即使通常限制更严格和依赖于平台,操作系统也会通过引发堆栈溢出来阻止您这样做 - 这通常会终止进程,以避免发生糟糕的事情。 - CAFxX
1
我突然想到,也许你在询问另一种类型的堆栈溢出,即导致堆栈损坏的那种。这是不同的,通常由进程运行时通过在堆栈中放置“哨兵”来检查。如果哨兵被覆盖,就意味着堆栈已经损坏,并会引发堆栈溢出/损坏异常(但通常是由进程运行时而不是操作系统引发)。 - CAFxX
@CAFxX- 我在这个问题中指的是操作系统级别的“你的堆栈已经增长过大,因此你的进程已经崩溃”的溢出,但感谢你提出了关于堆栈损坏的观点。 - templatetypedef

2

这只是直觉,但确保栈不会干扰堆听起来像是JVM的工作。我认为我没有理由不能够创建一个自己糟糕的编程语言,在其中允许栈开始覆盖堆(然后崩溃)。


@WuHoUnited- 对于JVM来说是这样的,但JVM是一种模拟堆栈和堆的软件,因此在管理它时具有更多的主动控制。特别是,它可以设置堆栈深度限制。我更加好奇的是,在像C或C++这样的语言中使用本地代码时会发生什么情况,因为操作系统不负责设置每个堆栈帧。 - templatetypedef

2

堆栈溢出是在堆栈中向后进行的——它们通过覆盖已经初始化的堆栈部分中的数据来工作,这正是因为堆栈向下增长才可能实现的。

因此,操作系统无法检测到这种情况,因为进程可以随时修改其堆栈的初始化部分。

扩展堆栈是通过进程使用堆栈空间,使操作系统陷入页面错误处理程序中,因为没有设置页面。一些操作系统只允许在“守卫页面”上进行这些访问,即当前堆栈之前的页面将触发重新分配,其他操作系统在故障发生时查看堆栈指针寄存器的内容,以确定这是否应该是堆栈访问。


2
大多数现代处理器都有内存管理单元(MMU),它是一种“硬件”设备,为每个程序应用虚拟地址,并且每个程序都生存在自己的内存空间中。操作系统处理这个MMU,当程序想要读取或写入专用内存之外的地址时,MMU会引发中断。

http://en.wikipedia.org/wiki/Memory_management_unit

这样它如何检测SO就很明显了。堆栈不在连续的内存地址上。
我也看到过使用保护字的不同方法。为每个进程分配的堆栈位于堆中,并填充了保护词(类似于0xc0cac01a)。这样,通过计算保护词的数量,很容易得到每个进程的堆栈大小。如果没有至少一个保护词,操作系统会引发紧急情况。

请问一下,守卫字是如何工作的?难道不应该由操作系统完成吗?您是指程序员可以在汇编甚至C等低级语言中完成吗? - Smart Humanism
1
这通常适用于嵌入式系统的低级操作系统或简单的操作系统环境,例如:虚拟机(如Java VM或Python VM)或异步运行时环境。 这意味着,在实现此类环境时,您将实现守卫词,但不会将其作为用户使用软件针对此类环境的目标。 - Luka Rahne

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接