Skip to content

Page Fault Handler

Frank edited this page Jan 18, 2026 · 1 revision

Page Fault Handler

cupid-os implements a unique interactive page fault handler that transforms what would normally be a fatal system crash into an educational debugging experience. Instead of halting the system, page faults drop users into a "fault shell" where they can inspect the fault details and choose how to proceed.

Overview

Page faults occur when a program attempts to access memory that is not currently mapped or accessible. In most operating systems, this results in immediate termination. However, cupid-os provides an interactive fault shell that allows users to:

  • Inspect fault details: View the faulting address, error codes, and CPU state
  • Understand the fault: Decode error flags to understand what went wrong
  • Choose recovery options: Continue execution, reboot, or get more information
  • Learn about memory management: Hands-on experience with virtual memory concepts

Page Fault Mechanism

What Causes Page Faults?

Page faults are triggered by:

  • Accessing unmapped memory: Reading/writing to addresses without page table entries
  • Permission violations: Writing to read-only pages or accessing kernel pages from user mode
  • Reserved bit violations: Setting reserved bits in page table entries
  • Instruction fetch faults: Executing code from non-executable pages

CPU Page Fault Handling

When a page fault occurs:

  1. Hardware saves state: CPU pushes error code, EIP, CS, EFLAGS to stack
  2. CR2 register: Stores the faulting virtual address
  3. Error code: Provides details about the fault type
  4. Interrupt 14: Transfers control to the page fault ISR

cupid-os Page Fault Response

Instead of the typical kernel panic, cupid-os:

if (r->int_no == 14) {  // Page fault vector
    uint32_t cr2;
    __asm__ volatile("mov %%cr2, %0" : "=r"(cr2));

    // Temporarily enable interrupts for interactive prompt
    __asm__ volatile("sti");
    pf_action_t action = shell_pagefault_prompt(r, cr2);
    __asm__ volatile("cli");

    if (action == PF_ACTION_REBOOT) {
        system_reboot();  // Never returns
    }
    // PF_ACTION_CONTINUE: Return and retry the faulting instruction
    return;
}

Fault Shell Interface

Accessing the Fault Shell

The fault shell is triggered automatically on page faults. To test it manually:

> testpf
Testing page fault handler...
Attempting to access invalid memory address 0xDEADBEEF

This executes:

volatile uint32_t* invalid_addr = (volatile uint32_t*)0xDEADBEEF;
uint32_t value = *invalid_addr;  // This triggers the page fault

Fault Shell Display

When a page fault occurs, the system displays:

PAGE FAULT
CR2: 0xDEADBEEF
ERR: 0x00000000  (not-present read kernel )
EIP: 0x0010ABCD  CS: 0x0008  EFLAGS: 0x00000202

Type: continue | reboot | info | help
pf>

Display Fields

  • CR2: The virtual address that caused the fault
  • ERR: Error code in hexadecimal and decoded format
  • EIP: Instruction pointer (address of faulting instruction)
  • CS: Code segment selector
  • EFLAGS: CPU flags at time of fault

Error Code Decoding

The error code is a bit field that provides detailed fault information:

Bit Name Description
0 P Present - 0 = page not present, 1 = protection violation
1 W Write - 0 = read access, 1 = write access
2 U User - 0 = kernel mode, 1 = user mode
3 R Reserved - Reserved bit was set
4 I Instruction - 0 = data access, 1 = instruction fetch

Decoding Examples

  • 0x00000000: (not-present read kernel) - Reading unmapped kernel memory
  • 0x00000002: (not-present write kernel) - Writing to unmapped kernel memory
  • 0x00000004: (not-present read user) - Reading unmapped user memory
  • 0x00000006: (not-present write user) - Writing to unmapped user memory
  • 0x00000001: (present read kernel) - Protection violation reading kernel memory

Fault Shell Commands

continue (c)

Attempts to continue execution by returning from the fault handler.

pf> continue
Continuing (iret)...
  • What it does: Returns to the faulting instruction via IRET
  • Risk: May fault again if the underlying problem isn't resolved
  • Use case: Testing if fault was transient or recoverable

reboot (r)

Performs a hard reboot of the system.

pf> reboot
  • What it does: Calls system_reboot() to restart the system
  • Use case: When fault cannot be recovered from
  • Note: This is equivalent to the reboot shell command

info

Redisplay the fault information.

pf> info

PAGE FAULT
CR2: 0xDEADBEEF
ERR: 0x00000000  (not-present read kernel )
EIP: 0x0010ABCD  CS: 0x0008  EFLAGS: 0x00000202

Type: continue | reboot | info | help
pf>
  • Use case: When fault details scroll off screen or need to be re-examined

help

Display available commands.

pf> help
continue (c)  - return from the page fault handler
reboot   (r)  - hard reboot
info          - reprint CR2/flags/registers

Implementation Details

Fault Shell Structure

The fault shell is a minimal line editor that runs during interrupt context:

pf_action_t shell_pagefault_prompt(const struct registers* r, uint32_t cr2) {
    char buf[80 + 1];
    int pos = 0;

    // Display fault information
    pf_print_info(r, cr2);
    print("\nType: continue | reboot | info | help\n");
    print("pf> ");

    // Minimal command processing loop
    while (1) {
        key_event_t event;
        keyboard_read_event(&event);

        // Process input...
    }
}

Interrupt Handling During Faults

During page fault processing, interrupts are temporarily re-enabled:

// Enable interrupts for keyboard input in fault shell
__asm__ volatile("sti");
pf_action_t action = shell_pagefault_prompt(r, cr2);
// Disable interrupts before returning to avoid races
__asm__ volatile("cli");

This allows the keyboard driver to function while in the fault shell, but requires careful state management.

Register State Preservation

The fault shell has access to the complete CPU state at the time of the fault:

struct registers {
    // Segment registers
    uint32_t ds, es, fs, gs;
    // General purpose registers
    uint32_t edi, esi, ebp, esp, ebx, edx, ecx, eax;
    // Interrupt information
    uint32_t int_no, err_code;
    // Execution state
    uint32_t eip, cs, eflags, useresp, ss;
};

Educational Value

Learning Memory Management

The page fault handler provides hands-on experience with:

  • Virtual memory concepts: Understanding address translation
  • Page table structure: How virtual addresses map to physical memory
  • Memory protection: Kernel vs user space, read/write permissions
  • Error handling: How operating systems deal with faults

Debugging Techniques

Users can learn:

  • Fault analysis: Reading error codes and CR2 values
  • Memory inspection: Understanding what addresses are valid
  • Recovery strategies: When and how to continue vs reboot
  • System stability: The importance of proper error handling

Common Page Fault Scenarios

1. Null Pointer Dereference

int* ptr = NULL;
*ptr = 42;  // Page fault at CR2: 0x00000000

Error code: 0x00000002 (not-present write kernel) Cause: Attempting to write to address 0, which is not mapped

2. Stack Overflow

void recursive() {
    char buffer[1024];
    recursive();  // Eventually exhausts stack space
}

Error code: 0x00000002 (not-present write kernel) Cause: Stack pointer goes beyond allocated stack pages

3. Accessing Freed Memory

int* ptr = kmalloc(sizeof(int));
// ... use ptr ...
kfree(ptr);
*ptr = 42;  // Page fault if page was unmapped

Error code: Varies depending on memory state Cause: Accessing memory after it was freed and unmapped

4. Invalid System Calls

// Attempting to access kernel-only addresses from user mode
volatile uint32_t* kernel_addr = (volatile uint32_t*)0xC0000000;
uint32_t value = *kernel_addr;

Error code: 0x00000005 (present read user + protection violation) Cause: User mode accessing kernel memory

Limitations and Considerations

Security Implications

  • Ring 0 execution: All code runs in kernel mode
  • No process isolation: Faults can affect the entire system
  • Debugging access: Full system visibility for educational purposes

Performance Impact

  • Interrupt overhead: Page faults disable interrupts during handling
  • Interactive delays: User must respond to fault prompts
  • Recovery limitations: Cannot always safely continue after faults

Recovery Constraints

  • State corruption: Registers and memory may be in inconsistent state
  • Resource leaks: Continuing may leave resources in undefined states
  • Nested faults: Faults during fault handling can be problematic

Extending the Fault Handler

Additional Commands

Future enhancements could include:

  • Memory inspection: examine <address> to view memory contents
  • Stack trace: backtrace to show call stack
  • Register modification: set reg value to modify CPU state
  • Memory mapping: map to allocate pages and retry

Advanced Features

  • Fault logging: Record faults to filesystem for analysis
  • Recovery scripts: Automated recovery for known fault patterns
  • Context saving: Save/restore system state around faults
  • Multi-fault handling: Handle faults during fault processing

Testing and Verification

Manual Testing

Test various fault scenarios:

# Test different types of invalid accesses
> testpf  # Basic invalid access

# Create custom test cases
void test_custom_fault() {
    // Test specific fault conditions
    volatile char* ptr = (volatile char*)0xDEADBEEF;
    *ptr = 'x';  // Write fault

    volatile int* read_ptr = (volatile int*)0xCAFEBABE;
    int value = *read_ptr;  // Read fault
}

Automated Testing

Create shell commands to test different fault types:

static void shell_test_read_fault(const char* args) {
    print("Testing read fault...\n");
    volatile uint32_t* ptr = (volatile uint32_t*)0xDEADBEEF;
    uint32_t value = *ptr;
    print("Unexpected: read succeeded with value ");
    print_hex(value);
    print("\n");
}

static void shell_test_write_fault(const char* args) {
    print("Testing write fault...\n");
    volatile uint32_t* ptr = (volatile uint32_t*)0xDEADBEEF;
    *ptr = 0xCAFEBABE;
    print("Unexpected: write succeeded\n");
}

References

  • Intel Architecture Manuals: Page fault specifications
  • OSDev Wiki: Page fault handling guides
  • Linux Kernel: Advanced fault handling techniques

For more information about memory management:

Clone this wiki locally