如果不能恢复,为什么段错误称为错误(而不是中止)?

时间:2018-03-21 00:38:08

标签: x86 operating-system kernel x86-64 interrupt

我对以下术语的理解是

  

1)中断
  是"通知"由硬件启动以调用操作系统来运行其处理程序

     

2)陷阱
  是"通知"这是由softare发起的,要求操作系统运行其处理程序

     

3)故障
  是处理器在发生错误但可以恢复的情况下引发的异常

     

4)中止
  是处理器在发生错误但不可恢复的情况下引发的异常

为什么我们称之为segmentation fault而不是segmentation abort呢?

  

分段错误
  是你的程序试图访问它的时候   没有被操作系统分配,或者不是   不允许访问。

我的经验(主要是在测试C代码时)是,只要程序抛出segmentation fault,它就会回到绘图板 - 是否存在程序员实际可以捕获&的情况#34;例外并用它做一些有用的事情吗?

2 个答案:

答案 0 :(得分:2)

在CPU级别,现代操作系统不使用x86段限制来保护内存。 (事实上​​,即使他们想要长时间模式(x86-64),他们也不可能;段基数固定为0,限制为-1。

操作系统使用虚拟内存页表,因此超出内存访问的实际CPU异常是页面错误。

x86手册称之为 #PF(fault-code)例外,例如见the list of exceptions add can raise。有趣的事实:在段限制之外访问的x86例外是#GP(0)

由OS的页面错误处理程序来决定如何处理它。许多#PF例外是正常操作的一部分:

  • 写入时写入映射:复制页面并在页面表中将其标记为可写,然后返回用户空间以重试出现故障的指令。
  • 软页面错误:内核很懒,并且实际上没有更新页面表以反映进程所做的映射。 (例如mmap(2)没有MAP_POPULATE)。
  • 硬页面错误:找到一些物理内存并从磁盘读取文件(文件映射或来自交换文件/分区的匿名页面)。

在排除上述任何一项后,更新CPU自己读取的页表,并在必要时使该TLB条目无效。 (例如有效但只读更改为有效+读写)。

只有当内核发现进程实际上没有映射到该地址的任何内容(或者它写入只读映射)时,内核才会发送 {{ 1}} 到流程。 这是纯粹的软件事,在整理出硬件异常的原因之后。

SIGSEGVfrom strerror(3))的英文文本在所有Unix / Linux系统上都是"分段错误" ,因此'&当子进程从该信号中消失时,(由shell)打印出来。

这个术语很好理解,所以尽管它主要是出于历史原因,但硬件并没有使用分段。

请注意,您还可以获得SIGSEGV,例如尝试在用户空间中执行特权指令(如SIGSEGVwrmsr (write model-specific register))。在CPU级别,当您不在环0(内核模式)时,特权指令的x86异常为wbinvd

对于未对齐的SSE指令(如#GP(0)),尽管其他平台上的某些Unix发送movaps未对齐的访问错误(例如SPARC上的Solaris)。

  

为什么我们称之为分段错误而不是分段中止呢?

可恢复。这就是它与SIGBUS不同的可捕捉信号的原因。通常你不能恢复执行,但你可以有效地记录故障的位置(例如,打印一个精确的异常错误消息,甚至是堆栈回溯)。

SIGSEGV的信号处理程序可以是SIGKILL或其他什么。或者如果需要SIGSEGV,则在从信号处理程序返回之前修改用于加载的代码或指针。 (例如for a Meltdown exploit,虽然有更高效的技术可以在错误预测的阴影下执行链式加载或其他抑制异常的东西,而不是实际让CPU引发异常并捕获内核提供的SIGSEGV )

一些沙盒语言的JIT编译器(如Javascript)使用硬件内存访问检查来消除NULL指针检查。在正常情况下,没有错误,因此错误情况的缓慢程度并不重要。

Java JVM可以将JVM线程收到的longjmp转换为SIGSEGV,用于运行的Java代码,而JVM没有任何问题。< /强>

另一个技巧是将数组的末尾放在页面的末尾(后跟一个足够大的未映射区域),因此每次访问的边界检查都是由硬件免费完成的。如果您可以静态地证明索引始终为正数,并且它不能大于32位,那么您已全部设置。

陷阱与中止

我不认为有这个区别的标准术语。这取决于你正在谈论什么样的恢复。显然,操作系统可以在任何用户空间可以使硬件完成之后继续运行,否则无特权的用户空间可能会使机器崩溃。

相关:开 When an interrupt occurs, what happens to instructions in the pipeline?,Andy Glew(负责英特尔P6微体系结构的CPU架构师)说&#34;陷阱&#34;基本上是由运行的代码(而不是外部信号)引起的任何中断,并且是同步发生的。 (例如,当故障指令到达管道的退出阶段而没有先发现分支错误预测或其他异常时)。

&#34;中止&#34;不是标准的CPU架构术语。就像我说的那样,你希望操作系统能够继续,无论如何,只有硬件故障或内核错误通常会阻止它。

AFAIK,&#34; abort&#34;也不是非常标准的操作系统术语。 Unix有信号,其中一些是无法捕获的(如SIGKILL和SIGSTOP),但大多数都可以捕获。

<强> SIGABRT can be caught by a signal handler 即可。如果处理程序返回,则该进程退出,因此如果您不希望您可以NullPointerException退出该进程。但AFAIK没有错误条件引发SIGABRT;它仅由软件手动发送,例如通过调用longjmp库函数。 (它通常会导致堆栈回溯。)

x86例外术语

如果您查看x86手册或this exception table on the osdev wiki,则在此上下文中有特定含义(thanks to @MargaretBloom for the descriptions):

  • 陷阱:在指令成功完成后引发,返回地址指向陷阱inst之后。 abort()调试和#DB溢出(#OF)异常是陷阱。 (Some sources of #DB are faults instead)。但into或其他软件中断指令也是陷阱,int 0x80也是如此(但它将返回地址放在syscall中而不是推送它; rcx也不例外,因此,在这个意义上并不是真正的陷阱)

  • 错误:在尝试执行然后回滚后引发;返回地址指向错误指令。 (大多数异常类型都是错误)

  • abort 是指返回地址指向不相关的位置(即syscall双故障和#DF机器检查)。无法处理三重故障;当CPU遇到试图运行双故障处理程序的异常时会发生什么,并且确实会阻止整个CPU。

请注意,即使像Andy Glew这样的英特尔CPU架构师有时也会使用术语&#34;陷阱&#34;更常见的是,当使用讨论计算机体系结构理论时,我认为意味着任何同步异常。不要指望人们坚持使用上述术语,除非您实际上正在讨论在x86上处理特定异常。虽然它是有用且合理的术语,但您可以在其他环境中使用它。但是如果你想要区分,你应该澄清每个术语的含义,这样每个人都可以在同一页面上。

答案 1 :(得分:0)

有两种类型的例外:故障和陷阱。发生故障时,可以重新启动指令。发生陷阱时,无法重新启动指令。

例如,当发生页面错误时,操作系统异常处理程序会加载缺少的页面并重新启动导致错误的指令。

如果处理器定义了&#34;分段错误&#34;然后导致异常的指令是可重启的 - 但操作系统的处理程序可能不会重新启动指令。