1

I think my problem is a silly error on my part but I have been scouting the OSDev wiki and other resources for a while and am still lost.

I am making a small OS in x86_64 assembly and in C and I am trying to add keyboard support to build a shell. Everything boots in QEMU using multiboot in long mode until I add the sti command in my IDT loader, where my OS proceeds to triple fault (I'm pretty sure) on boot. I know other people have had similar problems, but I haven't found a solution to mine.

Below is the 64-bit assembly file:

global keyboard_handler
global read_port
global write_port
global load_idt
global long_mode_start

extern kernel_main
extern keyboard_handler_main

section .text
bits 64
read_port:
    mov edx, [esp + 4]
    in al, dx   ; al is the lower 8 bits of eax; dx is the lower 16 bits of edx
    ret

write_port:
    mov   edx, [esp + 4]
    mov   al, [esp + 8]
    out   dx, al
    ret

load_idt:
    mov edx, [esp + 6]
    lidt [edx]
    ;sti      ; turn on interrupts
    ret

keyboard_handler:
    call    keyboard_handler_main
    iretd

long_mode_start:
  ; Load NULL into all data registers

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

  call kernel_main
  hlt

And here is an excerpt in the fault trail (using -d int in QEMU) after I uncomment sti in load_idt:

check_exception old: 0xffffffff new 0xd
     0: v=0d e=0800 i=0 cpl=0 IP=0008:0000000000100c7d pc=0000000000100c7d SP=0000:0000000000108fb8 env->regs[R_EAX]=0000000000108fc0
RAX=0000000000108fc0 RBX=0000000000000000 RCX=0000000000000036 RDX=0000000000000000
RSI=000000000000000a RDI=0000000000108fc0 RBP=0000000000108fe0 RSP=0000000000108fb8
R8 =0000000000000000 R9 =0000000000000000 R10=0000000000000000 R11=0000000000000000
R12=0000000000000000 R13=0000000000000000 R14=0000000000000000 R15=0000000000000000
RIP=0000000000100c7d RFL=00000206 [-----P-] CPL=0 II=1 A20=1 SMM=0 HLT=0
ES =0000 0000000000000000 00000000 00000000
CS =0008 0000000000000000 00000000 00209a00 DPL=0 CS64 [-R-]
SS =0000 0000000000000000 00000000 00000000
DS =0000 0000000000000000 00000000 00000000
FS =0000 0000000000000000 00000000 00000000
GS =0000 0000000000000000 00000000 00000000
LDT=0000 0000000000000000 0000ffff 00008200 DPL=0 LDT
TR =0000 0000000000000000 0000ffff 00008b00 DPL=0 TSS64-busy
GDT=     00000000001001b8 0000000f
IDT=     e2c3f000ff53f000 0000ff53
CR0=80000011 CR2=0000000000000000 CR3=0000000000102000 CR4=00000020
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=0000000000000001 CCD=0000000000000006 CCO=ADDQ    
EFER=0000000000000500
check_exception old: 0xd new 0xd
     1: v=08 e=0000 i=0 cpl=0 IP=0008:0000000000100c7d pc=0000000000100c7d SP=0000:0000000000108fb8 env->regs[R_EAX]=0000000000108fc0
RAX=0000000000108fc0 RBX=0000000000000000 RCX=0000000000000036 RDX=0000000000000000
RSI=000000000000000a RDI=0000000000108fc0 RBP=0000000000108fe0 RSP=0000000000108fb8
R8 =0000000000000000 R9 =0000000000000000 R10=0000000000000000 R11=0000000000000000
R12=0000000000000000 R13=0000000000000000 R14=0000000000000000 R15=0000000000000000
RIP=0000000000100c7d RFL=00000206 [-----P-] CPL=0 II=1 A20=1 SMM=0 HLT=0
ES =0000 0000000000000000 00000000 00000000
CS =0008 0000000000000000 00000000 00209a00 DPL=0 CS64 [-R-]
SS =0000 0000000000000000 00000000 00000000
DS =0000 0000000000000000 00000000 00000000
FS =0000 0000000000000000 00000000 00000000
GS =0000 0000000000000000 00000000 00000000
LDT=0000 0000000000000000 0000ffff 00008200 DPL=0 LDT
TR =0000 0000000000000000 0000ffff 00008b00 DPL=0 TSS64-busy
GDT=     00000000001001b8 0000000f
IDT=     e2c3f000ff53f000 0000ff53
CR0=80000011 CR2=0000000000000000 CR3=0000000000102000 CR4=00000020
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=0000000000000001 CCD=0000000000000006 CCO=ADDQ    
EFER=0000000000000500
check_exception old: 0x8 new 0xd
     2: v=03 e=0000 i=1 cpl=0 IP=0008:00000000000f1203 pc=00000000000f1203 SP=0010:0000000000000fb4 env->regs[R_EAX]=00000000000f6006

I have checked my OI handling and ISR descriptor, and I think they're fine. I think I am just doing something silly with my memory movement in load_idt, but any advice would be much appreciated. I'll be happy to provide more code / explanation. Thank you.

EDIT: Here is where load_idt is called (variables in CAPS are all the usual values). I have given the 32-bit and 64-bit (commented) initialization of the IDT. Someone made a good point that my read/write ports may not be working properly, which could explain things:

#include "keyboard.h"
#include "print.h"

unsigned char keyboard_map[128] =
{
    0,  27, '1', '2', '3', '4', '5', '6', '7', '8', /* 9 */
  '9', '0', '-', '=', '\b', /* Backspace */
  '\t',         /* Tab */
  'q', 'w', 'e', 'r',   /* 19 */
  't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n', /* Enter key */
    0,          /* 29   - Control */
  'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', /* 39 */
 '\'', '`',   0,        /* Left shift */
 '\\', 'z', 'x', 'c', 'v', 'b', 'n',            /* 49 */
  'm', ',', '.', '/',   0,              /* Right shift */
  '*',
    0,  /* Alt */
  ' ',  /* Space bar */
    0,  /* Caps lock */
    0,  /* 59 - F1 key ... > */
    0,   0,   0,   0,   0,   0,   0,   0,
    0,  /* < ... F10 */
    0,  /* 69 - Num lock*/
    0,  /* Scroll Lock */
    0,  /* Home key */
    0,  /* Up Arrow */
    0,  /* Page Up */
  '-',
    0,  /* Left Arrow */
    0,
    0,  /* Right Arrow */
  '+',
    0,  /* 79 - End key*/
    0,  /* Down Arrow */
    0,  /* Page Down */
    0,  /* Insert Key */
    0,  /* Delete Key */
    0,   0,   0,
    0,  /* F11 Key */
    0,  /* F12 Key */
    0,  /* All other keys are undefined */
};


/* current cursor location */
unsigned int current_loc = 0;
/* video memory begins at address 0xb8000 */
char *vidptr = (char*)0xb8000;

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

struct IDT_entry_64 { // Will possibly use later
    unsigned short int offset_lowerbits;
    unsigned short int selector;
    unsigned char ist;
    unsigned char type_attr;
    unsigned short int offset_middlebits;
    unsigned long int offset_higherbits;
    unsigned long int zero;
};

struct IDT_entry IDT[IDT_SIZE];

void idt_init(void) {
    unsigned long keyboard_address;
    unsigned long idt_address;
    unsigned long idt_ptr[2];

    /* populate IDT entry of keyboard's interrupt */
    keyboard_address = (unsigned long)keyboard_handler;
    print_str( " Keyboard address: " );
    print_int( keyboard_address );
    print_newline();
    IDT[0x21].offset_lowerbits = keyboard_address & 0x0000ffff;
    IDT[0x21].selector = KERNEL_CODE_SEGMENT_OFFSET;
    IDT[0x21].zero = 0;
    IDT[0x21].type_attr = INTERRUPT_GATE;
    IDT[0x21].offset_higherbits = (keyboard_address & 0xffff0000) >> 16;

  // IDT[0x21].offset_lowerbits = keyboard_address & 0x000000000000ffff;
  // IDT[0x21].offset_middlebits = (keyboard_address & 0x00000000ffff0000) >> 16;
  // IDT[0x21].offset_higherbits = (keyboard_address & 0xffffffff00000000) >> 32;
    // IDT[0x21].selector = KERNEL_CODE_SEGMENT_OFFSET;
    // IDT[0x21].ist = 0;
    // IDT[0x21].type_attr = INTERRUPT_GATE;
  // IDT[0x21].zero = 0;


    /*     Ports
    *    PIC1   PIC2
    *Command 0x20   0xA0
    *Data    0x21   0xA1
    */

    /* ICW1 - begin initialization */
    write_port(0x20 , 0x11);
    write_port(0xA0 , 0x11);

    /* ICW2 - remap offset address of IDT */
    /*
    * In x86 protected mode, we have to remap the PICs beyond 0x20 because
    * Intel have designated the first 32 interrupts as "reserved" for cpu exceptions
    */
    write_port(0x21 , 0x20);
    write_port(0xA1 , 0x28);

    /* ICW3 - setup cascading */
    write_port(0x21 , 0x00);
    write_port(0xA1 , 0x00);

    /* ICW4 - environment info */
    write_port(0x21 , 0x01);
    write_port(0xA1 , 0x01);
    /* Initialization finished */

    /* mask interrupts */
    write_port(0x21 , 0xfd);
    write_port(0xA1 , 0xff);

    /* fill the IDT descriptor */
    idt_address = (unsigned long)IDT;
    print_str( " IDT address: " );
    print_int( idt_address );
    print_newline();
    print_str( " Pre-assignment IDT pointers: " );
    print_int( idt_ptr[0] );
    print_str( ", " );
    print_int( idt_ptr[1] );
    print_newline();
    idt_ptr[0] = (sizeof (struct IDT_entry) * IDT_SIZE) + ((idt_address & 0x00000000ffffffff) << 32);
    idt_ptr[1] = idt_address >> 16;

    print_str( " Post-assignment IDT pointers: " );
    print_int( idt_ptr[0] );
    print_str( ", " );
    print_int( idt_ptr[1] );
    print_newline();

    load_idt(idt_ptr);
}

void keyboard_init(void) {
    /* 0xFD is 11111101 - enables only IRQ1 (keyboard)*/
    write_port(0x21 , 0xFD);
}

void keyboard_handler_main(void) {
    unsigned char status;
    char keycode;

    /* write EOI */
    write_port(0x20, 0x20);

    status = read_port(KEYBOARD_STATUS_PORT);
    /* Lowest bit of status will be set if buffer is not empty */
    if( status & 0x01 ) {
        keycode = read_port(KEYBOARD_DATA_PORT);
        if(keycode < 0)
            return;

        if( keycode == ENTER_KEY_CODE ) {
      print_str( " Pressed enter" );
            print_newline();
            return;
        }

        vidptr[current_loc++] = keyboard_map[(unsigned char) keycode];
        vidptr[current_loc++] = 0x07;
    }
}
zhn11tau
  • 205
  • 3
  • 10
  • why do you use 32-bit addresses in 64-bit mode? `mov edx, [esp + 4]` – phuclv Jul 23 '21 at 09:40
  • Hi there. Using rsp and rdx changes nothing about my problem. – zhn11tau Jul 23 '21 at 09:58
  • 2
    you don't use register sizes randomly like that. In general prefer 32-bit registers for code sizes, but addresses must be 64-bit – phuclv Jul 23 '21 at 10:00
  • 1
    I'm not used to QEMU log dumps but what about `IDT= e2c3f000ff53f000 0000ff53`? This seems off. – Margaret Bloom Jul 23 '21 at 10:19
  • @MargaretBloom, seems really off, but I don't know enough assembly. I'm sure it has something to do with my `mov` command. I lazily just C&P'd from the read/write port routines. This is obviously being thrown into `lidt` without much thought. I'd love to be directed to somewhere which could tell me what my IDT memory should look like for a successful run. – zhn11tau Jul 23 '21 at 10:22
  • as phuclv said, you must use the correct address size. You may get away with it now because the kernel seems to be below 4GiB. Also, there is no code that shows how `load_idt` is used. – Margaret Bloom Jul 23 '21 at 10:34
  • 2
    ```check_exception old: 0x8 new 0xd``` means you're getting a page fault (```#PF```) followed by a double fault (```#DF```). The IDT settings are invalid; ```e2c3...``` isn't a valid address in 4-level paging, and the limit is almost always ```fff```, which means you're loading garbage values with ```lidt```. I don't know what the code was trying to do, but instructions like ```mov edx, [esp + 6]``` wouldn't even be right in 32-bit mode as the stack should be 4-byte aligned. I would strongly suggest not attempting long mode until you know protected mode well. – sj95126 Jul 23 '21 at 14:19
  • 1
    Where is `load_idt` called from? That `mov edx, [esp + 6]` looks suspicious , because you don't normally use an offset of 6 from the stack pointer to load dwords. – 1201ProgramAlarm Jul 23 '21 at 14:44
  • 1
    Yes, please show the code where you call these functions. Their code for retrieving their arguments is definitely wrong (e.g. assuming that `read_port` was reached with a 64-bit `call`, then `mov edx, [esp + 4]` will load the high 4 bytes of the return address, which certainly won't be the port you are trying to read). But without seeing how you intend to call, there's no way to say what the correct code would be. – Nate Eldredge Jul 23 '21 at 15:04
  • Thank you everyone, for your suggestions. I have updated the query with `keyboard.c`, which is where the IDT is loaded and the IO is taken care of. :) – zhn11tau Jul 23 '21 at 17:09
  • So, still confused. Your C code is presumably built as 64 bit, then it will pass the argument to `read_port` in the `rdi` register, not on the stack, so your attempt to retrieve the argument from the stack makes no sense. The same for all your other asm functions. – Nate Eldredge Jul 23 '21 at 17:26
  • 1
    And if you're in 64 bit mode, then why are you working with `IDT_entry` instead of `IDT_entry_64`? To be honest, this code looks like it was derived from some combination of examples and tutorials in both 64 and 32 bit code, and bits were taken from both without even a clear understanding of which was which. At least with the asm part, I think you ought to start over from scratch. – Nate Eldredge Jul 23 '21 at 17:39
  • Call it a case of bad reading comprehension on my part. I just thought the 'e' registers were the lower 32 bits of the 'r' registers, so kind of thought you could use them interchangeably if you 'agreed' not to use the higher 32 bits. That's probably not the case. I'm not at all experienced in computers. I just have to do this so that someone else can run the .iso on their VM. The C code is compiled with 64-bit elf and the assembly with nasm. – zhn11tau Jul 23 '21 at 17:52
  • 1
    The `e` registers *are* the low half of the corresponding `r` register, but absolute addresses often are 64-bit. Writing a 32-bit register zero-extends to 64-bit implicitly. [Why do x86-64 instructions on 32-bit registers zero the upper part of the full 64-bit register?](https://stackoverflow.com/q/11177137). You should always prefer 64-bit address-size, but you can use 32-bit operand-size for things that don't need to be 64-bit. [The advantages of using 32bit registers/instructions in x86-64](https://stackoverflow.com/q/38303333) – Peter Cordes Jul 23 '21 at 18:55
  • 1
    Other comments are focusing on your use of esp and edx but that's not the cause of your problem. The cause is undoubtedly `mov edx [esp+6]`. You need to figure out where the parameter to load_idt is being passed, either rdi or esp+4 or esp+8. – prl Jul 24 '21 at 17:33

0 Answers0