Using eprintln! in GlobalAlloc::alloc

Trying to print the layout.size() inside the implemented alloc method to the terminal results in a
'cargo run' terminated by signal SIGSEGV (Address boundary error) error message.

This happens with any format! or eprintln! macro.
Using the println! macro makes cargo run hang forever.

Minimal example:

extern crate alloc;

use std::alloc::{GlobalAlloc, System, Layout};
use std::io::Write;

struct MyAllocator;

unsafe impl GlobalAlloc for MyAllocator {
    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
        
        let ptr = System.alloc(layout);
        //let _t = format!("line"); <= 'cargo run' terminated by signal SIGSEGV (Address boundary error)
        //eprintln!("in alloc");    <= 'cargo run' terminated by signal SIGSEGV (Address boundary error)
        //println!("in alloc");     <= execution hangs forever

        //without it runs fine
        
        ptr
    }

    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
        System.dealloc(ptr, layout)
    }
}

#[global_allocator]
static GLOBAL: MyAllocator = MyAllocator;

fn main() {
    let mut v = Vec::new();
    v.push(1);
}

I am working through the "Rust in Action" Book and came across this in the "dynamic memory allocation" chapter.
(code/ch6/ch6-particles/src/main.rs at 1st-edition · rust-in-action/code · GitHub)

I am not an expert in this (that's why I bought this book) but it may have something to do because I am on a Linux System (Manjaro)..

Toolchain: stable-x86_64-unknown-linux-gnu (default)
Rust version: rustc 1.81.0

Any ideas how to print any information from inside the alloc method to the terminal?

I suspect what's happening is some of those formatting or print functions require allocation. In turn they will call the allocator and you get infinite recursion and stack overflow.

Maybe the compiler is detecting this and just inserting code to force a segfault? I'm not sure on that part.

It's worth stepping through the code in gdb to see what's happening

rust-gdb ./target/debug/global-alloc

break main
layout asm
start
ni

If you're implementing your own allocator and still want to be able to debug log, you would need to implement some logging functions which won't allocate. Here's some I wrote when I was doing something similar. I used libc's puts (put string) to output the nul terminated c string.

extern "C" {
    fn puts(_: *const core::ffi::c_char);
}
fn put(s: &core::ffi::CStr) {
    unsafe { puts(s.as_ptr()) };
}

#[inline]
fn to_hex(value: u8) -> u8 {
    assert!(value < 16);
    if value < 10 {
        value + b'0'
    } else {
        value + b'a' - 10
    }
}

// this assumes machine is little endian
fn hex_write_rev(buf: &mut [u8], mut value: usize) {
    assert!(buf.len() > 16);

    buf[16] = b'\0';
    for i in 0..16 {
        buf[17 - i] = to_hex((value & 0b1111) as u8);
        value >>= 4;
    }
    buf[0] = b'0';
    buf[1] = b'x';
}
fn put_hex(value: usize) {
    let mut buf: [u8; 20] = [0; 20];
    hex_write_rev(&mut buf, value);
    unsafe { puts(buf.as_ptr() as *const i8) };
}
3 Likes

Yes, it seems to be the case here..
This makes me dive deeper than expected but that's a good thing I guess.

For simplicity I use the extern "C" printf for now:

extern "C" {
    fn printf(format: *const c_char, ...) -> c_int;
}
.
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
        let start = Instant::now();
        let ptr = System.alloc(layout);
        let end = Instant::now();
        let ellapsed = end - start;
        let bytes_requested = layout.size();
      
        let format: *const c_char = "bytes_requested: %d, ellapsed: %f \n".as_ptr().cast();
        let _ = printf(format, bytes_requested, ellapsed.as_secs_f32() as c_double);
        
        ptr
        
}

This works with my minimal example without any problems.

Using it in the more sophisticated example from the book I get a lot of non-crashing messages:

/rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/core/src/ptr/const_ptr.rsunsafe precondition(s) violated: slice::from_raw_parts_mut requires the pointer to be aligned and non-null, and the total size of the slice not to exceedisize::MAXis_nonoverlapping: size_of::() * countoverflows a usize/rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/core/src/iter/adapters/enumerate.rssrc/main.rs/home/user/.cargo/registry/src/index.crates.io-6f17d22bba15001f/gfx-0.18.3/src/pso/buffer.rsUnsupportedBindOther

I am not sure where it comes from and why - to investigate another time.

Thanks for your answer.

Your format string isn't null terminated which is required for the c printf.

Make it a c string literal to append the null byte.

let format: *const c_char = c"bytes_requested: %d, ellapsed: %f \n".as_ptr().cast();

Maybe there's other issues too. I don't know if printf is guaranteed to not allocate.

1 Like

That's it. Thanks again!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.