异常处理问题

Issues with exception handling

我在 GDT、IDT 和 ISR 上关注 Bran's tutorial。我编写了异常处理程序,但是当我通过除以零来测试它时,它出现了三重错误。我不确定我做错了什么。这里是 descriptor_table.h:

#ifndef VOS_DESCRIPTOR_TABLE_H
#define VOS_DESCRIPTOR_TABLE__H

#include <stddef.h>
#include <stdint.h>
#include <string.h>

#define SEGMENT_BASE 0
#define SEGMENT_LIMIT 0xFFFFF

#define CODE_RX_TYPE 0xA
#define DATA_RW_TYPE 0x2

#define GDT_NUM_ENTRIES 6
#define TSS_SEGSEL (5*8)

struct gdt_entry
{
  uint16_t limit_low;
  uint16_t base_low;
  uint8_t base_middle;
  uint8_t access;
  uint8_t granularity;
  uint8_t base_high;
} __attribute__((packed));
typedef struct gdt_entry gdt_entry_t;

struct gdt_ptr
{
  uint16_t limit;
  uint32_t base;
} __attribute__((packed));
typedef struct gdt_ptr gdt_ptr_t;

struct idt_entry
{
  uint16_t base_low;
  uint16_t sel;
  uint8_t always0;
  uint8_t flags;
  uint16_t base_high;
} __attribute__((packed));
typedef struct idt_entry idt_entry_t;

struct idt_ptr
{
  uint16_t limit;
  uint32_t base;
} __attribute__((packed));
typedef struct idt_ptr idt_ptr_t;

struct regs
{
  uint32_t gs, fs, es, ds;
  uint32_t edi, esi, ebp, ebx, edx, ecx, eax;
  uint32_t int_no, err_code;
  uint32_t eip, cs, eflags, useresp, ss;
};
typedef struct regs regs_t;

void gdt_set_gate(int, unsigned long, unsigned long, unsigned char, unsigned char);
void idt_set_gate(uint8_t, uint32_t, uint16_t, uint8_t);
int gdt_init();
int idt_init();
int isrs_init();
int descriptors_init();
#endif

这里是descriptor_table.c:

#include <stddef.h>
#include <stdint.h>
#include <string.h>

#include <kernel/system.h>
#include <kernel/descriptor_table.h>

gdt_entry_t gdt[3];
gdt_ptr_t gp;
idt_entry_t idt[256];
idt_ptr_t idtp;

extern void gdt_flush();
extern void idt_load();

void gdt_set_gate(int num, unsigned long base, unsigned long limit, unsigned char access, unsigned char gran)
{
  gdt[num].base_low = (base & 0xFFFF);
  gdt[num].base_middle = (base >> 16) & 0xFF;
  gdt[num].base_high = (base >> 24) & 0xFF;

  gdt[num].limit_low = (limit & 0xFFFF);
  gdt[num].granularity = ((limit >> 16) & 0x0F);

  gdt[num].granularity |= (gran & 0xF0);
  gdt[num].access = access;
}

int gdt_init()
{
  gp.limit = (sizeof(gdt_entry_t) * 3) - 1;
  gp.base = (uint32_t)&gdt;

  // Null descriptor
  gdt_set_gate(0, 0, 0, 0, 0);

  // Code segment
  gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF);

  // Data segment
  gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF);

  gdt_flush();

  return 1;
}

void idt_set_gate(uint8_t num, uint32_t base, uint16_t sel, uint8_t flags)
{
  idt[num].base_low = base & 0x0000FFFF;
  idt[num].base_high = (base >> 16) & 0x0000FFFF;

  idt[num].sel = sel;
  idt[num].always0 = 0;
  idt[num].flags = flags;
}

int idt_init()
{
  // IDT pointer
  idtp.limit = (sizeof(idt_entry_t) * 256) - 1;
  idtp.base = (uint32_t)&idt;

  // Clear out IDT
  memset(&idt, 0, sizeof(idt_entry_t) * 256);

  // Add ISRs
  // TODO

  idt_load();

  return 1;
}

int descriptors_init()
{
  kernel_log("Initializing GDT...");
  gdt_init();
  kernel_log("GDT initialized.");

  kernel_log("Initializing IDT...");
  idt_init();
  kernel_log("IDT initialized.");

  return 1;
}

这里是isrc.c:

#include <stddef.h>
#include <stdint.h>

#include <kernel/descriptor_table.h>

#include <kernel/serial.h>
#include <kernel/system.h>
#include <kernel/tty.h>

extern void isr0();
extern void isr1();
extern void isr2();
extern void isr3();
extern void isr4();
extern void isr5();
extern void isr6();
extern void isr7();
extern void isr8();
extern void isr9();
extern void isr10();

extern void isr11();
extern void isr12();
extern void isr13();
extern void isr14();
extern void isr15();
extern void isr16();
extern void isr17();
extern void isr18();
extern void isr19();
extern void isr20();

extern void isr21();
extern void isr22();
extern void isr23();
extern void isr24();
extern void isr25();
extern void isr26();
extern void isr27();
extern void isr28();
extern void isr29();
extern void isr30();
extern void isr31();

int isrs_init()
{
  idt_set_gate(0, (uint32_t)isr0, 0x08, 0x8E);
  idt_set_gate(1, (uint32_t)isr1, 0x08, 0x8E);
  idt_set_gate(2, (uint32_t)isr2, 0x08, 0x8E);
  idt_set_gate(3, (uint32_t)isr3, 0x08, 0x8E);
  idt_set_gate(4, (uint32_t)isr4, 0x08, 0x8E);
  idt_set_gate(5, (uint32_t)isr5, 0x08, 0x8E);
  idt_set_gate(6, (uint32_t)isr6, 0x08, 0x8E);
  idt_set_gate(7, (uint32_t)isr7, 0x08, 0x8E);

  idt_set_gate(8, (uint32_t)isr8, 0x08, 0x8E);
  idt_set_gate(9, (uint32_t)isr9, 0x08, 0x8E);
  idt_set_gate(10, (uint32_t)isr10, 0x08, 0x8E);
  idt_set_gate(11, (uint32_t)isr11, 0x08, 0x8E);
  idt_set_gate(12, (uint32_t)isr12, 0x08, 0x8E);
  idt_set_gate(13, (uint32_t)isr13, 0x08, 0x8E);
  idt_set_gate(14, (uint32_t)isr14, 0x08, 0x8E);
  idt_set_gate(15, (uint32_t)isr15, 0x08, 0x8E);

  idt_set_gate(16, (uint32_t)isr16, 0x08, 0x8E);
  idt_set_gate(17, (uint32_t)isr17, 0x08, 0x8E);
  idt_set_gate(18, (uint32_t)isr18, 0x08, 0x8E);
  idt_set_gate(19, (uint32_t)isr19, 0x08, 0x8E);
  idt_set_gate(20, (uint32_t)isr20, 0x08, 0x8E);
  idt_set_gate(21, (uint32_t)isr21, 0x08, 0x8E);
  idt_set_gate(22, (uint32_t)isr22, 0x08, 0x8E);
  idt_set_gate(23, (uint32_t)isr23, 0x08, 0x8E);

  idt_set_gate(24, (uint32_t)isr24, 0x08, 0x8E);
  idt_set_gate(25, (uint32_t)isr25, 0x08, 0x8E);
  idt_set_gate(26, (uint32_t)isr26, 0x08, 0x8E);
  idt_set_gate(27, (uint32_t)isr27, 0x08, 0x8E);
  idt_set_gate(28, (uint32_t)isr28, 0x08, 0x8E);
  idt_set_gate(29, (uint32_t)isr29, 0x08, 0x8E);
  idt_set_gate(30, (uint32_t)isr30, 0x08, 0x8E);
  idt_set_gate(31, (uint32_t)isr31, 0x08, 0x8E);

  return 1;
}

char* exception_messages[] =
{
  "Division by Zero",
  "Debug",
  "Non-maskable Interrupt",
  "Breakpoint",
  "Into Detected Overflow",
  "Out of Bounds",
  "Invalid Opcode",
  "No Coprocessor",

  "Double Fault",
  "Coprocessor Segment Overrun",
  "Bad TSS",
  "Segment Not Present",
  "Stack Fault",
  "General Protection Fault",
  "Page Fault",
  "Unknown Interrupt",

  "Coprocessor Fault",
  "Alignment Check",
  "Machine Check",
  "Reserved", // 19
  "Reserved", // 20
  "Reserved", // 21
  "Reserved", // 22
  "Reserved", // 23

  "Reserved", // 24
  "Reserved", // 25
  "Reserved", // 26
  "Reserved", // 27
  "Reserved", // 28
  "Reserved", // 29
  "Reserved", // 30
  "Reserved", // 31
};

void fault_handler(regs_t *r)
{
  if(r->int_no < 32)
  {
    kernel_log_partA("Exception Detected: ");
    //kernel_log_partB(exception_messages[r->int_no]);
    //kernel_log("System Halted.");

    // terminal_writeline("Exception Detected: ");
    // terminal_reset();
    // terminal_write(exception_messages[r->int_no]);
    // terminal_writeline("System Halted!");

    // Halting system
    // TODO: Reboot the system to halt
    for(;;);
  }
}

这里是gdt.S:

.intel_syntax noprefix

.global gdt_flush
.extern gp
gdt_flush:
  lgdt [gp]
  mov ax, 0x10
  mov ds, ax
  mov es, ax
  mov fs, ax
  mov gs, ax
  mov ss, ax
  jmp 0x08:flush2
flush2:
  ret

这里是idt.S:

.intel_syntax noprefix

.global idt_load
.extern idtp
idt_load:
    lidt [idtp]
    ret

这里是isr.S:

.intel_syntax noprefix

.global isr0
.global isr1
.global isr2
.global isr3
.global isr4
.global isr5
.global isr6
.global isr7
.global isr8
.global isr9
.global isr10

.global isr11
.global isr12
.global isr13
.global isr14
.global isr15
.global isr16
.global isr17
.global isr18
.global isr19
.global isr20

.global isr21
.global isr22
.global isr23
.global isr24
.global isr25
.global isr26
.global isr27
.global isr28
.global isr29
.global isr30

.global isr31

# Divide by zero exception
isr0:
    cli
    push 0x0
    push 0x0
    jmp isr_common_stub

# Debug exception
isr1:
    cli
    push 0x0
    push 0x1
    jmp isr_common_stub

isr2:
    cli
    push 0x0
    push 0x2
    jmp isr_common_stub

isr3:
    cli
    push 0x0
    push 0x3
    jmp isr_common_stub

isr4:
    cli
    push 0x0
    push 0x4
    jmp isr_common_stub

isr5:
    cli
    push 0x0
    push 0x5
    jmp isr_common_stub

isr6:
    cli
    push 0x0
    push 0x6
    jmp isr_common_stub

isr7:
    cli
    push 0x0
    push 0x7
    jmp isr_common_stub

isr8:
    cli
    push 0x8
    jmp isr_common_stub

isr9:
    cli
    push 0x0
    push 0x9
    jmp isr_common_stub

isr10:
    cli
    push 0x10
    jmp isr_common_stub

isr11:
    cli
    push 0x11
    jmp isr_common_stub

isr12:
    cli
    push 0x12
    jmp isr_common_stub

isr13:
    cli
    push 0x13
    jmp isr_common_stub

isr14:
    cli
    push 0x14
    jmp isr_common_stub

isr15:
    cli
    push 0x0
    push 0x15
    jmp isr_common_stub

isr16:
    cli
    push 0x0
    push 0x16
    jmp isr_common_stub

isr17:
    cli
    push 0x0
    push 0x17
    jmp isr_common_stub

isr18:
    cli
    push 0x0
    push 0x18
    jmp isr_common_stub

isr19:
    cli
    push 0x0
    push 0x19
    jmp isr_common_stub

isr20:
    cli
    push 0x0
    push 0x20
    jmp isr_common_stub

isr21:
    cli
    push 0x0
    push 0x21
    jmp isr_common_stub

isr22:
    cli
    push 0x0
    push 0x22
    jmp isr_common_stub

isr23:
    cli
    push 0x0
    push 0x23
    jmp isr_common_stub

isr24:
    cli
    push 0x0
    push 0x24
    jmp isr_common_stub

isr25:
    cli
    push 0x0
    push 0x25
    jmp isr_common_stub

isr26:
    cli
    push 0x0
    push 0x26
    jmp isr_common_stub

isr27:
    cli
    push 0x0
    push 0x27
    jmp isr_common_stub

isr28:
    cli
    push 0x0
    push 0x28
    jmp isr_common_stub

isr29:
    cli
    push 0x0
    push 0x29
    jmp isr_common_stub

isr30:
    cli
    push 0x0
    push 0x30
    jmp isr_common_stub

isr31:
    cli
    push 0x0
    push 0x31
    jmp isr_common_stub

.extern fault_handler

isr_common_stub:
    pusha
    push ds
    push es
    push fs
    push gs
    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov eax, esp
    push eax
    mov eax, fault_handler
    call eax
    pop eax
    pop gs
    pop fs
    pop es
    pop ds
    popa
    add esp, 8
    iret

QEMU调试后的结果如下:

EAX=8b0cec83 EBX=00010000 ECX=00000000 EDX=00000000
ESI=00000000 EDI=00000000 EBP=00000000 ESP=00106824
EIP=8b0cec83 EFL=00200002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     00105030 00000017
IDT=     00105060 000007ff
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
DR6=ffff0ff0 DR7=00000400
CCS=00000008 CCD=00106868 CCO=ADDL    
EFER=0000000000000000
FCW=037f FSW=0000 [ST=0] FTW=00 MXCSR=00001f80
FPR0=0000000000000000 0000 FPR1=0000000000000000 0000
FPR2=0000000000000000 0000 FPR3=0000000000000000 0000
FPR4=0000000000000000 0000 FPR5=0000000000000000 0000
FPR6=0000000000000000 0000 FPR7=0000000000000000 0000
XMM00=00000000000000000000000000000000 XMM01=00000000000000000000000000000000
XMM02=00000000000000000000000000000000 XMM03=00000000000000000000000000000000
XMM04=00000000000000000000000000000000 XMM05=00000000000000000000000000000000
XMM06=00000000000000000000000000000000 XMM07=00000000000000000000000000000000
qemu: fatal: Trying to execute code outside RAM or ROM at 0x8b0cec83

我在这里做错了什么?关于如何解决这个问题有什么建议吗?

问题是 NASM(您链接的教程中使用的汇编器)和 GAS(又名 GNU 汇编器,我假设您正在使用)之间的细微差别。具体来说,就是 isr.S:

中的这一行
mov eax, fault_handler

在NASM中,一切都被视为label/value。所以,上面的语句汇编成

mov eax, <address of fault_handler>

即"move 32-bit immediate into eax"。但是,在 GAS 中,它将 this 视为一个变量并自动取消引用它,因此它的行为类似于

mov eax, dword ptr [address of fault_handler]

即它提取函数 fault_handler 中的前 4 个字节并将它们分配给 eax,导致 QEMU 转储中显示的垃圾 eip

对此有一些修复:

  1. 使用lea代替mov(即lea eax, fault_handler
  2. 使用 offset 关键字 (mov eax, offset fault_handler)
  3. 直接调用函数即可(call fault_handler)