Demangle C++ Symbols from Binaries with llvm-cxxfilt-18
A linker error full of names like _Z3foov is useless until llvm-cxxfilt-18 turns it back into foo(). It works as command-line arguments or inside a pipeline, which makes it handy for symbol tables and crash reports too. The examples use Ubuntu's llvm-18 package, version 18.1.3, and take about five minutes to try. No elevated privileges are needed.
The route
Jump straight to the step you need, or tick off Done means at the end.
What you need
Install or otherwise obtain the llvm-cxxfilt-18 executable before starting. On the machine used for these examples, it is provided by the llvm-18 package:
command -v llvm-cxxfilt-18
llvm-cxxfilt-18 --version
Expected output includes:
/usr/bin/llvm-cxxfilt-18
llvm-cxxfilt-18
Ubuntu LLVM version 18.1.3
Checkpoint
If the first command prints nothing, stop here. The command is not available on your PATH; changing the shell configuration or installing packages is separate from demangling.
1. Demangle named symbols
Pass one or more symbol names after the command. Each result is printed on its own line, in the same order as the arguments.
llvm-cxxfilt-18 _Z3foov _Z3bari not_mangled
The output is:
foo()
bar(int)
not_mangled
Example
An input that is not recognised is printed unchanged, as not_mangled is above. That is a useful property when processing mixed symbol lists: an unchanged line means "no demangling was applied", not necessarily that the command failed.
2. Demangle a stream from standard input
With no names on the command line, llvm-cxxfilt-18 reads names interactively from standard input. In a script, make the input explicit with a pipe:
printf '%s\n' '| _Z3foov *** _Z3bari *** not_mangled |' | llvm-cxxfilt-18
Expected output:
| foo() *** bar(int) *** not_mangled |
Input lines are split around characters that cannot belong to an Itanium mangled name. Letters, numbers, full stops, dollar signs and underscores are retained as possible name characters; separators are copied to the output. This lets you feed text containing punctuation, but it does not parse arbitrary compiler diagnostics or identify which words are definitely symbols.
For a real symbol table, keep the producer and consumer in one pipeline. For example, if symbols.txt contains one mixed line per record:
llvm-cxxfilt-18 < symbols.txt > symbols-readable.txt
Warning
This creates or replaces symbols-readable.txt. The command does not alter symbols.txt, but shell redirection can overwrite an existing destination without asking. Check the destination name before pressing Enter, or choose a new output file.
To undo this example, remove only the output file if it is disposable:
rm -- symbols-readable.txt
That removal is irreversible unless another copy exists. Do not use it for a file you need to keep.
3. Choose how much detail to show
The default auto format detects the mangling style. You can request the GNU/Itanium style explicitly:
llvm-cxxfilt-18 --format=gnu _Z3foov
It prints foo(). The short form is -s gnu. Use this when a script has already established the format; otherwise, leave detection enabled.
To omit function parameters and return types, use --no-params or -p:
llvm-cxxfilt-18 --no-params _Z3foov _Z3bari
foo
bar
- Shorter, but lossier. This form is convenient for a quick list, but it throws away information that can distinguish overloaded functions. Keep the default output when investigating a compile or link failure.
- Types too. Use
--typesor-twhen the input may contain encoded type names as well as function names. - Check what your build supports. Use
--helpor-hto inspect the options provided by this installed executable.
4. Handle leading underscores deliberately
--strip-underscore (short form -_) removes one leading underscore before demangling. --no-strip-underscore (short form -n) preserves it. On ordinary non-Mach-O hosts, preserving the underscore is the default; Mach-O based hosts default to stripping it.
Warning
Do not add one of these switches just because a symbol looks unfamiliar. First check how the producer recorded the name and whether the target is Mach-O. Stripping the wrong underscore can turn valid input into a name that cannot be demangled.
You can check the local default without changing any files:
llvm-cxxfilt-18 --no-strip-underscore _Z3foov
The expected result on this Linux machine is foo(). The option controls preprocessing of each input name; it does not rename anything in the binary.
Common traps and failure checks
- Nothing changes. The input may already be readable, may use a mangling scheme this tool does not recognise, or may have been damaged by an earlier text filter. Try one known value such as
_Z3foovbefore diagnosing the larger pipeline. - Arguments and files look different. Command-line arguments produce one output line per argument. Standard input preserves separators and can demangle several candidates within one line.
- Shell expansion changes a name. Quote names containing
$, spaces or wildcard characters. For example, usellvm-cxxfilt-18 'name$part', not an unquoted value. - A script continues after a bad invocation. The program returns zero unless it encounters a usage error. Capture its status when the distinction matters:
llvm-cxxfilt-18 --bad-option; printf 'status=%s\n' "$?".
Done means
- Version confirmed.
llvm-cxxfilt-18 --versionreports the executable you intended to use. - Basic case works. A known symbol such as
_Z3foovbecomesfoo(). - Separators preserved. Your pipeline preserves separators and leaves unrecognised text visible.
- Options chosen deliberately. You selected parameter and underscore behaviour knowingly, rather than treating an unchanged name as a fatal error.