有什么办法可以保证段错误?

时间:2019-02-21 23:33:36

标签: c segmentation-fault undefined-behavior

我知道segfault是未定义行为的常见表现。但是我对此有两个小问题:

  1. 所有段错误都是不确定的行为吗?

  2. 如果否,是否有任何方法可以确保出现段错误?

  

What is a segmentation fault?比我的问题笼统得多,没有一个答案可以回答我的任何问题。

4 个答案:

答案 0 :(得分:5)

分段错误只是意味着您对内存进行了无效访问-要么是因为未映射请求的地址(映射错误),要么是因为您没有访问该地址的权限(访问错误)。

  1. 存在预期的分段错误。可以找到一个这样的示例here -一个微型应用程序,该应用程序故意使用内存页面的权限播放,以检测给定功能在何处进行写操作。

  2. 最简单的方法是使用raise函数。

来源:

#include <signal.h>    
int main() {
    raise(SIGSEGV);
    return 0;
}

答案 1 :(得分:3)

  
      
  1. 所有段错误都是不确定的行为吗?
  2.   

这个问题比看起来要棘手,因为“未定义的行为”是对C源程序的描述,或者是在描述抽象C程序行为的“抽象机”中运行C程序的结果;但是“分段错误”是特定操作系统的可能行为,通常是在特定CPU功能的帮助下。

C标准根本没有说明分段错误。它确实说的一件很相关的事情是,如果程序执行不具有未定义的行为,则实际实现的程序执行将具有与抽象机的执行相同的可观察到的行为。并且“可观察到的行为”被定义为仅包括对易失性对象的访问,写入文件的数据以及交互式设备的输入和输出。

如果我们可以假设“分段错误”始终阻止程序执行进一步的操作,则任何不存在未定义行为的分段错误都只能在所有可观察到的行为均按预期完成之后发生。 (但是请注意,有效的优化有时可能导致事情发生的顺序与明显的顺序不同。)

因此,尽管没有未定义的行为(根据C标准),程序仍会导致(对于OS)分段错误的情况对于真正的编译器和OS来说并没有多大意义,但我们不能裁定完全解决了。

但是,所有这些都假设计算机是完美的。如果RAM损坏,则预期的地址值可能最终会更改。甚至在非常少见但可测量的事件中,宇宙射线在原本不错的RAM中可能会发生一些变化。诸如此类的软错误可能会导致分段错误(在发生“分段错误”的系统上),实际上对于任何完美编写的C程序而言,在任何实现或输入上都不可能存在未定义的行为。

  
      
  1. 如果否,是否有任何方法可以确保段错误?
  2.   

这取决于上下文以及“确保”的含义。

您可以编写一个总是会导致段错误的C程序吗?不,因为某些计算机甚至可能没有这样的概念。

如果在计算机上可能的话,可以编写一个总是导致段错误的C程序吗?不可以,因为某些情况下某些编译器可能会采取措施避免实际问题。并且由于程序的行为是不确定的,因此不导致段错误与导致段错误一样有效。尤其是,您可能遇到的一个真正的障碍是,即使做一些简单的事情(例如故意取消引用空指针值),编译器优化有时也会假设输入和逻辑总会出现,因此不会发生未定义的行为,因为这样做可以对于确实导致不确定行为的输入,请不要执行程序说的话。

了解有关一个特定的操作系统(可能还有CPU)如何处理内存并有时会产生分段错误的详细信息,您能否编写将始终导致段错误的汇编指令?当然,如果段故障处理根本没有任何价值。您是否可以编写将以大致相同的方式触发段错误的C程序?很有可能。

答案 2 :(得分:2)

取消引用NULL指针永远不会出错。

int main() {
  int *a = 0;
  *a = 0;
  return 0;
}

正如评论正确地提到的那样,这将不会100%地起作用,并且是平台特定的。但是它应该可以在大多数常见平台上使用。

答案 3 :(得分:1)

假设平台完全支持段错误,则有一些可能性:

  • 使用raise。这将产生明显不同的siginfo_t,但这通常无关紧要。
  • 取消引用非易失性指针。可能会将其优化为“无法访问”。
  • 取消引用易失性指针。这样可以防止编译器优化访问权限。
  • 使用asm volatile并取消引用指针。确保包括稍后使用的伪输出,否则编译器仍然可以执行不需要的优化。这需要针对每个平台的特殊代码。
  • 构建一个内核模块,该模块直接生成带有适当的SIGSEGV的{​​{1}}。假设甚至可以加载(甚至编译) 内核模块。

如果要取消引用指针,请执行以下操作:

  • 通读它,或
  • 通过它编写,或
  • 都是吗?

,它应该是什么指针?

  • siginfo_t是一个流行的选择,但是有时可以映射该页面。如今,具有安全意识的内核通常不允许这样做。
  • 使用有效但未对齐的指针。这高度依赖于平台,并且可能产生不同的NULL。甚至是siginfo_t之类的东西。
  • 使用未对齐的指针跨越页面,如下所示,其中一页未被映射或受保护。
  • 使用禁止进入您正在尝试的保护的电话SIGBUS。我不记得这个mmap是否可区分。可以竞赛,但前提是程序中的其他线程是敌对的。 siginfo_t也有可能失败。
  • 先调用mmap,再调用mmap,然后取消引用指针。在信号被阻塞的单线程程序中,这可以产生保证不会被映射的地址。但是,它可能会在多线程程序中竞争,并且初始munmap可能会失败。
  • 循环调用mmap直到失败,因为您已经用尽了内核对映射内存区域数量的64k限制。然后解析/ proc / self / maps并查找其中任何一个都不覆盖的地址。如果其他线程当前mmap正在运行,则这可能很不明智。如果当前正在使用其他线程munmap,则在您的段错误终止进程之前,它们可能会以神秘的方式失败。

总而言之,没有完全可靠/便携式的方法,但是有很多方法足够好。