General Questions about basic kernel code

Programming, for all ages and all languages.
Post Reply
gsingh2011
Member
Member
Posts: 83
Joined: Tue Feb 03, 2009 11:37 am

General Questions about basic kernel code

Post by gsingh2011 »

So I made a basic OS with a GDT and an IDT and I understand almost everything but I have a few questions.

First of all, this assembly function is called whenever there is an IRQ,

Code: Select all

irq_common_stub:
    pusha
    push ds
    push es
    push fs
    push gs

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

    push eax
    mov eax, irq_handler
    call eax
    pop eax

    pop gs
    pop fs
    pop es
    pop ds
    popa
    add esp, 8
    iret
First of all, the function irq_handler has an argument struct regs *r which is

Code: Select all

struct regs
{
    unsigned int gs, fs, es, ds;      /* pushed the segs last */
    unsigned int edi, esi, ebp, esp, ebx, edx, ecx, eax;  /* pushed by 'pusha' */
    unsigned int int_no, err_code;    /* our 'push byte #' and ecodes do this */
    unsigned int eip, cs, eflags, useresp, ss;   /* pushed by the processor automatically */ 
};
How does the function get the argument data from the stack? Does it do it automatically when its called, taking all all the data that it requires?
Also, in this code irq_handler is moved to eax before being called. What's the difference between this and just calling irq_handler directly?

To setup each IRQ in the table, the following function is called idt_set_gate(32, (unsigned)irq0, 0x08, 0x8E); The function is defined as,

Code: Select all

void idt_set_gate(unsigned char num, unsigned long base, unsigned short sel, unsigned char flags) // Sets an IDT entry
{
    idt[num].base_lo = (base & 0xFFFF); // Sets first 16 bits of base address
    idt[num].base_hi = (base >> 16) & 0xFFFF; // Sets last 16 bits of base address

    idt[num].sel = sel; // Sets selector
    idt[num].always0 = 0; // This value is always 0
    idt[num].flags = flags; // Sets flags
}
So the selector points to the IRQ, right? So setting the selector to 0x08 just points it to the code segment. How does this point t to the interrupt function?

Finally, in the following code,

Code: Select all

global gdt_flush     ; Allows the C code to link to this
extern gp            ; Says that 'gp' is in another file
gdt_flush:
    lgdt [gp]        ; Load the GDT with our '_gp' which is a special pointer
    mov ax, 0x10      ; 0x10 is the offset in the GDT to our data segment
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax
    jmp 0x08:flush2   ; 0x08 is the offset to our code segment (because he GDT starts with a NULL descriptor): Far jump!
flush2:
    ret               ; Returns back to the C code!
I'm confused on the purpose of the jmp 0x08:flush2 line. The purpose of this function is to point the correct registers to the GDT entries right? So I see how jumping to 0x08 points to the code segment but how does calling flush right after that set the CS to that value?
User avatar
bewing
Member
Member
Posts: 1401
Joined: Wed Feb 07, 2007 1:45 pm
Location: Eugene, OR, US

Re: General Questions about basic kernel code

Post by bewing »

1. You need to realize that irq_handler only takes one argument. That one argument is the very last thing that got pushed on the stack. Which is a pointer to the bottom of the structure that you just finished creating (which just happened to be on the stack, too). The function uses that pointer to access everything in the structure, and the function does not care where the structure is located in memory. With a "normal calling convention", the arguments to a function are always pushed onto the stack just prior to the call.

2. Moving the address to eax, and then calling eax does precisely the same thing as calling the address directly. The person who wrote the code just didn't know what they were doing.

3. The interrupt function pointer is called "base" in the argument list. It is just named stupidly. But that is how the IDT knows the function address -- because you tell it the function address.

4. After you set up a new GDT, in some sense all the old selectors are now invalid, and should probably be updated with new values by loading them all over again. Which is what this code does. For all segment registers except CS, you do this with a MOV instruction. To set CS, you must use a far jump. But where to jump to? The code example that you show does the traditional answer. Just do a far jump to the very next line in the code. The far jump itself sets CS -- that is its purpose.
gsingh2011
Member
Member
Posts: 83
Joined: Tue Feb 03, 2009 11:37 am

Re: General Questions about basic kernel code

Post by gsingh2011 »

3. Well, according to the code, each irq gets pointed to 0x08. So each irq is pointed to the same entry in the table. And if its the same entry, isn't the base the same? Which would mean that each irq is pointed to the same spot in memory? I'm confused... what's going on?

4. Well the part I don't get is how a jump sets the value of a register? All you do is jump there so the value of IP gets changed to wherever you're jumping. Why can't you just set it like you do for the data segment?
User avatar
Combuster
Member
Member
Posts: 9301
Joined: Wed Oct 18, 2006 3:45 am
Libera.chat IRC: [com]buster
Location: On the balcony, where I can actually keep 1½m distance
Contact:

Re: General Questions about basic kernel code

Post by Combuster »

the part I don't get is how a jump sets the value of a register? All you do is jump there so the value of IP gets changed to wherever you're jumping.
And IP is not a register? it's just treated completely separately because it makes no sense to do logic or arithmetic on it. For the same reason you can't modify CS directly because the jump you get requires that the source and destination are exactly a multiple of 16 + the instruction length away, which is a nonsensical constraint to have to apply.
"Certainly avoid yourself. He is a newbie and might not realize it. You'll hate his code deeply a few years down the road." - Sortie
[ My OS ] [ VDisk/SFS ]
User avatar
Owen
Member
Member
Posts: 1700
Joined: Fri Jun 13, 2008 3:21 pm
Location: Cambridge, United Kingdom
Contact:

Re: General Questions about basic kernel code

Post by Owen »

bewing wrote:2. Moving the address to eax, and then calling eax does precisely the same thing as calling the address directly. The person who wrote the code just didn't know what they were doing.
I can't decide whether they were
  • Stupid, or
  • Lazy
The second could apply if they were just modifying code which dispatched the interrupt from the assembly side of things. i.e. their code would have looked something like

Code: Select all

    push eax
    mov eax, [esp + irq_number_offset]
    mov eax, [irq_dispatch_table + 4 * eax]
    call eax
    pop eax
(I also can't remember if call has sufficient addressing modes to roll the latter load into...)
Post Reply