我自己编写内核,在第一页错误中断处理程序之后,当执行IRET时,它会导致中断13(一般保护),错误代码为0x18。我不知道出了什么问题,堆栈上推送的内容来自cpu。
这是中断发生时的寄存器状态,以及存储寄存器的存储器。此外,IRET从页面错误中断处理程序返回。
确保在执行IRET和中断之前%ESP是相同的。
答案 0 :(得分:7)
如果异常来自IRET
本身,则很可能IRET
无法恢复其中一个已保存的段寄存器,但值(8或0x18,顺便说一下?)在某种程度上是错误的。这可能是错误的,因为您从未(重新)在受保护模式下初始化寄存器,或者您的处理程序在执行IRET
之前将其设置为错误值或GDT发生了某些事情...
编辑:从图片中可以看出,页面错误处理程序在执行ESP
之前没有删除异常代码(IRET
中地址的值为4)。因此,IRET
将EIP
的新值解释为4,将{100}作为CS
的新值,将0x23解释为EFLAGS
的新值,而应使用0x1000018 ,这三个寄存器的0x23和0x3206。显然,数据段选择器(0x1000018被解释为截断为0x0018后)无法加载到CS
,这会导致#GP(0x18)。
答案 1 :(得分:2)
扩展Alexey:
当某些中断发生时(但不会发生其他中断),它们会自动将4字节错误代码推送到堆栈。页面错误就是其中之一。
此错误代码包含有关中断的额外信息。
Intel Manual Volume 3 System Programming Guide - 325384-056US September 2015表6-1。 "保护模式异常和中断"栏"错误代码"告诉我们哪些中断推送错误代码,哪些没有。
38.9.2.2"页面错误代码"解释了错误的含义。
所以你需要:
pop %eax
/* Do something with %eax */
iret
或者如果您想忽略错误代码:
add $4, %esp
iret
有关最小示例,请参阅this page handler并尝试发表评论pop
。
将上述内容与不需要弹出堆栈的Division error exception进行比较。
请注意,如果只执行int $14
,则不会推送额外的字节:这只发生在实际的异常上。
处理这个问题的一种巧妙方法是在堆栈上推送一个虚拟错误代码0
,用于不执行此操作以使事情统一的中断。 James Molloy的教程does exactly that。
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
时执行推送。