OSDev.org

The Place to Start for Operating System Developers
It is currently Thu Mar 28, 2024 5:02 am

All times are UTC - 6 hours




Post new topic Reply to topic  [ 24 posts ]  Go to page 1, 2  Next
Author Message
 Post subject: GDT reset code seems to have no effect
PostPosted: Sun Jan 22, 2023 9:05 pm 
Offline
Member
Member
User avatar

Joined: Fri Oct 27, 2006 9:42 am
Posts: 1925
Location: Athens, GA, USA
I have made a number of changes to Ordo, most notably to the linker script - I now have the object files named explicitly in the script, rather than relying on the Makefile, and have added separate sections for several of the system elements, such as the GDT, IDT, and page tables. I also stubbed in a stack_base section, for when I start remapping things in the new page tables.

Code:
/* The bootloader will look at this image and start execution at the symbol
   designated at the entry point. */
ENTRY(kstart)

INPUT(
   obj/kernel.o
   obj/terminal.o
   obj/mem.o
   obj/idt.o
   obj/gdt.o
    obj/paging.o
   obj/acpi.o
)

OUTPUT(kernel.elf)

OUTPUT_FORMAT(elf32-i386)
STARTUP(obj/kstart.o)

/* Tell where the various sections of the object files will be put in the final
   kernel image. */
SECTIONS
{
   /* Begin putting sections at the higher half. */
   . = 0xC0000000;

   /* the .text section. */
   .text : ALIGN(4K)
   {
      *(.text)
   }

   /* Read-only data. */
   .rodata : ALIGN(4K)
   {
      *(.rodata)
   }

   /* Read-write data (initialized) */
   .data : ALIGN(4K)
   {
      *(.data)
   }

   /* Read-write data (uninitialized) and stack */
   .bss : ALIGN(4K)
   {
      *(COMMON)
      *(.bss)
   }

    /* hardware tables */
    . = 0xC0100000;

   .gdt BLOCK(64K) :
   {
      gdt = .;
      . = . + 64K;
   }

   .tss BLOCK(4K) : ALIGN(4K)
   {
      default_tss = .;
      . = . + 4K;
   }

    .idt BLOCK(4K) : ALIGN(4K)
   {
      idt = .;
      . = . + 4K;
   }

    .paging BLOCK(4K) : ALIGN(4K)
   {
        page_directory = .;
        . = . + 4K;
      page_tables = .;
      . = . + (1K * 4K);
   }

    /* set up the stack */
    .stack : ALIGN(4M)
   {
      stack_base = .;
        *(.stack)
   }
}


I expect that I've made at least a few mistakes in this, but as things stand, I have the IDT pointing to the linker-defined section and it works correctly.

When I went to replace the stub GDT from my bootloader with one constructed by and accessible to the kernel, it worked exactly as before.

Code:
void reset_gdt()
{
    struct GDT_R gdt_r = { sizeof(union GDT_Entry) * MAX_GDT_ENTRIES, gdt };

    union GDT_Entry *entry = gdt;
    // set the null GDT entry
    entry->raw_entry = 0;
    // system code selector
    set_gdt_entry(++entry, 0x0fffff, 0, true, true, RING_0);
    // system data selector
    set_gdt_entry(++entry, 0x0fffff, 0, false, true, RING_0);
    // system TSS selector
    set_gdt_entry(entry, (uint32_t) default_tss, sizeof(struct TSS), false, true, RING_0);
    (entry++)->fields.access.non_sys = false;
    // user code selector
    set_gdt_entry(++entry, 0x08ffff, 0, true, true, RING_3);
    // user data selector
    set_gdt_entry(++entry, 0x08ffff, 0, false, true, RING_3);

    // set the GDT register
    __asm__ __volatile__ (
        "lgdt %0"
        :
        : "m" (gdt_r));
}


The fact that it worked on the first go seemed suspicious to me, though, so I checked what happened when I commented out the null descriptor - and nothing changed. This leads me to think that reset_gdt() is not, in fact, changing the GDTR.

I am not certain how to check this further. I know that there is a SGDT instruction, and I suppose I could write a function to check the GDTR's values using that, but I am not certain just what that would accomplish.

Can anyone see anything I've missed?

_________________
Rev. First Speaker Schol-R-LEA;2 LCF ELF JAM POEE KoR KCO PPWMTF
Ordo OS Project
Lisp programmers tend to seem very odd to outsiders, just like anyone else who has had a religious experience they can't quite explain to others.


Top
 Profile  
 
 Post subject: Re: GDT reset code seems to have no effect
PostPosted: Sun Jan 22, 2023 9:22 pm 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5099
The GDTR only tells the CPU where to look when it needs to load a segment descriptor. You won't see the effect of your new GDT until you load a segment.


Top
 Profile  
 
 Post subject: Re: GDT reset code seems to have no effect
PostPosted: Sun Jan 22, 2023 9:41 pm 
Offline
Member
Member
User avatar

Joined: Fri Oct 27, 2006 9:42 am
Posts: 1925
Location: Athens, GA, USA
Octocontrabass wrote:
The GDTR only tells the CPU where to look when it needs to load a segment descriptor. You won't see the effect of your new GDT until you load a segment.


Thank you, I didn't realize that. Is this something I am likely to need to concern myself with at this point, then? I don't expect that a segment descriptor would be accessed at this stage of the kernel, at least not until I try to launch a user-mode process. Is there any way to test the state of the segment descriptors, and is it even worth being concerned about?

Except for the specific location of the GDT - and the location of the default TSS descriptor's base - the new table should be almost exactly the same as the previous one; any faults in it are probably shared by the bootloader version.

_________________
Rev. First Speaker Schol-R-LEA;2 LCF ELF JAM POEE KoR KCO PPWMTF
Ordo OS Project
Lisp programmers tend to seem very odd to outsiders, just like anyone else who has had a religious experience they can't quite explain to others.


Top
 Profile  
 
 Post subject: Re: GDT reset code seems to have no effect
PostPosted: Sun Jan 22, 2023 9:53 pm 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5099
Aside from fast system calls, every event or instruction that loads a segment selector will also load a descriptor, even if the selector doesn't change. That includes interrupts and the IRET instruction.


Top
 Profile  
 
 Post subject: Re: GDT reset code seems to have no effect
PostPosted: Wed Jan 25, 2023 2:06 am 
Offline
Member
Member
User avatar

Joined: Fri Oct 27, 2006 9:42 am
Posts: 1925
Location: Athens, GA, USA
Returning to this topic, after identifying fixing some issues with my GDT entry structure and the code for populating them, and I have re-written my GDTR loading code as a stand-alone assembly language function, and add a function for reloading the segment selectors (based on the function given in the GDT Tutorial).

Code:
void reset_gdt()
{
    struct GDT_R gdt_r = { GDT_SIZE, gdt };

    union GDT_Entry *entry = gdt;

    // first, clear the whole table
    memset(entry, 0, GDT_SIZE);

    // set the null GDT entry = redundant, but still worth doing
    entry->raw_entry = 0;
    // system code descriptor
    set_gdt_entry(++entry, 0x000fffff, 0, true, true, RING_0);
    // system data descriptor
    set_gdt_entry(++entry, 0x000fffff, 0, false, true, RING_0);

    // system TSS descriptor
    kprintf("\nDefault TSS location %p\n", &default_tss);
    set_gdt_entry(++entry, sizeof(struct TSS), (uint32_t) &default_tss, true, false, RING_0);
    entry->fields.access.accessed = true;
    entry->fields.access.non_sys = false;
    entry->fields.limit_and_flags.bits_32 = false;
    entry->fields.limit_and_flags.granularity = false;

    // user code descriptor
    set_gdt_entry(++entry, 0x0008ffff, 0, true, true, RING_3);
    // user data descriptor
    set_gdt_entry(++entry, 0x0008ffff, 0, false, true, RING_3);

    // set the GDT register
    set_gdt(&gdt_r);
    // reload_segments();
}


and
Code:
   global set_gdt
   global reload_segments


; set_gdt(gdt_r) takes a pointer to the GDTR structure
set_gdt:
        lgdt [esp + 4]
        ret


%define system_code_selector (1 << 3)
%define system_data_selector (2 << 3)

; code for reload_segments() taken from
; https://wiki.osdev.org/GDT_Tutorial
reload_segments:
        ; Reload CS register containing code selector
        ; both CS and DS should be zero at this point

        jmp system_code_selector:.reload_cs
    .reload_cs:
        ; Reload data segment registers
        mov ax, system_data_selector
        mov ds, ax
        mov es, ax
        mov fs, ax
        mov gs, ax
        mov ss, ax
        ret


However, calling reload_segments() causes the system to triple fault. I'm not sure whether the problem is with set_gdt() or reload_segments().

_________________
Rev. First Speaker Schol-R-LEA;2 LCF ELF JAM POEE KoR KCO PPWMTF
Ordo OS Project
Lisp programmers tend to seem very odd to outsiders, just like anyone else who has had a religious experience they can't quite explain to others.


Top
 Profile  
 
 Post subject: Re: GDT reset code seems to have no effect
PostPosted: Wed Jan 25, 2023 2:17 am 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5099
Code:
        lgdt [esp + 4]

You're loading the function parameter into the GDTR, but the function parameter is the address of the struct instead of the contents of the struct.

Is there any particular reason you decided not to use inline assembly for this instruction?


Top
 Profile  
 
 Post subject: Re: GDT reset code seems to have no effect
PostPosted: Wed Jan 25, 2023 9:25 am 
Offline
Member
Member
User avatar

Joined: Fri Oct 27, 2006 9:42 am
Posts: 1925
Location: Athens, GA, USA
I originally had been using inline assembly for it, and seemed to be having a problem with it in that regards. The previous version for this specific part was
Code:
    // set the GDT register
    __asm__ __volatile__ (
        "lgdt %0"
        :
        : "m" (gdt_r));

_________________
Rev. First Speaker Schol-R-LEA;2 LCF ELF JAM POEE KoR KCO PPWMTF
Ordo OS Project
Lisp programmers tend to seem very odd to outsiders, just like anyone else who has had a religious experience they can't quite explain to others.


Top
 Profile  
 
 Post subject: Re: GDT reset code seems to have no effect
PostPosted: Wed Jan 25, 2023 9:37 am 
Offline
Member
Member
User avatar

Joined: Fri Oct 27, 2006 9:42 am
Posts: 1925
Location: Athens, GA, USA
OK, so that was in fact the problem. I switched back to the inline version for the LGDT instruction, and now it seems to work.

_________________
Rev. First Speaker Schol-R-LEA;2 LCF ELF JAM POEE KoR KCO PPWMTF
Ordo OS Project
Lisp programmers tend to seem very odd to outsiders, just like anyone else who has had a religious experience they can't quite explain to others.


Top
 Profile  
 
 Post subject: Re: GDT reset code seems to have no effect
PostPosted: Wed Jan 25, 2023 10:02 am 
Offline
Member
Member

Joined: Wed Aug 30, 2017 8:24 am
Posts: 1593
For the record, the out-of-line solution would have been
Code:
set_gdt:
  mov eax, [esp+4]
  lgdt [eax]
  ret
A possibly more elegant solution would have been:
Code:
;void set_gdt(void *gdt, size_t gdt_size)
set_gdt:
  mov eax, [esp+4]
  mov cx, [esp+8]
  sub esp, 8
  dec cx
  mov eax, [esp+4]
  mov cx, [esp+2]
  lgdt [esp+2]
  add esp, 8
  ret
Then you don't even need to define a GDTR structure in C but can just call it as
Code:
set_gdt(gdt, sizeof gdt);

_________________
Carpe diem!


Top
 Profile  
 
 Post subject: Re: GDT reset code seems to have no effect
PostPosted: Wed Jan 25, 2023 10:11 am 
Offline
Member
Member
User avatar

Joined: Fri Oct 27, 2006 9:42 am
Posts: 1925
Location: Athens, GA, USA
OK, so a new development has arisen. I have a section of data passed from my bootloader which contains, among other things, the memory map. At the beginning of the kernel, I move that data to a new location using my version of memcpy() to a fixed location set in the linker script. The GDT and IDT are also in spaces assigned using the linker script.

I have a debugging printout function, print_mmap() which I routinely call to show that it is what I expect it to be. Now, when I call this function before I call reset_gdt() with the new data location, it works fine. It also works correctly if I pass it the old data location regardless of where I call it. However, if I call it with the new data location after calling reset_gdt(), the data is missing - it shows an entry count of zero.

I've confirmed that there is no overlap between the boot data table and the GDT, so I don't think it is a case of the boot data table being overwritten.

Furthermore, if I set up the IDT and enable interrupts following the call to reset_gdt(), the OS immediately triple faults.

While I am not certain, this seems to indicate that something isn't quite right with either the system data descriptor, or with how I am resetting the DS and/or the ES selectors. I have a function for printing the GDT entries, and the descriptors have the values I expected them to have, leading me to think that the problem is in reload_segments().

_________________
Rev. First Speaker Schol-R-LEA;2 LCF ELF JAM POEE KoR KCO PPWMTF
Ordo OS Project
Lisp programmers tend to seem very odd to outsiders, just like anyone else who has had a religious experience they can't quite explain to others.


Top
 Profile  
 
 Post subject: Re: GDT reset code seems to have no effect
PostPosted: Wed Jan 25, 2023 11:08 am 
Offline
Member
Member
User avatar

Joined: Fri Oct 27, 2006 9:42 am
Posts: 1925
Location: Athens, GA, USA
Quick update: If I move the boot data after the GDT is reset, it prints out correctly.

Image

_________________
Rev. First Speaker Schol-R-LEA;2 LCF ELF JAM POEE KoR KCO PPWMTF
Ordo OS Project
Lisp programmers tend to seem very odd to outsiders, just like anyone else who has had a religious experience they can't quite explain to others.


Top
 Profile  
 
 Post subject: Re: GDT reset code seems to have no effect
PostPosted: Wed Jan 25, 2023 11:49 am 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5099
Sounds like memory corruption. I thought it might be a problem with memset() or memcpy(), and they are wrong (the return value should be the first argument), but I don't think that explains what's happening.

You can dump the boot data memory using either the QEMU console or GDB to see if the contents after the apparent memory corruption resembles data that belongs somewhere else.

Page tables are also in memory, and can also be responsible for data that belongs in one location appearing elsewhere. You can check them with "info tlb" and "info mem" in the QEMU console.

This is again unrelated, but is there any particular reason you're setting the ring 3 segment limits to 0x8FFFFFFF? That's an unusual value, and paging already enforces privilege separation.


Top
 Profile  
 
 Post subject: Re: GDT reset code seems to have no effect
PostPosted: Wed Jan 25, 2023 12:21 pm 
Offline
Member
Member
User avatar

Joined: Fri Oct 27, 2006 9:42 am
Posts: 1925
Location: Athens, GA, USA
Octocontrabass wrote:
Sounds like memory corruption. I thought it might be a problem with memset() or memcpy(), and they are wrong (the return value should be the first argument),


OK, I fixed that, that was careless of me.

Octocontrabass wrote:
You can dump the boot data memory using either the QEMU console or GDB to see if the contents after the apparent memory corruption resembles data that belongs somewhere else.

Page tables are also in memory, and can also be responsible for data that belongs in one location appearing elsewhere. You can check them with "info tlb" and "info mem" in the QEMU console.


I haven't really use the QEMU console, but I will take a look into it.

Octocontrabass wrote:
This is again unrelated, but is there any particular reason you're setting the ring 3 segment limits to 0x8FFFFFFF? That's an unusual value, and paging already enforces privilege separation.


I am not certain where I got that value, and never knew the reason for it myself - I suspect it was supposed to be 0xBFFFFFFF, though as you say it shouldn't make a difference once paging is enabled. It probably was an error which I copied from some now-forgotten source.

_________________
Rev. First Speaker Schol-R-LEA;2 LCF ELF JAM POEE KoR KCO PPWMTF
Ordo OS Project
Lisp programmers tend to seem very odd to outsiders, just like anyone else who has had a religious experience they can't quite explain to others.


Top
 Profile  
 
 Post subject: Re: GDT reset code seems to have no effect
PostPosted: Wed Jan 25, 2023 4:37 pm 
Offline
Member
Member
User avatar

Joined: Fri Oct 27, 2006 9:42 am
Posts: 1925
Location: Athens, GA, USA
I was having some trouble getting the memory dump from the QEMU monitor, so figuring I would need some sort of dump utility in the OS anyway, I wrote something which gives a conventional hex dump. It is a bit ugly, more due to the limitations of my kprintf() (I'll need to address that later), but it works.

I was able to confirm that the GDT is overwriting the boot data, but now I am at a bit of a loose end in determining how and where - It may require me to simply walk through the entire thing and see where it is happening, and even then I can't be certain if I will find it.

I may need to take a step back for a bit to think this through.
Image

Code:
#define LINE_SIZE 16

void dump_line(void* src, uint8_t size)
{
    uint8_t* p = (uint8_t *) src;

    for (uint8_t i= 0, *b = p; i < size; i++, b++)
    {
        if (i == (LINE_SIZE / 2))
        {
            kprintf("\t");
        }
        if (*b < 0x10)
        {
            kprintf("0");
        }
        kprintf("%x ", *b);
    }
    if (size < LINE_SIZE)
    {
        for (uint8_t i = 0; i < (LINE_SIZE - size); i++)
        {
            kprintf("   ");
        }
        if (size < (LINE_SIZE / 2))
        {
            kprintf("\t");
        }
    }

    kprintf("\t");

    for (uint8_t i = 0, *b = p; i < size; i++, b++)
    {
        if (i == LINE_SIZE / 2)
        {
            kprintc('-', GREEN, BLACK);
        }
        kprintf("%c", (*b < 'a' ? '.' : *b));
    }

    kprintf("\n");
}


void memdump(void* src, uint32_t size)
{
    uint8_t* p = (uint8_t *) src;
    uint32_t remainder = size % LINE_SIZE;
    uint32_t lines = size / LINE_SIZE;


    for (uint32_t i = 0; i < lines; i++, p += LINE_SIZE)
    {
        dump_line(p, LINE_SIZE);
    }

    p += LINE_SIZE;
    if (remainder != 0)
    {
        dump_line(p, remainder);
    }
}

_________________
Rev. First Speaker Schol-R-LEA;2 LCF ELF JAM POEE KoR KCO PPWMTF
Ordo OS Project
Lisp programmers tend to seem very odd to outsiders, just like anyone else who has had a religious experience they can't quite explain to others.


Top
 Profile  
 
 Post subject: Re: GDT reset code seems to have no effect
PostPosted: Wed Jan 25, 2023 4:46 pm 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5099
Schol-R-LEA wrote:
I was able to confirm that the GDT is overwriting the boot data, but now I am at a bit of a loose end in determining how and where

Setting a watchpoint in GDB should make this very easy, assuming it isn't page table corruption.

If it is page table corruption, you should see it using "info tlb" and "info mem" in the QEMU monitor. (Oh, did I mention "-monitor stdio"? That option might make it easier to control the QEMU monitor.)


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 24 posts ]  Go to page 1, 2  Next

All times are UTC - 6 hours


Who is online

Users browsing this forum: Bing [Bot], DotBot [Bot] and 67 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group