From: rakinar2@onesoftnet.eu.org   
      
   On 3/1/25 7:15 PM, Dan Cross wrote:   
   > [Note: Followup-To: set to alt.os.development]   
   >   
   > In article <871pvje5yq.fsf@onesoftnet.eu.org>,   
   > Ar Rakin 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?   
   >   
      
   The page start address was originally 0x1000000, due to the triple fault   
   I was trying to use different addresses. Sorry for the confusion. And   
   the kernel is loaded by GRUB, and apparently that means the kernel gets   
   loaded at 0x100000.   
      
   >> #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.   
      
   I tried doing something like this:   
      
   pml4_tbl:   
    .quad pdpt_tbl | 0x3   
      
   But the assembler complained that I can't use the '|' operator. Is   
   there any other way I could define entries directly here?   
      
   >> .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.   
      
   Yes, I have the following linker script:   
      
   ENTRY(_start)   
      
   SECTIONS {   
    . = 1M; /* Load the kernel at 1MB (GRUB loads it here) */   
      
    .multiboot2_header : {   
    *(.multiboot2_header)   
    } :headers   
      
    .text ALIGN(4K) : {   
    *(.text)   
    } :text   
      
    .rodata ALIGN(4K) : {   
    *(.rodata)   
    } :rodata   
      
    .data ALIGN(4K) : {   
    *(.data)   
    } :data   
      
    .bss ALIGN(4K) : {   
    *(.bss)   
    } :bss   
   }   
      
   PHDRS {   
    headers PT_LOAD;   
    text PT_LOAD FLAGS(5);   
    rodata PT_LOAD;   
    data PT_LOAD;   
    bss PT_LOAD;   
   }   
      
   >> 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.   
      
   Makes sense!   
      
   >> 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.   
      
   Now that I have mentioned the page start address (0x1000000), yeah, I   
   wanted to create 1:1 identity page mapping for simplicity.   
      
   >> /* 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.   
      
   Noted.   
      
   >> /* 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.   
      
   Correct, thanks! I forgot to load a GDT.   
      
   >> .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.   
      
   Noted.   
      
   >> 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.   
   >   
      
   [continued in next message]   
      
   --- SoupGate-Win32 v1.05   
    * Origin: you cannot sedate... all the things you hate (1:229/2)   
|