OSDev.org https://forum.osdev.org/ |
|
QEMU resetting problems with interrupts. https://forum.osdev.org/viewtopic.php?f=1&t=32540 |
Page 1 of 1 |
Author: | thatoneguy1233 [ Sat Nov 04, 2017 9:24 pm ] |
Post subject: | QEMU resetting problems with interrupts. |
I'm on Linux btw, all the code is compiled on GCC 6.1 gdt.c Code: /* *gdt.c by *This file contains everything related to GDT and stuff. * * The GDT is a table, or rather an array of elements *called "segment descriptors". Segment descriptors are basically structs which contain information about the segments itself(where it *begins, its size, whether its available for use etc), and the permissions (kernel level, or userlevel) etc. *What we need to do is call upon the holy instruction "lgdtr DWORD PTR address_of_the_first_element_in_the_array", then we'll be fine. *Before we proceed though, let's see what a segment is made up of- *========================================================================================= *| Base | G | size | R | avl | Seg. Lim | Present | Privilege | DType | SType | Base | *| 31:24 | | | | | 19:16 | | | | | 23:16 | *========================================================================================= *^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ *63 55 54 53 52 51 47 46 44 43 39 32 * *======================== *| Base addr | Seg. Lim| *| 15:00 | 15:00 | *======================== *^ ^ ^ *31 15 0 *Base/Base addr -> The base of the segment *G -> Granularity (0 => 1 byte, 1 => 4 KiB) *size -> Default size of operation (0 => 16-bit segment, 1 =>32-bit segment) *R -> Reserved (Available for 64 bit OSes, look it up in the intel manual Volume 3), set to 0 *avl -> Whether the segment is available for use *Seg. Lim -> The Limit of the Segment (It's size - 1 byte) *Present -> Whether the segment is in memory (do you have some hardware mapped to that address space, or is it empty?) *Previlege -> Kernel level code execution or User level execution *DType -> Type of descriptor (0 => Segment, 1 => Code/Data) *SType -> Type of Segment **/ #include "descriptors.h" #include <stdint.h> #define KERNEL_LEVEL 0b00 #define USER_LEVEL 0b11 extern void lgdt(void* gdt_ptr);//This is the function which will actually load our GDT, it's an asm only instruction. I find asm() too ugly, so I'll define it in a separate .s file extern void jump_gdt();//Use this to far jump to the gdt Code segement struct G_descriptor_t{ uint16_t seg_lim_low;//The lower 16 bits of the segment limit uint16_t base_low;//The lower 16 bits of the base address uint8_t base_mid;//Bits 16:23 of the base address uint8_t flags;//Refer to the block diagram /*uint8_t lim_high : 4;//The higher 4 bits of the segment limit uint8_t avl : 1;//The availability flags uint8_t R : 1;//Reserved uint8_t size : 1;//The default size of operations*/ uint8_t gran; uint8_t base_high;//The highest 8 bits of the base address }__attribute__((packed)); typedef struct G_descriptor_t G_descriptor; struct gdt_ptr_t{ uint16_t limit; uint32_t base; }__attribute((packed)); typedef struct gdt_ptr_t gdt_ptr; //We don't have a memory manager on us right now, so forget about malloc-ing for dynamic memory. But this will change once a memory manager has been implemented //TODO: Change to dynamic memory model. G_descriptor G_descriptors[4]; gdt_ptr gdt_pointer; uint8_t make_flags(uint8_t present, uint8_t privilege, uint8_t DType, uint8_t SType){ return (present << 7) | (privilege << 5) | (DType << 4) | (SType); } void init_gdt(){ asm volatile("cli"); G_descriptors[0] = (G_descriptor){0, 0, 0, 0, 0, /*0, 0, 0, */0};//Null descriptor. Usefule for debugging G_descriptors[1] = (G_descriptor){0xFFFF, 0x00, 0x00, make_flags(1, KERNEL_LEVEL, 1, 0xA), /*0xF, 1, 0, 1*/0xCF, 0x00};//Code segment G_descriptors[2] = (G_descriptor){0xFFFF, 0x00, 0x00, make_flags(1, KERNEL_LEVEL, 1, 0x2), /*0xF, 1, 0, 1*/0xCF, 0x00};//Text segment G_descriptors[3] = (G_descriptor){0, 0, 0, 0, 0, 0, /*0, 0, 0*/};//Null descriptor 'cuz this where the TSS wil go. TODO:Implement TSS gdt_pointer = (gdt_ptr){sizeof(G_descriptor)*4 - 1, G_descriptors}; lgdt(&gdt_pointer); asm volatile("sti"); //jump_gdt(); } Code: ;;; gdt.s ;;; The function lgdt and jump_gdt is defined here. global lgdt section .text lgdt: push ebp mov ebp, esp ; lgdt [ebp + 8] ; pop ebp ; ret mov eax, [ebp + 8] ; Get the pointer to the GDT, passed as a parameter. lgdt [eax] ; Load the new GDT pointer mov ax, 0x10 ; 0x10 is the offset in the GDT to our data segment mov ds, ax ; Load all data segment selectors mov es, ax mov fs, ax mov gs, ax mov ss, ax jmp 0x08:.flush ; 0x08 is the offset to our code segment: Far jump! .flush: pop ebp ret global jump_gdt jump_gdt: mov ax, 0x10 mov ds, ax mov es, ax mov fs, ax mov gs, ax mov ss, ax jmp 0x08:.flush .flush: ret Code: /** idt.c This file contains all the information needed to fill the IDT. */ #include "descriptors.h" #include <stddef.h> #include <stdint.h> #include <common.h> #include <libc/string/string.h> extern void lidt(void* idt_ptr); //Interrupts extern void reserved_handler(); extern void divide_error_handler(); extern void debug_exception_handler(); extern void nmi_handler(); extern void breakpoint_handler(); extern void overflow_handler(); extern void bound_exceeded_handler(); extern void undefined_opcode_handler(); extern void device_NA_handler(); extern void double_fault_handler(); extern void coprocessor_segment_overrun_handler(); extern void invalid_tss_handler(); extern void segment_NA_handler(); extern void stack_segment_fault_handler(); extern void general_protection_fault_handler(); extern void page_fault_handler(); extern void math_fault_handler(); extern void machine_check_abort_handler(); extern void alignment_check_handler(); extern void simd_exception_handler(); typedef struct I_descriptor_T{ uint16_t address_low;//The lower word of the address to the entry point of the interrupt uint16_t selector;//Points to a valid selector in the GDT uint8_t zero;//Must always be zero uint8_t type_attrib;//See above uint16_t address_high;//Higher word of the address to the entry point of tyhe ISR }__attribute__((packed)) I_descriptor; typedef struct idt_ptr_T{ uint32_t base_address;//Base address of the IDT uint16_t limit;//Limit of the IDT }__attribute__((packed)) idt_ptr; I_descriptor idt_entries[256]; idt_ptr idt_pointer; static uint8_t make_flags(uint8_t present, uint8_t dpl, uint8_t isInterrupt, uint8_t size){ return (present << 7) | (dpl << 5) | (isInterrupt?(0b110):(0b111)) | ((size & 0x01) << 3) ; } I_descriptor make_interrupt_gate(uint32_t interrupt_handler, uint16_t segment, uint8_t type_attrib){ return (I_descriptor){interrupt_handler & 0xFFFF, segment, 0, type_attrib, ((interrupt_handler & (0xFFFF0000)) >> 16)}; } I_descriptor make_null_gate(){ //TODO Make a null interrupt handler return make_interrupt_gate(0, 0, 0); } void init_idt(){ asm volatile("cli"); idt_pointer.base_address = idt_entries; idt_pointer.limit = sizeof(I_descriptor)*256 - 1; //strlen("Hello!"); memset(idt_entries, 0, sizeof(idt_entries)); unsigned int i = 0; idt_entries[i++] = make_interrupt_gate((uint32_t)divide_error_handler, 0x08, make_flags(1, 0, 1, 1)); idt_entries[i++] = make_interrupt_gate((uint32_t)debug_exception_handler, 0x08, make_flags(1, 0, 1, 1)); idt_entries[i++] = make_interrupt_gate((uint32_t)nmi_handler, 0x08, make_flags(1, 0, 1, 1)); idt_entries[i++] = make_interrupt_gate((uint32_t)breakpoint_handler, 0x08, make_flags(1, 0, 1, 1)); idt_entries[i++] = make_interrupt_gate((uint32_t)overflow_handler, 0x08, make_flags(1, 0, 1, 1)); idt_entries[i++] = make_interrupt_gate((uint32_t)bound_exceeded_handler, 0x08, make_flags(1, 0, 1, 1)); idt_entries[i++] = make_interrupt_gate((uint32_t)undefined_opcode_handler, 0x08, make_flags(1, 0, 1, 1)); idt_entries[i++] = make_interrupt_gate((uint32_t)device_NA_handler, 0x08, make_flags(1, 0, 1, 1)); idt_entries[i++] = make_interrupt_gate((uint32_t)double_fault_handler, 0x08, make_flags(1, 0, 1, 1)); idt_entries[i++] = make_interrupt_gate((uint32_t)coprocessor_segment_overrun_handler, 0x08, make_flags(1, 0, 1, 1)); idt_entries[i++] = make_interrupt_gate((uint32_t)invalid_tss_handler, 0x08, make_flags(1, 0, 1, 1)); idt_entries[i++] = make_interrupt_gate((uint32_t)segment_NA_handler, 0x08, make_flags(1, 0, 1, 1)); idt_entries[i++] = make_interrupt_gate((uint32_t)stack_segment_fault_handler, 0x08, make_flags(1, 0, 1, 1)); idt_entries[i++] = make_interrupt_gate((uint32_t)general_protection_fault_handler, 0x08, make_flags(1, 0, 1, 1)); idt_entries[i++] = make_interrupt_gate((uint32_t)page_fault_handler, 0x08, make_flags(1, 0, 1, 1)); idt_entries[i++] = make_interrupt_gate((uint32_t)reserved_handler, 0x08, make_flags(1, 0, 1, 1)); idt_entries[i++] = make_interrupt_gate((uint32_t)math_fault_handler, 0x08, make_flags(1, 0, 1, 1)); idt_entries[i++] = make_interrupt_gate((uint32_t)machine_check_abort_handler, 0x08, make_flags(1, 0, 1, 1)); idt_entries[i++] = make_interrupt_gate((uint32_t)alignment_check_handler, 0x08, make_flags(1, 0, 1, 1)); idt_entries[i++] = make_interrupt_gate((uint32_t)simd_exception_handler, 0x08, make_flags(1, 0, 1, 1)); idt_entries[i++] = make_interrupt_gate((uint32_t)reserved_handler, 0x08, make_flags(1, 0, 1, 1)); idt_entries[i++] = make_interrupt_gate((uint32_t)reserved_handler, 0x08, make_flags(1, 0, 1, 1)); idt_entries[i++] = make_interrupt_gate((uint32_t)reserved_handler, 0x08, make_flags(1, 0, 1, 1)); idt_entries[i++] = make_interrupt_gate((uint32_t)reserved_handler, 0x08, make_flags(1, 0, 1, 1)); idt_entries[i++] = make_interrupt_gate((uint32_t)reserved_handler, 0x08, make_flags(1, 0, 1, 1)); idt_entries[i++] = make_interrupt_gate((uint32_t)reserved_handler, 0x08, make_flags(1, 0, 1, 1)); idt_entries[i++] = make_interrupt_gate((uint32_t)reserved_handler, 0x08, make_flags(1, 0, 1, 1)); idt_entries[i++] = make_interrupt_gate((uint32_t)reserved_handler, 0x08, make_flags(1, 0, 1, 1)); idt_entries[i++] = make_interrupt_gate((uint32_t)reserved_handler, 0x08, make_flags(1, 0, 1, 1)); idt_entries[i++] = make_interrupt_gate((uint32_t)reserved_handler, 0x08, make_flags(1, 0, 1, 1)); idt_entries[i++] = make_interrupt_gate((uint32_t)reserved_handler, 0x08, make_flags(1, 0, 1, 1)); idt_entries[i++] = make_interrupt_gate((uint32_t)reserved_handler, 0x08, make_flags(1, 0, 1, 1)); asm volatile("sti"); lidt(&idt_pointer); } void test_idt(){ asm volatile("int $0x3"); asm volatile("cli");//NOTE:DEBUG ONLY } interrupt.c Code: #include <stddef.h> #include <stdint.h> #include <common.h> #include <vga.h> void null_isr_handler(uint8_t error_code){ WARN("ISR handler run - With error code"); } void null_isr_handler_no_error(){ WARN("ISR handler run - With error code"); } interrupts.s Code: ALIGN 4 section .data message: db "Recieved interrupt", 0x00 section .text extern null_isr_handler extern term_print extern null_isr_handler_no_error %macro NULL_ISR_NOERROR 1 global %1 %1: ; pushad ; cld ; push message ; call term_print ; popad ; iret cli push byte 0 push byte 0 jmp isr_common_stub %endmacro %macro NULL_ISR_ERROR 1 global %1 %1: ; pushad ; cld ; push message ; call term_print ; popad ; iret cli push byte 0 jmp isr_common_stub %endmacro %macro ISR_HANDLER_NOERROR 1 global %1 %1: call [%1]_c iret %endmacro isr_common_stub: pusha ; Pushes edi,esi,ebp,esp,ebx,edx,ecx,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 null_isr_handler_no_error pop eax ; reload the original data segment descriptor mov ds, ax mov es, ax mov fs, ax mov gs, ax popa ; Pops edi,esi,ebp... add esp, 8 ; Cleans up the pushed error code and pushed ISR number sti iret ; pops 5 things at once: CS, EIP, EFLAGS, SS, and ESP NULL_ISR_NOERROR divide_error_handler NULL_ISR_NOERROR debug_exception_handler NULL_ISR_NOERROR nmi_handler NULL_ISR_NOERROR breakpoint_handler NULL_ISR_NOERROR overflow_handler NULL_ISR_NOERROR bound_exceeded_handler NULL_ISR_NOERROR undefined_opcode_handler NULL_ISR_NOERROR device_NA_handler NULL_ISR_ERROR double_fault_handler NULL_ISR_NOERROR coprocessor_segment_overrun_handler NULL_ISR_ERROR invalid_tss_handler NULL_ISR_ERROR segment_NA_handler NULL_ISR_ERROR stack_segment_fault_handler NULL_ISR_ERROR general_protection_fault_handler NULL_ISR_ERROR page_fault_handler NULL_ISR_NOERROR math_fault_handler NULL_ISR_NOERROR machine_check_abort_handler NULL_ISR_ERROR alignment_check_handler NULL_ISR_NOERROR simd_exception_handler NULL_ISR_NOERROR reserved_handler That I believe is all the relevant code. The code runs fine till I execute test_idt, after which it seems to reset. There are few glaring issues with the code I know, like not dealing with the error code, but int 3 doesn't deal with that stuff, and I simply want to get this stuff working before I move on to that stuff. If anybody has any idea as to why it crashes, please help me out. WARN btw, is nothing more than a macro which sets colors, prints the parameter, and then resets the colors to their original values. It's enclosed in curly braces, so it runs in its own scope. In the last piece of code, I've tried replacing popa/pusha with popad/pushad, but it doesn't seem to work. My interrupt asm code didn't work, so the current one is a modified version of James Molloy's tutorial. The stuff that's commented out in the nasm macros are the original ISRs, written by me, btw. Thanks |
Author: | MichaelPetch [ Sat Nov 04, 2017 11:32 pm ] |
Post subject: | Re: QEMU resetting problems with interrupts. |
It would be more helpful if you made your project available on something like Github. You don't show us everything which includes things like the function lidt nor did you show the code for the exception handler for int 3. |
Author: | MichaelPetch [ Sun Nov 05, 2017 12:24 am ] |
Post subject: | Re: QEMU resetting problems with interrupts. |
thatoneguy1233 wrote: %macro NULL_ISR_NOERROR 1 I did notice in what you say was your original code that the _ERROR version doesn't actually add 4 to ESP before doing the iretd to skip the error code that the processor pushed after the return address was placed on the stack. Although I see you already know that no that I reread your question global %1 %1: ; pushad ; cld ; push message ; call term_print ; popad ; iret cli push byte 0 push byte 0 jmp isr_common_stub %endmacro %macro NULL_ISR_ERROR 1 global %1 %1: ; pushad ; cld ; push message ; call term_print ; popad ; iret cli push byte 0 jmp isr_common_stub %endmacro As well you push message but you don't actually remove the parameter from the stack with something like add esp, 4 right after the call (or you could do a pop eax for the same effect). Failure to do that would also make the popad restore improperly. |
Author: | thatoneguy1233 [ Sun Nov 05, 2017 2:19 am ] |
Post subject: | Re: QEMU resetting problems with interrupts. |
Thanks. Here's the link to the github repo - https://github.com/animefreak1233/potential-broccoli Excuse the name, I used the random generator But that aside, I tried what you said, nothing happened (Well, it resets again, and since it's an interrupt, I can't quite debug it using Qemu. But I did forget one thing, the OS never resets when stepping through the code in Qemu) but the IDT and GDT structures are all valid, that part can't be wrong unless I got some major misunderstanding as to how the GDT and IDT are structured.) |
Author: | MichaelPetch [ Sun Nov 05, 2017 4:04 am ] |
Post subject: | Re: QEMU resetting problems with interrupts. |
Although not a fix for all your issues the obvious problem is that in init_gdt you turn on interrupts after you set the GDT. Problem is that you haven't set up the IDT at all, and you can't actually do an STI until after you do an LIDT. You should remove the STI from init_gdt and in init_idt you should move the STI after the call to LIDT. That's just the start of the issues. Seems to also be a problem with your IDT entries as well. Will look at it a bit later when I have more time. For GDT and IDT issues I recommend using grub-mkrescue to create a bootable ISO and using BOCHs to debug. Bochs can debug GDT and IDT issues much easier than QEMU. |
Author: | MichaelPetch [ Sun Nov 05, 2017 4:21 am ] |
Post subject: | Re: QEMU resetting problems with interrupts. |
I also noticed this problem: Code: typedef struct idt_ptr_T{ The problem here is that you have limit and base swapped around. limit is first and then the base. It should look like:uint32_t base_address;//Base address of the IDT uint16_t limit;//Limit of the IDT }__attribute__((packed)) idt_ptr; Code: typedef struct idt_ptr_T{ You also have a bug in your lidt assembly function: uint16_t limit;//Limit of the IDT uint32_t base_address;//Base address of the IDT }__attribute__((packed)) idt_ptr; Code: lidt: The problem is that it is using the address [esp + 4] as the location of the idt_ptr structure. The problem is that you want to get the address at [esp + 4] and use it as the memory operand to lidt. It could look like this: lidt [esp + 4] ret Code: lidt: mov eax, [esp + 4] lidt [eax] ret Originally I didn't look closely enough but you don't actually set up IRQ interrupts in your IDT. Until you do that you shouldn't enable external interrupts with STI. You can still do things like int $0x3 with interrupts off as software interrupts are not affected by the interrupt flag status. Before you set up your IRQ handlers in your IDT you should read the OS Dev wiki regarding configuring the PICs. You'll need to remap the IRQs so that they don't overlap with the Intel processors exceptions. You can also tell the PICs which interrupts you wish to be sent to the processor. |
Author: | thatoneguy1233 [ Sun Nov 05, 2017 10:13 am ] |
Post subject: | Re: QEMU resetting problems with interrupts. |
Oh man, I feel stupid to not have noticed that. But thanks, it finally runs, and bochs is kinda amazing for debugging. |
Page 1 of 1 | All times are UTC - 6 hours |
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group http://www.phpbb.com/ |