ОСДЕВ. Почему мой int 0x80 вызывает ошибку общей защиты?

Я учусь использовать gcc, nasm и qemu для разработки игрушечной ОС на i386.
И я попытался инициализировать IDT и добавил несколько обработчиков прерываний. Но я получил странную ошибку, которую я не могу понять.

Вот репозиторий github. Ниже приведены некоторые подробности о проблеме.

Думаю проблема скрыта в main.c.

#define IDT_ADDR 0x2000
#define AR_INTGATE32 0x8e

typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
typedef unsigned int uint32_t;

//remapping pic
#define PIC0_ICW1 0x20
#define PIC0_OCW2 0x20
#define PIC0_IMR  0x21
#define PIC0_ICW2 0x21
#define PIC0_ICW3 0x21
#define PIC0_ICW4 0x21
#define PIC1_ICW1 0xa0
#define PIC1_OCW2 0xa0
#define PIC1_IMR  0xa1
#define PIC1_ICW2 0xa1
#define PIC1_ICW3 0xa1
#define PIC1_ICW4 0xa1

static inline void outb(uint16_t port, uint8_t val) {
    asm volatile ( "outb %0, %1" : : "a"(val), "Nd"(port) );
}

void init_pic() {
    outb(PIC0_IMR, 0xff);
    outb(PIC1_IMR, 0xff);
    outb(PIC0_ICW1, 0x11);
    outb(PIC0_ICW2, 0x20);   /* IRQ0-7 -> INT 0x20-0x27*/
    outb(PIC0_ICW3, 1<<2);
    outb(PIC0_ICW4, 0x01);
    outb(PIC1_ICW1, 0x11);
    outb(PIC1_ICW2, 0x28);   /* IRQ8-15 -> INT 0x28-0x2f */
    outb(PIC1_ICW3, 2);
    outb(PIC1_ICW4, 0x01);
    outb(PIC0_IMR, 0xfc);    /* 11111011 PIC1*/
    outb(PIC1_IMR, 0xff);
}

struct idt_entry_t {
    uint16_t    isr_low;      // The lower 16 bits of the ISR's address
    uint16_t    kernel_cs;    // The GDT segment selector that the CPU will load into CS before calling the ISR
    uint8_t     reserved;     // Set to zero
    uint8_t     attributes;   // Type and attributes; see the IDT page
    uint16_t    isr_high;     // The higher 16 bits of the ISR's address
} __attribute__((packed));

void set_intrdesc(struct idt_entry_t *id, void* isr, uint16_t cs, uint8_t attr) {
    id->isr_low = (uint32_t)isr & 0xffff;
    id->kernel_cs = cs;
    id->reserved = 0;
    id->attributes = attr;
    id->isr_high = (uint32_t)isr >> 16;
}

struct idt_entry_t *idt = (struct idt_entry_t*)IDT_ADDR;

struct interrupt_frame {
    uint32_t tf_eip;
    uint16_t tf_cs;
    uint16_t tf_padding4;
    uint32_t tf_eflags;
};

__attribute__ ((interrupt))
void interrupt_handler_0x80(struct interrupt_frame *frame) {
    asm("mov $0x20, %eax");
    while (1);
}

__attribute__ ((interrupt))
void general_protection_handler13(struct interrupt_frame *frame, uint32_t error_code) {
    asm("mov $13, %eax");
    while (1);
}

#define KERNEL_CS 0x8

void init_idt() {
    for (int i = 0; i < 256; i++) {
        set_intrdesc(idt + i, 0, 0, 0);
    }
    set_intrdesc(idt + 13, general_protection_handler13, KERNEL_CS, AR_INTGATE32);
    
    struct {uint16_t limit; uint32_t addr;} __attribute__((packed)) idtr;
    idtr.limit = 255;
    idtr.addr = idt;
    asm volatile ("lidt %0" :: "m" (idtr));
}

void main() {
    init_idt();
    init_pic();

    asm("sti");
    
    set_intrdesc(idt + 0x80, interrupt_handler_0x80, KERNEL_CS, AR_INTGATE32);
    asm("int $0x80");

    while (1) {
        asm("hlt");
    }
}

Когда я пытаюсь make log "TERMINAL=xfce4-terminal"

The result of make log "TERMINAL=xfce4-terminal" -- just-print

nasm boot.s -o boot.bin
nasm setup.s -o setup.bin
gcc main.c -O -march=i386 -m32 -g -fno-builtin -fno-PIC -Wall -nostdinc -fno-stack-protector -ffreestanding -ffunction-sections -mgeneral-regs-only -c -o main.o
ld -nostdlib -Tmain.ld main.o -o main.bin.elf
objdump -S main.bin.elf > main.bin.asm
objcopy -S -O binary main.bin.elf main.bin
dd if=/dev/zero of=test.img count=100
dd if=boot.bin of=test.img conv=notrunc
dd if=setup.bin of=test.img seek=1 conv=notrunc
dd if=main.bin of=test.img seek=2 conv=notrunc
qemu-system-i386 -no-reboot -S -s -d int  -D q.log -parallel stdio -hda test.img -serial null &
sleep 2
xfce4-terminal -e "gdb -q -x gdbinit"

, после int $0x80gdb остановится на general_protection_handler13.

Вот interrupt frame и error code об исключении (я использую плагин gdb под названием gef).

Breakpoint 1, general_protection_handler13 (frame=0xefff4, error_code=0x402) at main.c:74
74      asm("mov $13, %eax");
gef➤  p *frame
$1 = {
  tf_eip = 0x823d, 
  tf_cs = 0x8, 
  tf_padding4 = 0x0, 
  tf_eflags = 0x247
}
gef➤  x/i 0x823d
   0x823d <main+61>:    int    0x80
gef➤  python print(bin(0x402), bin(0x80))
0b10000000010 0b10000000

Тогда я знаю, что GPE был вызван int $0x80, и индекс селектора в error_code был правильным.

Запись IDT для GPE и 0x80:

gef➤  p idt[13]
$2 = {
  isr_low = 0x8249, 
  kernel_cs = 0x8, 
  reserved = 0x0, 
  attributes = 0x8e, 
  isr_high = 0x0
}
gef➤  p idt[0x80]
$3 = {
  isr_low = 0x8242, 
  kernel_cs = 0x8, 
  reserved = 0x0, 
  attributes = 0x8e, 
  isr_high = 0x0
}

А вот лог qemu о прерываниях.

     0: v=80 e=0000 i=1 cpl=0 IP=0008:0000823d pc=0000823d SP=0010:000f0000 env->regs[R_EAX]=00002000
EAX=00002000 EBX=00000000 ECX=00000001 EDX=00000000
ESI=00000000 EDI=00000000 EBP=000e0000 ESP=000f0000
EIP=0000823d EFL=00000247 [---Z-PC] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     0000802c 00000017
IDT=     00002000 000000ff
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
DR6=ffff0ff0 DR7=00000400
CCS=00000001 CCD=00000000 CCO=SARL    
EFER=0000000000000000

check_exception old: 0xffffffff new 0xd
     1: v=0d e=0402 i=0 cpl=0 IP=0008:0000823d pc=0000823d SP=0010:000f0000 env->regs[R_EAX]=00002000
EAX=00002000 EBX=00000000 ECX=00000001 EDX=00000000
ESI=00000000 EDI=00000000 EBP=000e0000 ESP=000f0000
EIP=0000823d EFL=00000247 [---Z-PC] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     0000802c 00000017
IDT=     00002000 000000ff
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
DR6=ffff0ff0 DR7=00000400
CCS=00000001 CCD=00000000 CCO=SARL    
EFER=0000000000000000

Я думаю, что мои GDT и IDT верны.

Я прочитал много ответов о подобных проблемах на разных сайтах, но не мог понять, почему мой int $0x80 вызывает General Prorection Exception и как это исправить.

Извините за мою глупость, но мне действительно нужна помощь других людей.

@MichaelPetch Большое спасибо!!! Как и ожидалось, это глупая проблема, не могу поверить, что я этого не заметил...... QAQ. Должно быть, я слишком устал за эти дни.

wqooopw 07.10.2022 15:48
Знайте свои исключения!
Знайте свои исключения!
В Java исключение - это событие, возникающее во время выполнения программы, которое нарушает нормальный ход выполнения инструкций программы. Когда...
Управление ответами api для исключений на Symfony с помощью KernelEvents
Управление ответами api для исключений на Symfony с помощью KernelEvents
Много раз при создании api нам нужно возвращать клиентам разные ответы в зависимости от возникшего исключения.
2
1
119
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

IDT=00002000 000000ff предполагает, что ваш IDT имеет размер 0xff байт (+1) или 256. Но каждая запись IDT имеет размер 8 байт. Возможно, вы хотели установить размер 256*8-1 или 2047 (0x7ff). Поэтому мне не кажется, что размер правильный, а исключение 0x80 выходит за пределы IDT, вызывая ошибку #GP.

Я заметил, что код для этого был в вопросе: idtr.limit = 255; должен быть idtr.limit = 256*sizeof(idt_entry_t)-1;. Я также задаюсь вопросом, почему вы решили поместить IDT по адресу 0x2000 и просто не определили массив из 256 idt_entry_t записей в C, например struct idt_entry_t idt[256];, а затем просто измените код, чтобы использовать IDT в качестве массива. Затем вы можете установить лимит IDT с помощью sizeof(idt)-1.

SP=0010:000f0000 выглядит подозрительно. CR0 говорит, что пейджинг отключен. Физический адрес 0xf0000 находится в области памяти ROM BIOS. Возможно, установите ESP стека на 0x90000.

Другие вопросы по теме

Похожие вопросы

Как правильно протестировать пользовательское исключение с помощью SerializationInfo?
Как обрабатывать исключения в Spring, которые не выбрасываются не на уровне контроллера?
Когда WINAPI вызывает мой код и генерируется исключение, должен ли я перехватывать его и вместо этого возвращать HRESULT?
Ошибка компилятора Java «ожидается класс, интерфейс или перечисление», но я не могу найти ни одной из распространенных причин этой ошибки
Как автоматически запускать уведомления по электронной почте для конечных пользователей в случае сбоя моей функции Azure
Почему и как исключение в любом потоке приводит к сбою всего приложения в Android
NonImplementedError при использовании torch.onnx.export
Ошибка синтаксического анализа в действительных данных JSON с библиотекой nlohmann C++
Как поймать исключение «проверка нулевого оператора на нулевое значение» флаттера
Как использовать только `handleAsync` для обработки неасинхронных исключений?