Deutsch   English   Français   Italiano  
<vpv19u$b1b$1@reader1.panix.com>

View for Bookmarking (what is this?)
Look up another Usenet article

Path: ...!weretis.net!feeder9.news.weretis.net!panix!.POSTED.spitfire.i.gajendra.net!not-for-mail
From: cross@spitfire.i.gajendra.net (Dan Cross)
Newsgroups: comp.lang.c,alt.os.development
Subject: Re: [OSDev] How to switch to long mode in x86 CPUs?
Followup-To: alt.os.development
Date: Sat, 1 Mar 2025 13:15:42 -0000 (UTC)
Organization: PANIX Public Access Internet and UNIX, NYC
Message-ID: <vpv19u$b1b$1@reader1.panix.com>
References: <871pvje5yq.fsf@onesoftnet.eu.org>
Injection-Date: Sat, 1 Mar 2025 13:15:42 -0000 (UTC)
Injection-Info: reader1.panix.com; posting-host="spitfire.i.gajendra.net:166.84.136.80";
	logging-data="11307"; mail-complaints-to="abuse@panix.com"
X-Newsreader: trn 4.0-test77 (Sep 1, 2010)
Originator: cross@spitfire.i.gajendra.net (Dan Cross)
Bytes: 7783
Lines: 204

[Note: Followup-To: set to alt.os.development]

In article <871pvje5yq.fsf@onesoftnet.eu.org>,
Ar Rakin  <rakinar2@onesoftnet.eu.org> wrote:
>Hello there,
>
>I am trying to develop my own, simple operating system to learn more
>about how kernels work and low level stuff like that.  However, I am
>stuck at setting up paging while switching long mode (64-bit protected
>mode) in x86 processors.

As has been mentioned, comp.lang.c is not the appropriate place
to ask this.  I have set the `Followup-To:` header to
alt.os.development, and am cross-posting this post to that
newsgroup.

>The assembly code I currently have:
>
>#define PG_START 0x000000000

Just to be clear, this means that you have decide to make your
_virtual_ address space starts at absolute address 0?  What
address do you link your kernel at?

>#define MSR_EFER 0xc0000080
>
>.section .bss, "aw", @nobits
>.align 4096
>pml4_tbl:
>  .skip 4096
>pdpt_tbl:
>  .skip 4096

This is fine, but note that, instead of using code to fill in
your page tables, you could simply define them here with the
expected entries, as they are very simple.

>.text
>.globl _mboot_start
>_mboot_start:
>  /* GRUB executes this code in 32-bit protected mode. */
>
>  /* Write (pdpt_tbl | 0x3) to the first 8 bytes of pml4_tbl */
>  movl $pdpt_tbl, %eax
>  orl $0x3, %eax
>  movl $pml4_tbl, %edi
>  movl %eax, (%edi)

Note that this sequence implicitly assumes that you are starting
with an identity mapping between between the physical and
virtual address spaces.  In particular, when you
`movl $pdpt_tbl, %eax` you are copying whatever address the
linker assigns to `$pdpt_tbl` into %eax (the low 32-bits of it
anyway, though the assembler would probably sqwuak at you if
didn't fit into a 32 bit immediate).  Page table entries must
refer to physical addresses, so if you've arranged for the
linker to use some base address other than 0 for your kernel,
you've got to take care to account for an offset here.

>  xorl %eax, %eax
>  movl %eax, 4(%edi)

Note that, as you're doing this in assembly, the upper bits in
the table are already filled with zeros, so there's no need for
the `xorl %eax, %eax; movl %eax 4(%edi)` sequence.

>  movl $pdpt_tbl, %edi
>  movl $PG_START, %eax
>  /* 0x83 = 0b10000011; flags: present, writable, upervisor-only,
>     1GB huge page */
>  movl $0x83, (%edi) 
>  movl %eax, 4(%edi)

This looks correct.  Your page tables will now map a single
gigabyte of address space starting at (virtual) address zero to
physical address 0, and nothing else.  To be clear, is that what
you want?  When coming out of protected mode, I generally try to
map the whole 32-bit address space; that is, all 4 GiB.

>  /* Enable Physical Address Extension (PAE) */
>  movl %cr4, %eax
>  btsl $5, %eax
>  movl %eax, %cr4
>
>  /* Load the address of the PML4 table into %cr3 */
>  movl $pml4_tbl, %edi
>  movl %edi, %cr3

Note that the same caveat about physical addresses of the PML4
apply here as applied to the PDPT above.

>  /* Enable long mode */
>  movl $MSR_EFER, %ecx
>  rdmsr
>  btsl $8, %eax
>  wrmsr
>
>  /* Enable paging */
>  movl %cr0, %eax
>  btsl $31, %eax
>  movl %eax, %cr0

So immediately after executing this instruction, the processor
will be executing with paging enabled.  This means that the very
next instruction (the ljmp below here) _must_ be mapped at the
address in %rip.  If not, you will fault.

Your post suggests that you fault on the ljmp, but this may not
be why.  Any easy test would be to add a `jmp .` here and see if
that faults in the QEMU monitor.

If you do not fault here, then your page tables are ok (at least
so far), and your problem lies elsewhere.  See below.

>  /* Jump to 64-bit code */
>  ljmpl $0x08, $long_mode_entry

Have you set up a GDT with an entry for a 64-bit code segment
by this point?  It doesn't look like it.  My guess is that that
is the source of your fault; note that the multiboot1 spec says
that you must set up a GDT and should not rely on the one that
it set up to get you into 32-bit protected mode.  Certainly
there is no guarantee that there's a 64-bit code segment at
offset 0x8 in whatever table it set up.

My guess is that this is the source of your problem.

>.loop:
>  hlt
>  jmp .loop

I would delete this loop; you can't ever really hit it: either
the long jump will succeed and skip over it, or it will fault.

>long_mode_entry:
>  .code64
>  xorw %ax, %ax
>  movw %ax, %ds
>  movw %ax, %es
>  movw %ax, %fs
>  movw %ax, %gs
>  movw %ax, %ss
>  
>  callq kmain

You should probably give yourself a stack before calling C.
What's in `%rsp` here?  My guess is that this would fault if you
got here: the `callq` will push the address of the next
instruction onto the stack, but since you haven't set one up,
%rsp is whatever it is (either it's reset value, 0, or something
random set up by multiboot).  Suppose it's 0; then the call will
attempt to push %rip to -8; that's fine (the processor will
happily wrap around to 0xfffffffffffffff8) but you definitely
don't have anything mapped there, so you'll get a fault.

In 32-bit mode, this will wrap around to 0xfffffff8, which is
in the active stack segment; there may be RAM there, which is
why it doesn't fault in protected mode.

Since you haven't set up an IDT yet, any fault this early will
be a triple fault.

Or perhaps multiboot left you a stack by chance.  The multiboot1
specification explicitly says that the OS should set up it's own
stack, though, so I wouldn't rely on that.

Try adding a page for more for stack space in your BSS, and set
%rsp to point to the top of that region, e.g., back in bss:

stack:
	.skip 4096

....

	movabsq $(stack + 4096), %rsp
	callq kmain

>  callq kabort

I take it you never get here.  :-)

>I am not sure what is wrong, but when I run my kernel in
>qemu-system-x86_64, it causes a triple fault when trying to jump to the
========== REMAINDER OF ARTICLE TRUNCATED ==========