当堆栈和堆碰撞时会发生啥
Posted
技术标签:
【中文标题】当堆栈和堆碰撞时会发生啥【英文标题】:What Happens When Stack and Heap Collide当堆栈和堆碰撞时会发生什么 【发布时间】:2010-11-22 23:21:28 【问题描述】:我很想知道当栈和堆碰撞时会发生什么。如果有人遇到过这种情况,请他们解释一下这个场景。
【问题讨论】:
我很确定当你无法阻止内心的伤害时会发生这种情况 我不同意票数接近 (NaRQ)。所描述的情况是真实的,即使在今天,在小型系统中也很重要,很糟糕,值得理解。 这里应该没有接近投票。这就像是《皇帝的新装》的反面。人们关闭是因为他们不理解这个问题,而不是因为这不是一个好问题。 @JohnSaunders 如果人们不理解这个问题,那么它就没有它可能的那么好。也许它可以改进...... @NormanRamsey:去吧。 【参考方案1】:如果幸运的话,您会遇到内存不足异常或堆栈异常。如果你不走运,程序会进入无效内存并引发错误的内存异常。如果你非常不走运,程序会继续运行并丢弃不应该的东西,你永远不知道程序失败的原因。
当然,宇宙最终可能会破裂。
【讨论】:
+1 MS-DOS 宇宙已经破解,但我们还没有看到繁荣 注意栈溢出不能是C++异常。 @atorras - MSDOS 宇宙不会破解,除非他们从我冷酷的死手手中夺取 for 命令 :-)【参考方案2】:这将取决于平台。在许多平台上它实际上根本不会发生(堆和堆栈分配在不同的页面中,两者都不会相遇。
请记住,堆向上增长和堆栈向下增长的想法只是概念性的。在非常小的系统(如运行 CP/M 的旧 8 位微控制器)和一些 PIC 和其他平面内存模型系统(没有 MMU 或任何其他虚拟或受保护内存支持的系统)上,堆和堆栈实际上可能是以这种方式实施。在那种情况下,行为将是未定义的......但几乎可以肯定,只要代码试图返回损坏堆栈顶部的某个地址或跟随从堆的一部分到另一部分的间接指针或 . ..
无论如何,您都不会在任何现代通用工作站或服务器上看到它。您将达到资源限制并导致 malloc 失败,或者您将遇到虚拟内存,最终系统将自己陷入颤抖的“点击红色开关”。
【讨论】:
【参考方案3】:在现代操作系统上运行的现代语言中,当您尝试增加堆时,您会遇到堆栈溢出(万岁!)或malloc()
或sbrk()
或mmap()
将失败。但并不是所有的软件都是现代的,所以让我们来看看故障模式:
如果堆栈增长到堆中,通常的 C 编译器将默默地开始覆盖堆的数据结构。在现代操作系统上,会有一个或多个虚拟内存保护页面,以防止堆栈无限增长。只要保护页面中的内存量至少与增长过程的激活记录的大小一样大,操作系统就会向您保证段错误。如果你在没有 MMU 的机器上运行 DOS,你可能会被水洗。
如果堆增长到堆栈中,操作系统应该始终注意这种情况,并且某种系统调用将会失败。 malloc()
的实现几乎肯定会注意到失败并返回 NULL
。之后会发生什么取决于您。
我总是对编译器编写者希望操作系统放置保护页以防止堆栈溢出的意愿感到惊讶。当然,这个技巧很有效,直到你开始拥有数千个线程,每个线程都有自己的堆栈......
【讨论】:
即使使用单线程,也有可能出现未检测到的堆栈-堆冲突:gcc.gnu.org/ml/gcc-help/2014-07/msg00076.html(尽管这在实践中不太可能发生,也许除非是在特定攻击的情况下)。 GCC 手册页建议在多线程程序中使用-fstack-check
标志(在我的示例中,此标志还允许检测到冲突)。
@vinc17 这也用-fsanitise=thread -g
检测到(-g
是添加行号)。程序运行速度慢了 2 到 20 倍,但它检测到大量线程安全问题。还检测到-fsanitise=address
。在任何问题上,它都会打印它发生的确切行号。在 Ubuntu 18.04 + gcc 9.3.0 下从@vinc17 的上述链接运行示例程序没有发现任何问题:即使使用-O3
编译,程序正确也会立即以segmentation fault
终止。添加-fsanitise=X
选项(上图)显示了确切的代码行。
@Contango 我在 2014-07 年使用 GCC 4.9.1 完成了测试,程序成功终止。现在我在GETADDR(c)
之前的printf
期间遇到分段错误,即使使用GCC 4.6.4(2013-04 发布)也是如此。因此,系统中似乎发生了一些变化,以实现对堆栈堆冲突的快速保护,独立于 GCC(binutils?Linux 内核?)。我很想知道。 GCC 中的情况也可能发生了变化,但后来(2017+),所以不关心 GCC 4.6.4:gcc.gnu.org/legacy-ml/gcc-patches/2017-06/msg01343.html
“C 编译器”不会覆盖任何内容。该程序可以。一个重要的区别。【参考方案4】:
在这样的时代,是时候求助于 Egon Spengler 博士的圣言了....
博士。 Egon Spengler:有件非常重要的事情我忘了告诉你。 博士。彼得文克曼:什么? 博士。 Egon Spengler:不要让堆与栈发生冲突。 博士。彼得文克曼:为什么? 博士。 Egon Spengler:那会很糟糕。 博士。 Peter Venkman:我对这里的“好/坏”这件事有点模糊。 “坏”是什么意思? 博士。 Egon Spengler:试着想象一下你所知道的所有生命都会瞬间停止,你体内的每个分子都以光速爆炸。 博士。 Ray Stantz:质子完全逆转! 博士。彼得文克曼:那很糟糕。好的。好的,重要的安全提示。谢谢,埃贡。【讨论】:
奇怪的是,我想到的那句话是“狗和猫住在一起” 很棒的插图。希望universed -debug
释放所有内存!【参考方案5】:
如果发生堆栈/堆溢出,您将收到分段错误或内存分配错误。这是一个例子:
void recursiveFun ()
static int i;
// char *str= (char *)malloc (100);
printf ("%d\t", i++);
recursiveFun ();
// free (str);
假设,你调用上面的函数,它会用完堆栈,程序会崩溃。现在,删除注释行并再次调用该函数,您会发现分段错误发生的时间比早期版本更少,递归更少。 [在我的测试环境中,堆栈溢出发生在第一种情况下 5237765 递归之后,而在第二种情况下,它发生在 2616325 递归之后。]
【讨论】:
太棒了。这两行注释是如何在程序执行中发挥作用的?【参考方案6】:它会给出堆栈溢出错误。否则会导致 malloc() 等新的堆内存分配函数失败。
【讨论】:
以上是关于当堆栈和堆碰撞时会发生啥的主要内容,如果未能解决你的问题,请参考以下文章