本文介绍了无法修改数据段寄存器.尝试抛出“常规保护错误"时的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have been trying to create an ISR handler following thistutorial by James Molloy but I got stuck. Whenever I throw a software interrupt, general purpose registers and the data segment register is pushed onto the stack with the variables automatically pushed by the CPU. Then the data segment is changed to the value of 0x10 (Kernel Data Segment Descriptor) so the privilege levels are changed. Then after the handler returns those values are poped. But whenever the value in ds is changed a GPE is thrown with the error code 0x2544 and after a few seconds the VM restarts. (linker and compiler i386-elf-gcc , assembler nasm)

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

I tried placing hlt instructions in between instructions to locate which instruction was throwing the GPE. After that I was able to find out that the the `mov ds,ax' instruction. I tried various things like removing the stack which was initialized by the bootstrap code to deleting the privilege changing parts of the code. The only way I can return from the common stub is to remove the parts of my code which change the privilege levels but as I want to move towards user mode I still want them to stay.

这是我的常见存根:

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处理程序宏:

My ISR handler macros:

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"

My C handler which results in "Received interrupt: 0xD err. code 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
}

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

As you can see the code was supposed to output,

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
...

这一直持续到VM重新启动.

Which goes on until the VM restarts itself.

我在做什么错了?

推荐答案

代码尚不完整,但我想您将看到的是James Molloy的OSDev教程中一个众所周知的错误的结果. OSDev社区已在勘误表中编译了一系列已知错误.我建议检查并修复此处提到的所有错误.特别是在这种情况下,我相信导致问题的错误是这个:

The code isn't complete but I'm going to guess what you are seeing is a result of a well known bug in James Molloy's OSDev tutorial. The OSDev community has compiled a list of known bugs in an errata list. I recommend reviewing and fixing all the bugs mentioned there. Specifically in this case I believe the bug that is causing problems is this one:

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

This article previously told you to know the ABI. If you do you will see a huge problem in the interrupt.s suggested by the tutorial: It breaks the ABI for structure passing! It creates an instance of the struct registers on the stack and then passes it by value to the isr_handler function and then assumes the structure is intact afterwards. However, the function parameters on the stack belongs to the function and it is allowed to trash these values as it sees fit (if you need to know whether the compiler actually does this, you are thinking the wrong way, but it actually does). There are two ways around this. The most practical method is to pass the structure as a pointer instead, which allows you to explicitly edit the register state when needed - very useful for system calls, without having the compiler randomly doing it for you. The compiler can still edit the pointer on the stack when it's not specifically needed. The second option is to make another copy the structure and pass that

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

The problem is that the 32-bit System V ABI doesn't guarantee that data passed by value will be unmodified on the stack! The compiler is free to reuse that memory for whatever purposes it chooses. The compiler probably generated code that trashed the area on the stack where DS is stored. When DS was set with the bogus value it crashed. What you should be doing is passing by reference rather than value. I'd recommend these code changes in the assembly code:

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 GS (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:

And then modify irq_handler to use registers_t *regs instead of 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)的函数,那么您将修改代码:

I'd actually recommend each interrupt handler take a pointer to registers_t to avoid unnecessary copying. If your interrupt handlers and the interrupt_handlers array used function that took registers_t * as the parameter (instead of registers_t) then you'd modify the code:

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

成为:

interrupt_handlers[r->int_no](regs);

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

Important: You have to make these same type of changes for your ISR handlers as well. Both the IRQ and ISR handlers and associated code have this same problem.

这篇关于无法修改数据段寄存器.尝试抛出“常规保护错误"时的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-03 18:13