Embedding Values in Rust Binaries

Stop Optimising out my shit!!!
Up Home
Updated

I wanted to embed some versioning info into my output binaries when bulding a firmware. This wasn’t as easy as adding a static string to the binary, in part because I wanted it to be automatic, and in part because the fucking compiler/linker refised to not optimise out the “dead code”. So here’s how I got it to behave:


//! main.rs
/// Embed a redable git ID and cargo package version into the binary
pub mod version {
    #[unsafe(no_mangle)] // Perserve the name of the symbol
    #[used]              // It's not Dead Code lld, I promise!
    /// Git ID for build
    pub static BUILD: &str = env!("GIT_BUILD_ID");
    #[unsafe(no_mangle)]
    #[used]
    /// Cargo SemVer
    pub static VERSION: &str = env!("CARGO_PKG_VERSION");
}

//! build.rs
fn main() {
    let git_hash = Command::new("git")
        .args(["describe", "--always", "--dirty=-dev"])
        .output()
        .ok()
        .and_then(|output| String::from_utf8(output.stdout).ok())
        .map(|s| s.trim().to_string())
        .unwrap_or_else(|| "unknown".to_string());

    // I also use a LOT of cargo feature-flags, so it's important to fingerprint that, too
    let mut features = Vec::new();
    for (key, _) in std::env::vars() {
        let Some(feature) = key.strip_prefix("CARGO_FEATURE_") else {
            continue;
        };
        features.push(feature.to_string());
    }
    features.sort();

    let mut hash = DefaultHasher::new();
    features.hash(&mut hash);
    let build_id = format!("{git_hash}-{:016x}", hash.finish());

    println!("cargo:rerun-if-changed=.git/HEAD");
    println!("cargo:rerun-if-changed=.git/index");
    println!("cargo:rustc-env=GIT_BUILD_ID={build_id}");
}

And the shell script I use to yoink the generated values out of the binary. You can also just have the program log them, they’re normal contants useable as version::BUILD, after all. But the Point was to be able to do the following.


# version-finder.sh
#!/bin/bash

binary="$1"

if [ ! -f "$binary" ]; then
    echo "Error: File '$binary' not found"
    exit 1
fi

for symbol in BUILD VERSION; do
    fat_ptr_address=$(arm-none-eabi-nm "$binary" | grep " $symbol$" | cut -d' ' -f1)
    
    if [ -z "$fat_ptr_address" ]; then
        echo "Error: Symbol '$symbol' not found in binary"
        exit 1
    fi
    
    string_address=$(arm-none-eabi-objdump -s --start-address=0x$fat_ptr_address --stop-address=$((0x$fat_ptr_address + 4)) "$binary" 2>/dev/null |
                     grep "^ " | 
                     cut -c7-42 | 
                     xxd -r -p | 
                     xxd -e -g4 | 
                     head -1 | 
                     awk '{print $2}')
    
    str_len=$(arm-none-eabi-objdump -s --start-address=$((0x$fat_ptr_address+4)) --stop-address=$((0x$fat_ptr_address + 8)) "$binary" 2>/dev/null |
              grep "^ " | 
              cut -c7-42 | 
              xxd -r -p | 
              xxd -e -g4 | 
              head -1 | 
              awk '{print $2}')
    str_len=$((0x$str_len))
    
    content=$(arm-none-eabi-objdump -s --start-address=0x$string_address --stop-address=$((0x$string_address + 100)) "$binary" 2>/dev/null |
              grep "^ " |
              cut -c7-42 |
              xxd -r -p |
              head -c $str_len)
    
printf "%-10s %s\n" "$symbol:" "$content"
done

If you found this useful, please email me, it’d make by day.