Strip a Linux Binary Without Losing Its Debug Info

Ship a binary full of symbols and you leak build paths for nothing; strip carelessly and your next crash report is useless. This guide gets you a smaller copy of a Linux object file or executable while keeping an unstripped recovery copy and, when useful, a separate debug file. The examples use GNU Binutils 2.42 from the installed Ubuntu packages: binutils-common:amd64, binutils-aarch64-linux-gnu and binutils-x86-64-linux-gnu. The generic strip command and both listed target-prefixed commands share the same installed manual page here.

Allow about fifteen minutes. You need a shell, a file in an object format the selected tool understands, and enough permission to read and write the directory. These commands modify files unless an output file is selected, so do not start with the only copy of a release binary.

1. Check the tool and identify the input

Start with read-only checks. This does not need elevated privileges:

$ command -v strip
/home/linuxbrew/.linuxbrew/bin/strip
$ strip --version | head -1
GNU strip (GNU Binutils for Ubuntu) 2.42
$ file /path/to/program
/path/to/program: ELF 64-bit LSB pie executable, x86-64, ...

Use the strip that matches the file's target: x86_64-linux-gnu-strip for x86-64, aarch64-linux-gnu-strip for AArch64. A target-prefixed tool is useful in a cross-compilation workflow because the prefix documents which output format you intend. The installed version is 2.42 even though the executable's vendor wording may vary slightly.

Checkpoint: if file reports a script, text file or an architecture you did not expect, stop. Do not use strip as a general way to make arbitrary files smaller.

2. Make a recovery copy

GNU strip rewrites the named object file in place. Make a copy before testing, and preserve the original metadata where practical:

$ cp --preserve=all /path/to/program /path/to/program.full
$ file /path/to/program.full
/path/to/program.full: ELF 64-bit LSB pie executable, x86-64, ...

Safety boundary: this is not optional ceremony. Full stripping can remove symbols that a debugger, profiler, crash reporter or post-mortem investigation needs. If the command produces an unusable result, restore the copy with an explicit replacement:

$ cp --preserve=all /path/to/program.full /path/to/program

That replacement changes the destination, so confirm both paths before running it. If the binary is owned by a service or protected directory, arrange the copy and later replacement through your normal deployment process. Do not add sudo merely because a command example contains a path you have not checked.

3. Remove debugging information only

For a release artefact that still needs its ordinary symbols, start with --strip-debug, also available as -g, -S or -d:

$ strip --strip-debug -o /path/to/program.stripped /path/to/program.full
$ file /path/to/program.stripped
/path/to/program.stripped: ELF 64-bit LSB pie executable, x86-64, ...
$ test -s /path/to/program.stripped && echo 'non-empty output'
non-empty output

The -o form writes one stripped output file instead of replacing the input, and it accepts only one input object file. Use a new destination while you are checking the result, then install or rename it through an explicit deployment step. The command may reduce the file size, but the exact reduction depends on how it was built, so do not treat a size change as the only success test.

Compare symbols when the tool is available:

$ nm -C /path/to/program.full | head
$ nm -C /path/to/program.stripped | head

Some symbols can remain after debug-only stripping. That is expected: this mode targets debugging sections, not every symbol in the file. A failed nm check is not proof that strip failed; use file, the program's own tests and the relevant debugger or loader as well.

4. Remove all symbols only when you mean it

Use --strip-all, or its short form -s, when the consumer does not need symbols:

$ strip --strip-all -o /path/to/program.release /path/to/program.full
$ file /path/to/program.release
/path/to/program.release: ELF 64-bit LSB pie executable, x86-64, ...
$ /path/to/program.release --help

The final command is an application-specific smoke test; replace it with a harmless invocation supported by your program. Do not run an unknown binary just to see whether stripping worked. A successful strip does not guarantee an application retains every tool-specific feature, and a program that expects symbols for a plugin or diagnostic path may no longer behave as intended.

Keep the unstripped file outside the public release if it contains sensitive names or paths, but retain it in controlled storage. A stripped binary is not a security boundary: strings, code, metadata and other sections can still disclose information.

5. Keep symbols in a separate debug file

For a fully linked executable, the manual documents a two-file workflow: create a debug-only companion, strip the executable, then add a GNU debug link.

$ objcopy --only-keep-debug /path/to/program.full /path/to/program.debug
$ strip --strip-debug /path/to/program.full
$ objcopy --add-gnu-debuglink=/path/to/program.debug /path/to/program.full
$ file /path/to/program.full /path/to/program.debug
/path/to/program.full:  ELF 64-bit LSB pie executable, x86-64, ...
/path/to/program.debug: ELF 64-bit LSB shared object, x86-64, ...

The debug filename extension is a convention, not a requirement. Keep the debug file available to the debugger and keep the stripped executable in the deployment package. This workflow is intended for fully linked files; it is not a substitute for preserving complete debug information from each incomplete object file.

Warning: do not delete program.debug until your symbol server or debugging workflow has stored it. Removing that file is irreversible unless another copy exists. If the final executable must be restored, copy the saved unstripped file back rather than trying to reconstruct removed sections.

Common traps

Done means