Strip a MinGW-W64 Windows Binary Without Losing Your Debug Build
You will produce a smaller Windows executable with x86_64-w64-mingw32-strip, keep an unstripped copy for debugging, and check that the result is still a valid PE file. Allow about ten minutes if the toolchain is already installed.
The route
Jump straight to the step you need, or tick off Done means at the end.
This guide uses GNU Binutils 2.41.90.20240122 from Debian package binutils-mingw-w64-x86-64, version 2.41.90.20240122-1ubuntu1+11.4. The x86_64-w64-mingw32ucrt-strip name is an alias for the same strip behaviour. The examples run in an ordinary shell. No command needs sudo unless your input files are in a directory you cannot read or write.
Warning
Stripping changes a binary, and the default form replaces the named file. Do not run it on the only copy of a release artefact. Keep the original or write to a separate output path until you have tested the result.
1. Check the installed tool and its target formats
Confirm which executable is being used, then record its version. These are read-only checks:
$ command -v x86_64-w64-mingw32-strip
/usr/bin/x86_64-w64-mingw32-strip
$ x86_64-w64-mingw32-strip --version
GNU strip (GNU Binutils) 2.41.90.20240122
$ x86_64-w64-mingw32-strip --info
x86_64-w64-mingw32-strip: supported targets: pe-x86-64 pei-x86-64 pe-bigobj-x86-64 elf64-x86-64 ...
The output is abbreviated above. The important detail is that this cross-tool knows the PE targets used by 64-bit Windows binaries. --info also lists other formats, but it does not convert a file by itself.
Checkpoint
If command -v finds a different tool, stop and check your PATH. The generic host strip may not select the MinGW-W64 target you intended.
2. Make a safe test binary and inspect it
If you already have a release executable, replace build/app.exe in the commands below with its path. First copy it to a temporary working directory, or create a harmless test executable:
$ mkdir -p /tmp/strip-demo
$ cat > /tmp/strip-demo/main.c <<'EOF'
#include <stdio.h>
int main(void) {
puts("strip test");
return 0;
}
EOF
$ x86_64-w64-mingw32-gcc -g -O0 -o /tmp/strip-demo/app.exe /tmp/strip-demo/main.c
$ x86_64-w64-mingw32-nm /tmp/strip-demo/app.exe | grep ' main$'
0000000140001584 T main
The compiler command creates a PE executable with debug information. The -g flag is only for this demonstration. In a real build, preserve the unstripped artefact produced by your normal release or diagnostic configuration.
Keep a recovery copy before changing anything:
$ cp --preserve=all /tmp/strip-demo/app.exe /tmp/strip-demo/app.full.exe
$ x86_64-w64-mingw32-objdump -f /tmp/strip-demo/app.exe
/tmp/strip-demo/app.exe: file format pei-x86-64
There is no undo operation inside strip. Recovery means restoring this copy, rebuilding the executable, or rerunning the link step. The copy is your rollback point.
3. Write a stripped output beside the original
Use --strip-debug for the normal first pass. It removes debugging symbols and sections while leaving symbols needed for the executable to link and run. The -o option writes a new file, so the input remains untouched:
$ x86_64-w64-mingw32-strip --strip-debug \
-o /tmp/strip-demo/app.stripped.exe \
/tmp/strip-demo/app.exe
$ test -s /tmp/strip-demo/app.stripped.exe && echo 'stripped output exists'
stripped output exists
$ x86_64-w64-mingw32-objdump -f /tmp/strip-demo/app.stripped.exe
/tmp/strip-demo/app.stripped.exe: file format pei-x86-64
-g, -S and -d are short aliases for --strip-debug. With -o, exactly one input object file is allowed. Do not accidentally place two input paths after it and assume both were processed.
Compare the files before replacing anything:
$ stat -c '%n %s bytes' /tmp/strip-demo/app.exe /tmp/strip-demo/app.stripped.exe
/tmp/strip-demo/app.exe ... bytes
/tmp/strip-demo/app.stripped.exe ... bytes
$ x86_64-w64-mingw32-nm /tmp/strip-demo/app.exe | grep ' main$'
0000000140001584 T main
$ x86_64-w64-mingw32-nm /tmp/strip-demo/app.stripped.exe | grep ' main$' || echo 'main is no longer in the stripped symbol table'
main is no longer in the stripped symbol table
Exact sizes and addresses vary with the compiler and Binutils build. The useful checks are that the output exists, remains pei-x86-64, and no longer exposes the debug-only symbol listing from the original.
4. Choose the level of symbol removal deliberately
The default command removes all symbols, according to the installed manpage. Explicit options make the intent easier to review in build scripts:
--strip-debugremoves debugging symbols and sections. Start here when you still want a conventional executable.--strip-unneededalso removes symbols not needed for relocation processing. Test this with your loader and plugins before adopting it.--strip-allor-sremoves all symbols. This is the most aggressive common choice and can remove information used by diagnostics or tooling.--discard-allremoves non-global symbols, while--discard-localsremoves compiler-generated local symbols. These are narrower choices, not replacements for understanding the complete symbol table.
Do not confuse a smaller file with a secure file. Stripping is not encryption, signing, malware removal or a substitute for access control. It may reduce useful diagnostic information, including names that crash reporters or profilers rely on.
5. Keep symbols in a separate debug file
For a release pipeline, the useful split is usually an unstripped debug artefact retained privately and a stripped executable shipped to users. The manpage documents --only-keep-debug as a way to create a file containing debugging information, followed by --strip-debug on the executable:
$ x86_64-w64-mingw32-strip --only-keep-debug \
-o /tmp/strip-demo/app.debug.exe \
/tmp/strip-demo/app.exe
$ x86_64-w64-mingw32-strip --strip-debug \
-o /tmp/strip-demo/app.release.exe \
/tmp/strip-demo/app.exe
$ x86_64-w64-mingw32-objdump -f /tmp/strip-demo/app.debug.exe /tmp/strip-demo/app.release.exe
/tmp/strip-demo/app.debug.exe: file format pei-x86-64
/tmp/strip-demo/app.release.exe: file format pei-x86-64
Keep app.debug.exe with the build metadata needed by your debugger, and publish only the tested release file. The exact debugger workflow depends on the Windows debugging tools you use. Do not delete the debug artefact until you have checked that your crash and symbol service can consume it.
6. Know the risky options
--remove-section=NAME removes a named section in addition to the normal stripping. The manpage warns that using it incorrectly can make the output unusable. The same warning applies to --remove-relocations. Do not add either option merely to chase a few more bytes; inspect the sections first and test the resulting executable.
--strip-section-headers is specific to ELF files and implies --strip-all. It is not a general PE-size optimisation for this MinGW-W64 workflow. Options such as --input-target and --output-target select formats; they do not repair an incorrectly built executable.
When processing archives, --enable-deterministic-archives makes member metadata and the archive index reproducible. This installed build reports deterministic archive output as the default. Use --disable-deterministic-archives only when preserving real member metadata is a deliberate requirement.
Done means
- You confirmed the MinGW-W64 strip executable and its Binutils version.
- You kept an unstripped recovery or debug copy before changing a binary.
- You used
-oto create a separate output and checked its PE format. - You selected
--strip-debug,--strip-unneededor--strip-allfor a stated reason. - You tested the stripped executable and retained the matching debug artefact where diagnostics matter.