How can movw %ax, %ds page fault?

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.
Post Reply
User avatar
restingwitchface
Posts: 20
Joined: Sat Nov 02, 2024 2:58 pm
GitHub: https://github.com/NyxNeptune

How can movw %ax, %ds page fault?

Post by restingwitchface »

After setting up paging and enabling long mode (PAE, LME, loading the PML4T & setting PG), I load the GDT and do a far jump to the higher half. My code is as follows:

Code: Select all

.section .multiboot.data, "aw"
/* GDT */
null_descriptor:
.quad 0
code_descriptor:
.quad 0x00AF9B000000FFFF
data_descriptor:
.quad 0x009F93000000FFFF
gdtr:
.long 23
.short 0x8000

/* ... */

.section .multiboot.text, "a"
.global _start
.type _start, @function

.code32

/* ... */

	/* Setup a 64-bit GDT, then far return to the higher half */
	lgdt gdtr
	movw $16, %ax
	movw %ax, %ds
	movw %ax, %es
	movw %ax, %fs
	movw %ax, %gs
	movw %ax, %ss
	ljmp $0x08, $trampoline
I've verified with the QEMU monitor's info tlb command that the identity mapping of 0x200000 - 0x400000 (which includes where the image is loaded) works correctly. However, there is still a page fault:

Code: Select all

check_exception old: 0xffffffff new 0xe
     0: v=0e e=0000 i=0 cpl=0 IP=0010:00000000002011ac pc=00000000002011ac SP=0018:0000000000202100 CR2=0000000080000010
EAX=80000010 EBX=00000000 ECX=c0000080 EDX=00000000
ESI=00000000 EDI=00000000 EBP=00000000 ESP=00202100
EIP=002011ac EFL=00000086 [--S--P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0010 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS64-busy
GDT=     0000000080000000 00000017
IDT=     0000000000000000 00000000
CR0=80000011 CR2=0000000080000010 CR3=0000000000203000 CR4=00000020
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=00210000 CCD=80000011 CCO=LOGICL
EFER=0000000000000500
The instruction at 0x002011ac is movw %ax, %ds (although disassembly shows this as mov %eax, %ds), The value in CR2 doesn't appear to be an address, and nowhere in the long mode enabling code do we move to CR2. I'd expect a failure to read the GDT to cause the fault at the lgdt instruction, which is 0x002011a1, and some kind of permissions error to raise a GPF, not a page fault. What's going on here?
she/her, please.

Don't flirt with me.
Oderint dum metuant.
GPG fingerprints:

Code: Select all

b16598566a5124946d87279fbc2d2dd299af811e (encryption)
b504ba54c5c164257156f53a32409b59258453e0 (signing)
Octocontrabass
Member
Member
Posts: 5805
Joined: Mon Mar 25, 2013 7:01 pm

Re: How can movw %ax, %ds page fault?

Post by Octocontrabass »

restingwitchface wrote: Tue May 13, 2025 11:27 amI'd expect a failure to read the GDT to cause the fault at the lgdt instruction,
Your expectation is incorrect. The LGDT instruction doesn't involve accessing the GDT at all.
restingwitchface wrote: Tue May 13, 2025 11:27 amWhat's going on here?
You're loading a segment selector into the visible portion of a segment register. When you do this, the CPU will also load the corresponding segment descriptor from a descriptor table (GDT or LDT) into the hidden portion of the segment register. According to your QEMU log, the page fault is occurring at the address of the segment descriptor:

Code: Select all

GDT=     0000000080000000 00000017
CR0=80000011 CR2=0000000080000010
Which means either you've forgotten to set up page tables to map your GDT, or your GDTR is wrong. Since you don't recognize the address in CR2, I'd guess the problem is your GDTR.
restingwitchface wrote: Tue May 13, 2025 11:27 am

Code: Select all

gdtr:
.long 23
.short 0x8000
...So, the limit should be 16 bits, the base should be 64 bits, and the base should be the address of your GDT instead of 0x8000.
User avatar
saliccio
Posts: 1
Joined: Sat May 10, 2025 3:38 pm

Re: How can movw %ax, %ds page fault?

Post by saliccio »

Hi,

lgdt loads the GDTR (base + limit) from memory. It does not access the GDT entries themselves. When you later move into ds, CPU needs to load the descriptor for selector 0x10. GDT Base address must be mapped as well.

I believe your GDT descriptor is incorrect as well. Base address must be 64 bits while the size must be 16 bits. That might be the reason for seeing an invalid page fault address in CR2.
Developer of complementOS, a hobby OS project:

https://github.com/saliccio/complementOS
User avatar
restingwitchface
Posts: 20
Joined: Sat Nov 02, 2024 2:58 pm
GitHub: https://github.com/NyxNeptune

Re: How can movw %ax, %ds page fault?

Post by restingwitchface »

Oh, I see. Thanks. I carelessly copied specifically the GDT over from somewhere else...
she/her, please.

Don't flirt with me.
Oderint dum metuant.
GPG fingerprints:

Code: Select all

b16598566a5124946d87279fbc2d2dd299af811e (encryption)
b504ba54c5c164257156f53a32409b59258453e0 (signing)
User avatar
bellezzasolo
Member
Member
Posts: 118
Joined: Sun Feb 20, 2011 2:01 pm

Re: How can movw %ax, %ds page fault?

Post by bellezzasolo »

restingwitchface wrote: Tue May 13, 2025 12:25 pm Oh, I see. Thanks. I carelessly copied specifically the GDT over from somewhere else...
I think I've had similar with GDTs in the past and long mode. For something that isn't even really used, just legacy cruft that's necessary to get running, they feel like no end of grief sometimes...
Whoever said you can't do OS development on Windows?
https://github.com/ChaiSoft/ChaiOS
Post Reply