Strip Command in Linux with Examples

Linux system administrators and developers constantly seek ways to optimize their binaries for production deployment. The strip command is a powerful GNU Binutils tool designed to reduce executable file sizes by removing unnecessary symbols and debugging information. This comprehensive guide explores how to effectively use the strip command to create lightweight, production-ready binaries while understanding when and why to apply different stripping techniques.
What is the Strip Command in Linux?
The strip command is a fundamental utility within the GNU Binutils package that removes symbols, debugging information, and other unnecessary data from compiled object files and executables. When developers compile programs using GCC or other compilers, the resulting binaries contain extensive metadata including symbol tables, debugging sections, relocation information, and section headers that aid in development but are unnecessary for production execution.
Strip operates by analyzing ELF (Executable and Linkable Format) files—the standard binary format for Linux executables—and selectively removing specific sections while preserving the essential code and data required for program execution. The tool modifies files in place by default, though it offers options to preserve original files while creating stripped versions.
Why Use Strip Command?
File Size Reduction: Compiled binaries often contain 30-50% additional data in the form of symbol tables and debugging information. Stripping these sections significantly reduces file sizes, which is crucial for Docker containers, embedded systems, and network deployments where bandwidth and storage are limited.
Production Deployment: Development builds include extensive debugging symbols to facilitate troubleshooting. Production environments rarely need this information, making stripped binaries ideal for final deployment scenarios where performance and size matter more than debugging capabilities.
Security Enhancement: While not a comprehensive security solution, stripping symbols makes reverse engineering more challenging. Attackers analyzing stripped binaries face additional obstacles when attempting to understand program logic or identify vulnerabilities, though determined adversaries can still decompile and analyze machine code.
Embedded Systems: Resource-constrained devices running Linux—from IoT sensors to industrial controllers—benefit tremendously from stripped binaries. Every kilobyte saved translates to reduced flash memory requirements and faster loading times.
Understanding Binary Symbols and Debugging Information
Before diving into practical examples, understanding what gets removed during stripping proves essential. Compiled ELF binaries contain multiple sections serving different purposes.
The symbol table (.symtab section) stores information about functions, variables, and objects within the binary. Each entry includes the symbol name, memory address, size, and type. Debuggers and profiling tools use this information to map machine code back to source code constructs.
The dynamic symbol table (.dynsym section) contains symbols needed for dynamic linking with shared libraries. Unlike the regular symbol table, dynamic symbols must often be preserved to maintain runtime functionality.
Debugging symbols reside in specialized sections like .debug_info, .debug_line, and .debug_frame. These sections contain detailed mappings between source code lines and machine instructions, variable types, stack unwinding information, and optimization details. DWARF (Debugging With Attributed Record Formats) is the standard format for this debugging data.
Relocation information helps the dynamic linker adjust addresses when loading shared libraries. The .rel and .rela sections store these fixup records, though statically linked binaries require fewer relocations.
Section headers describe the binary’s structure, mapping virtual memory addresses to file offsets. While essential for linking and loading, some headers can be removed from standalone executables without affecting execution.
During development, these components enable powerful debugging with GDB, memory profiling with Valgrind, and performance analysis with perf. Production executables sacrifice these capabilities for efficiency.
Strip Command Syntax and Basic Usage
The strip command follows a straightforward syntax:
strip [options] objfile...
The command accepts one or more object files or executables as arguments. By default, strip overwrites input files with stripped versions, so maintaining backups of unstripped binaries is crucial for debugging production issues.
Verify strip installation on your system:
strip --version
This displays the GNU Binutils version and confirms availability. Most Linux distributions include strip by default as part of the binutils package.
Access comprehensive help documentation:
strip --help
man strip
The manual page provides detailed explanations of all available options and their effects on different binary formats.
Preparing a Sample Program for Examples
Creating a test program demonstrates strip’s impact on real binaries. Save this C program as example.c:
#include <stdio.h>
static int static_var = 10;
int global_var = 20;
int increment_function() {
static int counter = 0;
return (++counter);
}
int main(void) {
int result = increment_function();
printf("\nSum: %d\n", (result + global_var + static_var));
return 0;
}
Compile the program without stripping:
gcc -o example example.c
Check the initial file size:
ls -lh example
Typical output shows files ranging from 16KB to 20KB depending on the system and compiler version.
Install essential verification tools if not already available:
sudo apt-get install binutils # Debian/Ubuntu
sudo yum install binutils # RHEL/CentOS
The nm command displays symbol tables:
nm example
This outputs dozens of symbols including function names, variables, and system library references.
The readelf command provides detailed ELF structure analysis:
readelf -s example
Save this output for comparison after stripping:
readelf -s example > example_before.txt
The file command identifies basic file properties:
file example
Output indicates the file type, architecture, linking status, and whether symbols are present.
Essential Strip Command Options with Examples
Strip All Symbols (-s or –strip-all)
The most aggressive stripping option removes all symbol information including both the symbol table and debugging sections. This option produces the smallest possible executable while maintaining functionality.
strip -s example
Check the size reduction:
ls -lh example
Files typically shrink by 30-50% depending on compilation settings and binary complexity. A 16KB binary might reduce to 10KB or less.
Verify symbol removal:
nm example
Output displays: “nm: example: no symbols”
Confirm with readelf:
readelf -s example
The symbol table section either disappears entirely or contains minimal dynamic symbols required for shared library linking.
Test the executable still functions correctly:
./example
The program executes normally despite lacking debugging information. Use -s for final production deployments where debugging capabilities are unnecessary and file size is critical.
Strip Debug Symbols Only (–strip-debug, -g, -S, -d)
When you need to preserve some symbol information while removing debugging data, the --strip-debug option provides a middle ground. This approach removes DWARF debugging sections but retains function and variable symbols useful for basic profiling and crash analysis.
strip --strip-debug example
Alternative equivalent options:
strip -g example
strip -S example
strip -d example
All four commands produce identical results. The multiple option names exist for compatibility with different Unix variants and user preferences.
Compare symbol tables:
nm example | wc -l
This shows significantly fewer symbols than the original binary but more than a fully stripped version. Dynamic symbols and exported functions remain visible.
Examine remaining sections:
readelf -S example | grep debug
No output appears, confirming all .debug_* sections were removed. However:
readelf -s example
This still displays symbol table entries for functions and global variables.
Use --strip-debug for testing environments where basic profiling tools need function names but full debugging isn’t required. This strikes a balance between size optimization and diagnostic capability.
Create Output File (-o or –output-file)
Preserving original unstripped binaries while creating stripped versions is essential for production debugging. The -o option specifies an output filename, leaving the source file unchanged.
strip -s example -o example_stripped
Verify both files exist:
ls -lh example*
Output displays:
- example: 16384 bytes (original with symbols)
- example_stripped: 10240 bytes (stripped version)
This workflow enables deployment of stripped binaries while maintaining debug-enabled versions for troubleshooting. Store unstripped binaries in a secure location with version control to match deployed releases.
Best practices for naming conventions:
- Append “_stripped” or “.stripped” to indicate processed files
- Use version numbers:
example-1.2.3andexample-1.2.3-stripped - Maintain separate directories:
dist/for stripped binaries,debug/for originals
Archive unstripped binaries with compression:
tar czf example-debug-symbols.tar.gz example
This dramatically reduces storage requirements while preserving the ability to debug production crashes by loading symbols alongside core dumps.
Strip Unneeded Symbols (–strip-unneeded)
The --strip-unneeded option intelligently removes symbols not required for relocation processing. This selective approach preserves symbols necessary for dynamic linking while discarding others.
strip --strip-unneeded example
This option is particularly valuable for shared libraries where dynamic symbols must remain for proper linking:
strip --strip-unneeded /usr/local/lib/libexample.so
Compare with full stripping:
# Create two copies for comparison
cp example example_unneeded
cp example example_all
strip --strip-unneeded example_unneeded
strip --strip-all example_all
ls -lh example_*
The --strip-unneeded version typically falls between the original and fully stripped sizes. Symbols required by the dynamic linker remain accessible.
Verify symbol preservation:
nm -D example_unneeded
The -D flag displays dynamic symbols, which remain intact. Linux From Scratch builds commonly use this option during system compilation to optimize binaries while maintaining necessary linking information.
Remove Specific Sections (-R or –remove-section)
Advanced users can target individual sections for removal using the -R option. This surgical approach provides maximum control over the stripping process.
List available sections:
readelf -S example
Output displays numerous sections including .text (code), .data (initialized data), .bss (uninitialized data), .rodata (read-only data), and various auxiliary sections.
Remove a specific section:
strip -R .comment example
The .comment section contains compiler version information and build metadata—useful for forensics but unnecessary for execution.
Remove multiple sections:
strip -R .comment -R .note -R .gnu.version example
Each -R flag targets one section. Verify removal:
readelf -S example | grep -E "comment|note|gnu.version"
Empty output confirms successful section removal.
Warning: Removing essential sections like .text, .data, or .dynamic renders executables unusable. Only remove auxiliary sections after verifying they’re non-essential. Consult ELF specification documentation when uncertain about section purposes.
Common safe-to-remove sections:
- .comment: Compiler version strings
- .note.gnu.build-id: Build identification
- .note.ABI-tag: ABI version information
Keep Specific Symbols (-K or –keep-symbol)
When full stripping is desired except for specific debugging or profiling symbols, the -K option provides selective preservation.
strip -s -K main -K increment_function example
This command strips all symbols except main and increment_function. Verify:
nm example
Output displays only the preserved symbols. Use multiple -K flags to keep multiple symbols:
strip -s -K main -K global_var -K increment_function example
Wildcard patterns work with the -w flag:
strip -s -w -K "increment_*" example
This preserves all symbols matching the pattern increment_*. Wildcards follow shell globbing rules with * matching any sequence of characters and ? matching single characters.
Use cases include preserving specific exported API functions in shared libraries while stripping internal implementation details, or maintaining symbols for specific profiling tools while removing unnecessary debugging information.
Remove Specific Symbol (-N or –strip-symbol)
The inverse of -K, the -N option removes individual symbols while preserving others.
strip -N static_var example
Verify removal:
readelf -s example | grep static_var
No output appears for the removed symbol, while others remain visible. Remove multiple symbols:
strip -N static_var -N global_var example
This technique proves useful when you want to hide internal implementation details of libraries while maintaining public API symbols. Combine with other options:
strip --strip-debug -N internal_function example
This removes all debugging information and specifically strips the internal_function symbol.
Advanced Strip Command Options
Preserve Timestamps (-p or –preserve-dates)
By default, strip updates file modification times. The -p option maintains original access and modification timestamps, crucial for build systems and caching mechanisms.
stat example | grep -E "Access|Modify"
strip -s -p example
stat example | grep -E "Access|Modify"
Timestamps remain unchanged despite file content modification. This ensures Make-based build systems don’t incorrectly trigger rebuilds due to timestamp changes.
Automated build scripts should always use -p:
find /usr/local/bin -type f -executable -exec strip -s -p {} \;
This strips all executables in a directory while preserving timestamps for version control and deployment tracking.
Strip DWO Sections (–strip-dwo)
DWARF Object (DWO) sections contain split debugging information when using GCC’s -gsplit-dwarf compilation option. This technique separates debugging data into .dwo files, leaving skeleton information in the main binary.
strip --strip-dwo example
This removes only DWO sections while preserving standard debugging information. Combine with other options:
strip --strip-debug --strip-dwo example
Use this option when working with split debug compilation workflows where .dwo files are maintained separately for debugging purposes.
Keep Debug Information Only (–only-keep-debug)
Creating separate debug symbol files enables stripped production binaries while maintaining debugging capabilities through external symbol files.
strip --only-keep-debug example -o example.debug
This creates a file containing only debugging information. Strip the original:
strip -s example
Link the debug file to the stripped binary:
objcopy --add-gnu-debuglink=example.debug example
GDB automatically loads the debug symbols when debugging:
gdb ./example
This workflow is standard in Linux distributions where debug packages (debuginfo RPMs or dbgsym DEBs) provide separate symbol files for system binaries.
Discard Local Symbols (-X or –discard-locals)
Compiler-generated local symbols beginning with “L” or temporary symbols starting with “.” can be removed while preserving user-defined symbols.
strip -X example
This removes compiler artifacts like .L123 labels used for local jump targets but maintains function names and global variables. Verify:
nm example | grep "\.L"
Empty output confirms local symbol removal. This option provides a lighter touch than full stripping, useful when maintaining some debugging capability while reducing file size.
Verbose Output (-v or –verbose)
Monitor strip operations with detailed output:
strip -v -s example
Output displays:
strip: removing symbols from 'example'
Verbose mode is particularly valuable when processing multiple files:
strip -v -s *.o
Each file’s processing status appears, helping identify any errors or issues during batch operations.
Comparing Strip Command vs GCC -s Option
GCC offers a -s flag that strips binaries during compilation:
gcc -s -o example example.c
This produces a stripped executable directly without requiring a separate strip command invocation. However, the standalone strip command offers several advantages:
Flexibility: Compile with full debugging symbols for testing, then strip only production builds. The same source compilation serves both purposes.
Preservation: Use strip -o to maintain unstripped versions for debugging while creating stripped deployments. The GCC -s flag provides no such option.
Granular Control: Strip command offers dozens of options for selective symbol removal. GCC’s -s flag performs basic all-symbol stripping equivalent to strip -s.
Build System Integration: Separate compilation and stripping stages in Makefiles provides clearer build logic:
example: example.o
$(CC) -o $@ $^
example.stripped: example
strip -s $< -o $@
Post-Build Processing: Strip third-party binaries or executables from sources without build system control:
strip -s /usr/local/bin/third_party_tool
Use GCC -s for simple projects where stripped binaries suffice and no debugging is anticipated. Use the strip command for production workflows requiring both debug and release builds.
Practical Use Cases and Real-World Scenarios
Production Deployments
Web applications, microservices, and server daemons benefit from stripped binaries. Consider a Go web service:
go build -o webserver main.go
ls -lh webserver # 8.2MB
strip -s webserver
ls -lh webserver # 5.8MB (29% reduction)
Deploying to dozens of servers saves significant bandwidth and reduces deployment time. Container images shrink proportionally:
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN go build -o app .
RUN strip -s app
FROM alpine:latest
COPY --from=builder /app/app /usr/local/bin/
CMD ["app"]
This multi-stage Docker build pattern creates minimal production images by stripping binaries before final image assembly.
Embedded Systems Development
Embedded Linux systems on ARM, MIPS, or RISC-V architectures face severe storage constraints. A typical embedded build strips all binaries:
# Cross-compilation for ARM
arm-linux-gnueabihf-gcc -o sensor sensor.c
arm-linux-gnueabihf-strip -s sensor
Buildroot and Yocto Project automatically strip binaries during rootfs creation:
# Buildroot strips with --strip-unneeded by default
BR2_STRIP_strip=y
Flash memory costs and boot times directly correlate with binary sizes. An embedded system with 200 binaries averaging 30% size reduction saves 10-20MB—significant on devices with 64MB flash.
Security Hardening
Stripped binaries increase reverse engineering difficulty. Attackers using tools like IDA Pro, Ghidra, or radare2 face challenges when symbol information is absent:
strip -s proprietary_algorithm
Function names disappear, replaced with memory addresses. Variable names vanish, leaving only data accesses. While determined attackers can still decompile machine code, stripping raises the skill and time requirements.
Combine stripping with other hardening techniques:
gcc -O2 -fPIC -fstack-protector-strong -D_FORTIFY_SOURCE=2 -o app app.c
strip -s app
This layered approach including optimization, position-independent code, stack protection, and symbol stripping creates robust production binaries.
Continuous Integration/Deployment
Automate stripping in CI/CD pipelines using GitLab CI, GitHub Actions, or Jenkins:
# .gitlab-ci.yml
build:
script:
- gcc -O2 -o myapp src/*.c
- cp myapp myapp-debug
- strip -s myapp
artifacts:
paths:
- myapp
- myapp-debug
This preserves debug versions as artifacts for post-deployment troubleshooting while deploying stripped binaries. Kubernetes deployments incorporate stripping into container build processes for optimal image sizes.
Working with Multiple Files and Batch Operations
Strip multiple executables simultaneously:
strip -s program1 program2 program3
Use shell wildcards for batch processing:
strip -s *.o
strip --strip-debug /usr/local/lib/*.so
Process all executables in a directory tree:
find /opt/myapp/bin -type f -executable -exec strip -s {} \;
The -type f ensures only regular files are processed, while -executable tests for execution permissions. The {} placeholder represents each found file.
Create sophisticated scripts for selective stripping:
#!/bin/bash
for binary in /usr/local/bin/*; do
if file "$binary" | grep -q "ELF.*not stripped"; then
echo "Stripping $binary"
strip -s -p "$binary"
fi
done
This script checks file types before stripping, avoiding errors on non-ELF files.
Handle errors gracefully in production scripts:
strip -s -o output_file input_file || {
echo "Strip failed for input_file" >&2
exit 1
}
Read filenames from a list:
xargs -a files_to_strip.txt -I {} strip -s {}
This processes files listed in files_to_strip.txt, one per line, enabling version-controlled stripping configurations.
Verifying Strip Operations
Always verify stripped binaries retain functionality and meet size expectations.
Using nm: Check symbol table status:
nm example
Output “no symbols” for fully stripped binaries or displays remaining symbols for partial stripping.
Using readelf: Analyze ELF structure comprehensively:
readelf -S example # Section headers
readelf -s example # Symbol table
readelf -d example # Dynamic section
Compare before and after:
diff <(readelf -s original) <(readelf -s stripped)
Using file: Quickly check strip status:
file example
Output includes “not stripped” or “stripped” status alongside architecture and linking information.
Using objdump: Disassemble and examine binary contents:
objdump -t example # Symbol table
objdump -d example # Disassembly
Size comparison: Quantify space savings:
du -h example_original example_stripped
Functionality testing: Most importantly, execute stripped binaries:
./example_stripped
Run comprehensive test suites on stripped production builds before deployment. Stripping should never affect runtime behavior—only debugging capability.
Automated verification:
#!/bin/bash
ORIGINAL_SIZE=$(stat -f%z original)
STRIPPED_SIZE=$(stat -f%z stripped)
REDUCTION=$((100 - (STRIPPED_SIZE * 100 / ORIGINAL_SIZE)))
echo "Size reduction: ${REDUCTION}%"
./stripped && echo "Functionality verified" || echo "ERROR: Execution failed"
Best Practices and Recommendations
Maintain Unstripped Archives: Always preserve original unstripped binaries with version tags matching production deployments. When production crashes occur, debug symbols enable meaningful stack trace analysis.
mkdir -p /var/debuginfo/myapp-v2.1.3/
cp binary /var/debuginfo/myapp-v2.1.3/binary-debug
strip -s binary -o binary-stripped
Test Thoroughly: Execute comprehensive test suites against stripped binaries before production deployment. While stripping shouldn’t affect functionality, verification prevents unexpected issues.
Use –only-keep-debug for Distribution: Create separate debug packages following distribution conventions:
strip --only-keep-debug myapp -o myapp.debug
strip -s myapp
objcopy --add-gnu-debuglink=myapp.debug myapp
Strip Production Only: Never strip development or testing builds. Debugging stripped binaries proves frustrating and time-consuming. Configure build systems with separate debug and release targets.
Document Stripping Strategy: Maintain README or wiki documentation explaining which stripping options are used, why those specific options were chosen, and where unstripped versions are archived.
Preserve Timestamps in Builds: Use -p flag in automated builds to prevent timestamp-triggered rebuilds:
strip -s -p -o release/app build/app
Automate in Build Systems: Integrate stripping into Make, CMake, or Meson build configurations:
# CMakeLists.txt
add_custom_command(TARGET myapp POST_BUILD
COMMAND ${CMAKE_STRIP} --strip-all $<TARGET_FILE:myapp>
COMMENT "Stripping symbols from myapp"
)
Consider Shared Libraries Carefully: Avoid stripping shared libraries with --strip-all. Use --strip-unneeded instead to preserve dynamic linking symbols:
strip --strip-unneeded /usr/lib/libmylib.so
Balance Size and Diagnostics: For user-facing applications where crash reports matter, consider --strip-debug instead of -s to maintain basic symbol information for error reporting services.
Common Pitfalls and Troubleshooting
“No symbols” Debugger Errors: Attempting to debug stripped binaries with GDB produces unhelpful stack traces. Solution: Maintain separate debug builds or use --only-keep-debug symbol files.
gdb -ex "symbol-file example.debug" -ex "file example" -ex "run"
Accidental Overwrites: Strip modifies files in place by default. Accidentally running strip -s important_binary without backups results in permanent symbol loss. Solution: Always use -o for critical files:
strip -s important_binary -o important_binary.stripped
Shared Library Breakage: Fully stripping shared libraries removes dynamic symbols required for linking:
strip -s /lib/x86_64-linux-gnu/libc.so.6 # DON'T DO THIS
Programs linking against this library fail to start. Solution: Use --strip-unneeded for libraries:
strip --strip-unneeded mylib.so
Permission Denied: Attempting to strip system binaries without appropriate privileges fails. Solution: Use sudo or process files you own:
sudo strip -s /usr/bin/system_tool
Corrupted Binary Errors: Strip fails when processing damaged or non-ELF files:
strip: example: file format not recognized
Verify file integrity and format:
file example
readelf -h example
Cross-Platform Issues: Stripping binaries compiled for different architectures requires corresponding strip tools:
arm-linux-gnueabihf-strip armprogram # Not just 'strip'
Using the wrong strip version produces errors or corrupted binaries.
Recovery from Accidental Stripping: No technical solution exists to restore removed symbols. Prevention is critical—implement version control for all binaries:
git lfs track "*.bin"
git add important_binary
git commit -m "Archive unstripped binary"