Accessing GDT when using GRUB

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
mrjbom
Member
Member
Posts: 322
Joined: Sun Jul 21, 2019 7:34 am

Re: Accessing GDT when using GRUB

Post by mrjbom »

PeterX wrote:
mrjbom wrote:
PeterX wrote: If you really need to access GDT in C, don't you the struct you mentioed, but use an ordinary pointer to the beginning of the GDT.
How do I find this pointer? Is it "gdt:" (asm code) or something else?
Yes, type something like:

Code: Select all

global gdt
gdt:
Yes, I've already done that. Now I think I'll be able to manage the GDT.

As far as I understand, for each userland process I have to configure a separate LDT, will I be able to make system calls in LDT?
nexos
Member
Member
Posts: 1081
Joined: Tue Feb 18, 2020 3:29 pm
Libera.chat IRC: nexos

Re: Accessing GDT when using GRUB

Post by nexos »

The LDT is obselete. Use address spaces with paging instead.
"How did you do this?"
"It's very simple — you read the protocol and write the code." - Bill Joy
Projects: NexNix | libnex | nnpkg
User avatar
mrjbom
Member
Member
Posts: 322
Joined: Sun Jul 21, 2019 7:34 am

Re: Accessing GDT when using GRUB

Post by mrjbom »

PeterX wrote:Like this:

Code: Select all

 ;global descriptor table
  gdt:

  gdt_null:
  dq 0

  gdt_code:
  dw 0FFFFh
  dw 0

  db 0
  db 10011010b
  db 11001111b
  db 0

  gdt_data:
  dw 0FFFFh
  dw 0

  db 0
  db 10010010b
  db 11001111b
  db 0

gdt_ring3_code:
dw ...
...

gdt_ring3_data:
dw ...
...
  gdt_end:
I wrote this code:

Code: Select all

global gdt
start:
  cli
  mov esp, stack_top
  push ebx
  push eax

  jmp load_gdt

  gdt:
  ;global descriptor table (kernel)
  gdt_kernel:

  gdt_null:
  dq 0

  gdt_kernel_code:
  ;Kernel Space code (Offset: 0x8 bytes)
	dw 0FFFFh 			;limit low
	dw 0 				    ;base low
	db 0 				    ;base middle
	db 10011010b		;access - Notice that bits 5 and 6 (privilege level) are 0 for Ring 0
	db 11001111b		;granularity
	db 0 		        ;base high

  gdt_kernel_data:
  ;Kernel Space data (Offset: 16 (0x10) bytes
	dw 0FFFFh 			;limit low
	dw 0 				    ;base low
	db 0 				    ;base middle
	db 10010010b 		;access - Notice that bits 5 and 6 (privilege level) are 0 for Ring 0
	db 11001111b 		;granularity
	db 0				    ;base high
  gdt_kernel_end:

  gdt_userspace:
  gdt_userspace_code:
  ;User Space code (Offset: 24 (0x18) bytes)
	dw 0FFFFh 			;limit low
	dw 0 				    ;base low
	db 0 				    ;base middle
	db 11111010b 	  ;access - Notice that bits 5 and 6 (privilege level) are 11b for Ring 3
	db 11001111b 		;granularity
	db 0 				    ;base high
 gdt_userspace_data:
  ;User Space data (Offset: 32 (0x20) bytes
	dw 0FFFFh 			;limit low (Same as code)10:56 AM 7/8/2007
	dw 0 				    ;base low
	db 0 				    ;base middle
	db 11110010b 		;access - Notice that bits 5 and 6 (privilege level) are 11b for Ring 3
	db 11001111b 		;granularity
	db 0				    ;base high
  gdt_userspace_end:
  gdt_end:

  gdt_kernel_desc:
  dw gdt_kernel_end - gdt_kernel - 1
  dd gdt_kernel

  load_gdt:
  lgdt [gdt_kernel_desc]  ;load GDT
  mov ax, 0x10
  mov ds, ax
  mov es, ax
  mov fs, ax
  mov gs, ax
  mov ss, ax
  jmp 0x08:.setcs
  .setcs:
  
  call kmain
  .hltloop:
  hlt
  jmp .hltloop
By default, I load GDT only for the kernel (see load_gdt:).

When I want to run a userland task, I will need to load gdt_userland.
Is that right?
nullplan
Member
Member
Posts: 1916
Joined: Wed Aug 30, 2017 8:24 am

Re: Accessing GDT when using GRUB

Post by nullplan »

mrjbom wrote:By default, I load GDT only for the kernel (see load_gdt:).

When I want to run a userland task, I will need to load gdt_userland.
Is that right?
You load kernel segments. The GDT stays the same. Before you switch to user mode, load user segments into DS, ES, FS, and GS (though I do not know the significance of user data segments). SS and CS are set by the IRET into user space. You should probably put the code to switch segments into the code that enters and leaves the kernel, i.e. the interrupt/syscall code.
Carpe diem!
PeterX
Member
Member
Posts: 590
Joined: Fri Nov 22, 2019 5:46 am

Re: Accessing GDT when using GRUB

Post by PeterX »

mrjbom wrote:When I want to run a userland task, I will need to load gdt_userland.
Is that right?
Your code looks right for me. (Though I haven't checked every GDT bit.)

Yes, you need to get gdt_userland's offset (0x18 and 0x20) into CS and DS.

Someone wrote in this forum that you need to apply a trick to get into ring 3. Unfortunately I can't find that thread.

@nexos: Are you sure LDT is obsolete?

Greetings
Peter
User avatar
mrjbom
Member
Member
Posts: 322
Joined: Sun Jul 21, 2019 7:34 am

Re: Accessing GDT when using GRUB

Post by mrjbom »

PeterX wrote:
mrjbom wrote:When I want to run a userland task, I will need to load gdt_userland.
Is that right?
Your code looks right for me. (Though I haven't checked every GDT bit.)
If you want to use this code, check the bits, and if you find an error, please write me.
I'm not quite sure about the bits.

Thank you so much for your help.
I will try to read something to solve my problem(get to Ring 3 and back).
PeterX
Member
Member
Posts: 590
Joined: Fri Nov 22, 2019 5:46 am

Re: Accessing GDT when using GRUB

Post by PeterX »

Octocontrabass
Member
Member
Posts: 5885
Joined: Mon Mar 25, 2013 7:01 pm

Re: Accessing GDT when using GRUB

Post by Octocontrabass »

nullplan wrote:Before you switch to user mode, load user segments into DS, ES, FS, and GS (though I do not know the significance of user data segments).
IRET will set DS/ES/FS/GS to the null segment if you don't load them with user segments. FS and GS are typically used as pointers to per-thread data; the details depend on the ABI.
PeterX wrote:Are you sure LDT is obsolete?
I'm sure. There's usually only one per-thread descriptor that needs to be updated, so it's faster to have the per-thread descriptor in the (per-CPU) GDT and update it there instead of dealing with a LDT.
nexos
Member
Member
Posts: 1081
Joined: Tue Feb 18, 2020 3:29 pm
Libera.chat IRC: nexos

Re: Accessing GDT when using GRUB

Post by nexos »

In my OS, I ignore segmentation as much as possible. I have 5 segments in the GDT, which are kernel code, kernel data, user code, user data (which are flat segements), and one TSS. The LDT has been obsolete for 35 years now (since the release of the 386 and paging). Modern compiler are not aware of segmentation. I love OS development, but there is one word that makes me cringe, and it is segmentation.
"How did you do this?"
"It's very simple — you read the protocol and write the code." - Bill Joy
Projects: NexNix | libnex | nnpkg
nullplan
Member
Member
Posts: 1916
Joined: Wed Aug 30, 2017 8:24 am

Re: Accessing GDT when using GRUB

Post by nullplan »

nexos wrote: I have 5 segments in the GDT, which are kernel code, kernel data, user code, user data (which are flat segements), and one TSS.
If I were you, I would add an FS and GS segment, switching out the base address when switching tasks, and provide a syscall to set at least the base address (limit isn't really needed). This allows user space to implement a very cheap thread pointer. In 64-bit mode, the actual FS and GS selector doesn't matter, either, there you have FS and GS base as MSR. Actually, you could probably just set FS and GS to their respective selectors at the start, and then just reload them when switching tasks. Unlike DS and ES, FS and GS are not used normally by the C compiler, so you can just leave them whatever they were when switching to kernel mode.
Carpe diem!
Octocontrabass
Member
Member
Posts: 5885
Joined: Mon Mar 25, 2013 7:01 pm

Re: Accessing GDT when using GRUB

Post by Octocontrabass »

nullplan wrote:FS and GS are not used normally by the C compiler,
They are used to implement thread-local variables, following the appropriate ABI.
nullplan
Member
Member
Posts: 1916
Joined: Wed Aug 30, 2017 8:24 am

Re: Accessing GDT when using GRUB

Post by nullplan »

Octocontrabass wrote:They are used to implement thread-local variables, following the appropriate ABI.
Yes, but that is typically not supported (and not reasonably supportable) in a kernel. You need to use special syntax for this (namely "__thread"), so if you avoid that, the code will never use FS or GS. Now, in long mode, I use GS to store the CPU pointer (pointer to CPU-local stuff), but then, I can get that pointer loaded quickly with "swapgs". Which is an option not available to the OP, since their base ISA does not include "swapgs".

However, I just realized that with the proposed scheme, they will need a CPU-local GDT, anyway, since multiple CPUs will run different processes with different FS/GS base addresses. So you might as well add a kernel data segment with a base address for CPU-local data.
Carpe diem!
Post Reply