繁体   English   中英

堆栈溢出是否会导致除分段错误之外的其他内容?

[英]Can a stack overflow result in something other than a segmentation fault?

在一个已编译的程序中(假设是C或C ++,但我想这个问题可以扩展到任何带有调用堆栈的非VM语言) - 通常当你溢出堆栈时, 会出现分段错误

堆栈溢出是[a]原因,分段错误就是结果。

但这总是如此吗? 堆栈溢出是否会导致其他类型的程序/操作系统行为?

我也问过非Linux,非Windows操作系统和非X86硬件。 (当然,如果你没有硬件内存保护或操作系统支持(例如MS-DOS)那么就没有分段错误;我问的是你可能会遇到分段错误的情况但是还会发生其他事情)。

注意:假设除了堆栈溢出之外,程序是有效的,并且不会尝试访问超出其边界的数组,取消引用无效指针等。

是的,即使在标准OS(Linux)和标准硬件(x86)上也是如此。

void f(void) {
    char arr[BIG_NUMBER];
    arr[0] = 0; // stack overflow
}

请注意,在x86上,堆栈会增长,因此我们将分配到数组的开头以触发溢出。 通常的免责声明适用......确切的行为取决于比本答案中讨论的更多因素,包括C编译器的细节。

如果BIG_NUMBER刚刚大到足以溢出,您将遇到堆栈保护并获得分段错误。 这就是堆栈保护的用途,它可以小到单个4 KiB页面(但不小,在Linux 4.12之前使用这个4 KiB大小)或者它可以更大(Linux 4.12上默认为1 MiB) ,见mm:大堆保护间隙 ),但总是有一些特殊的尺寸。

如果BIG_NUMBER足够大,溢出可以跳过堆栈保护并落在其他一块内存上,可能是有效的内存。 这可能会导致您的程序行为不正确但不会崩溃,这基本上是最糟糕的情况:我们希望我们的程序在错误时崩溃,而不是做一些无意识的事情。

有一件事是当你溢出堆栈时在运行时会发生什么,这可能是很多事情。 包括但不仅限于; 分段错误,覆盖任何溢出的变量,导致非法指令,什么都没有,等等。 “旧的”经典论文Smashing The Stack For Fun和Profit描述了很多人可以用这些东西“玩得开心”的方式。

另一件事是在编译时会发生什么。 在C和C ++中,超出数组或超出堆栈大小的写入是Undefined Behavior,当程序包含UB的任何地方时 ,编译器基本上可以随意对程序的任何部分执行任何操作 现代编译器在利用UB进行优化时变得非常积极 - 通常假设UB从未发生过,导致他们只是删除包含UB的代码或导致分支始终或永远不会被占用,因为替代方案会导致UB。 有时,编译器会引入时间旅行调用源代码中从未调用的函数以及许多其他可能导致实际上令人困惑的运行时行为的函数

也可以看看:

每个C程序员应该知道什么是未定义的行为#1/3

每个C程序员应该知道什么是未定义的行为#2/3

每个C程序员应该知道什么是未定义的行为#3/3

C和C ++中未定义行为指南,第1部分

C和C ++中未定义行为指南,第2部分

C和C ++中未定义行为指南,第3部分

其他答案已经很好地涵盖了PC方面。 我将谈谈嵌入式世界中的一些问题。

嵌入式代码确实有类似于段错误的东西。 代码存储在某种非易失性存储器中(这些天通常是闪存,但过去是某种ROM或PROM)。 写这个需要特殊的操作来设置它; 正常的内存访问可以从中读取但不能写入。 此外,嵌入式处理器通常在其存储器映射中存在较大间隙。 如果处理器获得对只读存储器的写请求,或者如果它获得对物理上不存在的地址的读或写请求,则处理器通常会抛出硬件异常。 如果连接了调试器,则可以检查系统状态以查找出现问题的情况,如核心转储。

但是不能保证堆栈溢出会发生这种情况。 堆栈可以放在RAM中的任何位置,这通常与其他变量并列。 堆栈溢出的结果通常是破坏这些变量。

如果您的应用程序也使用堆(动态分配),那么通常会分配一段内存,其中堆栈从该部分的底部开始向上扩展,堆从该部分的顶部开始并向下扩展。 显然,这意味着动态分配的数据将成为第一个受害者。

如果你运气不好,你甚至可能没有注意到它何时发生,然后你需要找出你的代码行为不正确的原因。 在最具讽刺意味的情况下,如果被覆盖的数据是一个指针,那么当指针试图访问无效内存时,你仍然可能会得到一个硬件异常 - 但这将是堆栈溢出后的一段时间,并且自然的假设通常是它的代码中的错误。

嵌入式代码有一个共同的模式来处理这个问题,即通过将每个字节初始化为已知值来对堆栈进行“水印”。 有时编译器可以这样做; 或者有时您可能需要在main()之前在启动代码中自己实现它。 您可以从堆栈的末尾回头查找它不再设置为此值的位置,此时您知道堆栈使用的高水位标记; 或者如果它都是不正确的,那么你知道你有溢出。 嵌入式应用程序通常(并且是良好实践)将其作为后台操作连续轮询,并且能够将其报告以用于诊断目的。

由于可以跟踪堆栈使用情况,大多数公司将设置可接受的最坏情况余量以避免溢出。 这通常在75%到90%之间,但总会有一些备用。 这不仅有可能存在您尚未看到的更糟糕的最坏情况,而且当需要添加使用更多堆栈的新代码时,它还使未来开发的生活更轻松。

Stackoverflow是程序未定义行为众多原因之一 在这种情况下,您可以获得预期的结果或分段错误,或者您的硬盘可能被擦除等。不要指望任何已定义的行为,因为它是未定义的行为。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM