2012-02-05 158 views
1

几个月来,我一直在研究“自制”操作系统。 目前,它启动并进入32位保护模式。 我已经加载了中断表,但还没有设置分页(还)。操作系统开发:如何避免例外程序后的无限循环

现在在编写我的异常例程时,我注意到当一条指令抛出一个异常时,异常例程就会执行,但是CPU会跳回到引发异常的指令!这并不适用于每一个异常(例如,一个div零异常会跳回指令除法指令之后),但是让我们考虑下面的一般保护性异常:

MOV EAX, 0x8 
MOV CS, EAX 

我的例行很简单:调用显示红色错误消息的函数。

结果:MOV CS,EAX失败 - >我的错误消息显示 - > CPU跳回到MOV CS - >无限循环垃圾邮件错误消息。

我在操作系统和unix安全方面与老师讨论过这个问题。 他告诉我他知道Linux有办法绕过它,但他不知道哪一个。

幼稚的解决方案是从例程中解析抛出指令,以获得该指令的长度。 该解决方案非常复杂,我感到有点不舒服,在每个受影响的例外程序中增加一个相对较重的功能的呼叫...

因此,我想知道是否是解决问题的另一种方法。也许有一个“神奇”的寄存器,包含一点可以改变这种行为?

-

非常感谢您提前任何建议/信息。

-

编辑:好像很多人想知道为什么我想跳过有问题的指示,恢复正常运行。

我有两个方面的原因:

  1. 首先,杀的过程将是一个可能的解决方案,而不是一个干净的。这不是它在Linux中的做法,例如,内核发送信号(我认为是SIGSEGV),但不会立即中断执行。这很有意义,因为应用程序可以阻止或忽略信号并恢复自己的执行。这是一个非常优雅的方式来告诉应用程序它做了错误的国际海事组织。

  2. 另一个原因:如果内核本身执行非法操作会怎么样?可能是由于一个错误,但也可能是由于内核扩展。正如我在评论中所说:在这种情况下我应该怎么做?我只需要杀死内核并用笑脸显示一个漂亮的蓝屏?

这就是为什么我希望能够跳过指令。 “猜测”指令的大小显然不是一种选择,解析指令看起来相当复杂(不是我介意实现这样的例程,但我需要确保没有更好的方法)。

回答

2

不同的例外有不同的原因。一些例外是正常的,并且该例外仅在软件继续运行之前告诉内核它需要做什么。例如,页面错误告诉内核它需要从交换空间加载数据,一个未定义的指令异常告诉内核它需要模拟一个CPU不支持的指令,或者一个调试/断点异常告诉内核它需要通知调试器。对于这些内核来说,修正事情并默默继续是正常的。

某些异常表示异常情况(例如软件崩溃)。处理这类异常的唯一理由是停止运行软件。您可以保存信息(例如核心转储)或显示信息(例如“蓝屏死机”)以帮助进行调试,但最终软件会停止(程序终止,或内核进入“不做任何事情直到用户重置计算机“状态)。

忽略异常情况只会让人们很难找出问题所在。例如,假设指令去厕所:

  • 进入浴室
  • 删除裤子
  • 开始产生输出

现在想象一下,第2步失败,因为你穿短裤(一种“找不到裤子”的例外)。你是否想在那个时候停下来(有一个很容易理解的错误信息或者其他的东西),或者忽略那一步,并且在所有有用的诊断信息都消失之后,试图找出以后出错的地方?

+0

我明白你的观点。我知道继续执行产生段错误的进程是没有意义的,但我只是想*做到这一点,主要是出于好奇。 从我目前所了解的情况来看,不可能以简单的方式来完成(例如,没有定义中断行为的神奇寄存器)。 – 2012-02-08 15:05:35

+1

如果你确实想要做到这一点,那么你必须实现一个解码器来确定RAM中原始字节的指令长度。对于80x86(可变长度指令,前缀等)这将是一大笔工作。一旦你得到了它的“工作”,它在某些情况下仍然不起作用。例如,想象一下由“页面不存在”导致的页面错误 - 您无法尝试解码不存在的字节。 – Brendan 2012-02-08 15:18:28

+0

我知道这将是一个大量的工作。没有想到页面错误,但这不是一个很大的问题(至少在我的情况下,还没有)。 – 2012-02-08 15:47:03

2

如果我理解正确,您希望跳过导致异常的指令(例如mov cs, eax),并在下一条指令中继续执行程序。

你为什么要这么做?通常,程序的其他部分不应该依赖于成功执行该指令的效果吗?

一般来说,有三种方法的异常处理:

  • 对待异常作为不可修复的条件和终止进程。例如,零除以通常以这种方式处理。

  • 修复环境,然后再次执行指令。例如,页面错误有时以这种方式处理。

  • 使用软件模拟指令并在指令流中跳过指令。例如,有时以这种方式处理复杂的算术指令。

+0

如果指令出现在一个进程中,那么杀死进程将是最简单的解决方案。但是,如果指令正确显示在内核中呢?不一定是基本的内核代码,也可能是内核扩展......我不能杀死内核! (我没有试图重新创建Windows);)我不确定我是否理解了关于软件仿真的第三点意味着什么,但它似乎比简单解码指令更复杂! – 2012-02-05 18:29:48

+0

关于仿真的第三点是针对类似CPU不支持浮点的情况,但您希望在软件中模拟这些指令。 – Nayuki 2012-02-05 18:45:37

+0

你能回答这些问题吗? “你为什么要这样做?通常,程序的其他部分不应该依赖于成功执行该指令的效果吗?” – Nayuki 2012-02-05 18:48:02

2

你所看到的是一般保护例外的特征。英特尔系统编程指南中明确指出(6.15异常和中断参考/中断13 - 一般保护性异常(#GP)):

Saved Instruction Pointer 
The saved contents of CS and EIP registers point to the instruction that generated the 
exception. 

因此,你需要编写一个异常处理程序将跳过该指令(这可能有些奇怪),或者只是简单地用“一般保护例外$SAVED_EIP”或类似消息来终止违规流程。

0

我可以想象出一些情况,其中一个人想要通过解析失败的指令,模拟其操作,然后返回指令来响应GPF。正常模式是设置事件,以便指令(如果重试)能够成功,但可以例如有一些代码需要访问地址为0x000A0000-0x000AFFFF的硬件,并希望在缺少这种硬件的机器上运行它。在这种情况下,人们可能不希望在该空间的“真实”内存中存储资金,因为每一次访问都必须单独进行处理和处理。我不确定是否有任何方法可以解决这个问题,而不必解码任何指令试图访问内存,尽管我知道一些虚拟PC程序似乎管理得很好。

否则,我建议你应该为每个线程设置一个跳转向量,当系统遇到GPF时应该使用该跳转向量。通常情况下,该向量应该指向一个线程退出例程,但是要使用指针做一些“可疑”事情的代码可以将其设置为适合该代码的错误处理程序(代码应该在向该区域错误处理程序本来是合适的)。

我可以想象在哪里可能想要模仿一个指令而不执行它的情况,以及可能想要将控制权转移给错误处理程序例程的情况,但我无法想象任何人想要跳过的地方通过一条可能导致GPF的指令。

+0

最后一段的+1。 – Nayuki 2012-02-06 03:36:56