Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Intel x86 - Interrupt Service Routine responsibility

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?

like image 276
0x1C1B Avatar asked Dec 10 '22 01:12

0x1C1B


2 Answers

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.

How x86 processors handle interrupt and 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.

Faults

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.

Traps

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.

Aborts

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.

How divide error exceptions are normally handled

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.

Read the documentation

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.

like image 192
Ross Ridge Avatar answered Jan 12 '23 16:01

Ross Ridge


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.

like image 45
Jester Avatar answered Jan 12 '23 14:01

Jester