I have to agree with nexos that BOCHS might be useful here. It has a command 'info idt' to look at the IDT structure to see if it is correct and many other useful tools. It does a reasonable job at helping find very low level bugs early on in OSDEV. If you are using QEMU then I'd recommend running it with the options '-d int -no-reboot -no-shutdown' . When the first interrupt occurs it is likely faulting and then ends up with a double fault and triple fault. These extra options with QEMU might be able to tell you what caused the original fault, as QEMU will dump the processor state (including most of the registers), error code etc and then stop. It might be helpful to even post that output here in your question. Putting your entire project in Github would also help if you want people to help debug it. The code you show may not even be the source of the issue.
Although your LIDT inline assembly looks correct (and likely works) it has a subtle bug that an optimising compiler could in theory generate unexpected code. This:
Code:
__asm__ volatile("lidt (%0)" : : "r" (&mainIdtReg));
Would be better written as:
Code:
__asm__ volatile("lidt %0" : : "m" (mainIdtReg));
. Your version says that the pointer is an input, but not the data that the pointer points to. It is possible (but unlikely in this case) that the compiler would not fill in the mainIdtReg structure before calling this inline assembly if it thinks the inline assembly doesn't use the data at that address. My version ensures that the entire mainIdtReg structure will be realized into memory first. But this is not likely the source of your problems.
Another thing is that x86-64 also has registers R8 to R15 and your push and pop macros aren't using them.
I did notice one big problem in isr_common:
Code:
add rsp, 8
at the end should be
Code:
add rsp, 16
since the error code and interrupt number are pushed as 2 8 byte values (16 total). It also is not needed to do a CLI to begin and an STI to end an interrupt the way you are doing it. If you create an interrupt gate for the IDT entries the processor will automatically disable external interrupts before the interrupt handler is called and the interrupt state will be restored when the IRETQ is done.