Page 1 of 2

Page Fault after enabling interrupt using sti

Posted: Sat Nov 09, 2013 10:37 pm
by kemosparc
Hi,

I am writing a 64-bit kernel, and I have managed to boot using my bootloader, switch to protected mode, then long mode, and jump to my C++ kernel code. I have allocated 1 GB ane enabled paging. I have also managed to setup my IDT and setup IRQ. I was testing IRQ0 with PIC and it work fine and the clock ticks with no problem.

Just before enabling IRQs, I had all my interrupts cleared, but as soon as I issue "sti" within my code I start getting "Page Fault Exception" and the cr2 has the address 0x40000f01 which is above 1 GB with 0xf01 bytes. The problem is that I dont know why this page fault is bing fired as I never access this address at all.


Here is my page enabling routine:

Code: Select all



EnablePaging:
        pushf
        push ds
        push es
        push di
        push si

        mov eax, cr0                                   ; Set the A-register to control register 0.
        and eax, 01111111111111111111111111111111b     ; Clear the PG-bit, which is bit 31.
        mov cr0, eax                                   ; Set control register 0 to the A-register.

        mov edi, 0x1000    ; Set the destination index to 0x1000.
        mov cr3, edi       ; Set control register 3 to the destination index.
        xor eax, eax       ; Nullify the A-register.
        mov ecx, 4096      ; Set the C-register to 4096.
        rep stosd          ; Clear the memory.
        mov edi, cr3       ; Set the destination index to control register 3.

        mov DWORD [edi], 0x2003      ; Set the double word at the destination index to 0x2003.
        add edi, 0x1000              ; Add 0x3000 to the destination index.
        mov DWORD [edi], 0x3003      ; Set the double word at the destination index to 0x3003.
        add edi, 0x1000              ; Add 0x3000 to the destination index.

        mov ebx, 0x100003
        mov ecx, 0x200
        .LoadLastSegment:
            mov DWORD [edi], ebx
            add ebx,0x1000
            add edi,8
        loop .LoadLastSegment

        add edi, 0x1000              
        mov edi, 0x100000
        mov ebx, 0x00000003          ; Set the B-register to 0x00000003.
        mov ecx, 0x40000                 ; Set the C-register to 0x40000 (LOOP for 1 GB)

        .SetEntry:
            mov DWORD [edi], ebx         ; Set the double word at the destination index to the B-register.
            add ebx, 0x1000              ; Add 0x1000 to the B-register.
            add edi, 8                   ; Add eight to the destination index.
            loop .SetEntry               ; Set the next entry.

        mov eax, cr4                 ; Set the A-register to control register 4.
        or eax, 1  << 5              ; Set the PAE-bit, which is the 6th bit (bit 5).
        mov cr4, eax                 ; Set control register 4 to the A-register.
        pop si
        pop di
        pop es
        pop ds
        popf
ret

Here also my main kernel function:

Code: Select all

void kernel_main()
{
    idtStart();
    Video video;
    video.clearScreen(COLOR_BLACK);
    video.putString("Welcome to KEMOX\n",COLOR_BLUE,COLOR_WHITE);
    video.putString("Author: Karim Sobh (kemosparc)\n",COLOR_CYAN,COLOR_BLACK);

    video.putDecimal(sizeof(idtEntry) * IDT_SIZE ,COLOR_CYAN,COLOR_BLACK);
    video.putString("\n",COLOR_CYAN,COLOR_BLACK);

    asm volatile ("sti");
    init_timer(50);
    video.setPosition(0,15);
    video.putString("After Timer init\n",COLOR_CYAN,COLOR_BLACK);

}
Also find below a screenshot from my OS with the exception error. Notic that the tick is counting so IRQ0 is firing correctly.

Kindly if you have any clue about the reason behind firing the page fault let me know.

Thanks
Karim

Re: Page Fault after enabling interrupt using sti

Posted: Sat Nov 09, 2013 11:21 pm
by thepowersgang
1. Get the faulting instruction (bochs is helpful for this)
2. Ensure that your interrupt handling routines are mapped.
3. I don't see CR0.PG being re-set in your paging code.
4. (style) Document why not what. Your comments in that code are almost more than useless.

Re: Page Fault after enabling interrupt using sti

Posted: Sun Nov 10, 2013 1:31 am
by kemosparc
Okay,

First, regarding the style, I agree but for me this is just a sand box that I use to understand well and then start writing the whole thing in a more elegant way, so forgive me for that.
i.e. I am in the phase now of lighting up all the dark rooms before decorating them :)

Now, for Bochs I don't get the page fault that I get in qemu. It works like a charm!
Attached is a screenshot for the same code running on Bochs

Regarding the mapping of my interrupt routines, here is the code that I use

Code: Select all

void idtStart(void) {
   /* Set IDT Pointer */
   idtP.limit = (sizeof(idtEntry) * IDT_SIZE) - 1;
   idtP.base = (uint64_t)&idt;
   /* Clear Memory for IDT's */
   memset((uint8_t *)&idt, 0, sizeof(idtEntry) * IDT_SIZE);

   memset((uint8_t *)&interrupt_handlers, 0, sizeof(isr_t)*256);

    Ports::outportb(0x20, 0x11);
    Ports::outportb(0xA0, 0x11);
    Ports::outportb(0x21, 0x20);
    Ports::outportb(0xA1, 0x28);
    Ports::outportb(0x21, 0x04);
    Ports::outportb(0xA1, 0x02);
    Ports::outportb(0x21, 0x01);
    Ports::outportb(0xA1, 0x01);
    Ports::outportb(0x21, 0x0);
    Ports::outportb(0xA1, 0x0);


   /* Set IDT Gates */
   idtSet(0, (uint64_t)isr0, 0x08, 0x8E);
   idtSet(1, (uint64_t)isr1, 0x08, 0x8E);
   idtSet(2, (uint64_t)isr2, 0x08, 0x8E);
   idtSet(3, (uint64_t)isr3, 0x08, 0x8E);
   idtSet(4, (uint64_t)isr4, 0x08, 0x8E);
   idtSet(5, (uint64_t)isr5, 0x08, 0x8E);
   idtSet(6, (uint64_t)isr6, 0x08, 0x8E);
   idtSet(7, (uint64_t)isr7, 0x08, 0x8E);
   idtSet(8, (uint64_t)isr8, 0x08, 0x8E);
   idtSet(9, (uint64_t)isr9, 0x08, 0x8E);
   idtSet(10, (uint64_t)isr10, 0x08, 0x8E);
   idtSet(11, (uint64_t)isr11, 0x08, 0x8E);
   idtSet(12, (uint64_t)isr12, 0x08, 0x8E);
   idtSet(13, (uint64_t)isr13, 0x08, 0x8E);
   idtSet(14, (uint64_t)isr14, 0x08, 0x8E);
   idtSet(15, (uint64_t)isr15, 0x08, 0x8E);
   idtSet(16, (uint64_t)isr16, 0x08, 0x8E);
   idtSet(17, (uint64_t)isr17, 0x08, 0x8E);
   idtSet(18, (uint64_t)isr18, 0x08, 0x8E);
   idtSet(19, (uint64_t)isr19, 0x08, 0x8E);
   idtSet(20, (uint64_t)isr20, 0x08, 0x8E);
   idtSet(21, (uint64_t)isr21, 0x08, 0x8E);
   idtSet(22, (uint64_t)isr22, 0x08, 0x8E);
   idtSet(23, (uint64_t)isr23, 0x08, 0x8E);
   idtSet(24, (uint64_t)isr24, 0x08, 0x8E);
   idtSet(25, (uint64_t)isr25, 0x08, 0x8E);
   idtSet(26, (uint64_t)isr26, 0x08, 0x8E);
   idtSet(27, (uint64_t)isr27, 0x08, 0x8E);
   idtSet(28, (uint64_t)isr28, 0x08, 0x8E);
   idtSet(29, (uint64_t)isr29, 0x08, 0x8E);
   idtSet(30, (uint64_t)isr30, 0x08, 0x8E);
   idtSet(31, (uint64_t)isr31, 0x08, 0x8E);

    idtSet(32, (uint64_t)irq0, 0x08, 0x8E);
    idtSet(33, (uint64_t)irq1, 0x08, 0x8E);
    idtSet(34, (uint64_t)irq2, 0x08, 0x8E);
    idtSet(35, (uint64_t)irq3, 0x08, 0x8E);
    idtSet(36, (uint64_t)irq4, 0x08, 0x8E);
    idtSet(37, (uint64_t)irq5, 0x08, 0x8E);
    idtSet(38, (uint64_t)irq6, 0x08, 0x8E);
    idtSet(39, (uint64_t)irq7, 0x08, 0x8E);
    idtSet(40, (uint64_t)irq8, 0x08, 0x8E);
    idtSet(41, (uint64_t)irq9, 0x08, 0x8E);
    idtSet(42, (uint64_t)irq10, 0x08, 0x8E);
    idtSet(43, (uint64_t)irq11, 0x08, 0x8E);
    idtSet(44, (uint64_t)irq12, 0x08, 0x8E);
    idtSet(45, (uint64_t)irq13, 0x08, 0x8E);
    idtSet(46, (uint64_t)irq14, 0x08, 0x8E);
    idtSet(47, (uint64_t)irq15, 0x08, 0x8E);
   /* Load IDT Table */
   idtInit();
}

The ports operation I have encapsulated them in a class:

Code: Select all

#ifndef PORTS_H_
#define PORTS_H_

#include "defines.h"
#include "includes.h"

class Ports
{
    private:
    public:
        static void outportb (unsigned short port,unsigned char data);
        static unsigned char inportb( unsigned short port);
        static unsigned short inportw( unsigned short port);
};

#endif /* PORTS_H_ */

Code: Select all

#include "Ports.h"
void Ports::outportb (unsigned short port,unsigned char data)
{
     asm volatile ("outb %1, %0" : : "dN" (port), "a" (data));
}
unsigned char Ports::inportb( unsigned short port)
{
   unsigned char ret;
   asm volatile("inb %1, %0" : "=a" (ret) : "dN" (port));
   return ret;
}
unsigned short Ports::inportw( unsigned short port)
{
   unsigned short ret;
   asm volatile ("inw %1, %0" : "=a" (ret) : "dN" (port));
   return ret;
}

My IRQ Assembly code is here:

Code: Select all

[BITS 64]
%macro pushAll 0
      push   rax
      push   rbx
      push   rcx
      push   rdx
      push   rbp
      push   rsi
      push   rdi
      push   rsp
      push   r8
      push   r9
      push   r10
      push   r11
      push   r12
      push   r13
      push   r14
      push   r15
%endmacro

%macro popAll 0
      pop       r15
      pop       r14
      pop       r13
      pop       r12
      pop       r11
      pop       r10
      pop       r9
      pop       r8
      pop       rsp
      pop       rdi
      pop       rsi
      pop       rbp
      pop       rdx
      pop       rcx
      pop       rbx
      pop       rax
%endmacro

global idtInit
extern idtP
idtInit:
   lidt [idtP]
   ret

;/*
; * Interrupt Handler
; */
extern isrHandler

isrCommon:
    pushAll

    mov     ax, ds
    push    rax

    mov     ax, 0x10
    mov     ds, ax
    mov     es, ax
    mov     fs, ax
    mov     gs, ax

    call isrHandler

    pop      rbx
    mov      ds, bx
    mov      es, bx
    mov      fs, bx
    mov      gs, bx


    popAll
    add rsp,16
    sti
    iretq

extern irq_handler

; This is our common IRQ stub. It saves the processor state, sets
; up for kernel mode segments, calls the C-level fault handler,
; and finally restores the stack frame.
irq_common_stub:

    pushAll                    ; Pushes edi,esi,ebp,esp,ebx,edx,ecx,eax
    mov ax, ds               ; Lower 16-bits of eax = ds.
    push rax                 ; 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 irq_handler
    pop rbx        ; reload the original data segment descriptor
    mov ds, bx
    mov es, bx
    mov fs, bx
    mov gs, bx

    popAll                     ; Pops edi,esi,ebp..

    add rsp, 16      ; Cleans up the pushed error code and pushed ISR number
    sti
    iretq           ; pops 5 things at once: CS, EIP, EFLAGS, SS, and ESP

%macro ISR_NOERRCODE 1
  [GLOBAL isr%1]
  isr%1:
    cli
    push byte 0
    push byte %1
    jmp isrCommon
%endmacro

%macro ISR_ERRCODE 1
  [GLOBAL isr%1]
  isr%1:
    cli
    push byte %1
    jmp isrCommon
%endmacro

%macro IRQ 2
  global irq%1
  irq%1:
    cli
    push byte 0
    push byte %2
    jmp irq_common_stub
%endmacro

ISR_NOERRCODE 0
ISR_NOERRCODE 1
ISR_NOERRCODE 2
ISR_NOERRCODE 3
ISR_NOERRCODE 4
ISR_NOERRCODE 5
ISR_NOERRCODE 6
ISR_NOERRCODE 7
ISR_ERRCODE   8
ISR_NOERRCODE 9
ISR_ERRCODE   10
ISR_ERRCODE   11
ISR_ERRCODE   12
ISR_ERRCODE   13
ISR_ERRCODE   14
ISR_NOERRCODE 15
ISR_NOERRCODE 16
ISR_NOERRCODE 17
ISR_NOERRCODE 18
ISR_NOERRCODE 19
ISR_NOERRCODE 20
ISR_NOERRCODE 21
ISR_NOERRCODE 22
ISR_NOERRCODE 23
ISR_NOERRCODE 24
ISR_NOERRCODE 25
ISR_NOERRCODE 26
ISR_NOERRCODE 27
ISR_NOERRCODE 28
ISR_NOERRCODE 29
ISR_NOERRCODE 30
ISR_NOERRCODE 31
IRQ   0,    32
IRQ   1,    33
IRQ   2,    34
IRQ   3,    35
IRQ   4,    36
IRQ   5,    37
IRQ   6,    38
IRQ   7,    39
IRQ   8,    40
IRQ   9,    41
IRQ  10,    42
IRQ  11,    43
IRQ  12,    44
IRQ  13,    45
IRQ  14,    46
IRQ  15,    47
And finally my GDT declaration is here:

Code: Select all


GDT64:                           ; Global Descriptor Table (64-bit).
        .Null: equ $ - GDT64         ; The null descriptor.
        dw 0                         ; Limit (low).
        dw 0                         ; Base (low).
        db 0                         ; Base (middle)
        db 0                         ; Access.
        db 0                         ; Granularity.
        db 0                         ; Base (high).
        .Code: equ $ - GDT64         ; The code descriptor.
        dw 0                         ; Limit (low).
        dw 0                         ; Base (low).
        db 0                         ; Base (middle)
        db 10011000b                 ; Access.
        db 00100000b                 ; Granularity.
        db 0                         ; Base (high).
        .Data: equ $ - GDT64         ; The data descriptor.
        dw 0                         ; Limit (low).
        dw 0                         ; Base (low).
        db 0                         ; Base (middle)
        db 10010000b                 ; Access.
        db 00000000b                 ; Granularity.
        db 0                         ; Base (high).
    .Pointer:                    ; The GDT-pointer.
    dw $ - GDT64 - 1             ; Limit.
    dq GDT64                     ; Base.

Regarding the CR0.PG, I do that in a seperate routine that is call immediatly after EnablePaging, and here is the code for it:

Code: Select all

FromProtected:
        pushf
        push ds
        push es
        push di
        push si

    mov ecx, 0xC0000080          ; Set the C-register to 0xC0000080, which is the EFER MSR.
    rdmsr                        ; Read from the model-specific register.
    or eax, 1 << 8               ; Set the LM-bit which is the 9th bit (bit 8).
    wrmsr                        ; Write to the model-specific register.

    mov eax, cr0                 ; Set the A-register to control register 0.
    or eax,1 << 31               ; Set the PG-bit, which is the 32nd bit (bit 31).
    mov cr0, eax                 ; Set control register 0 to the A-register.

        pop si
        pop di
        pop es
        pop ds
        popf
ret

Thanks,
Karim.

Re: Page Fault after enabling interrupt using sti

Posted: Sun Nov 10, 2013 2:09 am
by Minoto
kemosparc wrote:First, regarding the style, I agree but for me this is just a sand box that I use to understand well and then start writing the whole thing in a more elegant way, so forgive me for that.
i.e. I am in the phase now of lighting up all the dark rooms before decorating them :)
All the more reason to do it well. In this line, what does the comment say that the code doesn't? How does it help you, or anyone else, understand what the code is meant to accomplish?
kemosparc wrote:mov DWORD [edi], 0x2003 ; Set the double word at the destination index to 0x2003.
The problem with the next line should be blatantly obvious...
kemosparc wrote:add edi, 0x1000 ; Add 0x3000 to the destination index.

Re: Page Fault after enabling interrupt using sti

Posted: Sun Nov 10, 2013 2:47 am
by kemosparc
I don't understand anything from what you wrote !!!

Forget about the code comments and look at my code. The comments were written initially and the code was changed million times while I was learing and trying different things.

If you see a problem in my code that leads to the page fault that I am experiencing on qemu, kindly highlight it in details as the most important thing from this forum is for people to learn and share information rather than showing off by writing very short comments

Karim,
Thanks.

Re: Page Fault after enabling interrupt using sti

Posted: Sun Nov 10, 2013 3:35 am
by Minoto
kemosparc wrote:I don't understand anything from what you wrote !!!
Not even the value of correctly documenting your code if you expect other people to help you fix it?

Re: Page Fault after enabling interrupt using sti

Posted: Sun Nov 10, 2013 3:48 am
by kemosparc
Thanks for the advise

It was very helpful.

Karim

Re: Page Fault after enabling interrupt using sti

Posted: Sun Nov 10, 2013 3:57 am
by bwat
kemosparc wrote: Forget about the code comments and look at my code. The comments were written initially and the code was changed million times while I was learing and trying different things.
1) Messy code is a result of messy thinking. You need to understand your problem better. As the old S.K. Langer quote goes every universe of discourse has its logical structure. You've not found the structure.
2) If you have to change code a million times, then you're fumbling in the dark instead of making a systematic attempt to solve a problem. Read this http://pespmc1.vub.ac.be/PROBSOLV.html for one definition of problem solving that might prompt you into a more systematic approach.

Work out 1 & 2 above and you're good to go!
kemosparc wrote: If you see a problem in my code that leads to the page fault that I am experiencing on qemu, kindly highlight it in details as the most important thing from this forum is for people to learn and share information rather than showing off by writing very short comments
Minoto might be trying to help you help yourself. This isn't showing off as you put it. Far from it in fact.

Re: Page Fault after enabling interrupt using sti

Posted: Sun Nov 10, 2013 7:40 am
by brighteningeyes
i didn't use qemu but maybe the problem is in your page handler

Re: Page Fault after enabling interrupt using sti

Posted: Sun Nov 10, 2013 7:53 am
by kemosparc
The symptom is that it is always 0xf01h bytes above the allocated memory; the location that causes the page fault.

I tried to map different sizes 1G, 2G, 3G, and 4G and it is always above by that number of bytes 0xf01h e.g. 0x80000f01 for the 2G

This is consistent.

I think that something is trying to access post memory size but I don't know how to identify who is accessing this address in memory. Is there anything I need to do to tell the processor my maximum allocated memory size ?

If this rings a bel for anyone please let me know.

Currently I am trying to clean up the code and document it well as suggested.

Thanks
Karim.

Re: Page Fault after enabling interrupt using sti

Posted: Sun Nov 10, 2013 8:47 am
by kemosparc
Okay,

I have some updates, here is my init_timer:

Code: Select all

void init_timer(uint32_t frequency)
{
    // Firstly, register our timer callback.
    register_interrupt_handler(IRQ0, &timer_callback);
    register_interrupt_handler(IRQ1, &keyboard_callback);

    // The value we send to the PIT is the value to divide it's input clock
    // (1193180 Hz) by, to get our required frequency. Important to note is
    // that the divisor must be small enough to fit into 16-bits.
    uint32_t divisor = 1193180 / frequency;

    // Send the command byte.
    Ports::outportb(0x43, 0x36);     <------- Comment out to work

    // Divisor has to be sent byte-wise, so split here into upper/lower bytes.
    uint8_t l = (uint8_t)(divisor & 0xFF);
    uint8_t h = (uint8_t)( (divisor>>8) & 0xFF );

    // Send the frequency divisor.
    Ports::outportb(0x40, l);    <------- Comment out to work
    Ports::outportb(0x40, h);   <------- Comment out to work
}

When I comment out those lines the page fault does not appear. I got this code from JamesM's tutorial, and I read aout the PIC in here http://wiki.osdev.org/PIC but I don't understand what those instruction do except as descriped for the first instruction in the tutorial, which is "This byte (0x36) sets the PIT to repeating mode (so that when the divisor counter reaches zero it's automatically refreshed) and tells it we want to set the divisor value".

After removing these line sill the timer on IRQ0 works and the keyboard interrupt as well.

Does anyone have a clue why those instruction cause such page fault ??

Thanks
Karim.

Re: Page Fault after enabling interrupt using sti

Posted: Sun Nov 10, 2013 9:07 am
by bwat
What happens when you configure the PIT to work in mode 2 not mode 3, i.e. change the 0x36 to 0x34.

Re: Page Fault after enabling interrupt using sti

Posted: Sun Nov 10, 2013 9:11 am
by kemosparc
The same !!!

Re: Page Fault after enabling interrupt using sti

Posted: Sun Nov 10, 2013 9:18 am
by bwat
kemosparc wrote:The same !!!
What are the contents of l and h?

Re: Page Fault after enabling interrupt using sti

Posted: Sun Nov 10, 2013 9:42 am
by bwat
kemosparc wrote:

Code: Select all

void kernel_main()
{
    idtStart();
    Video video;
    video.clearScreen(COLOR_BLACK);
    video.putString("Welcome to KEMOX\n",COLOR_BLUE,COLOR_WHITE);
    video.putString("Author: Karim Sobh (kemosparc)\n",COLOR_CYAN,COLOR_BLACK);

    video.putDecimal(sizeof(idtEntry) * IDT_SIZE ,COLOR_CYAN,COLOR_BLACK);
    video.putString("\n",COLOR_CYAN,COLOR_BLACK);

    asm volatile ("sti");
    init_timer(50);
    video.setPosition(0,15);
    video.putString("After Timer init\n",COLOR_CYAN,COLOR_BLACK);

}
Do you really want to execute a "sti" before you initialise the timer?