Gcc 无法修改数据段寄存器。尝试时,将抛出一般保护错误

Gcc 无法修改数据段寄存器。尝试时,将抛出一般保护错误,gcc,assembly,x86,osdev,isr,Gcc,Assembly,X86,Osdev,Isr,我一直在尝试创建一个ISR处理程序 詹姆斯·莫洛伊写的,但我被卡住了。每当我抛出一个软件中断时,通用寄存器和数据段寄存器被推送到堆栈上,变量由CPU自动推送。然后将数据段更改为0x10(内核数据段描述符)的值,从而更改权限级别。然后,在处理程序返回后,这些值将poped。但是,每当ds中的值发生更改时,就会抛出一个带有错误代码0x2544的GPE,几秒钟后,VM重新启动。(链接器和编译器i386 elf gcc,汇编程序nasm) 我尝试将hlt指令放在指令之间,以确定哪个指令抛出了GPE。之后

我一直在尝试创建一个ISR处理程序 詹姆斯·莫洛伊写的,但我被卡住了。每当我抛出一个软件中断时,通用寄存器和数据段寄存器被推送到堆栈上,变量由CPU自动推送。然后将数据段更改为0x10(内核数据段描述符)的值,从而更改权限级别。然后,在处理程序返回后,这些值将
pop
ed。但是,每当
ds
中的值发生更改时,就会抛出一个带有错误代码0x2544的GPE,几秒钟后,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
...
导致“接收到的中断:0xD错误代码0x2544”的我的C处理程序

正如您所看到的,代码应该输出

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
...
直到虚拟机自身重新启动


我做错了什么?

代码不完整,但我猜您看到的是James Molloy的OSDev教程中一个众所周知的错误的结果。OSDev社区已经在一个应用程序中编译了一个已知bug列表。我建议检查并修复这里提到的所有bug。特别是在这种情况下,我认为引起问题的bug是:

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

这篇文章之前告诉你要了解ABI。如果你这样做,你会的 在中断中看到一个巨大的问题。它是由教程建议的:It 打破结构通过的ABI!它创建一个 struct在堆栈上注册,然后按值将其传递给 isr_处理函数,然后假设结构完整 之后但是,堆栈上的函数参数属于 函数,并允许在其认为合适时丢弃这些值 (如果您需要知道编译器是否确实执行此操作,请 思维方式错误,但事实上确实如此)。有两种方法 围绕这个。最实用的方法是将结构作为一个整体传递 指针,它允许您显式编辑寄存器 在需要时声明-对于系统调用非常有用,无需 我会为你做的。编译器仍然可以编辑 当堆栈上没有特别需要的指针时。第二 选项是在结构上制作另一个副本并传递该副本

问题是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 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\u handler
以使用
寄存器\u t*regs
而不是
寄存器\u 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);
    }
}
实际上,我建议每个中断处理程序使用一个指向
寄存器的指针,以避免不必要的复制。如果中断处理程序和
中断处理程序
数组使用了将
寄存器
作为参数的函数(而不是
寄存器
),则您需要修改代码:

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

interrupt_handlers[r->int_no](regs);

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

请投票回答一个问题。嗯……您正在中断处理程序中调用printf()?什么是
寄存器?它的定义是什么?我可以测试你的答案,这似乎是它触发GPE的原因。实际上,我检查了关于教程已知错误的链接,但我从来没有想到这一点。谢谢你的回答
interrupt_handlers[r->int_no](*regs); 
interrupt_handlers[r->int_no](regs);