I do not have a problem in the true sense of the word, but rather I will try to clarify a question of content. Suppose we have a microkernel (PC Intel x86; 32 Bit Protected Mode) with working Interrupt Descriptor Table (IDT) and Interrupt Service Routine (ISR) for each CPU exception. The ISR is called successfully, say in case of a Division by Zero
exception.
global ir0
extern isr_handler
isr0:
cli
push 0x00 ; Dummy error code
push %1 ; Interrupt number
jmp isr_exc_handler
isr_exc_handler:
; Save the current processor state
pusha
mov ax, ds
push eax
mov ax, 0x10 ; Load kernel data segment descriptor
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
; Push current stack pointer
mov eax, esp
push eax
call isr_handler ; Additional C/C++ handler function
pop eax ; Remove pushed stack pointer
pop ebx ; Restore original data segment descriptor
mov ds, bx
mov es, bx
mov fs, bx
mov gs, bx
popa
add esp, 0x08 ; Clean up pushed error code and ISR number
sti
iret
The problem is that the interrupt is thrown again and again. As a result, the ISR is called again and again. By trial and error I found out that the line that provokes the exception,
int x = 5 / 0
, is executed in loop so the Instruction Pointer (EIP) is not incremented.
When I increment IP's value pushed to stack manually, the expected behavior occurs. The CPU executes then the next instruction after the malicious line of code. Of course after the ISR was called once.
To my actual question: Is it necessary that the ISR increments the IP? Or is this the responsibility of the "CPU/Hardware"? What's the correct behavior to move on?
You're responsible knowing how and why the processor will call your interrupt service routines and writing code for your ISRs accordingly. You're trying to treat an exception generated by a division by zero error as if it were generated by a hardware interrupt. However this is not how Intel x86 processors handle these kind of exceptions.
There several different kinds of events that will result in the processor invoking an interrupt service routine given in the interrupt vector table. Collectively these are called interrupts and exceptions, and there are three different ways the processor can handle an interrupt or exception, as a fault, as a trap, or as an abort. Your divide instruction generates a Divide Error (#DE) exception, which is handled as a fault. Hardware and software interrupts are handled as traps, while other kinds of exceptions are handled as one of these three ways, depending on the source of the exception.
The processor handles an exception as a fault if the nature of the exception allows for it to be corrected in some way. Because of this, the return address pushed on the stack points at the instruction that generated the exception so the the fault handler knows what exact instruction caused the fault and to make it possible to resume execution of the faulting instruction after fixing the problem. A Page Fault (#PF) exception is a good example of this. It can be used to implement virtual memory by having the fault handler provide a valid virtual mapping for the address that the faulting instruction tried to access. With a valid page mapping in place the instruction can be resumed and executed without generating another page fault.
Interrupts and certain kinds of exceptions, all of them software exceptions, are handled as traps. Traps don't imply an error in execution of a instruction. Hardware interrupts occur in between the execution of instructions, and software interrupts and certain software exceptions effectively mimic this behaviour. Traps are handled by pushing the address of next instruction would have been normally executed. This allows the trap handler to resume the normal execution of the interrupted code.
Serious and unrecoverable errors are handled as aborts. There only two exceptions that generate aborts, the Machine Check (#MC) exception and the Double Fault (#DF). Machine check instructions are the result of hardware failure in the processor itself being detected, this can't be fixed, and normal execution can't be reliably resumed. Double fault exceptions happen when a exception occurs during the handling of an interrupt or an exception. This leaves the CPU in an inconsistent state, somewhere in the middle of all many necessary steps to invoke an ISR, one that cannot be resumed. The return value pushed on the stack may or may not have anything to with whatever caused the abort.
Normally, most operating systems handle divide error exceptions by passing it along to a handler in the executing process to handle, or failing that by terminating the process, indicating that it had crashed. For example, most Unix systems send a SIGFPE signal to the process, while Windows does something similar using its Structured Exception Handling mechanism. This is so the process's programming language runtime can set up its own handler to implement whatever behaviour is necessary for the programming language being used. Since division by zero results in undefined behaviour in C and C++, crashing is an acceptable behaviour, so these languages don't normally install a divide by zero handler.
Note that while you could handle divide error exceptions by "incrementing EIP", this is harder than you might think and doesn't produce a very useful result. You can't just add one or some other constant value to EIP, you need to skip over entire instruction which could be anywhere from 2 to 15 bytes long. There's three instructions that can cause this exception, AAM, DIV and IDIV, and these can be encoded with various prefixes and operand bytes. You'll need decode the instruction to figure out how long it is. The result performing this increment will be as if the instruction was never executed. The faulting instruction won't calculate a meaningful value and you'll have no indication why the program isn't behaving correctly.
If you're writing your own operating system then you'll need to have the Intel Software Developer's Manual available so you can consult it often. In particular you'll need to read and learn pretty much everything in Volume 3: System Programming Guide, excluding the Virtual Machine Extension chapters and everything afterwards. Everything you need to know about how interrupts and exceptions is covered in detail there, plus a lot of other things you'll need to know.
Here is what the Intel64 and IA-32 Architectures Software Developer's Manual Volume 3 (3A, 3B, 3C & 3D): System Programming Guide, chapter 6.5 EXCEPTION CLASSIFICATIONS says:
Faults A fault is an exception that can generally be corrected and that, once corrected, allows the program to be restarted with no loss of continuity. When a fault is reported, the processor restores the machine state to the state prior to the beginning of execution of the faulting instruction. The return address (saved contents of the CS and EIP registers) for the fault handler points to the faulting instruction, rather than to the instruction following the faulting instruction.
While a division by zero can not typically be corrected, Table 6-1. Protected-Mode Exceptions and Interrupts still shows that the cpu designers decided that the #DE Divide error should be a fault type exception.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With