在从中断处理程序返回之前,是否必须弹出由某些异常推送到堆栈的错误代码?

时间:2021-09-30 12:52:20

I have loaded an idt table with 256 entries, all pointing to similar handlers:

我已经加载了一个包含256个条目的idt表,所有条目都指向类似的处理程序:

  • for exceptions 8 and 10-14, push the exception number (these exceptions push an error code automatically)
  • 对于例外8和10-14,推送异常编号(这些异常会自动推送错误代码)

  • for the others, push a "dummy" error code and the exception number;
  • 对于其他人,推送“虚拟”错误代码和异常号码;

  • then jump to a common handler
  • 然后跳转到一个公共处理程序

So when the common handler enters, the stack is properly aligned and contains the exception/interrupt number, error code (which may just be a dummy), eflags, cs and eip.

因此,当公共处理程序进入时,堆栈正确对齐并包含异常/中断号,错误代码(可能只是虚拟代码),eflags,cs和eip。

My question regards returning from the interrupt handler. I use iret to return after taking out the exception number and the error code from the stack, but this doesn't work for exception nr 8; if I leave the error code on the stack, then it returns fine!

我的问题是关于从中断处理程序返回。我从堆栈中取出异常编号和错误代码后使用iret返回,但这对异常nr 8不起作用;如果我把错误代码留在堆栈上,那么它返回正常!

Questions:

  • do I have to leave the error code on the stack for exceptions that put the error code there? If so, how does iret determine whether it has to pop an error code or not?
  • 我是否必须将错误代码留在堆栈上,以便将错误代码放在那里?如果是这样,iret如何确定是否必须弹出错误代码?

  • as soon as I enable interrupts I always get exception 8 (double fault), but then everything runs fine (I'm developing a hobby OS). Is this normal behavior or do I have a bug somewhere?
  • 一旦我启用中断,我总是得到异常8(双重故障),但一切都运行正常(我正在开发一个爱好操作系统)。这是正常行为还是我在某处有错误?

4 个解决方案

#1


If the CPU pushed an error code automatically, the handler must pop it before the iret. The iret instruction doesn't know where you're coming from, if it's a fault, a trap or an external interrupt. It always does the same, and it assumes that there's no error code on the stack.

如果CPU自动推送错误代码,则处理程序必须在iret之前弹出它。如果是故障,陷阱或外部中断,iret指令不知道你来自哪里。它总是一样,并假设堆栈上没有错误代码。

Quoting from the SDM (Software Developer's Manual), Volume 3, Chapter 5, section 5.13 titled Error Code:

引自SDM(软件开发人员手册),第3卷,第5章,第5.13节标题为错误代码:

The error code is pushed on the stack as a doubleword or word (depending on the default interrupt, trap, or task gate size). To keep the stack aligned for doubleword pushes, the upper half of the error code is reserved. Note that the error code is not popped when the IRET instruction is executed to return from an exception handler, so the handler must remove the error code before executing a return.

错误代码作为双字或字被压入堆栈(取决于默认中断,陷阱或任务门大小)。为了保持堆栈对齐双字推送,错误代码的上半部分是保留的。请注意,执行IRET指令以从异常处理程序返回时,不会弹出错误代码,因此处理程序必须在执行返回之前删除错误代码。

You can find the IA-32 Software Developer's Manual here: http://www.intel.com/products/processor/manuals/

您可以在此处找到IA-32软件开发人员手册:http://www.intel.com/products/processor/manuals/

Volume 3 part 1, chapter 5, describes exception and interrupt handling. Volume 2 part 1 has the spec for the iret instruction.

第3卷第1部分第5章描述了异常和中断处理。第2卷第1部分有iret指令的规范。

#2


I wrote a small x86 OS a while back. Take a look at the file isr.asm in the cvs repository.

我不久前写了一个小的x86操作系统。看一下cvs存储库中的文件isr.asm。

Notice how we set up the handlers, most push a dummy dword onto the stack to account for the few handlers that automatically get an error code pushed. Then when we return via an iret we can always assume 2 dwords on the stack irrespective of the interrupt and perform an add esp, 8 before the iret to clean things up nicely.

注意我们如何设置处理程序,大多数将虚拟dword压入堆栈以解释自动获取错误代码的少数处理程序。然后,当我们通过一个iret返回时,无论中断如何,我们总是可以在堆栈上假设2个dwords并执行添加esp,8之前我会很好地清理。

That should answer your first question.

这应该回答你的第一个问题。

As for your second question: A double fault when you enable interrupts, ...hmmm could be a problem with paging if you haven't set it up correctly. Could be a million other thing too :)

关于你的第二个问题:当你启用中断时出现双重故障,如果没有正确设置,哼哼可能是分页问题。也可能是一百万其他的东西:)

#3


I had a similar problem with "double faults" as soon as I enabled interrupts. Well, they looked like double faults, but they really were timer interrupts!

一旦启用中断,我就遇到了“双重故障”的类似问题。好吧,他们看起来像双重故障,但他们真的是计时器中断!

Double faults are interrupt number 8.

双故障是中断号8。

Unfortunately, a default PIC configuration signals timer interrupts as interrupt number (DEFAULT_PIC_BASE + TIMER_OFFSET) = (8 + 0) = 8.

不幸的是,默认PIC配置信号定时器中断为中断号(DEFAULT_PIC_BASE + TIMER_OFFSET)=(8 + 0)= 8。

Masking out all my PIC interrupts (until I was ready to properly configure the PIC) silenced these double-fault-lookalike timer interrupts.

屏蔽掉所有PIC中断(直到我准备好正确配置PIC),这些双故障类似的定时器中断静音。

(PICs require the CPU to acknowledge interrupts before they produce the next one. Since your code wasn't acknowledging the initial timer interrupt, the PIC never gave you any more! That's why you only got one, rather than the zillion one might have expected.)

(PIC要求CPU在产生下一个中断之前确认中断。由于你的代码没有确认初始定时器中断,PIC从来没有给你更多!这就是为什么你只有一个,而不是你可能预期的那么多。)

#4


Do I have to leave the error code on the stack for exceptions that put the error code there?

我是否必须将错误代码留在堆栈上以查找将错误代码放在那里的异常?

As others mentioned, you have to do either:

正如其他人提到的,你必须做到:

pop %eax
/* Do something with %eax */
iret

Or if you want to ignore the error code:

或者,如果您想忽略错误代码:

add $4, %esp
iret

If you don't, iret will interpret the error code as the new CS, and you are likely to get a general protection fault as mentioned at: Why does iret from a page fault handler generate interrupt 13 (general protection fault) and error code 0x18?

如果不这样做,iret会将错误代码解释为新的CS,并且您可能会遇到如下所述的一般性保护错误:为什么来自页面错误处理程序的iret会生成中断13(一般保护错误)和错误代码为0x18?

Minimal Working this page handler that I've created to illustrate this. Try commenting out the pop and see it blow up.

Minimal使用我创建的这个页面处理程序来说明这一点。尝试评论流行音乐并看到它爆炸。

Compare the above with a Division error exception which does not to pop the stack.

将上面的内容与一个不会弹出堆栈的Division错误异常进行比较。

Note that if you do simply int $14, no extra byte gets pushed: this only happens on the actual exception.

请注意,如果只执行int $ 14,则不会推送额外的字节:这只发生在实际的异常上。

Intel Manual Volume 3 System Programming Guide - 325384-056US September 2015 Table 6-1. "Protected-Mode Exceptions and Interrupts" column "Error Code" contains the list of interrupts that push the error code or not.

英特尔手册第3卷系统编程指南 - 325384-056US 2015年9月表6-1。 “保护模式异常和中断”列“错误代码”包含推送错误代码的中断列表。

38.9.2.2 "Page Fault Error Codes" explains what the error means.

38.9.2.2“页面错误错误代码”解释了错误的含义。

A neat way to deal with this is to push a dummy error code 0 on the stack for the interrupts that don't do this to make things uniform. James Molloy's tutorial does exactly that.

处理这个问题的一种巧妙方法是在堆栈上推送一个虚拟错误代码0,用于不执行此操作的中断以使事情统一。 James Molloy的教程正是如此。

The Linux kernel 4.2 seems to do something similar. Under arch/x86/entry/entry64.S it models interrupts with has_error_code:

Linux内核4.2似乎做了类似的事情。在arch / x86 / entry / entry64.S下,它使用has_error_code建模中断:

trace_idtentry page_fault do_page_fault has_error_code=1

and then uses it on the same file as:

然后在同一个文件上使用它:

.ifeq \has_error_code
pushq $-1 /* ORIG_RAX: no syscall to restart */
.endif

which does the push when has_error_code=0.

当has_error_code = 0时执行推送。

#1


If the CPU pushed an error code automatically, the handler must pop it before the iret. The iret instruction doesn't know where you're coming from, if it's a fault, a trap or an external interrupt. It always does the same, and it assumes that there's no error code on the stack.

如果CPU自动推送错误代码,则处理程序必须在iret之前弹出它。如果是故障,陷阱或外部中断,iret指令不知道你来自哪里。它总是一样,并假设堆栈上没有错误代码。

Quoting from the SDM (Software Developer's Manual), Volume 3, Chapter 5, section 5.13 titled Error Code:

引自SDM(软件开发人员手册),第3卷,第5章,第5.13节标题为错误代码:

The error code is pushed on the stack as a doubleword or word (depending on the default interrupt, trap, or task gate size). To keep the stack aligned for doubleword pushes, the upper half of the error code is reserved. Note that the error code is not popped when the IRET instruction is executed to return from an exception handler, so the handler must remove the error code before executing a return.

错误代码作为双字或字被压入堆栈(取决于默认中断,陷阱或任务门大小)。为了保持堆栈对齐双字推送,错误代码的上半部分是保留的。请注意,执行IRET指令以从异常处理程序返回时,不会弹出错误代码,因此处理程序必须在执行返回之前删除错误代码。

You can find the IA-32 Software Developer's Manual here: http://www.intel.com/products/processor/manuals/

您可以在此处找到IA-32软件开发人员手册:http://www.intel.com/products/processor/manuals/

Volume 3 part 1, chapter 5, describes exception and interrupt handling. Volume 2 part 1 has the spec for the iret instruction.

第3卷第1部分第5章描述了异常和中断处理。第2卷第1部分有iret指令的规范。

#2


I wrote a small x86 OS a while back. Take a look at the file isr.asm in the cvs repository.

我不久前写了一个小的x86操作系统。看一下cvs存储库中的文件isr.asm。

Notice how we set up the handlers, most push a dummy dword onto the stack to account for the few handlers that automatically get an error code pushed. Then when we return via an iret we can always assume 2 dwords on the stack irrespective of the interrupt and perform an add esp, 8 before the iret to clean things up nicely.

注意我们如何设置处理程序,大多数将虚拟dword压入堆栈以解释自动获取错误代码的少数处理程序。然后,当我们通过一个iret返回时,无论中断如何,我们总是可以在堆栈上假设2个dwords并执行添加esp,8之前我会很好地清理。

That should answer your first question.

这应该回答你的第一个问题。

As for your second question: A double fault when you enable interrupts, ...hmmm could be a problem with paging if you haven't set it up correctly. Could be a million other thing too :)

关于你的第二个问题:当你启用中断时出现双重故障,如果没有正确设置,哼哼可能是分页问题。也可能是一百万其他的东西:)

#3


I had a similar problem with "double faults" as soon as I enabled interrupts. Well, they looked like double faults, but they really were timer interrupts!

一旦启用中断,我就遇到了“双重故障”的类似问题。好吧,他们看起来像双重故障,但他们真的是计时器中断!

Double faults are interrupt number 8.

双故障是中断号8。

Unfortunately, a default PIC configuration signals timer interrupts as interrupt number (DEFAULT_PIC_BASE + TIMER_OFFSET) = (8 + 0) = 8.

不幸的是,默认PIC配置信号定时器中断为中断号(DEFAULT_PIC_BASE + TIMER_OFFSET)=(8 + 0)= 8。

Masking out all my PIC interrupts (until I was ready to properly configure the PIC) silenced these double-fault-lookalike timer interrupts.

屏蔽掉所有PIC中断(直到我准备好正确配置PIC),这些双故障类似的定时器中断静音。

(PICs require the CPU to acknowledge interrupts before they produce the next one. Since your code wasn't acknowledging the initial timer interrupt, the PIC never gave you any more! That's why you only got one, rather than the zillion one might have expected.)

(PIC要求CPU在产生下一个中断之前确认中断。由于你的代码没有确认初始定时器中断,PIC从来没有给你更多!这就是为什么你只有一个,而不是你可能预期的那么多。)

#4


Do I have to leave the error code on the stack for exceptions that put the error code there?

我是否必须将错误代码留在堆栈上以查找将错误代码放在那里的异常?

As others mentioned, you have to do either:

正如其他人提到的,你必须做到:

pop %eax
/* Do something with %eax */
iret

Or if you want to ignore the error code:

或者,如果您想忽略错误代码:

add $4, %esp
iret

If you don't, iret will interpret the error code as the new CS, and you are likely to get a general protection fault as mentioned at: Why does iret from a page fault handler generate interrupt 13 (general protection fault) and error code 0x18?

如果不这样做,iret会将错误代码解释为新的CS,并且您可能会遇到如下所述的一般性保护错误:为什么来自页面错误处理程序的iret会生成中断13(一般保护错误)和错误代码为0x18?

Minimal Working this page handler that I've created to illustrate this. Try commenting out the pop and see it blow up.

Minimal使用我创建的这个页面处理程序来说明这一点。尝试评论流行音乐并看到它爆炸。

Compare the above with a Division error exception which does not to pop the stack.

将上面的内容与一个不会弹出堆栈的Division错误异常进行比较。

Note that if you do simply int $14, no extra byte gets pushed: this only happens on the actual exception.

请注意,如果只执行int $ 14,则不会推送额外的字节:这只发生在实际的异常上。

Intel Manual Volume 3 System Programming Guide - 325384-056US September 2015 Table 6-1. "Protected-Mode Exceptions and Interrupts" column "Error Code" contains the list of interrupts that push the error code or not.

英特尔手册第3卷系统编程指南 - 325384-056US 2015年9月表6-1。 “保护模式异常和中断”列“错误代码”包含推送错误代码的中断列表。

38.9.2.2 "Page Fault Error Codes" explains what the error means.

38.9.2.2“页面错误错误代码”解释了错误的含义。

A neat way to deal with this is to push a dummy error code 0 on the stack for the interrupts that don't do this to make things uniform. James Molloy's tutorial does exactly that.

处理这个问题的一种巧妙方法是在堆栈上推送一个虚拟错误代码0,用于不执行此操作的中断以使事情统一。 James Molloy的教程正是如此。

The Linux kernel 4.2 seems to do something similar. Under arch/x86/entry/entry64.S it models interrupts with has_error_code:

Linux内核4.2似乎做了类似的事情。在arch / x86 / entry / entry64.S下,它使用has_error_code建模中断:

trace_idtentry page_fault do_page_fault has_error_code=1

and then uses it on the same file as:

然后在同一个文件上使用它:

.ifeq \has_error_code
pushq $-1 /* ORIG_RAX: no syscall to restart */
.endif

which does the push when has_error_code=0.

当has_error_code = 0时执行推送。