Linker map shows `write_char` (and others) duplicated for embedded project

⚓ Rust    📅 2026-07-14    👤 surdeus    👁️ 2      

surdeus

Hey Rustecean... Rusttastions... Rust...
Hey everyone,

I'm working on a little embedded project for the mipsel-sony-psx target, which compiles into a flat binary format. Since I only have 2MB of RAM available I occasionally check the memory map generated, to identify large functions and whatnot.

Today I noticed the following entry
<puddle_sdk[9d06d2269bc47416]::support::StdOut as core[7361b8d8ea116b67]::fmt::Write>::write_char being there twice. On closer look I noticed that the two functions have slightly different mangled names:

  • _RNvYNtNtCsdtQE6lUraTi_10puddle_sdk7support6StdOutNtNtCs9UaNIwQESFv_4core3fmt5Write10write_charB6_
  • _RNvYNtNtCsdtQE6lUraTi_10puddle_sdk7support6StdOutNtNtCs9UaNIwQESFv_4core3fmt5Write10write_charCskDZwcgprsr3_18puddle_integration.

With a disassembler I verified that both functions have the exact same instructions.

  • puddle-sdk is the name of my crate to handle all the low level PSX stuff
  • puddle-integration is a collection of integration tests.

puddle-integration is the executable I am compiling.

I noticed that the _RNvYNtNtCsdtQE6lUraTi_10puddle_sdk7support6StdOutNtNtCs9UaNIwQESFv_4core3fmt5Write10write_charCskDZwcgprsr3_18puddle_integration entry disappears when I do not use println in puddle-integration.

Am I implementing println inefficiently somehow?

In the puddle-sdk I implement it like this:

#[macro_export]
macro_rules! println {
    ($($args:tt)*) => {
        {
            use core::fmt::Write;
            let _ = write!($crate::support::StdOut::new(), "{}\n", format_args!($($args)*));
        }
    }
}

With StdOut implemented like this:

pub struct StdOut {}

impl StdOut {
    pub const fn new() -> StdOut {
        StdOut{}
    }
}

impl Default for StdOut {
    fn default() -> Self {
        Self::new()
    }
}

impl core::fmt::Write for StdOut {
    fn write_str(&mut self, s: &str) -> core::fmt::Result {
        use crate::psx::bios;

        // `%.*s` is printing the sub string with the specified length
        unsafe{bios::printf(c"%.*s".as_ptr(), s.len(), s.as_ptr());}
        Ok(())
    }
}

Any ideas on how to avoid duplication or why it happens?

2 posts - 2 participants

Read full topic

🏷️ Rust_feed