Match Renamed C++ Symbols Across Builds with llvm-cxxmap

llvm-cxxmap compares two lists of C++ mangled names and tells you which ones are equivalent, even when the mangling changed underneath you. That is exactly what happens when a standard-library namespace or an ABI tag shifts between builds and every naive symbol diff suddenly looks like the whole binary was rewritten. Allow about fifteen minutes for a small comparison, plus time to gather the symbol lists themselves.

This guide uses the installed llvm-20 package. The executable reports Ubuntu LLVM 20.1.8 on this machine. The local manual has an older generated LLVM header, so every example below is checked against the installed executable as well as the manual. No root access is needed: the tool only reads its inputs and writes to standard output or a file you choose.

1. Check the installed command

Confirm the command on your PATH is the one you intend to use:

$ command -v llvm-cxxmap-20
/usr/bin/llvm-cxxmap-20
$ llvm-cxxmap-20 --version
Ubuntu LLVM version 20.1.8
  Optimized build.

2. Prepare two small symbol lists

For a safe first run, use a temporary working directory and hand-written names. This example compares a function called bar with a function called baz, while keeping one unchanged function as a control:

$ work=$(mktemp -d)
$ printf '%s\n' '_Z3barv' '_Z3foov' > "$work/old.txt"
$ printf '%s\n' '_Z3bazv' '_Z3foov' > "$work/new.txt"
$ cat "$work/old.txt"
_Z3barv
_Z3foov

These are Itanium ABI encodings. The leading _Z marks a mangled C++ name, 3bar and 3foov are length-prefixed name fragments, and the trailing v is a void parameter list. In real work, generate the lists from the binaries or build artefacts you are actually investigating: do not feed source-level names such as foo() unless your symbol-producing command already emitted them in mangled form.

Checkpoint: make sure each input is readable and has one symbol per line.

$ test -r "$work/old.txt" && test -r "$work/new.txt" && echo 'symbol files are readable'
symbol files are readable

3. Describe the equivalent fragments

Create a remapping file with one rule per line, in the format fragmentkind fragment1 fragment2. Use name for name fragments, type for type fragments, and encoding for encoding fragments:

$ printf '%s\n' '# bar and baz are equivalent for this comparison' 'name 3bar 3baz' > "$work/remap.txt"
$ cat "$work/remap.txt"
# bar and baz are equivalent for this comparison
name 3bar 3baz

4. Run the comparison to standard output

Pass the remapping file with -r or --remapping-file, followed by the two symbol files:

$ llvm-cxxmap-20 --remapping-file="$work/remap.txt" "$work/old.txt" "$work/new.txt"
_Z3barv _Z3bazv
_Z3foov _Z3foov

Each output line is a symbol from the first file, a space, then its equivalent from the second. On this installed 20.1.8 executable, identical pairs are still printed even though the local manual claims identical mappings are omitted; treat what the executable actually does as authoritative for scripts on this machine. If a later step only needs the changed names, filter out identical columns explicitly after saving the result, rather than assuming they will not be there.

Warning: a clean, successful run only means the comparison completed. It does not prove a proposed equivalence is correct in the ABI or source code. Review the remapping rules and the matched pairs yourself before they drive a release, a compatibility shim, or a symbol-renaming operation.

5. Save output without losing diagnostics

Use -o or --output when a later command needs a stable result file:

$ llvm-cxxmap-20 -r "$work/remap.txt" -o "$work/matches.txt" "$work/old.txt" "$work/new.txt"
$ cat "$work/matches.txt"
_Z3barv _Z3bazv
_Z3foov _Z3foov

The output file holds the matches; normal warnings still go to standard error. Avoid writing straight over a result you might need to compare later. Write to a new name first and check it before replacing the old file:

$ llvm-cxxmap-20 -r "$work/remap.txt" -o "$work/matches.new" "$work/old.txt" "$work/new.txt"
$ test -s "$work/matches.new" && mv "$work/matches.new" "$work/matches.txt"

Recovery: that final mv changes the directory entry, so skip this pattern if another process is reading the result concurrently. To undo it, restore a backup or remove the new result with your normal reviewed cleanup procedure. The comparison itself never modifies the two input files or the remapping file.

6. Turn on warnings for incomplete or ambiguous matches

Add -Wincomplete when every symbol in the first file ought to have a counterpart. Add -Wambiguous when the second file must not contain multiple distinct candidates for one remapped symbol:

$ llvm-cxxmap-20 -r "$work/remap.txt" -Wincomplete -Wambiguous \
    "$work/old.txt" "$work/new.txt"
_Z3barv _Z3bazv
_Z3foov _Z3foov

Neither warning fixes the data for you; they are most useful against real lists, where the file and line references point straight at the problem.

Checkpoint: treat either warning as a review stop. Check whether the build genuinely removed a symbol, whether the remapping rule is too broad, or whether the input list has duplicates or an unintended variant.

7. Stay inside the supported ABI

The local manual limits remapping to C++ names following the Itanium C++ ABI, which covers Clang C++ targets other than Windows. It does not make a Windows MSVC-mangled list comparable just because both files came from C++ builds. For unsupported mangling, reach for a tool and rules built for that ABI instead of forcing an encoding rule to do the job.

If the command complains that the remapping file is missing, check the option first:

$ llvm-cxxmap-20 "$work/old.txt" "$work/new.txt"
llvm-cxxmap-20: for the --remapping-file option: must be specified at least once!

If a remapping line is rejected, check that the first word is exactly name, type or encoding, and that the fragments suit the kind you picked. Keep the failing file itself unchanged while diagnosing it; correct a copy in the temporary directory rather than editing a shared build recipe.

Done means