Higher Half x86 Bare Bones (Backup): Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content added Content deleted
(Nope, it's perfectly acceptable and encourage to use the freestanding compiler headers.)
(add maintenance template)
 
(21 intermediate revisions by 10 users not shown)
Line 1: Line 1:
{{Disputed|Talk:Higher_Half_x86_Bare_Bones}}
{{FirstPerson}}
{{TutorialExplain}}
{{BeginnersWarning}}
{{You}}
{{Rating|2}}
{{Rating|2}}


Here is some sample code for a kernel that is loaded by GRUB and is mapped in the upper half of memory. In this case, the kernel is loaded at 1MB in the physical address space (0x00100000), but is mapped at 3GB + 1MB in the virtual address space (0xC0100000). It is recommended that you have a firm grasp of the contents within the [[Bare bones|Bare bones tutorial]] before attempting this.
Here is some sample code for a kernel that is loaded by GRUB and is mapped in the upper half of memory. In this case, the kernel is loaded at 1MB in the physical address space (0x00100000), but is mapped at 3GB + 1MB in the virtual address space (0xC0100000). It is recommended that you have a firm grasp of the contents within the [[Bare bones|Bare bones tutorial]] and [[Paging]] before attempting this.

'''WARNING'''<br />
This code doesn't work with the default nasm installed on Ubuntu < 8.10 (0.99.) You need to download and compile the latest version of nasm.


==loader.asm==
==loader.asm==
This piece of code is taking over control from the Multiboot bootloader. It sets up a page directory with page table entries that identity map the first 4MB, and also map the first 4MB to virtual 3GB. After setting up paging, it unmaps the identity mapping so that the kernel is entirely in the higher half and jumps into the kernel proper.
This piece of code is taking over control from the Multiboot bootloader. It sets up a page directory with page table entries that identity map the first 4MB, and also map the first 4MB to virtual 3GB. After setting up paging, it unmaps the identity mapping so that the kernel is entirely in the higher half and jumps into the kernel proper.


<source lang="asm">
<syntaxhighlight lang="asm">
global _loader ; Make entry point visible to linker.
global _loader ; Make entry point visible to linker.
extern _main ; _main is defined elsewhere
extern _main ; _main is defined elsewhere
Line 104: Line 106:
align 32
align 32
stack:
stack:
resb STACKSIZE ; reserve 16k stack on a quadword boundary
resb STACKSIZE ; reserve 16k stack on a uint64_t boundary
</syntaxhighlight>
</source>

==kernel.c==
This is not exactly your average int main(). Most notably, you do not have any library stuff available. Welcome to kernel land.

<source lang="c">
void _main( void* mbd, unsigned int magic )
{
//write your kernel here
}
</source>


==linker.ld==
==linker.ld==
This is a little trickier than it was for the [[Bare bones|C kernel tutorial]], since you need to distinguish between virtual addresses (which will be in the higher half) and load addresses, which GRUB needs to decide where to put your kernel.
This is a little trickier than it was for the [[Bare bones|C kernel tutorial]], since you need to distinguish between virtual addresses (which will be in the higher half) and load addresses, which GRUB needs to decide where to put your kernel.


<syntaxhighlight lang="c">
<pre>
ENTRY(loader)
ENTRY(loader)
OUTPUT_FORMAT(elf32-i386)
OUTPUT_FORMAT(elf32-i386)
Line 146: Line 138:
}
}
}
}
</syntaxhighlight>
</pre>


Note that we use loader (and not _loader) as our entry point. This is due to the fact that _loader's address is approximately 0xC0100000, if we try to set our eip to that address it will not find our loader function.
Note that we use loader (and not _loader) as our entry point. This is due to the fact that _loader's address is approximately 0xC0100000, if we try to set our eip to that address it will not find our loader function.
Also note our entry point is not being converted to physical address. GRUB does this conversion when calculating starting value of EIP, and if you attempt to do the translation, you may get your execution when you don't want it or get "entry point isn't in a segment" error.


==Troubleshooting==
==kernel.c==
;Grub <tt><nowiki>Error 7: Loading below 1MB is not supported</nowiki></tt>
:Many older versions of GRUB ignore the 'physical address hint' of the [[ELF]] sections. Try to make sure you are using at least GRUB v 0.94.


Using the [[Bare bones#Writing_a_kernel_in_C|kernel.c code]] from the original bare bones tutorial will work fine, with one small change. On the fourth line in terminal_initialize(), change:
;Grub <tt>Error 28: Selected item cannot fit into memory</tt>
: Make sure your linker script is correct. See the example above. GRUB is attempting to load your kernel at the ''physical'' address of 0xC0000000, instead of 0x100000. The AT commands in the linker script fix this.


<syntaxhighlight lang="c">
;It doesn't work with BOCHS
terminal_buffer = (uint16_t*) 0xB8000;
:Some prebuilt versions of Bochs do not support 4MB pages. You may need to build your own with the following options:
</syntaxhighlight>
<pre>
--enable-4meg-pages support 4Megabyte pages extensions
--enable-pae support Physical Address Extensions
</pre>


to
;I got a page fault (#PF) when accessing my GRUB multiboot info structure

: The address passed by loader.s is physical, you have to make it virtual to add your virtual base to it.
<syntaxhighlight lang="c">
: For example, in your loader.s:
terminal_buffer = (uint16_t*) 0xC00B8000;
<source lang="asm">
</syntaxhighlight>

This accomodates for the kernel's new offset into higher-half space. Any direct memory access by the kernel at this point should take place with respect to this offset where necessary.

==Troubleshooting==
;I got a page fault (#PF) when accessing my GRUB Multiboot info structure
The address passed by loader.s is physical, you have to make it virtual to add your virtual base to it. For example, in your loader.s:
<syntaxhighlight lang="asm">
add ebx, KERNEL_VIRTUAL_BASE ; make the address virtual
add ebx, KERNEL_VIRTUAL_BASE ; make the address virtual
push ebx ; push it on the stack for _main()
push ebx ; push it on the stack for _main()
</syntaxhighlight>
</source>
Don't forget to make all addresses pointing to memory locations in the Multiboot info structure also virtual.

: Don't forget to make all addresses pointing to memory locations in the multiboot info structure also virtual.


==See Also==
==See Also==
===Articles===
===Articles===
*[[Bare bones|Bare bones]]
*[[Bare bones|Bare bones]]
*An [[User:Glauxosdever/Higher_Half_x86_Bare_Bones|alternate]] higher half Bare Bones.


[[Category:Bare bones tutorials|Higher Half bare bones]]
[[Category:Bare bones tutorials|Higher Half bare bones]]

Latest revision as of 08:16, 17 June 2024

The factual accuracy of this article is disputed.
Please see the relevant discussion on the talk page.
This article refers to its readers or editors using I, my, we or us.
It should be edited to be in an encyclopedic tone.
This tutorial is not explained enough.
This tutorial needs to explain what the code does as tutorials are not just copy paste. You can help out by editing this page to include more context to what the code does.
WAIT! Have you read Getting Started, Beginner Mistakes, and some of the related OS theory?
This article refers to its readers using you in an unencyclopedic manner.
It should be edited to be in an encyclopedic tone.
Difficulty level

Medium

Here is some sample code for a kernel that is loaded by GRUB and is mapped in the upper half of memory. In this case, the kernel is loaded at 1MB in the physical address space (0x00100000), but is mapped at 3GB + 1MB in the virtual address space (0xC0100000). It is recommended that you have a firm grasp of the contents within the Bare bones tutorial and Paging before attempting this.

loader.asm

This piece of code is taking over control from the Multiboot bootloader. It sets up a page directory with page table entries that identity map the first 4MB, and also map the first 4MB to virtual 3GB. After setting up paging, it unmaps the identity mapping so that the kernel is entirely in the higher half and jumps into the kernel proper.

global _loader                          ; Make entry point visible to linker.
extern _main                            ; _main is defined elsewhere

; setting up the Multiboot header - see GRUB docs for details
MODULEALIGN equ  1<<0             ; align loaded modules on page boundaries
MEMINFO     equ  1<<1             ; provide memory map
FLAGS       equ  MODULEALIGN | MEMINFO  ; this is the Multiboot 'flag' field
MAGIC       equ    0x1BADB002     ; 'magic number' lets bootloader find the header
CHECKSUM    equ -(MAGIC + FLAGS)  ; checksum required

; This is the virtual base address of kernel space. It must be used to convert virtual
; addresses into physical addresses until paging is enabled. Note that this is not
; the virtual address where the kernel image itself is loaded -- just the amount that must
; be subtracted from a virtual address to get a physical address.
KERNEL_VIRTUAL_BASE equ 0xC0000000                  ; 3GB
KERNEL_PAGE_NUMBER equ (KERNEL_VIRTUAL_BASE >> 22)  ; Page directory index of kernel's 4MB PTE.


section .data
align 0x1000
BootPageDirectory:
    ; This page directory entry identity-maps the first 4MB of the 32-bit physical address space.
    ; All bits are clear except the following:
    ; bit 7: PS The kernel page is 4MB.
    ; bit 1: RW The kernel page is read/write.
    ; bit 0: P  The kernel page is present.
    ; This entry must be here -- otherwise the kernel will crash immediately after paging is
    ; enabled because it can't fetch the next instruction! It's ok to unmap this page later.
    dd 0x00000083
    times (KERNEL_PAGE_NUMBER - 1) dd 0                 ; Pages before kernel space.
    ; This page directory entry defines a 4MB page containing the kernel.
    dd 0x00000083
    times (1024 - KERNEL_PAGE_NUMBER - 1) dd 0  ; Pages after the kernel image.


section .text
align 4
MultiBootHeader:
    dd MAGIC
    dd FLAGS
    dd CHECKSUM

; reserve initial kernel stack space -- that's 16k.
STACKSIZE equ 0x4000

; setting up entry point for linker
loader equ (_loader - 0xC0000000)
global loader

_loader:
    ; NOTE: Until paging is set up, the code must be position-independent and use physical
    ; addresses, not virtual ones!
    mov ecx, (BootPageDirectory - KERNEL_VIRTUAL_BASE)
    mov cr3, ecx                                        ; Load Page Directory Base Register.

    mov ecx, cr4
    or ecx, 0x00000010                          ; Set PSE bit in CR4 to enable 4MB pages.
    mov cr4, ecx

    mov ecx, cr0
    or ecx, 0x80000000                          ; Set PG bit in CR0 to enable paging.
    mov cr0, ecx

    ; Start fetching instructions in kernel space.
    ; Since eip at this point holds the physical address of this command (approximately 0x00100000)
    ; we need to do a long jump to the correct virtual address of StartInHigherHalf which is
    ; approximately 0xC0100000.
    lea ecx, [StartInHigherHalf]
    jmp ecx                                                     ; NOTE: Must be absolute jump!

StartInHigherHalf:
    ; Unmap the identity-mapped first 4MB of physical address space. It should not be needed
    ; anymore.
    mov dword [BootPageDirectory], 0
    invlpg [0]

    ; NOTE: From now on, paging should be enabled. The first 4MB of physical address space is
    ; mapped starting at KERNEL_VIRTUAL_BASE. Everything is linked to this address, so no more
    ; position-independent code or funny business with virtual-to-physical address translation
    ; should be necessary. We now have a higher-half kernel.
    mov esp, stack+STACKSIZE           ; set up the stack
    push eax                           ; pass Multiboot magic number

    ; pass Multiboot info structure -- WARNING: This is a physical address and may not be
    ; in the first 4MB!
    push ebx

    call  _main                  ; call kernel proper
    hlt                          ; halt machine should kernel return


section .bss
align 32
stack:
    resb STACKSIZE      ; reserve 16k stack on a uint64_t boundary

linker.ld

This is a little trickier than it was for the C kernel tutorial, since you need to distinguish between virtual addresses (which will be in the higher half) and load addresses, which GRUB needs to decide where to put your kernel.

ENTRY(loader)
OUTPUT_FORMAT(elf32-i386)

SECTIONS {
   /* The kernel will live at 3GB + 1MB in the virtual
      address space, which will be mapped to 1MB in the
      physical address space. */
   . = 0xC0100000;

   .text : AT(ADDR(.text) - 0xC0000000) {
       *(.text)
       *(.rodata*)
   }

   .data ALIGN (0x1000) : AT(ADDR(.data) - 0xC0000000) {
       *(.data)
   }

   .bss : AT(ADDR(.bss) - 0xC0000000) {
       _sbss = .;
       *(COMMON)
       *(.bss)
       _ebss = .;
   }
}

Note that we use loader (and not _loader) as our entry point. This is due to the fact that _loader's address is approximately 0xC0100000, if we try to set our eip to that address it will not find our loader function. Also note our entry point is not being converted to physical address. GRUB does this conversion when calculating starting value of EIP, and if you attempt to do the translation, you may get your execution when you don't want it or get "entry point isn't in a segment" error.

kernel.c

Using the kernel.c code from the original bare bones tutorial will work fine, with one small change. On the fourth line in terminal_initialize(), change:

terminal_buffer = (uint16_t*) 0xB8000;

to

terminal_buffer = (uint16_t*) 0xC00B8000;

This accomodates for the kernel's new offset into higher-half space. Any direct memory access by the kernel at this point should take place with respect to this offset where necessary.

Troubleshooting

I got a page fault (#PF) when accessing my GRUB Multiboot info structure

The address passed by loader.s is physical, you have to make it virtual to add your virtual base to it. For example, in your loader.s:

   add ebx, KERNEL_VIRTUAL_BASE ; make the address virtual
   push ebx ; push it on the stack for _main()

Don't forget to make all addresses pointing to memory locations in the Multiboot info structure also virtual.

See Also

Articles