Split Debug Files for Embedded Applications: Keep Stripping and Build-ID Lookup in Agreement

Create the separate debug file from the same fully linked executable that you strip, then verify both the pairing metadata and the debugger’s actual lookup. Matching filenames alone are insufficient. Build-ID lookup and .gnu_debuglink lookup use different evidence and should have independent controls.

This guide pins GNU Binutils 2.44 and GDB 16.3 documentation. GCC 14.2.0 and Binutils 2.44 produced and inspected an owned x86-64 fixture. GDB was not installed, so debugger discovery, line lookup and breakpoint tests remain proposed. No running target was attached.

Preserve the original before stripping

Keep the fully linked ELF executable with its debug information as the source of the split pair. Record compiler/linker identity, build options and the original digest. Do not attempt to reconstruct missing DWARF from a previously stripped executable.

The Binutils 2.44 objcopy documentation specifies –only-keep-debug for fully linked files. It is not a general replacement for compiler split-DWARF handling of individual object files. Keep .dwo/.dwp workflows separate if the build uses them.

The executed fixture linked a small program with -g, -O0 and -Wl,–build-id=sha1. It retained app.full untouched and produced two additional files:

objcopy --only-keep-debug app.full app.debug
objcopy --strip-debug app.full app.stripped
objcopy --add-gnu-debuglink=app.debug app.stripped

These commands operated only on newly created fixture artifacts. For a real release, use the target-compatible utilities and preserve the original before any packaging transformation.

An untouched full ELF produces a stripped executable and matching debug file, with verified CRC and build-ID pairing followed by separately proposed debuglink and build-ID lookup controls.
Original split-debug artifact and lookup map, distinguishing executed pairing checks from unexecuted GDB tests.

Verify what each identity mechanism contains

The GDB 16.3 separate-debug-file chapter defines .gnu_debuglink as a basename, alignment padding and a four-byte CRC of the complete debug file. The CRC uses the executable’s byte order. It is not just a hash of the debug file’s symbol names.

The host fixture extracted .gnu_debuglink, confirmed the app.debug basename and independently recomputed its CRC over the complete debug file. The stored and calculated values matched. A separately built wrong debug file had a different CRC.

Read build IDs independently with readelf -n. The full executable, stripped executable and matching debug file retained the same identifier in this fixture. The deliberately different build had a different identifier.

The GNU ld build-ID documentation warns that a build ID is not a checksum of the file’s current contents. Later transformations can change bytes while preserving the original linked-file identifier. Keep final file digests as well; neither the ID nor debuglink CRC is a signature authenticating an approved release.

Check debug content, not just matching labels

The executed section inspection found .debug_info in app.full and no .debug_info in app.stripped. The stripped fixture still executed its simple functional check successfully. That confirms the scoped transformation preserved that program’s behavior, not the correctness of every stripping option.

–strip-debug does not mean that every ordinary symbol table is removed. A function name can remain visible even when source-line information is absent. The debugger acceptance test must therefore request a known source line or local-variable type, not merely find a function’s name.

The separate file must contain the required debug information and correspond to the same section layout. A copied filename, preserved note or empty debug artifact is not sufficient. Inspect required DWARF sections and use a deliberately known function as the lookup target.

Qualify debuglink discovery in isolation

In a disposable analysis directory, place the stripped executable and correctly named debug file together. Start a fresh GDB session with user initialization disabled, automatic network debug retrieval disabled and an explicit local debug-directory setting.

Load the executable and request source-line information for the fixture’s marker function. Record which separate file was actually opened. This test is proposed here; no GDB lookup result is claimed.

Then remove the matching file from that test layout and require the expected source information to become unavailable. In another case, put the wrong build’s debug file under the expected basename and require rejection rather than plausible but incorrect line information.

Prevent alternate successful lookup routes from masking these controls. For the debuglink-only test, use an isolated executable copy without its build-ID lookup path and an empty global debug directory. Do not change the retained release artifacts or modify installed system debug files.

Qualify build-ID discovery separately

For build-ID lookup, GDB searches a .build-id hierarchy beneath its global debug directories. The first two hexadecimal ID characters name the subdirectory; the remaining characters form the filename followed by .debug.

Construct that layout inside an owned fixture directory, using the actual retained ID. Remove the debuglink section only from the dedicated lookup-test copy, and ensure no adjacent matching debug file remains. Select the fixture root with set debug-file-directory and test the known source-line lookup.

Repeat with the expected ID path absent, then with the wrong build’s debug file at that path. A correct pathname is not permission to accept mismatched contents. Preserve diagnostics and the exact file GDB considered; those results distinguish failed discovery from failed identity validation.

Keep source discovery separate from symbol discovery

Finding the correct debug file does not guarantee the original source tree exists at its recorded paths. If symbols load but listing source fails, qualify source-directory mapping separately. Do not substitute a nearby source revision merely because its filenames match.

Cross-target debugging also requires the correct architecture support, runtime shared libraries and relocation context. This article’s artifact pairing does not establish those conditions, and it does not authorize attachment to a production process.

Close with a paired-artifact receipt

Retain the original, stripped file and debug file hashes; all three build IDs; debuglink basename and CRC; section checks; and debugger lookup outcomes. Here the artifact creation, CRC, ID, section and simple execution checks passed, while GDB discovery remains untested.

A release is ready for symbol recovery only after its actual delivered pair passes the intended debugger route and negative controls. A successful objcopy invocation is a useful construction step, not the final lookup proof.

Similar Posts