Use "println!" in libpanic_unwind

#1

I want to use the macro “println!” to print some debug information in “libpanic_unwind”, but get error:

error: cannot find macro println! in this scope

–> src/libpanic_unwind/dwarf/eh.rs:57:2 | 57 | println!(“find_eh_action”); | ^^^^^^^

error: aborting due to previous error

What should I do to use this macro? or what else function should I use for such case?

Thanks, Baoshan

#2

libpanic_unwind is no_std (because it’s linked into libstd, for a start), so you can’t access OS-level stdio facilities. Not sure if there’s a way to work around it

#3

When in a no_std environment with access to the libc, you can use puts or printf.

The only issue is it requiring a null-terminated byte-string:

fn puts (s: &'_ [u8])
{
    if s.contains(&b'\0') { unsafe {
        ::libc::puts(s.as_ptr() as *const ::libc::c_char);
    }}
}

fn main ()
{
    puts(b"Hello, world!\0");
}
#4

I think you want ends_with there, not contains. Also, you might want an assert! rather than an if.

1 Like
#5

I agree that failing paths (e.g., no silent truncation or even silently ignoring a bad argument) are the most sensitive thing to do, in the general case.

  • By the way, ends_with is not flawless either, since it ignores inner null bytes. s.iter().position(|&x| x == b'\0') == Some(s.len() - 1) would be the way to go

Since the OP wanted a debug function, however, in this case a convenience “do the best with what I get” function seemed to me more appropriate than a panic! within a panic! leading to a harder to debug abort, even at the cost of potential message truncation and the worst-case which silently ignores the call altogether.

But I should have stated so in my post, so thanks for pointing that out.


Here goes another shot at handling bad case scenarios (always within a no_std environment):

/// if `$assertion` is `false`, there is an index out of bounds access at compile time.
macro_rules! static_assert {($assertion:expr) => ({
    static _ASSERTION: () = [(); 1][(!$assertion) as usize];
})}

fn puts (s: &'_ [u8])
{
    const BUF_CAPACITY: usize
        = 256
    ;
    const TRUNCATION_MESSAGE_LEN: usize
        = 6
    ;
    static TRUNCATION_MESSAGE: &'static [u8; TRUNCATION_MESSAGE_LEN]
        = b"<...>\0"
    ;
    static_assert!(
        BUF_CAPACITY >= TRUNCATION_MESSAGE_LEN
    );
    static_assert!(
        TRUNCATION_MESSAGE[TRUNCATION_MESSAGE_LEN - 1] == b'\0'
    );
    unsafe {
        if s.iter().position(|&x| x == b'\0') == Some(s.len() - 1) { 
            ::libc::puts(s.as_ptr() as *const ::libc::c_char);
        } else {
            let mut buffer = [0_u8; BUF_CAPACITY];
            buffer
                .iter_mut()
                .zip(s.iter().cloned().filter(|&x| x != b'\0'))
                .for_each(|(p, x)| *p = x);
            if !buffer.ends_with(b"\0") {
                buffer[BUF_CAPACITY - TRUNCATION_MESSAGE_LEN ..]
                    .copy_from_slice(TRUNCATION_MESSAGE)
                ;
            }
            ::libc::puts(buffer.as_ptr() as *const ::libc::c_char);
        }
    }
}
#6

You could avoid needing to copy anything, or worrying about truncation of the message, by looping over the buffer calling putchar.

fn puts (s: &[u8])
{
    use ::libc::{putchar, c_int};
    unsafe {
        for c in s {
           putchar(*c as c_int);
        }
        putchar('\n' as c_int);
    }
}

Debugging messages ought to go to stderr so they don’t corrupt any primary output of the program, and a function like this ought to lock and unlock the FILE object itself so no other thread’s output can get mixed in, which would look like this …

fn eputs (s: &[u8])
{
    use ::libc::{c_int, stderr, fputc_unlocked,
                 flockfile, funlockfile, fflush};
    unsafe {
        flockfile(stderr);
        for c in s {
           fputc_unlocked(*c as c_int, stderr);
        }
        fputc_unlocked('\n' as c_int, stderr);
        fflush(stderr);
        funlockfile(stderr);
    }
}

… but unfortunately libc doesn’t expose flockfile, funlockfile, fputc_unlocked, or even stderr (!) An alternative would be to bypass the stdio layer:

fn eputs (s: &[u8])
{
    use ::libc::{write, c_void, STDERR_FILENO};
    if s.len() > 0 {
        unsafe {
            write(STDERR_FILENO, s.as_ptr() as *const c_void, s.len());
        }
    }
}

With this version, callers have to provide a trailing newline themselves. All of these functions ignore errors, and the last one assumes all of the message will be consumed by a single call to write, which is not guaranteed but usually okay for what stderr gets connected to.