As a part of my hobby OS project, I'm working on a loader that will load an ELF into memory and run.
Having Linux as a backgroud platform for testing, I came up with the following test implementation of a console:
#![no_std]
#![no_main]
#![feature(asm)]
#![feature(const_fn_trait_bound)]
use core::fmt::Write;
#[no_mangle]
fn _start() {
let msg = "hi there!\n";
let mut console = console::Console::new(platform::StdOut);
console.write_str(&msg);
write!(&mut console, "hi");
platform::exit(0);
}
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}
mod platform {
use crate::console::ConsoleDevice;
pub fn exit(code: isize) -> ! {
unsafe {
asm!(
"syscall",
in("rax") 60,
in("rdi") code,
options(nomem, nostack, noreturn)
)
}
}
pub fn write(s: &str) -> usize {
let chars_written: usize;
unsafe {
asm!(
"syscall",
inlateout("rax") 1_usize => chars_written,
in("rdx") s.len(),
in("rdi") 1,
in("rsi") s.as_ptr(),
options(nostack)
)
}
chars_written
}
pub struct StdOut;
impl ConsoleDevice for StdOut {
fn write(&mut self, s: &str) {
write(s);
}
}
}
mod console {
use core::fmt::{Result, Write};
pub trait ConsoleDevice {
fn write(&mut self, s: &str);
}
pub struct Console<T: ConsoleDevice> {
device: T
}
impl<T: ConsoleDevice> Console<T> {
pub const fn new(device: T) -> Self {
Console { device }
}
}
impl<T: ConsoleDevice> Write for Console<T> {
fn write_str(&mut self, s: &str) -> Result {
self.device.write(s);
Ok(())
}
}
}
The problem is write_str
works fine, but write!
macro segfaults. I use panic = "abort" and build as
RUSTFLAGS="-C link-arg=-nostartfiles" cargo build`.
Release profile compiles fine. For the debug profile, the linker complains about not being able to find references to memcpy
and memset
. This is the issue I believe, but I remember reading about using write!
in no_std
bare-metal apps, and that it works fine.
What am I doing wrong and how to properly resolve the issue? Am I to implement memcpy
and memset
myself?