无法修改数据段寄存器。尝试抛出“常规保护错误”时

时间:2019-06-06 16:22:05

标签: gcc assembly x86 osdev isr

我一直在尝试创建一个ISR处理程序 tutorial是James Molloy撰写的,但我被卡住了。每当我抛出软件中断时,通用寄存器和数据段寄存器都会被CPU自动推入的变量压入堆栈。然后,将数据段更改为0x10的值(内核数据段描述符),以便更改特权级别。然后,在处理程序返回之后,这些值将被pop进行处理。但是只要更改ds中的值,就会抛出GPE,错误代码为0x2544,几秒钟后VM重新启动。 (链接器和编译器i386-elf-gcc,汇编程序nasm)

我尝试将hlt条指令放置在两条指令之间,以查找抛出GPE的指令。之后,我发现了`mov ds,ax'指令。我尝试了各种操作,例如删除由引导程序代码初始化的堆栈,以删除代码的特权更改部分。从通用存根返回的唯一方法是删除代码中更改特权级别的部分,但是当我想进入用户模式时,我仍然希望它们保持不变。

这是我的常见存根:

isr_common_stub:
    pusha                    ; Pushes edi,esi,ebp,esp,ebx,edx,ecx,eax
    xor eax,eax
    mov ax, ds               ; Lower 16-bits of eax = ds.
    push eax                 ; save the data segment descriptor

    mov ax, 0x10  ; load the kernel data segment descriptor
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    call isr_handler

    xor eax,eax
    pop eax
    mov ds, ax ; This is the instruction everything fails;
    mov es, ax
    mov fs, ax
    mov gs, ax
    popa
    iret

我的ISR处理程序宏:

extern isr_handler

%macro ISR_NOERRCODE 1
  global isr%1        ; %1 accesses the first parameter.
  isr%1:
    cli
    push byte 0
    push %1
    jmp isr_common_stub
%endmacro

%macro ISR_ERRCODE 1
  global isr%1
  isr%1:
    cli
    push byte %1
    jmp isr_common_stub
%endmacro
ISR_NOERRCODE 0
ISR_NOERRCODE 1
ISR_NOERRCODE 2
ISR_NOERRCODE 3
...

我的C处理程序导致“收到的中断:0xD错误代码0x2544”

#include <stdio.h>
#include <isr.h>
#include <tty.h>

void isr_handler(registers_t regs) {
    printf("ds: %x \n" ,regs.ds);
    printf("Received interrupt: %x with err. code: %x \n", regs.int_no, regs.err_code);
}

还有我的主要功能:

void kmain(struct multiboot *mboot_ptr) {
    descinit(); // Sets up IDT and GDT
    ttyinit(TTY0); // Sets up the VGA Framebuffer
    asm volatile ("int $0x1"); // Triggers a software interrupt
    printf("Wow"); // After that its supposed to print this
}

如您所见,代码应该输出,

ds: 0x10
Received interrupt: 0x1 with err. code: 0

但会导致

...
ds: 0x10
Received interrupt: 0xD with err. code: 0x2544

ds: 0x10
Received interrupt: 0xD with err. code: 0x2544
...

继续进行直到虚拟机重新启动。

我在做什么错了?

我也可以上传整个源代码。

1 个答案:

答案 0 :(得分:3)

代码还不完整,但是我想您会发现是James Molloy的OSDev教程中一个众所周知的错误的结果。 OSDev社区已在errata list中编译了已知错误的列表。我建议检查并修复此处提到的所有错误。特别是在这种情况下,我相信引起问题的错误是这个:

  

问题:中断处理程序破坏了中断状态

     

本文先前已告诉您了解ABI。如果你这样做   在本教程建议的interrupt.s中看到一个巨大的问题:它   破坏ABI进行结构传递!它创建了一个实例   堆栈上的struct寄存器,然后按值将其传递给   isr_handler函数,然后假定结构完整   然后。但是,堆栈上的函数参数属于   该函数,并允许在认为合适的时候丢弃这些值   (如果您需要知道编译器是否确实这样做,   思考错误的方式,但实际上确实如此)。有两种方法   围绕这个。最实用的方法是将结构作为   指针代替,它允许您显式编辑寄存器   需要时状态-对于系统调用非常有用,而无需   编译器会为您随机执行。编译器仍然可以编辑   不需要时,将指针放在堆栈上。第二   选项是制作另一个副本结构并传递该

问题在于32位System V ABI无法保证按值传递的数据不会在堆栈上被修改!编译器可以随意选择出于任何目的重用该内存。编译器可能生成的代码已破坏了存储 DS 的堆栈区域。当将 DS 设置为伪造值时,它崩溃了。您应该做的是通过引用传递而不是传递价值。我建议在汇编代码中进行以下代码更改:

irq_common_stub:
    pusha
    mov ax, ds
    push eax
    mov ax, 0x10 ;0x10
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    push esp                 ; At this point ESP is a pointer to where DS (and the rest
                             ; of the interrupt handler state resides)
                             ; Push ESP as 1st parameter as it's a 
                             ; pointer to a registers_t  
    call irq_handler
    pop ebx                  ; Remove the saved ESP on the stack. Efficient to just pop it 
                             ; into any register. You could have done: add esp, 4 as well
    pop ebx
    mov ds, bx
    mov es, bx
    mov fs, bx
    mov gs, bx
    popa
    add esp, 8
    sti
    iret

然后将irq_handler修改为使用registers_t *regs而不是registers_t regs

void irq_handler(registers_t *regs) {
    if (regs->int_no >= 40) port_byte_out(0xA0, 0x20);
    port_byte_out(0x20, 0x20);

    if (interrupt_handlers[regs->int_no] != 0) {
        interrupt_handlers[regs->int_no](*regs);
    }
    else
    {
        klog("ISR: Unhandled IRQ%u!\n", regs->int_no);
    }
}

我实际上建议每个中断处理程序使用一个指向registers_t的指针,以避免不必要的复制。如果您的中断处理程序和interrupt_handlers数组使用了以registers_t *作为参数(而不是registers_t)的函数,那么您将修改代码:

interrupt_handlers[r->int_no](*regs); 

成为:

interrupt_handlers[r->int_no](regs);

重要:您还必须对 ISR处理程序进行相同类型的更改。 IRQ和ISR处理程序以及关联的代码都存在相同的问题。