GDT reset code seems to have no effect

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
User avatar
Schol-R-LEA
Member
Member
Posts: 1925
Joined: Fri Oct 27, 2006 9:42 am
Location: Athens, GA, USA

GDT reset code seems to have no effect

Post by Schol-R-LEA »

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: Select all

/* 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: Select all

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.
Octocontrabass
Member
Member
Posts: 5563
Joined: Mon Mar 25, 2013 7:01 pm

Re: GDT reset code seems to have no effect

Post by Octocontrabass »

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.
User avatar
Schol-R-LEA
Member
Member
Posts: 1925
Joined: Fri Oct 27, 2006 9:42 am
Location: Athens, GA, USA

Re: GDT reset code seems to have no effect

Post by Schol-R-LEA »

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.
Octocontrabass
Member
Member
Posts: 5563
Joined: Mon Mar 25, 2013 7:01 pm

Re: GDT reset code seems to have no effect

Post by Octocontrabass »

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.
User avatar
Schol-R-LEA
Member
Member
Posts: 1925
Joined: Fri Oct 27, 2006 9:42 am
Location: Athens, GA, USA

Re: GDT reset code seems to have no effect

Post by Schol-R-LEA »

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: Select all

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: Select all

   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.
Octocontrabass
Member
Member
Posts: 5563
Joined: Mon Mar 25, 2013 7:01 pm

Re: GDT reset code seems to have no effect

Post by Octocontrabass »

Code: Select all

        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?
User avatar
Schol-R-LEA
Member
Member
Posts: 1925
Joined: Fri Oct 27, 2006 9:42 am
Location: Athens, GA, USA

Re: GDT reset code seems to have no effect

Post by Schol-R-LEA »

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: Select all

    // 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.
User avatar
Schol-R-LEA
Member
Member
Posts: 1925
Joined: Fri Oct 27, 2006 9:42 am
Location: Athens, GA, USA

Re: GDT reset code seems to have no effect

Post by Schol-R-LEA »

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.
nullplan
Member
Member
Posts: 1790
Joined: Wed Aug 30, 2017 8:24 am

Re: GDT reset code seems to have no effect

Post by nullplan »

For the record, the out-of-line solution would have been

Code: Select all

set_gdt:
  mov eax, [esp+4]
  lgdt [eax]
  ret
A possibly more elegant solution would have been:

Code: Select all

;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: Select all

set_gdt(gdt, sizeof gdt);
Carpe diem!
User avatar
Schol-R-LEA
Member
Member
Posts: 1925
Joined: Fri Oct 27, 2006 9:42 am
Location: Athens, GA, USA

Re: GDT reset code seems to have no effect

Post by Schol-R-LEA »

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.
User avatar
Schol-R-LEA
Member
Member
Posts: 1925
Joined: Fri Oct 27, 2006 9:42 am
Location: Athens, GA, USA

Re: GDT reset code seems to have no effect

Post by Schol-R-LEA »

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.
Octocontrabass
Member
Member
Posts: 5563
Joined: Mon Mar 25, 2013 7:01 pm

Re: GDT reset code seems to have no effect

Post by Octocontrabass »

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.
User avatar
Schol-R-LEA
Member
Member
Posts: 1925
Joined: Fri Oct 27, 2006 9:42 am
Location: Athens, GA, USA

Re: GDT reset code seems to have no effect

Post by Schol-R-LEA »

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.
User avatar
Schol-R-LEA
Member
Member
Posts: 1925
Joined: Fri Oct 27, 2006 9:42 am
Location: Athens, GA, USA

Re: GDT reset code seems to have no effect

Post by Schol-R-LEA »

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: Select all

#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.
Octocontrabass
Member
Member
Posts: 5563
Joined: Mon Mar 25, 2013 7:01 pm

Re: GDT reset code seems to have no effect

Post by Octocontrabass »

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.)
Post Reply