Brendan Ang

Search

Search IconIcon to open search

Exceptions

Last updated Jul 1, 2023 Edit Source

# Exceptions

A trap is a CPU generated interrupt caused by a software error or a request:

  • __unhandled exceptions in a program used to transfer control back to the OS __
  • user programs requesting execution of system calls which needs the OS CPU exceptions occur in various erroneous situations, for example, when accessing an invalid memory address or when dividing by zero. To react to them, we have to set up an interrupt descriptor table that provides handler functions.

On x86, there are about 20 different CPU exception types. The most important are:

# Interrupt Descriptor Table

The protected mode counterpart to the interrupt vector table. Each index contains bytes needed to run handlers for different exceptions. For example, the divide by 0 handler should go in the 0 index.

# Interrupt Calling Convention

Calling conventions specify the details of a function call. For example, they specify where function parameters are placed (e.g. in registers or on the stack) and how results are returned. On x86_64 Linux, the following rules apply for C functions (specified in the System V ABI):

# Preserved and Scratch Registers

The values of preserved registers must remain unchanged across function calls. So a called function (the “callee”) is only allowed to overwrite these registers if it restores their original values before returning. Therefore, these registers are called “callee-saved”. A common pattern is to save these registers to the stack at the function’s beginning and restore them just before returning.

In contrast, a called function is allowed to overwrite scratch registers without restrictions. If the caller wants to preserve the value of a scratch register across a function call, it needs to backup and restore it before the function call (e.g., by pushing it to the stack). So the scratch registers are caller-saved.

# x86-interrupt convention Preserving All Registers

In contrast to function calls, exceptions can occur on any instruction. Since we don’t know when an exception occurs, we can’t backup any registers before. This means we can’t use a calling convention that relies on caller-saved registers for exception handlers. The x86-interrupt calling convention does this by backing up registers overwritten by the function on function entry.

# Interrupt Stack Frame

A normal function call stack frame (the return address is pushed to the stack to allow the CPU to return back to the caller): An interrupt stack frame: 300

# Double Faults

A double fault is a special exception that occurs when the CPU fails to invoke an exception handler. For example, it occurs when a page fault is triggered but there is no page fault handler registered in the IDT. So it’s kind of similar to catch-all blocks in programming languages with exceptions.

Only certain combinations of exceptions can trigger double faults:

First ExceptionSecond Exception
Divide-by-zero,
Invalid TSS,
Segment Not Present,
Stack-Segment Fault,
General Protection Fault
Invalid TSS,
Segment Not Present,
Stack-Segment Fault,
General Protection Fault
Page FaultPage Fault,
Invalid TSS,
Segment Not Present,
Stack-Segment Fault,
General Protection Fault
A double fault must be handled properly, else some cases can easily transition into a triple fault causing a system reset. Kernel stack overflow is one of them.

# Kernel Stack Overflow

What happens if our kernel overflows its stack and the guard page is hit?