执行 IRQ 时出现无效操作码异常

Invalid Op Code Exception when implementing IRQs

我一直在尝试松散地遵循 this tutorial 基本内核开发。目前,目标架构是i386。

IRQ 的实现给我带来了问题;每当我尝试将寄存器(定义为 struct)作为函数参数传递时,我的中断处理程序都会报告一连串 Invalid Op Code 异常。这是引发异常的中断处理程序的代码:

void interrupt_handler(registers_t all_registers) {
    // Printing exception's name
    kprint("interrupt_handler.c (l. 53) : Exception raised was:", 0xB0);
    kprint(exception_messages[(int) all_registers.int_no], 0xB0);
    kprint("\n", 0xB0);

    // Celling test_handle to display the value of some registers
    // INVALID OP CODE ================>
    test_handle(all_registers); // works as expected if this line is commented out
   
}

void test_handle(registers_t all_registers) {
    kprint("interrupt_handler.c (l. 78) : Register DS contains", 0xD0);
    kprint("to be implemented", 0xD0);
}

结构体registers_t定义如下(复制自教程):

typedef struct {
   u32int ds;                                      /* Data segment selector */
   u32int edi, esi, ebp, esp, ebx, edx, ecx, eax;  /* Pushed by pusha. */
   u32int int_no, err_code;                        /* Interrupt number and error code (if applicable) */
   u32int eip, cs, eflags, useresp, ss;            /* Pushed by the processor automatically */
} __attribute__((packed)) registers_t;

尝试用其他结构调用函数,我发现 struct 中的变量数量很重要;任何具有 5 到 16 个 u32int 的 struct 都会触发异常。例如,以下结构在初始化并将空值传递给 test_handle 时不会引发异常:

// Same as registers_t with less arguments
typedef struct {
    u32int ds;
    u32int edi, esi;
}  __attribute__((packed))  test_t;

反汇编 .o 文件显示生成的代码使用 mov 指令传递 test_t 结构和 movsd 传递 registers_t。所以我怀疑是编译过程出了问题,因为编译器生成了无法识别的指令。

以下是我的Makefile的相关摘录:

C_FLAGS=-ffreestanding -nostartfiles -nodefaultlibs -fno-builtin -Wall -Wextra -fno-exceptions -m32 -target i386-pc-elf  -fno-rtti

# Compiling C code
%.o: %.c
    clang $(C_FLAGS) -c $< -o $@ 

# Linking
kernel/kernel.bin: $(O_FILES)
    ld -o $@ -Ttext 0x1000 $^ --oformat binary -m elf_i386

编译过程有什么问题吗?还是问题出在其他地方?

@Ross Ridge 弄明白了(感谢他!)。下面的细节是我从the OSDev wiki

中学到的

Streaming SIMD Extension (SSE) 扩展了 CPU 识别的指令集,增加了大约 70 条指令,并添加了更多的寄存器。在使用其指令和寄存器之前,需要启用 SSE。编译器生成的机器代码可以包含 SSE 指令,因此需要启用 SSE。

在上面的代码中,将 struct 传递给函数被编译成机器代码,其中涉及 xmm0 寄存器,它是 SSE 的一部分。

下面给出了启用 SSE 的汇编代码(改编自 OSDev wiki)。在进入 32 位保护模式之后和进入内核之前,我将它添加到我的引导加载程序中。这解决了问题!

mov eax, cr0        ; cr0 cannot be manipulated directly, manipulate eax instead
and ax, 0xFFFB      ; clear coprocessor emulation CR0.EM
or ax, 0x2          ; set coprocessor monitoring  CR0.MP
mov cr0, eax
mov eax, cr4        ; cr4 too cannot be manipulated directly
or ax, 3 << 9       ; set CR4.OSFXSR and CR4.OSXMMEXCPT at the same time
mov cr4, eax