我自己写了一个内核,在第一个页面错误中断处理程序之后,当IRET执行时,它会导致一个中断13(一般保护),错误代码是0x18。不知道怎么回事,压栈的内容来自cpu。
这是发生中断时的寄存器状态,以及存储寄存器的内存。此外,IRET 从页面错误中断处理程序中返回。
在 IRET 执行和中断发生之前,可以确定 %ESP 是相同的。
如果异常来自IRET
自身,那么很可能IRET
是无法恢复已保存的段寄存器之一,但值(8 或 0x18,顺便说一句?)在某种程度上是错误的。这可能是错误的,因为您从未在受保护模式下(重新)初始化寄存器,或者您的处理程序在执行之前将其设置为错误值IRET
或 GDT 发生了什么事情......
编辑ESP
:从图片中可以看出,页面错误处理程序在执行之前没有删除异常代码(地址 in 处的值 4 ) IRET
。因此IRET
将 4 解释为 的新值EIP
,将 0x1000018解释为 的新值,将 0x23CS
解释为 的新值EFLAGS
,而这三个寄存器应该使用 0x1000018、0x23 和 0x3206。显然,无法加载数据段选择器(0x1000018 被解释为截断为 0x0018 之后)CS
,这会导致#GP(0x18)。
扩展 Alexey:
当某些中断发生(但不是其他)时,它们会自动将一个 4 字节的错误代码压入堆栈。页面错误就是其中之一。
此错误代码包含有关中断的额外信息。
英特尔手册第 3 卷系统编程指南 - 325384-056US 2015 年 9 月表 6-1。“保护模式异常和中断”列的“错误代码”准确地告诉我们哪些中断会推送错误代码,哪些不会。
38.9.2.2 “页面错误错误代码”解释了错误的含义。
因此,您将需要:
pop %eax
/* Do something with %eax */
iret
或者,如果您想忽略错误代码:
add $4, %esp
iret
有关最小示例,请参阅此页面处理程序并尝试注释掉pop
.
将上述情况与不需要出栈的除法错误异常进行比较。
请注意,如果您只是这样做int $14
,则不会推送额外的字节:这只发生在实际异常中。
解决这个问题的一种巧妙方法是在堆栈上推送一个虚拟错误代码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
。