我正在尝试通过IDT处理内核中断。 我在Linux下使用Intel x86。
我设置了IDT和中断条目,并启动了一些测试来查看我的中断处理程序。
当我尝试int $0x0
时,它完美地运行:我的处理程序被调用但是当我尝试一些异常并且错误代码被推送时,我进入一个无限循环。
架构如下:
当异常到来时,我的处理程序的第一部分是在ASM中并调用一个公共C部分。
my_handler.c
void handler(int i)
{
printf("Exception %d caught\n", i);
}
my_handlers.S
common:
pushal
pushl %ds
pushl %es
pushl %fs
pushl %gs
addl $48, %esp // 4 4-bytes segments pushed
// + 8 4-bytes registers (pushal)
` // esp points on exception code
call handler // call the C handler with exception code
subl $48, %esp
popl %gs
popl %fs
popl %es
popl %ds
popal
addl $8, %esp // 4-byte error code + 4-byte exception number
iret
exception_de_handler:
pushl $0 // Fake error code
pushl $0 // interrupt number
jmp common
exception_gp_handler:
// error code is pushed by µproc.
pushl $13 // interrupt number
jmp common
exception_pf_handler:
// error code is pushed by µproc.
pushl $14 // interrupt number
jmp common
如果我尝试运行以下代码:
int* a = 0x0;
*a = 42;
它有效,在*a = 42;
但如果我尝试:
int* a = 0x0;
*a = 42;
*a = 1337;
它进入无限循环:
Exception 14 caught
Exception 13 caught
Exception 13 caught
Exception 13 caught
Exception 13 caught
.....
Exception 13 caught
Exception 13 caught
Exception 13 caught
.....
为什么第一个异常Page Fault(14)处理然后在General Protection(13)上循环?
感谢您的回答。
答案 0 :(得分:3)
我认为你弄乱你的筹码。您需要非常小心在中断处理程序中对堆栈执行的操作。在这种情况下,您似乎执行以下操作: -
推送错误代码(可由CPU完成) 推送注册 推送段注册
将0x48添加到堆栈指针,将堆栈一直卷回,以便指向错误代码。
调用你的C函数
这实际上做的是“释放”存储段寄存器的堆栈部分。事实上,您根本不需要担心C函数,因为返回地址被推送到在调用指令处堆叠并在您进入C调用之前消除ds和es的记录。当你从C调用回来时,你试图整理callstack,但是你没有把它弄得很正确 - 部分是因为你已经把它搞砸了,部分是因为你没有清理堆栈之后函数调用(假设处理程序使用_cdecl调用约定)。
这会导致您弹出ds的虚假值。当您将其加载到ds时,CPU会根据GDT检查该值,并发现它无效。此时它会引发你正在观看的GPF(例外13)。那,在某种程度上,恢复堆栈(CPU正在为你寻找SS)并保留ds set的旧值 - 所以你永远不会实际更改ds,这允许你再次运行printf。
你需要更加小心地对齐堆栈,每当你像往常一样添加到堆栈指针时,你需要考虑该范围内的数据永远消失,因为下一个人,或者可能是意外的中断,会打扰你。