[Rust] Push multiboot header in long mode

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
prolificpenguin
Posts: 3
Joined: Wed Oct 06, 2021 12:40 pm

[Rust] Push multiboot header in long mode

Post by prolificpenguin »

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: Select all

#![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: Select all

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/)
User avatar
iansjack
Member
Member
Posts: 4703
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

Re: [Rust] Push multiboot header in long mode

Post by iansjack »

Function parameters are passed in registers in 64-bit mode, not via the stack. (Not 100% true - check the appropriate ABI.)
klange
Member
Member
Posts: 679
Joined: Wed Mar 30, 2011 12:31 am
Libera.chat IRC: klange
Discord: klange

Re: [Rust] Push multiboot header in long mode

Post by klange »

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.
nullplan
Member
Member
Posts: 1790
Joined: Wed Aug 30, 2017 8:24 am

Re: [Rust] Push multiboot header in long mode

Post by nullplan »

You know, that code can be massively improved. The page tables are essentially constant, so you can just initialize them at assemble time:

Code: Select all

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!
prolificpenguin
Posts: 3
Joined: Wed Oct 06, 2021 12:40 pm

Re: [Rust] Push multiboot header in long mode

Post by prolificpenguin »

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: Select all

section .text
bits 64
long_mode_start:
    mov rdi, [ebx]
    call kmain
and rust kmain:

Code: Select all

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: Select all

{
    "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
}
nullplan
Member
Member
Posts: 1790
Joined: Wed Aug 30, 2017 8:24 am

Re: [Rust] Push multiboot header in long mode

Post by nullplan »

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: Select all

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!
prolificpenguin
Posts: 3
Joined: Wed Oct 06, 2021 12:40 pm

Re: [Rust] Push multiboot header in long mode

Post by prolificpenguin »

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: Select all

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.
User avatar
pvc
Member
Member
Posts: 201
Joined: Mon Jan 15, 2018 2:27 pm

Re: [Rust] Push multiboot header in long mode

Post by pvc »

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: Select all

.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: Select all

    extern "C" { static _mBootInfoPtr: *const multiboot::Info; }
Now you can acceess multiboot info structure as you would any other structure pointed by raw pointer.
Post Reply