How do I allocate memory on the heap? no_std no_main no libc windows_sys

Hi, I'm trying to write a rust program without std and without libc using only the windows api. I copied some code from GitHub - blaubart69/rust_win32_no_std_no_main: a little win32 statically linked sample using no_std and no_main and adapted it to use the windows_sys crate instead of winapi. When I don't use the heap it prints to stdout but when I allocate a Vec::new() on the heap it crashes immediately.

Does anybody have any idea how to get this to work?

main.rs:

#![no_std] // don't link the Rust standard library
#![no_main]
#![feature(lang_items)]
#![feature(alloc_error_handler)]

mod stdout;
pub mod win32alloc;
extern crate alloc;

use crate::win32alloc::Heapalloc;

use alloc::vec::Vec;

use core::panic::PanicInfo;

/// This function is called on panic.
#[panic_handler]
#[cfg(not(test))] //Stop Analyzer from complaining
fn panic(_info: &PanicInfo) -> ! {
    loop {}
}

#[lang = "eh_personality"]
#[no_mangle]
#[cfg(not(test))]
extern "C" fn eh_personality() {}

#[alloc_error_handler]
#[cfg(not(test))]
fn alloc_error_handler(layout: alloc::alloc::Layout) -> ! {
    //panic!("allocation error: {:?}", layout)
    loop {}
}

#[global_allocator]
static NO_STD_ALLOCATOR: Heapalloc = win32alloc::Heapalloc;

#[no_mangle] // don't mangle the name of this function
pub extern "C" fn _start() -> ! {
    stdout::write_stdout("TeStOz");
    let v: Vec<i8> = Vec::new();
    stdout::write_stdout("testoz2");
    loop {}
}

win32alloc.rs:

use alloc::alloc::{GlobalAlloc, Layout};
use core::ffi::c_void;
use windows_sys::Win32::System::Memory::{
    GetProcessHeap, HeapAlloc, HeapFree, HeapReAlloc, HEAP_ZERO_MEMORY,
};

pub struct Heapalloc;

unsafe impl GlobalAlloc for Heapalloc {
    unsafe fn alloc(&self, _layout: Layout) -> *mut u8 {
        HeapAlloc(GetProcessHeap(), 0, _layout.size()) as *mut u8
    }

    unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
        HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, layout.size()) as *mut u8
    }

    unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {
        HeapFree(GetProcessHeap(), 0, _ptr as *mut c_void);
    }

    unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
        HeapReAlloc(
            GetProcessHeap(),
            HEAP_ZERO_MEMORY,
            ptr as *mut c_void,
            new_size,
        ) as *mut u8
    }
}

stdout.rs:

use core::ffi::c_void;
use core::ptr::null_mut;
use core::str;

use windows_sys::Win32::Foundation::BOOL;
use windows_sys::Win32::Foundation::HANDLE;
use windows_sys::Win32::Storage::FileSystem::WriteFile;
use windows_sys::Win32::System::Console::GetStdHandle;
use windows_sys::Win32::System::Console::STD_OUTPUT_HANDLE;

pub fn write_stdout(text: &str) -> BOOL {
    unsafe {
        let stdout: HANDLE = GetStdHandle(STD_OUTPUT_HANDLE);

        let mut written: u32 = 0;

        let ok = WriteFile(
            stdout,
            text.as_ptr() as *const c_void,
            text.len() as u32,
            &mut written,
            null_mut(),
        );
        ok
    }
}

build.rs:

fn main() {
    println!("cargo:rustc-link-arg=-nostartfiles");
}
1 Like

Having some interest in this myself, I tried to get this working -- but I'm stuck before getting to the point of having an executable.

When the binary is being linked libcore and liballoc are missing memcpy, strlen, memmove, memset, memcmp and _fltused.

What does your Cargo.toml look like, and how are you building? cargo +nightly build --release?

I've never seen those errors, I'm cross compiling from linux with cargo +nightly build --release --target=x86_64-pc-windows-gnu.

I had to pull in mingw-w64-gcc

Cargo.toml:

name = "nostdwin"
version = "0.1.0"
edition = "2021"

[toolchain]
channel = "nightly"

[profile.dev]
panic = "abort"

[profile.release]
opt-level = 'z'     # Optimize for size.
lto = true          # Enable Link Time Optimization
codegen-units = 1   # Reduce number of codegen units to increase optimizations.
panic = 'abort'     # Abort on panic
strip = true        # Strip symbols from binary*

[dependencies]

[dependencies.windows-sys]
version = "0.36.1"
features = [
    "Win32_Foundation",
    "Win32_Storage_FileSystem",
    "Win32_System_IO",
    "Win32_System_Console",
    "Win32_System_Memory",
]

Does including the compiler_builtins crate help? It can provide implementations for memcpy() and friends when your target doesn't.

1 Like

I think I found what's wrong with your compilation, if you are on windows you need different linker arguments (build.rs).

# Linux
cargo rustc -- -C link-arg=-nostartfiles
# Windows
cargo rustc -- -C link-args="/ENTRY:_start /SUBSYSTEM:console"

And the first lines where missing in the code due to discourse only showing code on the next line after the three backticks. I had them on the same line, I've edited OP.

That was helpful -- I had tried that, but clearly not sufficiently: I had missed enabling the mem feature.

The _fltused is apparently needed for uefi builds? (This is an area where things start to feel like black magic to me -- why would UEFI builds be special?). The compiler-builtin crate has a dummy implementation which I simply reused.

Success!

I added #![windows_subsystem = "console"] to main.rs, and had the /ENTRY:_start in my build.rs.

Based on your original code I ran into three issues:

  • Missing __CxxFrameHandler3
  • Missing the mem* and strlen functions previously mentioned.
  • Missing _fltused.

I added dummy implementations for _CxxFrameHandler3 and _fltused and brought mem* and strlen from compiler-builtins (.. don't forget the mem feature ..), after which it Just Worked(tm). I added some more heap operations (made sure to grow the vector substantially), and it works fine.

I am building natively on Windows using msvc, so there's obviously some substantial differences in our build environment -- but your code should work.

3584 bytes executable :smirk:

I'll try a cross compilation over the next few days.

It sounds like you need this if you want the Windows CRT to load floating point support.

__fltused implies you are using or have at least declared some floats or doubles. The compiler injects this 'useless' symbol to cause a floating support .obj to get loaded from the crt. You can get around this by simply declaring a symbol with the name

1 Like