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

ss2ws0br  于 2023-05-07  发布在  其他
关注(0)|答案(6)|浏览(173)

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

  • 对于异常8和10-14,推送异常编号(这些异常自动推送错误代码)
  • 对于其他的,推送“虚拟”错误代码和异常编号;
  • 然后跳转到公共处理程序

因此当公共处理程序进入时,堆栈被正确对齐,并包含异常/中断号、错误代码(可能只是一个伪代码)、eflags、cs和eip。
我的问题与从中断处理程序返回有关。我使用iret从堆栈中取出异常号和错误代码后返回,但这对异常nr 8不起作用;如果我把错误代码留在堆栈上,那么它会正常返回!
问题:

  • 对于将错误代码放在堆栈中的异常,我是否必须将错误代码留在堆栈中?如果是这样,iret如何确定是否必须弹出错误代码?
  • 一旦我启用中断,我总是得到异常8(双重故障),但随后一切运行正常(我正在开发一个业余操作系统)。这是正常的行为还是我有一个错误的地方?
6psbrbz9

6psbrbz91#

如果CPU自动推送错误码,处理程序***必须***在iret之前弹出。iret指令不知道你来自哪里,如果它是一个错误,一个陷阱或外部中断。它总是做同样的事情,并假设堆栈上没有错误代码。
引自SDM(软件开发人员手册),第3卷,第5章,第5.13节,标题为错误代码:
错误代码以双字或字的形式压入堆栈(取决于默认中断、陷阱或任务门大小)。为了保持堆栈对齐以进行双字推送,保留了错误代码的上半部分。请注意,当执行IRET指令以从异常处理程序返回时,不会弹出错误代码,因此处理程序必须在执行返回之前删 debugging 误代码。
您可以找到IA-32 Software Developer's Manualherehttp://www.intel.com/products/processor/manuals/
第3卷第1部分第5章描述了异常和中断处理。第2卷第1部分有iret指令的规范。

p8h8hvxi

p8h8hvxi2#

我一启用中断就遇到了类似的“双故障”问题。嗯,它们看起来像是双误,但它们实际上是定时器中断!
双重故障是中断号8
不幸的是,默认PIC配置将定时器中断信号表示为中断号(DEFAULT_PIC_BASE + TIMER_OFFSET) = (8 + 0) = 8
屏蔽掉所有的PIC中断(直到我准备好正确配置PIC)会使这些类似于双故障的定时器中断静默。
(PICs要求CPU在产生下一个中断之前确认中断。由于您的代码没有确认初始定时器中断,PIC不会再给您任何中断!这就是为什么你只得到了一个,而不是一个人可能期望的无数个。

vqlkdk9b

vqlkdk9b3#

我写了一个small x86 OS一段时间回来。看看cvs仓库中的isr.asm文件。
注意我们是如何设置处理程序的,大多数处理程序都将一个虚拟的dword推送到堆栈上,以解决少数自动推送错误代码的处理程序。然后,当我们通过iret返回时,我们总是可以假设堆栈上有2个双字,而不管中断如何,并在iret之前执行add esp,8以很好地清理事情。
这应该回答了你的第一个问题。
关于你的第二个问题:当你启用中断时会出现双重错误,……嗯,如果你没有正确设置的话,可能是分页的问题。也可能是其他一百万件事:)

o8x7eapl

o8x7eapl4#

对于将错误代码放在堆栈中的异常,我是否必须将错误代码留在堆栈中?
正如其他人所提到的,你必须做的是:

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

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

add $4, %esp
iret

否则,iret会将错误代码解释为新的CS,您可能会得到一般保护故障,如:Why does iret from a page fault handler generate interrupt 13 (general protection fault) and error code 0x18?
我创建了这个页面处理程序来说明这一点。试着注解掉pop,看看它会爆炸。
将上述情况与不弹出堆栈的Division错误异常进行比较。
请注意,如果您只执行int $14,则不会推送额外的字节:这只发生在实际的异常上。
Intel Manual Volume 3 System Programming Guide - 325384-056US September 2015表6-1。“保护模式异常和中断”列“错误代码”包含推送错误代码或不推送错误代码的中断列表。
38.9.2.2 “Page Fault Error Codes”解释了错误的含义。
处理这个问题的一个简洁的方法是在堆栈上为不这样做的中断推送一个虚拟错误代码0,以使事情变得统一。James Molloy的教程正是这样做的。
Linux内核4.2似乎做了类似的事情。在arch/x86/entry/entry64.S下,它使用has_error_code对中断进行建模:

trace_idtentry page_fault do_page_fault has_error_code=1

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

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

has_error_code=0时执行推送。

whhtz7ly

whhtz7ly5#

在64位模式下,中断堆栈在16字节边界上对齐。因此,不管向量是由int指令、异常、硬件中断调用还是没有错误代码,都可以通过设置堆栈指针的最低有效位来弹出可能的错误代码。
下面的代码给出了一个一致的、16字节对齐的堆栈:

push 1
    push 0
    and rsp, -8  ; Align Stack

L1: ...

    test [rsp+8], 1
    jz L2

    ... ; Error code present

    jmp L3

L2: ... ; Error code absent

L3: ...

    add rsp, 24
    retiq
堆叠前
ff8:ssff8:ss
ff0:rspff0:rsp
fe8:rflagsfe8:rflags
Fe0:csFe0:cs
fd8:rip〈= rspfd8:rip
fd 0:错误代码〈= rsp
L1处的堆栈
----------------------------
ff8:ssff8:ss
ff0:rspff0:rsp
fe8:rflagsfe8:rflags
Fe0:csFe0:cs
fd8:ripfd8:rip
fd0:1fd 0:错误代码
公司简介fc8:1
fc0:xxxx〈= rspfc0:0〈= rsp

对于没有特权级别改变的情况,CPU将在压入ss之前在16字节边界上对齐堆栈。如果有特权级别更改(或使用IST),只需确保TSS中的条目是16字节对齐的。要返回,只需弹出垃圾或错误代码并执行iretq。
有关详细信息,请参阅“英特尔® 64和IA-32体系结构软件开发人员手册-第3A卷”中的第6.14.2和7.7节(可在here上找到)。

yb3bgrhw

yb3bgrhw6#

在64位模式下,中断堆栈在16字节边界上对齐。因此,无论向量是由int指令、异常、硬件中断调用还是没有错误代码,都可以通过设置堆栈指针的最低有效位来弹出可能的错误代码。

相关问题