我的通用isr存根定义为:
isr_common_stub:
pusha ; Pushes edi,esi,ebp,esp,ebx,edx,ecx,eax
mov ax, ds ; Lower 16-bits of eax = ds.
push eax ; save the data segment descriptor
mov ax, 0x10 ; load the kernel data segment descriptor
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
call isr_handler
;call saySomething
pop ebx ; reload the original data segment descriptor
mov ds, bx
mov es, bx
mov fs, bx
mov gs, bx
;call saySomething
popa ; Pops edi,esi,ebp...
;add esp, 8 ; Cleans up the pushed error code and pushed ISR number
;sti
;call saySomething
;iret ; pops 5 things at once: CS, EIP, EFLAGS, SS, and ESP
ret ;Return to the caller. Iret and interrupt enabling is handled within caller.
此代码从每个中断调用如下:
%macro ISR_NOERRCODE 1
global isr%1
isr%1:
cli ; Disable interrupts firstly.
push byte 0 ; Push a dummy error code.
push byte %1 ; Push the interrupt number.
jmp isr_common_stub ; Go to our common handler code.
sub esp, 2 ;Removes 2 bytes from stack
;sti ;Iret enables interrupts again
iret ;Return from interrupt
%endmacro
此代码适当地调用外部处理方法,但从外部处理方法返回后会导致gpf中断。
此外,我还映射了我的gdt,以覆盖整个4G地址空间,其中包含0特权级别的数据和代码描述符(以及空描述符)。感谢您的帮助。
开始。。。
a) 在宏中,执行cli
是愚蠢的。使用"中断门"(而不是"陷阱门"),CPU将自动为您禁用IRQ,而不会出现竞争条件的风险(例如,IRQ在中断处理程序启动后,但在它完成执行cli
之前)。
b) 在宏中,jmp
之后的代码从不执行,因此毫无意义。
c) 如果你把2个字节推到堆栈上,那么你就把堆栈对齐搞砸了,应该受到性能惩罚。推送2个双字可以避免这种情况。
d) CPU为某些异常提供32位错误代码;因此,您的伪错误代码也应该是32位的。
e) 您没有向ISR传递任何信息。大多数异常处理程序都需要知道异常发生时通用寄存器等的状态。
f) 您不能期望ret
从中断返回,并且需要取消对iret
的注释。
g) 丢弃所有段寄存器(DS、ES、FS、GS)可能是个坏主意。要么操作系统将它们视为常量,您没有理由在从ISR返回之前加载它们,要么必须保存它们,然后正确重新加载。
h) 不同的异常有不同的要求,所以使用"通用异常处理程序"是愚蠢的。最好为不同的异常提供不同的异常处理程序,这就是IDT所提供的(在这种情况下,在堆栈上推送"中断号"是没有意义的)。
j) IRQ不同于例外情况。对于IRQ,中断处理程序从不需要无意义的错误代码(或中断代码的状态)。请注意,在汇编存根中处理/隐藏PIC芯片的"伪IRQ"是很好的,这意味着检查PIC的"服务中寄存器",以区分真实IRQ7和伪IRQ7之间的差异,以及真实IRQ15和伪IRQ15之间的差异。对于伪造的IRQ15,你需要将EOI发送到主设备,而不是从设备,对于伪造的IRC7,你根本不能发送任何EOI。如果您使用IO APIC,那么没有合理的方法来禁用/屏蔽PIC芯片的虚假IRQ,因此您仍然需要两个PIC芯片的伪IRQ处理程序(除了APIC自己的虚假中断的处理程序)。