Невозможно изменить регистр сегмента данных. При попытке генеральной защиты выдается ошибка - PullRequest
4 голосов
/ 06 июня 2019

Я пытался создать обработчик ISR, следуя этому уроку Джеймса Моллоя, но я застрял.Всякий раз, когда я генерирую программное прерывание, регистры общего назначения и регистр сегмента данных помещаются в стек с переменными, автоматически передаваемыми ЦП.Затем сегмент данных изменяется на значение 0x10 (дескриптор сегмента данных ядра), поэтому уровни привилегий изменяются.Затем после того, как обработчик возвращает эти значения, pop ed.Но всякий раз, когда значение в ds изменяется, генерируется GPE с кодом ошибки 0x2544, и через несколько секунд виртуальная машина перезапускается.(компоновщик и компилятор 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
...

Обработчик My 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 Ответ

3 голосов
/ 07 июня 2019

Код не завершен, но я собираюсь предположить, что вы видите, что это результат хорошо известной ошибки в руководстве OSDev Джеймса Моллоя.Сообщество OSDev составило список известных ошибок в список ошибок .Я рекомендую просмотреть и исправить все ошибки, упомянутые там.Именно в этом случае я считаю, что ошибка, вызывающая проблемы, заключается в следующем:

Проблема: обработчики прерываний повреждают прерванное состояние

В этой статье ранее сообщалось, чтознать ABI.Если вы это сделаете, вы увидите огромную проблему в interrupt.s, предложенном в руководстве: он нарушает ABI для передачи структуры!Он создает экземпляр структурных регистров в стеке, а затем передает его по значению в функцию isr_handler, а затем предполагает, что структура не повреждена впоследствии.Тем не менее, параметры функции в стеке принадлежат этой функции, и разрешено отбрасывать эти значения по своему усмотрению (если вам нужно знать, действительно ли компилятор делает это, вы думаете, что это не так, но на самом деле это так).Есть два способа обойти это.Наиболее практичный метод - вместо этого передавать структуру в виде указателя, что позволяет вам при необходимости явно редактировать состояние регистра - очень полезно для системных вызовов, без необходимости случайного выбора компилятора.Компилятор все еще может редактировать указатель на стек, когда это не требуется специально.Второй вариант - сделать еще одну копию структуры и передать ее

Проблема в том, что 32-разрядный ABI System V не гарантирует, что данные, передаваемые по значению, не будут изменены в стеке!Компилятор может свободно использовать эту память для любых целей, которые он выберет.Компилятор, вероятно, сгенерировал кодированный, который переместил область в стеке, где хранится 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, и связанный код имеют ту же проблему.

...