一次中断后,C上的中断处理程序不起作用

Гош*_*ный 3 c assembly gcc osdev interrupt-handling

我正在尝试使用C和QEMU实现键盘中断处理程序。但是,当我执行程序时,我的处理程序仅打印一个字符。之后,处理程序将根本无法工作。

我的IDT设置:

struct IDT_entry {
    unsigned short int offset_lowerbits;
    unsigned short int selector;
    unsigned char zero;
    unsigned char type_attr;
    unsigned short int offset_higherbits;
};

void setup_idt() {
    struct IDT_entry IDT[256];
    unsigned long keyboard_address;
    unsigned long idt_address;
    unsigned long idt_ptr[2];

    keyboard_address = (unsigned long) keyboard_handler;
    IDT[0x21].offset_lowerbits = keyboard_address & 0xffff;
    IDT[0x21].selector = 0x8;
    IDT[0x21].zero = 0;
    IDT[0x21].type_attr = 0x8e;
    IDT[0x21].offset_higherbits = (keyboard_address & 0xffff0000) >> 16;

    /*
                PIC1   PIC2
    Commands    0x20   0xA0
    Data        0x21   0xA1

    */

    // ICW1 - init
    outb(0x20, 0x11);
    outb(0xA0, 0x11);

    // ICW2 - reset offset address if IDT
    // first 32 interrpts are reserved
    outb(0x21, 0x20);
    outb(0xA1, 0x28);

    // ICW3 - setup cascading
    outb(0x21, 0b0);
    outb(0xA1, 0b0);

    // ICW4 - env info
    outb(0x21, 0b00000011);
    outb(0xA1, 0b00000011);
    // init finished

    // disable IRQs except IRQ1
    outb(0x21, 0xFD);
    outb(0xA1, 0xff);

    idt_address = (unsigned long)IDT;
    idt_ptr[0] = (sizeof (struct IDT_entry) * 256) + ((idt_address & 0xffff) << 16);
    idt_ptr[1] = idt_address >> 16;

    __asm__ __volatile__("lidt %0" :: "m" (*idt_ptr));
    __asm__ __volatile__("sti");
}
Run Code Online (Sandbox Code Playgroud)

我的键盘处理程序:

// Variables for printing ==
unsigned int location = 0;
char* vga = (char*)0xb8000;
char letter;
// =========================

void keyboard_handler() {
    if (inb(0x64) & 0x01 && (letter = inb(0x60)) > 0) {
        vga[location] = keyboard_map[letter];
        vga[location+1] = 0x4;
        location += 2;
    }

    outb(0x20, 0x20);

    // __asm__ __volatile__("iret");
}
Run Code Online (Sandbox Code Playgroud)

主要功能(从我的asm引导加载程序执行):

void kmain() {
    setup_idt();

    for (;;) {}
}
Run Code Online (Sandbox Code Playgroud)

我认为问题出在“ iret”指令中。没有它,我的内核至少会打印出一些内容(只有一个字符,如我之前所说)。但是当我执行asm volatile(“ iret”)时;QEMU会打印一些垃圾,然后在每次按键后清除垃圾(“ SeaBios ...”)。我需要做什么?谢谢!

Pet*_*des 6

如果您不进行优化asm("iret")而进行编译,则可能会在堆栈指针仍指向已保存的EBP值时运行,因为这-fno-omit-frame-pointer是默认设置,并且清理结束语在函数的最后一个C语句之后发生。

或者它可能指向其他保存的寄存器。无论如何,欺骗编译器并跳出内联asm语句永远是不安全的(除非您asm goto曾经习惯于跳到函数内部的C标签,但这不能解决您的问题)。


同样,C调用约定允许函数破坏EAX,ECX,EDX和FPU状态。即使您确实设法入侵了iret您的函数,它也会破坏被中断的代码的状态。GCC将使用SSE / x87 _Atomic int64_t在32位模式下实现加载/存储以及复制大对象,除非您使用-mgeneral-regs-only

另请参阅@MichaelPetch在链接副本上的答案:在没有编译器生成的序言/结尾和RET指令的情况下创建C函数?了解更多有趣的观点以及一些非GCC信息。


这里有2个解决方案:

  • 编写一个纯asm包装器,以保存调用密集的reg,调用您的C函数,然后返回 iret
  • 声明您的函数__attribute__((interrupt))以告诉GCC这是一个中断处理程序。gcc手册的x86函数属性包含一个示例

    与传统嵌入式ISA(如ARM)相比,x86对该属性的支持在某种程度上是最近的,但是现代GCC确实知道如何发出保留所有regs并以结尾的函数iret。但是你仍然需要-mgeneral-regs-only

另请参见https://wiki.osdev.org/Interrupt_Service_Routines#GCC_.2F_G.2B.2B,它告诉您与该答案相同的内容。

(这也表明带有pushad/ 的恶意popad; leave; iret骇客只能在禁用优化的情况下使用。我建议您是否可以使用支持该interrupt属性的较新GCC 。)

Wiki页面的较早部分介绍了尝试使用您自己的时遇到的一般问题iret,因此您可以查看尝试总的asm(由编译器生成的+您的)。