Use objcopy to Strip, Split and Export ELF Files Safely
You will use GNU objcopy to create a smaller executable, keep its debugging information in a separate file, and export an ELF file as either raw binary data or Motorola S-record text. The original input remains untouched in every example. Allow about 15 minutes if you already have an ELF executable and the usual Binutils tools installed.
The route
Jump straight to the step you need, or tick off Done means at the end.
The commands below describe GNU Binutils 2.42 from Ubuntu package version 2.42-4ubuntu2.10 on this machine. The unprefixed objcopy, aarch64-linux-gnu-objcopy and x86_64-linux-gnu-objcopy commands are the installed variants. Their target defaults differ, so choose the command that matches the file you are building rather than assuming that the host architecture is the desired output architecture.
1. Check the tool and the input
Start with read-only checks. Replace the path with the executable or object file you actually intend to process:
$ objcopy --version
GNU objcopy (GNU Binutils for Ubuntu) 2.42
$ file /path/to/app
/path/to/app: ELF 64-bit LSB pie executable, x86-64, ...
Use readelf -h or objdump -f when you need to confirm the ELF class, machine and entry point. Do not use objcopy to discover a file's format by trial and error when a read-only inspection is enough.
Checkpoint
You have a readable input file and know whether it is a fully linked executable or a relocatable object. The manual warns that fully linked files can generally be copied between supported formats, while converting relocatable objects between formats may not produce the expected result.
2. Make a separate debug file first
Keep the original executable as a recovery copy, then extract its debugging sections into a new file:
$ cp --preserve=all /path/to/app /path/to/app.full
$ objcopy --only-keep-debug /path/to/app.full /path/to/app.debug
$ file /path/to/app.debug
/path/to/app.debug: ELF 64-bit LSB shared object, ..., with debug_info, not stripped
--only-keep-debug removes the contents of sections that would normally be stripped and retains the debugging sections. The resulting file is intended to accompany a stripped executable; it is not a replacement executable. The exact file wording varies with the file and installed version.
This step creates a new file and does not alter app.full. Keep both until the stripped result has been tested. If the input is valuable, put the copy in a controlled build directory and record its checksum before making more changes.
3. Strip a copy, never an irreplaceable original
Remove debugging symbols and sections from the copy:
$ objcopy --strip-debug /path/to/app.full /path/to/app.stripped
$ file /path/to/app.stripped
/path/to/app.stripped: ELF 64-bit LSB pie executable, x86-64, ..., not stripped
The phrase not stripped in some file output means that ordinary symbols remain. It does not mean that debug sections are still present. Check the sections directly:
$ readelf -S /path/to/app.stripped | grep -E 'debug|gnu_debuglink'
$ readelf -S /path/to/app.full | grep debug
The first command should not list the normal .debug_* sections, while the second should list them when the original was built with debug information. An empty result from the first command is expected at this point.
Warning
objcopy input with no output filename replaces the input using a temporary file and a destructive rename. Always provide a new output path until you have a verified backup. If a command writes a bad result, discard the new file and restore from app.full; do not try to repair a damaged binary in place.
4. Add a link to the separate debug file
Add a GNU debug-link section so debuggers can associate the stripped executable with its companion:
$ cd /path/to
$ objcopy --add-gnu-debuglink=app.debug app.stripped
$ readelf -S app.stripped | grep gnu_debuglink
[28] .gnu_debuglink PROGBITS ...
The referenced debug file must exist when this command runs. Using a relative name such as app.debug also records the name without a build-directory path, which is useful when the executable and debug file will later be installed in recognised debug-file locations. The section contains a checksum of the debug file, so replacing the debug file with unrelated contents will not satisfy the link.
This command changes app.stripped. To undo it, regenerate that file from app.full with the --strip-debug command, then add the link again only after checking the debug file. There is no separate inverse operation in this workflow.
5. Export a raw image or S-record
Use an explicit output target when another tool needs a firmware image rather than an ELF file. A raw binary is a memory dump of the copied sections, starting at the lowest copied load address; symbols and relocation information are discarded:
$ objcopy -O binary /path/to/app.stripped /path/to/app.bin
$ file /path/to/app.bin
/path/to/app.bin: data
The raw output has no ELF header, so file may report only data. Check its size and compare the intended address range with the linker's memory map before programming hardware:
$ stat --format='%n %s bytes' /path/to/app.bin
/path/to/app.bin 123456 bytes
For a Motorola S-record file, use the srec target:
$ objcopy -O srec /path/to/app.stripped /path/to/app.srec
$ head -n 2 /path/to/app.srec
S0...
S1...
The exact records and addresses depend on the input sections. If the image contains only selected sections, add repeated --only-section=PATTERN options, for example --only-section=.text and --only-section=.rodata. Check the result carefully: the manual warns that selecting sections incorrectly can make the output unusable. Do not combine --only-section and --remove-section; their combined behaviour is undefined.
6. Keep architecture and endianness assumptions visible
objcopy can read and write many BFD formats, but it cannot generally change the endianness of an input file. A format with endianness must be copied to an output format with the same endianness or to one without an endianness, such as S-record. --reverse-bytes changes byte order within groups for a specific conversion; it is not a general architecture conversion and should be used only when the receiving format explicitly requires it.
When the input format cannot be identified, set it with --input-target=NAME. Set the destination with --output-target=NAME. For an architecture-less raw input that you are turning into an object file, --binary-architecture=ARCH supplies the output architecture and creates _binary_..._start, _binary_..._end and _binary_..._size symbols. Confirm available names with:
$ objcopy --info
BFD header file version (GNU Binutils for Ubuntu) 2.42
...
Do not infer a valid target from a command that merely exits successfully. Inspect the output with file, readelf -h, or the receiving tool, and keep the original ELF until that check passes.
Done means
- The installed Binutils version and input ELF format were checked.
- A debug file was created before the executable was stripped.
- The stripped executable was written to a new path and its sections were inspected.
- The debug-link section names the matching debug file and its checksum can be checked by the debugger.
- Raw or S-record output was inspected for the expected size, format and address range.
- The original input and a full recovery copy remain available.