Embedding Values in Rust BinariesStop 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.