【问题标题】:Invalid Op Code Exception when implementing IRQs实现 IRQ 时出现无效的操作码异常
【发布时间】:2021-03-06 20:49:50
【问题描述】:

我一直试图在基本内核开发上松散地关注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

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

【问题讨论】:

  • 您的异常处理程序应该收到错误地址。检查该地址处的指令。还可以考虑通过指针传递你的结构。
  • 感谢您的建议!作为指针传递工作正常。我会尝试看看我是否能找出检索地址的代码,尽管“movsd”指令最有可能是可疑的,因为它是clang为test_t和@987654338生成的代码之间唯一不同的地方@
  • 我的猜测是编译器正在使用 SSE 指令复制结构,但您尚未启用 SSE 指令。
  • 啊,我不知道SSE!不过好像是这样,谢谢!今天晚些时候我会写一个回复。

标签: c assembly clang osdev irq


【解决方案1】:

@Ross Ridge 想通了(感谢他!)。下面的细节是我从the OSDev wiki学到的东西

流 SIMD 扩展 (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

【讨论】:

  • and ax, 0xFFFBand eax, 0xFFFFFFFB 更长更慢。后者可以用 3 个字节编码(使用符号扩展的一字节立即数)。由于指令解码器针对一字节和四字节立即数进行了优化,所以第一个导致解码延迟是因为两字节立即数。它需要将修改后的 ax 值与 eax 的前 16 位合并。这同样适用于or ax, 0x02or eax, 0x02。指令and al, 0xfbor al, 0x2 避免了大部分这些问题,但仍然需要合并eax 的高位。
  • @prl:这只在启动时运行一次,所以代码大小是要走的路。此外,Haswell 和后来的 AL 不会与 RAX 的其余部分分开重命名,而 AMD CPU 从来没有这样做过,所以 AL 上的 RMW 指令完全没问题,除非在较旧的 Intel 尤其是 P6 系列上,它实际上会导致部分注册稍后停止 (Why doesn't GCC use partial registers? / How exactly do partial registers on Haswell/Skylake perform? Writing AL seems to have a false dependency on RAX, and AH is inconsistent)
  • 也相关:How do I enable SSE for my freestanding bootable code? 除了问题想要使用 SSE。在这里,您可能希望像大多数内核一样使用-mno-sse禁用 SSE,这样您就不必在中断处理程序中保存/恢复用户空间 FPU/SIMD 状态! (不过,如果您没有用户空间,您仍然可以启用 SSE 以供用户空间使用,或者由内核的主要部分使用。就像 __attribute__((target("sse"))) 的倒数一样?)
猜你喜欢
  • 2018-04-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-09
  • 1970-01-01
  • 2020-11-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多