Split debug info sizes doesn't make sense?


I'm trying to create a stripped release binary (as small as possible) + a separate debug info artifact.

If I build release without debug info I get a 6.8 MB binary. Which is still larger than if I strip it with the strip command. Sure, there is some additional non-debug sections (symbol tables etc).

I believed CARGO_PROFILE_RELEASE_DEBUG=line-tables-only CARGO_PROFILE_RELEASE_SPLIT_DEBUGINFO=packed would do what I want. And this kind of works. I do indeed end up with a separate .dwp file containing debug info. But the binary is still over 8 MB (larger than if I build completely without debug info). That is very strange!

So, no problem, lets add CARGO_PROFILE_RELEASE_STRIP=symbols (on top of split debug info & debug=line-tables-only). It does produce a minimal binary (3.6 MB, same as a manual strip), but the debug symbols produced is also rather small (6.4 MB). So debug info + binary doesn't add up to binary with debug info in it. (Partial success?)

If I instead manually strip after the fact:

strip target/release/mybinary -o mybinary.stripped
strip target/release/mybinary --only-keep-debug -o mybinary.dbg

I end up with yet another result: small binary (3.6 MB as expected) and a large debug symbol file (13 MB as expected). Together they add up to about 100 KB less than the original binary with everything in it, which seems mostly reasonable (since there is more than just debug info that strip without arguments strips).

The options all seem to interact in some rather strange and confusing ways. Maybe it is an issue of documentation, maybe I'm just missing something obvious. But what I would expect is something that is equivalent to manually splitting into a binary and debug info using strip, but that doesn't seem to be what any of the cargo environment variables do?

Actual question

So in summary I think I have two unanswered questions from this:

  1. Why does CARGO_PROFILE_RELEASE_DEBUG=line-tables-only CARGO_PROFILE_RELEASE_SPLIT_DEBUGINFO=packed produce a bigger binary than building with no debug info? Shouldn't all the added debug info go into the separate debug info file?
  2. Why is the split debug info that cargo/rustc produces so much smaller than the debug info a manual strip step afterwards produces? What is missing when rust/cargo does it, and why?

OS etc

  • Rust version: 1.75 (from rustup)
  • OS: Arch Linux x86-64
  • Using mold as linker in ~/.cargo/config.toml
1 Like

On linux there are two separate ways for splitting debuginfo into a separate file:

  • Copying the debug sections into a separate file after linking and removing them from the executable using objcopy. This is what most distros do.
  • DWARF split debuginfo: The compiler outputs .dwo files with all debuginfo except for references to addresses inside the object files/executables and .o files with some debuginfo that references those .dwo files, but at the same time also contains the addresses that the debugger needs for the linker to relocate. This is what rustc uses. The executables are bigger than the other option, but linking is faster as most debuginfo doesn't pass through the linker. Also because the standard library is not compiled with split debuginfo enabled, the entire standard library debuginfo ends up in the executable.

Thank you for your answer.

This should probably be documented in the docs on these options, as it seems like quite a major limitation.

Also, it makes this sort of split debug info useless for release builds, where you want the smallest possible binary. I can see it being useful for development builds though, if it helps speed up the build.

This part seems like an outright bug, I would expect everything that is statically linked into the binary to be affected. Again there should be a disclaimer about this in the docs, as this makes the option useless for anything but speeding up local development builds.