分段错误是多年来一直困扰我的问题。我主要在嵌入式平台上工作,因为我们在裸机上运行,所以没有文件系统可用于记录核心转储。系统只是锁定并死亡,可能会通过串行端口发出几个告别字符。那些年中更加启迪人心的时刻之一是当我意识到分段错误(和类似的致命错误)是一件好事情。经历这样的错误并不好,但将它们作为硬性不可避免的失败点是有益的。
这样的故障并不轻易产生。硬件已经尝试了它所能做的一切,而错误是硬件警告您继续下去是危险的方式。实际上,让整个进程/系统崩溃甚至比继续进行更安全。即使在具有受保护/虚拟内存的系统中,继续执行此类故障也可能使其余系统变得不稳定。
如果捕获写入受保护内存的时刻
导致 segfault 的原因不仅仅是写入受保护内存。例如,还可以通过从具有无效值的指针读取来导致 segfault。这可能是由先前的内存损坏(已经造成了损害,因此无法恢复)或缺乏错误检查代码(应该被您的静态分析器和/或测试捕获)引起的。
为什么它不可恢复?
你不一定知道问题的原因或范围,所以你无法知道如何从中恢复。如果您的内存已被破坏,您不能信任任何东西。可以从中恢复的情况是您可以在事先检测到问题的情况下使用异常不是解决问题的正确方法。
请注意,一些此类问题在其他语言(如 C#)中是可以恢复的。这些语言通常具有额外的运行时层,在硬件生成故障之前检查指针地址并抛出异常。但是,像 C 这样的低级语言没有任何这样的功能。
为什么这个解决方案可以避免不可恢复状态?它真的吗?
这种技术“有效”,但仅适用于人为制造的简单用例。继续执行和恢复不是相同的事情。相关系统仍处于故障状态,存在未知的内存损坏,您只是选择继续前进而不是听从硬件的建议认真处理问题。此时无法预测程序会执行什么操作。在潜在的内存损坏之后继续执行的程序将是攻击者的早期圣诞礼物。
即使没有内存损坏,这个解决方案在许多常见用例中也会出现问题。你无法在已经处于受保护的代码块中时进入第二个受保护的代码块(例如内部帮助函数)。在受保护的代码块之外发生的任何段错误都将导致跳转到代码中难以预测的位置。这意味着每行代码都需要在受保护的代码块中,并且你的代码将很难理解。你无法调用外部库代码,因为那些代码不使用此技术,也不会设置setjmp锚点。你的“处理程序”块不能调用库函数或执行涉及指针的任何操作,否则你将面临无休止嵌套的块。某些东西,如自动变量,在longjmp之后可能处于不可预测的状态。
关于关键任务系统(或任何系统)有一件事情值得注意:在生产中的大型系统中,人们无法知道段错误在哪里,甚至是否存在,因此修复错误而不是症状的建议并不适用。
我不同意这种想法。我见过的大多数段错误都是由于在使用指针之前未先进行验证而导致的(直接或间接地)。在使用指针之前对它们进行检查将告诉你段错误出现的位置。将复杂语句(例如
my_array [ptr1->offsets [ptr2->index]]
)拆分为多个语句,以便您可以检查中间指针。Coverity等静态分析工具能够很好地找到使用未经验证的指针的代码路径。这不会保护你免受由明显的内存损坏引起的段错误,但无论如何都没有从那种情况中恢复的方法。
简而言之,我认为我的错误只是访问null等。
好消息!整个讨论都是毫无意义的。指针和数组索引可以在使用之前进行验证,并且提前检查的代码比等待问题发生并尝试恢复要少得多。
malloc
等内存分配函数的内部记账。然后稍后你调用malloc
,由于之前提到的破坏导致触发了分段错误。然后呢?这有点像在现实生活中从悬崖上跳下去,一旦跳了就无法恢复了。 - Jabberwockymalloc
很可能会再次触发 segmentation fault(分段错误)。”。 - Jabberwockystd::vector<int> v(1); v[i] = -1;
,其中i
等于32。这段代码可能不会引发段错误(https://godbolt.org/z/sh8TW34j9),但它可能会破坏堆空间。后续的某些堆函数可能会导致段错误。如果此时您并不知道发生了堆破坏,该如何恢复? - Daniel Langr