In this lesson, you’ll learn how to pinpoint exact machine code changes between firmware versions to identify unexpected binary bloat in constrained embedded systems.
When developing for resource-constrained hardware, your binary size might suddenly jump after a minor dependency update or refactor. While tools like bloat or size provide high-level summaries, they don't explain why the compiler chose different instruction patterns. Binary diffing bridges this gap by comparing the disassembly of two ELF files side-by-side.
Comparing the Machine Code
To identify size regressions, we look for "bloat sinks"—functions where the compiler may have inlined too aggressively, unrolled loops unnecessarily, or replaced optimized libcore routines with larger substitutes.
Suppose your main.rs contains a sensor polling function. After a refactor, you notice a 200-byte increase. You can generate assembly listings for both versions using cargo-objdump:
# Export the disassembly for the current and previous firmware
cargo objdump --release -- -d > current.disasm
git checkout main
cargo objdump --release -- -d > previous.disasm
Visualizing the Regression
Once you have the listings, you use a diffing tool (like bindiff or even standard diff) to compare them. Focus on the control flow graphs (CFGs) and the specific instruction count.
You might find that a seemingly trivial change, such as replacing a fixed-point calculation with a floating-point operation, triggered a massive injection of software-based floating-point emulation routines from the compiler's support library. Alternatively, you might see that the linker failed to perform Linker-Level Dead Code Elimination as effectively as before, leaving orphaned symbols in your .text section that were previously stripped.
By aligning the addresses in your diff, you can see if the compiler inserted redundant nop instructions or expanded your function prologue/epilogue due to changes in stack protection flags. If the diff reveals that multiple modules are now pulling in the same generic formatting traits, you know exactly where to apply manual optimization or trait-bounds tightening to reclaim that flash space.
Key Takeaway: Binary diffing at the assembly level reveals whether a size increase is due to inefficient code generation, unexpected library linking, or redundant inlining—allowing you to target your optimizations precisely rather than guessing.