OSDev.org

The Place to Start for Operating System Developers
It is currently Thu Mar 28, 2024 5:59 am

All times are UTC - 6 hours




Post new topic Reply to topic  [ 8 posts ] 
Author Message
 Post subject: [Rust] Push multiboot header in long mode
PostPosted: Wed Oct 06, 2021 2:15 pm 
Offline

Joined: Wed Oct 06, 2021 12:40 pm
Posts: 3
Hi,

After having made a working C kernel (text mode), I'm following this tutorial (https://intermezzos.github.io/book/firs ... world.html) to get a basic rust kernel working. It all ran fine and printed hello world to screen.

I want to switch to VGA graphics mode and as I understand this is done by passing the multiboot header info to the kernel main, then getting the framebuffer address. (see https://github.com/29jm/SnowflakeOS/blo ... oot/boot.S , line 98)

The problem is, my kernel panics when trying to parse the header (because "panic occurred" is printed to screen).

I've determined this is because the mb header pointer from asm is invalid: https://docs.rs/multiboot2/0.12.2/multi ... tml#safety

Pretty much no knowledge of assembly other than that you push and pop to pass function parameters, and that there are differences between 32-bit and 64-bit mode. I think what causes the issue is my "long_mode_start" function. I read that in 64-bit mode you should push r*x instead of e*x and Nasm doesn't allow me to push e*x in 64-bit mode, which is why I changed it.

my rust main:
Code:
#![no_main]
#![feature(lang_items)]
#![no_std]

use multiboot2::load;
use core::panic::PanicInfo;

// was trying to see what the issue is.
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    if let Some(s) = _info.payload().downcast_ref::<&str>() {
        vga::print_something(s);
    } else {
        vga::print_something("panic occurred");
    }
    loop {}
}

mod vga;
#[no_mangle]
pub extern fn kmain(multiboot_info_ptr: u64, magic: u64) -> ! {
    let boot_info = unsafe { load(multiboot_info_ptr as usize).unwrap() };

    vga::print_something("Hello World");
    loop { }
}


boot.asm (NASM syntax)
Code:
extern kmain
global start

section .text
bits 32
start:
    ; Point the first entry of the level 4 page table to the first entry in the
    ; p3 table
    mov eax, p3_table
    or eax, 0b11
    mov dword [p4_table + 0], eax

   ; Point the first entry of the level 3 page table to the first entry in the
    ; p2 table
    mov eax, p2_table
    or eax, 0b11
    mov dword [p3_table + 0], eax

    ; point each page table level two entry to a page
    mov ecx, 0         ; counter variable

.map_p2_table:
    mov eax, 0x200000  ; 2MiB
    mul ecx
    or eax, 0b10000011
    mov [p2_table + ecx * 8], eax

    inc ecx
    cmp ecx, 512
    jne .map_p2_table
   ;;;;;;;
       ; move page table address to cr3
    mov eax, p4_table
    mov cr3, eax

    ; enable PAE
    mov eax, cr4
    or eax, 1 << 5
    mov cr4, eax

    ; set the long mode bit
    mov ecx, 0xC0000080
    rdmsr
    or eax, 1 << 8
    wrmsr

    ; enable paging
    mov eax, cr0
    or eax, 1 << 31
    or eax, 1 << 16
    mov cr0, eax
    lgdt [gdt64.pointer]

   ; update selectors
   mov ax, gdt64.data
   mov ss, ax
   mov ds, ax
   mov es, ax
      ;   .data
;str:
;   .string "Abcdef"
   ; jump to long mode!
   jmp gdt64.code:long_mode_start

section .bss

align 4096

p4_table:
    resb 4096
p3_table:
    resb 4096
p2_table:
    resb 4096

section .rodata
gdt64:
    dq 0
.code: equ $ - gdt64
    dq (1<<44) | (1<<47) | (1<<41) | (1<<43) | (1<<53)
.data: equ $ - gdt64
    dq (1<<44) | (1<<47) | (1<<41)
.pointer:
    dw .pointer - gdt64 - 1
    dq gdt64

section .text
bits 64
long_mode_start:
    push dword rax
    push dword rbx

    call kmain


(vga stuff is based on https://os.phil-opp.com/vga-text-mode/)


Top
 Profile  
 
 Post subject: Re: [Rust] Push multiboot header in long mode
PostPosted: Wed Oct 06, 2021 11:41 pm 
Offline
Member
Member
User avatar

Joined: Sat Mar 31, 2012 3:07 am
Posts: 4591
Location: Chichester, UK
Function parameters are passed in registers in 64-bit mode, not via the stack. (Not 100% true - check the appropriate ABI.)


Top
 Profile  
 
 Post subject: Re: [Rust] Push multiboot header in long mode
PostPosted: Thu Oct 07, 2021 12:07 am 
Offline
Member
Member

Joined: Wed Mar 30, 2011 12:31 am
Posts: 676
iansjack wrote:
Function parameters are passed in registers in 64-bit mode, not via the stack. (Not 100% true - check the appropriate ABI.)

Yep, at least the first handful - the number and choice of registers before falling back to stack slots varies by ABI.

Barring any target overrides you may have made in setting up your toolchain, your Rust compiler is probably implementing the SysV calling convention same as a default target for GCC would, so your first two arguments should be in rdi and rsi.

_________________
toaruos on github | toaruos.org | gitlab | twitter | bim - a text editor


Top
 Profile  
 
 Post subject: Re: [Rust] Push multiboot header in long mode
PostPosted: Thu Oct 07, 2021 9:38 am 
Offline
Member
Member

Joined: Wed Aug 30, 2017 8:24 am
Posts: 1593
You know, that code can be massively improved. The page tables are essentially constant, so you can just initialize them at assemble time:
Code:
section .rodata
align 4096
p4_table: dq p3_table + 3
times 511 dq 0
p3_table: dq p2_table + 3
times 511 dq 0
p2_table:
%assign val 0x83
%rep 512
dq val
%assign val val + (1<<21)
%endrep

And then the whole part from your start label to the line with the dozen semi-colons can be removed.

_________________
Carpe diem!


Top
 Profile  
 
 Post subject: Re: [Rust] Push multiboot header in long mode
PostPosted: Thu Oct 07, 2021 9:39 am 
Offline

Joined: Wed Oct 06, 2021 12:40 pm
Posts: 3
klange wrote:
iansjack wrote:
Function parameters are passed in registers in 64-bit mode, not via the stack. (Not 100% true - check the appropriate ABI.)

Yep, at least the first handful - the number and choice of registers before falling back to stack slots varies by ABI.

Barring any target overrides you may have made in setting up your toolchain, your Rust compiler is probably implementing the SysV calling convention same as a default target for GCC would, so your first two arguments should be in rdi and rsi.


Alright, so instead of pushing to stack I should store the multiboot header and magic number in rdi and rsi respectively? What I'm trying to do is pass the address of the multiboot header (first parameter).

Have modified the assembly:
Code:
section .text
bits 64
long_mode_start:
    mov rdi, [ebx]
    call kmain


and rust kmain:
Code:
pub extern fn kmain(multiboot_info_ptr: u64) -> ! {
    let boot_info = unsafe { load(multiboot_info_ptr as usize).unwrap() };

    vga::print_something("Hello World");
    loop { }
}


It's still panicking.

I think ebx is where the multiboot2 header is stored and eax is where the magic number is (see section "3.5 EFI amd64 machine state with boot services enabled" in https://www.gnu.org/software/grub/manua ... iboot.html )

My concern is that in .start and .map_p2_table a lot is being done with "eax" and I don't know why it's using eax if that's where a magic number is. Hence why I removed the magic number parameter for now. (mov rsi, [eax] causes reboot repeatedly)

Since you mention target overrides, here's my target.json and I am using toolchain "stable-x86_64-apple-darwin"
Code:
{
    "arch": "x86_64",
    "cpu": "x86-64",
    "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
    "llvm-target": "x86_64-unknown-none-gnu",
    "linker-flavor": "gcc",
    "no-compiler-rt": true,
    "os": "intermezzos",
    "target-endian": "little",
    "target-pointer-width": "64",
    "target-c-int-width": "32",
    "features": "-mmx,-fxsr,-sse,-sse2,+soft-float",
    "disable-redzone": true,
    "eliminate-frame-pointer": false
}


Top
 Profile  
 
 Post subject: Re: [Rust] Push multiboot header in long mode
PostPosted: Thu Oct 07, 2021 10:00 am 
Offline
Member
Member

Joined: Wed Aug 30, 2017 8:24 am
Posts: 1593
prolificpenguin wrote:
I think ebx is where the multiboot2 header is stored and eax is where the magic number is[...]
Well, yes, that too. Obviously you are clobbering EAX. For EBX, you need to zero-extend it and move it into RDI. Thankfully this is easy because all instructions targeting a 32-bit register implicitly zero out the upper half. So it's just
Code:
long_mode_start:
  mov edi, ebx
  call kmain
If that doesn't work, your compiler may be using the Microsoft calling convention, in which case you need to replace EDI with EDX.

_________________
Carpe diem!


Top
 Profile  
 
 Post subject: Re: [Rust] Push multiboot header in long mode
PostPosted: Thu Oct 07, 2021 10:29 am 
Offline

Joined: Wed Oct 06, 2021 12:40 pm
Posts: 3
nullplan wrote:
You know, that code can be massively improved. The page tables are essentially constant, so you can just initialize them at assemble time:
Code:
section .rodata
align 4096
p4_table: dq p3_table + 3
times 511 dq 0
p3_table: dq p2_table + 3
times 511 dq 0
p2_table:
%assign val 0x83
%rep 512
dq val
%assign val val + (1<<21)
%endrep

And then the whole part from your start label to the line with the dozen semi-colons can be removed.


Thanks! Will keep this in mind.


Top
 Profile  
 
 Post subject: Re: [Rust] Push multiboot header in long mode
PostPosted: Thu Oct 07, 2021 3:57 pm 
Offline
Member
Member
User avatar

Joined: Mon Jan 15, 2018 2:27 pm
Posts: 201
Rust allows to mark functions (and some other symbols) as extern "C". Functions marked this way are guaranteed to follow C ABI of the target (SysV ABI most likely for Unix like targets). So if you mark your Rust entry point as extern "C", you can pass any argument to it the same way you would pass it to a C function (for example: mov rdi, rbx would pass PHYSICAL address of multiboot structure to your Rust entry point).

There is also another way of passing multiboot info structure to Rust code. You can pass it in memory. I do it this way because of reasons and it works perfectly fine. Actually, one of the very first things my OS does is to store multiboot info structure pointer somewhere in memory. I do that while still in 32 bit mode, only ofter initializing .bss, so I don't have to worry about keeping ebx intact anymore. Something like this:
Code:
.section .text

    ...
    # store multiboot info pointer
    mov [_mBootInfoPtr - KERNEL_BASE], ebx
    ...

.section .bss

...
.global _mBootInfoPtr
.comm _mBootInfoPtr, 8
...


Ignore KERNEL_BASE thing. It's only there to deal with high half kernel relocations.

Then in Rust:

Code:
    extern "C" { static _mBootInfoPtr: *const multiboot::Info; }


Now you can acceess multiboot info structure as you would any other structure pointed by raw pointer.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 8 posts ] 

All times are UTC - 6 hours


Who is online

Users browsing this forum: Bing [Bot], SemrushBot [Bot] and 61 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group