1

Whenever I try to enable interrupts using the sti command, the emulator instantly reboots. I am executing the following code in protected mode on bootup:

void* idt = (void*) 0x00000;
struct {
    uint16_t length;
    void*    base;
} __attribute__((packed)) IDTR = {128, idt};

asm ("lidt %0" : : "m"(IDTR));
asm ("sti");

There is a brief period of time when the OS has booted up, and even accepts input, but then it reboots. Why does this happen and how do I fix it?

EDIT: When adding -d int to qemu, I get the following output:

SMM: enter
EAX=000000b5 EBX=00008acc ECX=00005678 EDX=000ece30
ESI=07fbdde6 EDI=000ece30 EBP=00006c38 ESP=00006c38
EIP=000f8acb EFL=00000016 [----AP-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00c09300 DPL=0 DS   [-WA]
CS =0008 00000000 ffffffff 00c09b00 DPL=0 CS32 [-RA]
SS =0010 00000000 ffffffff 00c09300 DPL=0 DS   [-WA]
DS =0010 00000000 ffffffff 00c09300 DPL=0 DS   [-WA]
FS =0010 00000000 ffffffff 00c09300 DPL=0 DS   [-WA]
GS =0010 00000000 ffffffff 00c09300 DPL=0 DS   [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     000f7070 00000037
IDT=     000f70ae 00000000
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
DR6=ffff0ff0 DR7=00000400
CCS=00000014 CCD=00006c24 CCO=EFLAGS  
EFER=0000000000000000
SMM: after RSM
EAX=000000b5 EBX=00008acc ECX=00005678 EDX=000ece30
ESI=07fbdde6 EDI=000ece30 EBP=00006c38 ESP=00006c38
EIP=00008acc EFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =de00 000de000 ffffffff 00809300
CS =f000 000f0000 ffffffff 00809b00
SS =0000 00000000 ffffffff 00809300
DS =0000 00000000 ffffffff 00809300
FS =0000 00000000 ffffffff 00809300
GS =0000 00000000 ffffffff 00809300
LDT=0000 00000000 0000ffff 00008200
TR =0000 00000000 0000ffff 00008b00
GDT=     00000000 00000000
IDT=     00000000 000003ff
CR0=00000010 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
DR6=ffff0ff0 DR7=00000400
CCS=00000000 CCD=00000000 CCO=EFLAGS  
EFER=0000000000000000

For a while, then

check_exception old: 0xffffffff new 0x6

EDIT 2: -d cpu-reset is more helpful then -d int. It prints:

CPU Reset (CPU 0)
EAX=00000032 EBX=00000000 ECX=5153092a EDX=51530000
ESI=0000000a EDI=00100000 EBP=00000000 ESP=00000fb0

(snip)

XMM06=00000000000000000000000000000000 
XMM07=00000000000000000000000000000000
Triple fault

EDIT 3: Full output of -d int -no-reboot -no-shutdown os-image(excluding the SMM exceptions is as follows):

     0: v=20 e=0000 i=0 cpl=0 IP=0008:00001712 pc=00001712 SP=0010:00001f6c env->regs[R_EAX]=000000fa
EAX=000000fa EBX=00007d6a ECX=0000050c EDX=00000060
ESI=00007ca8 EDI=00000000 EBP=00001f80 ESP=00001f6c
EIP=00001712 EFL=00000206 [-----P-] 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=     00007c6b 00000018
IDT=     00000000 00000000
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000
DR6=ffff0ff0 DR7=00000400
CCS=00000004 CCD=00001f50 CCO=EFLAGS  
EFER=0000000000000000
check_exception old: 0xffffffff new 0xd
     1: v=0d e=0102 i=0 cpl=0 IP=0008:00001712 pc=00001712 SP=0010:00001f6c env->regs[R_EAX]=000000fa
EAX=000000fa EBX=00007d6a ECX=0000050c EDX=00000060
ESI=00007ca8 EDI=00000000 EBP=00001f80 ESP=00001f6c
EIP=00001712 EFL=00000206 [-----P-] 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=     00007c6b 00000018
IDT=     00000000 00000000
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000
DR6=ffff0ff0 DR7=00000400
CCS=00000004 CCD=00001f50 CCO=EFLAGS  
EFER=0000000000000000
check_exception old: 0xd new 0xd
     2: v=08 e=0000 i=0 cpl=0 IP=0008:00001712 pc=00001712 SP=0010:00001f6c env->regs[R_EAX]=000000fa
EAX=000000fa EBX=00007d6a ECX=0000050c EDX=00000060
ESI=00007ca8 EDI=00000000 EBP=00001f80 ESP=00001f6c
EIP=00001712 EFL=00000206 [-----P-] 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=     00007c6b 00000018
IDT=     00000000 00000000
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000
DR6=ffff0ff0 DR7=00000400
CCS=00000004 CCD=00001f50 CCO=EFLAGS  
EFER=0000000000000000
check_exception old: 0x8 new 0xd

The complete output including SMM exceptions/interrupts can found on pastebin.

EDIT 4: Here is my interrupt handler (based on osdev wiki's):

void* irq0handler(void) {
    volatile void* address;
    asm goto("jmp %l[ISREnd]" ::: "memory" : ISREnd);                                                                           
// jump to end of ISR
    asm volatile(".align 16" ::: "memory");
    ISRStart:
    asm volatile(
        "pushal             \n\t"                                                                                                                               
// save regs
        "pushl %%ebp        \n\t"
        "movl %%esp, %%ebp  \n\t"
        "cld                "                                                                                                                   
// direction flag is used by gcc
        ::: "memory");
    asm volatile(
        "pushl %%es         \n\t"                                                                                                               
// save segment regs
        "pushl %%ds         \n\t"
        "movw $16, %%cx     \n\t"                                                                                                               
// set segment regs for kernel
        "movw %%cx, %%ds    \n\t"
        "movw %%cx, %%es    \n\t"
        "addl $4, (%%esp)   \n\t"                                                                                                               
// add 4 to make it point to PUSHAD struct
        "call *%%eax        \n\t"                                                                                                               
// call IRQ func.
        :: "a"(irq0func): "memory");                                                                                                        
// eax is the IRQ 0 function
    asm volatile(
        "popl %%es          \n\t"                                                                                                               
// restore everything
        "popl %%ds          \n\t"
        "leave              \n\t"
        "popal              \n\t"
        "iret               "                                                                                                               
// return from interrupt handler
        ::: "memory");
    ISREnd:
    asm goto(
        ".intel_syntax noprefix        \n\t"
        "mov eax, offset %l[ISRStart]  \n\t"                                                                                        
// get address of ISR start
        "mov [ebx], eax                \n\t"                                                                                        
// set ebx to adderss
        ".att_syntax                       "
        :: "b"(&address) : "eax", "memory" : ISRStart);                                                                 
// ebx to be set to adderss
    return (void*) address;                                                                                                                         
// return adderss of ISR
}

void irq0func() {
    // do something
    _PIC_sendEOI(0);
}

It uses the function _PIC_sendEOI(unsigned char irqnum) (sends an end-of interrupt). The handler is loaded by running the function fillidt(idt+(0x20*8), 8, irq0handler(), 0xe, 0):

void fillidt(void* idt, int select, int offset, int type, int perm) {
  size_t idt_ent_size = (size_t) 8;       // 8 bytes per entry

    uint8_t type_attr;

    int segment = 0;

    type_attr = 1;                                                  //     present
    type_attr <<= 2;                                                //     2 bits to set
    type_attr |= perm;                                          // perm is 2 bits
    type_attr <<= 1;                                                // 1 bit to set
    type_attr |= segment;                                       // segment is 1 bit
    type_attr <<= 4;                                                // 4 bits to set
    type_attr |= type;                                          // type is 4 bits

  struct {
    uint16_t offset_1;                    // offset bits 0..15
    uint16_t selector;                    // a code segment selector in the GDT or LDT
    uint8_t zero;                         // unused
    uint8_t type_attr;                                      // type and attributes
    uint16_t offset_2;                    // offset bits 16..31
  } __attribute__ ((packed)) idt_desc = {offset & 0xffff, select, 0,     type_attr, (offset & 0xffff0000) >> 16};
  memcpy(&idt_desc, idt, idt_ent_size);     // copy the descriptor
}

EDIT 5: I know this is is a bit too many edits, but I have committed the latest code to the debug branch of my github project at https://github.com/nm111/NMOS/tree/debug.

EDIT 6: The issue appears to be fixed now, but this cannot be verified until later today, as I currently do not have access to a computer.

tl;dr Don't borrow code from Osdev Wiki.

  • 1
    There isn't enough information given to help. It is also confusing because you first say the emulator instantly reboots, but then you say there is a brief period where it accepts input. That doesn't seem to be instant? Was the keyboard input signalled through IRQ1 interrupts, or were you polling? If you showed us all of your code or you presented a minimal complete verifiable example we might be able to help? – Michael Petch May 13 '17 at 13:53
  • The reboot means there is a triple fault. Likely a problem with a bad interrupt handler, a missing interrupt handler, or a bug in an interrupt handler. If you are using paging that could be another issue. Since your IDT is memory address 0x0000 it means you either don't intend to go back to real mode (and your not using VM86) or your have paging enabled and you have remapped the kernel. I can't tell. – Michael Petch May 13 '17 at 13:57
  • I'd recommend debugging in BOCHS and use the option 'reset_on_triple_fault=0' in the configuration file . QEMU is useful for tracing interrupts if you use the option `-d int` option – Michael Petch May 13 '17 at 14:01
  • I hope you weren't attempting to use the existing real mode interrupt handler at 0x0000 as a protected mode IVT. That won't work. – Michael Petch May 13 '17 at 14:18
  • If you are going to use `-d int` I then also recommend adding `-no-reboot -no-shutdown` options so that the triple fault won't cause the emulator to reboot and will prevent the emulator from shutting down. The interrupt output that will be useful are the ones that start with `check_exception` . The SMM ones are of little value. – Michael Petch May 13 '17 at 16:18
  • After reviewing your pastebin and looking at the line `0: v=20 e=0000 i=0 cpl=0 IP=0008:00001712 pc=00001712 SP=0010:00001f6c env->regs[R_EAX]=000000fa` . This suggests you got an interrupt on vector 0x20 . Is it correct to assume that you remapped the master PIC to 0x20? My hunch is that v=20 is IRQ0 (The timer tick interrupt). If that is the case then I believe your problem is that you have an invalid IDT and/or the IDT entry for interrupt 0x20 is invalid. – Michael Petch May 13 '17 at 17:00
  • My guess is you don't have a proper IDT with a valid interrupt handler for Interrupt 0x20. When you turned on interrupts with `sti` the timer interrupt fired. If you have a bogus IDT that will likely generate a general protect fault (v=0d in the QEMU output) followed by a double fault exception (v=08) and then the triple fault. Since you don't show us any code of your IDT entries (just the IDTR record you loaded with LIDT) it is impossible to know for certain. If you didn't remap the master PIC to 0x20 then I'm also uncertain as to why 0x20 interrupt occurred in the first place. – Michael Petch May 13 '17 at 17:09
  • 2
    Something else of interest is this in the output `IDT= 00000000 00000000` The first number 00000000 is the base address of the interrupt table. The second one is the size (in bytes) of the IDT. That indicates you don't have a proper IDTR set up. (via LIDT instruction) – Michael Petch May 13 '17 at 17:35
  • You update the question with code, but please see my last comment. the IDT isn't even being set with a value. The base and length are zero as if the LIDT functionality hasn't been done. You likely won't get an answer unless you post your complete project. The best thing I can suggest is actually using BOCHS internal debugger for this since you can dump the IDT out to the console with the internal debugger as you step through the code. – Michael Petch May 14 '17 at 14:11
  • I just noticed for the first time you do in fact have a GitHub project for your code but it doesn't seem to be up to date with your interrupt handling. If you were to create a new branch for testing and update it with your latest code I could take a look at it. – Michael Petch May 14 '17 at 14:35
  • 1
    One serious problem you may encounter is that you load your kernel at 0x1000 and then you place the stack at 0x2000. The stack is growing down to your code. If the 2 collide then you'll get unusual behavior. Just from the output I know you are almost half way to the stack since you have code running at 00001712 and the stack pointer is at 00001f6c – Michael Petch May 14 '17 at 15:12
  • 1
    Borrowing code from OSDev Wiki is generally okay, but the interrupt service routine code you tried to use has to be used a specific way as it handles multitasking in a particular way and needs to be set up a specific way. Once you understand what is going on going back to that code may be useful – Michael Petch May 15 '17 at 14:28

0 Answers0