Trace Crash Addresses Back to C and C++ Source with llvm-symbolizer-18
You will turn a machine address from a native crash, backtrace or profiler into a function name and source location. This guide uses the llvm-symbolizer-18 package installed on Ubuntu, reported here as LLVM 18.1.3. Allow about 10 minutes if you already have the executable and its debug information, or longer if you need to find a matching debug file.
The route
Jump straight to the step you need, or tick off Done means at the end.
Before you start
- Have the exact object file that was running when the address was recorded. A rebuilt binary, a different shared library or a different architecture can make an address point somewhere else.
- Keep debug information available. Compile your own test program with
-g, or obtain the unstripped executable or separate debug package for a deployed binary. - Have one address in hexadecimal, such as
0x0000000000001170. A raw address from a shared library or position-independent executable may need the load offset corrected first.
No elevated privileges are needed. The command reads files and writes only to standard output. Do not paste sensitive crash logs into a remote service merely to symbolise them; local symbolisation keeps paths and function names on the machine where you run it.
Checkpoint 1: confirm the installed tool
Check that the versioned executable is the one you intend to use:
llvm-symbolizer-18 --version
On the system used for this guide, the result begins:
llvm-symbolizer
Ubuntu LLVM version 18.1.3
The package is llvm-18. If your shell says the command is not found, install the package using your normal package-management process. That is a system change, so check your distribution's repository and maintenance policy before doing it.
Step 1: symbolise one address
Pass the executable with --obj and put the address after it. This example uses a deliberately obvious placeholder path:
llvm-symbolizer-18 --obj=/path/to/my-program 0x0000000000400490
With usable debug data, the default LLVM output has the function first and the source location on the next line:
some_function()
/path/to/source.c:42:7
The exact function, path, line and column depend on your binary. If the address cannot be matched, the tool reports that source information was not found. That normally means the object, address, load adjustment or debug data does not match, not that the source line is necessarily invalid.
For a quick local check, this command uses the executable itself. It does not modify it:
llvm-symbolizer-18 --obj=/usr/bin/llvm-symbolizer-18 0x0000000000000000
An arbitrary address such as zero is expected not to resolve. Use a real address from a crash report for useful output.
Step 2: process addresses from a file or pipe
If you omit positional addresses, llvm-symbolizer-18 reads them from standard input. This is useful when a crash handler or another command already produces one address per line:
printf '%s\n' 0x0000000000400490 0x00000000004004d0 |
llvm-symbolizer-18 --obj=/path/to/my-program
By default, each result is separated in the readable LLVM format. If your input contains addresses from several files, include the object name on each input line instead of using one global --obj:
cat addresses.txt
FILE:/path/to/first-program 0x0000000000400490
FILE:/path/to/plugin.so 0x0000000000001234
Then run:
llvm-symbolizer-18 < addresses.txt
FILE: makes the meaning explicit; an unprefixed input name is also treated as an object-file path. A BUILDID: prefix instead asks for lookup by hexadecimal build ID. The latter needs a matching debug-binary search setup, so use an explicit object path when you have one.
Step 3: choose output for people or programs
Use pretty output while investigating a single crash:
llvm-symbolizer-18 --obj=/path/to/my-program \
--pretty-print 0x0000000000400490
The result is compact, for example some_function() at /path/to/source.c:42:7. Inlined calls are included by default. Add --no-inlines when you need only the outer frame, or --verbose when you need function-start and line metadata.
For a script, request JSON rather than parsing human-readable lines:
llvm-symbolizer-18 --output-style=JSON \
--obj=/path/to/my-program 0x0000000000400490
With command-line addresses, the result is a JSON array. With addresses supplied through standard input, the tool emits a series of JSON objects. That distinction matters if your consumer expects one document rather than a stream.
Step 4: make paths and addresses comparable
Absolute source paths can be noisy in logs. --basenames prints only the source filename, while --relativenames prints the path relative to the compilation directory:
llvm-symbolizer-18 --obj=/path/to/my-program \
--relativenames 0x0000000000400490
Do not use either option as a repair for missing debug data. They change how a resolved path is displayed.
When the recorded address is relative to a loaded image rather than the address range expected by the object file, use --adjust-vma with the verified offset:
llvm-symbolizer-18 --obj=/path/to/my-program \
--adjust-vma=0x1000 0x0000000000400490
Work out the offset from the crash report and the process's memory map. Do not guess it. An incorrect adjustment silently sends the lookup to the wrong place.
Common failure checks
- No source information: confirm that the object is the exact build that produced the address and that its debug information is present. A stripped executable may need its separate debug file and
--debug-file-directoryor--fallback-debug-path. - Unexpected function names: demangling is enabled by default. Use
--no-demangleto inspect the linker-level name, or--functions=shortand--functions=noneto control function-name detail. - Addresses from a shared object: check whether the logged value is an absolute process address or an offset inside the module. Apply
--adjust-vmaonly after confirming the arithmetic. - Different columns or frames: optimisation and inlining change the mapping. Compare the compiler flags and build ID, not only the filename.
- Remote debug lookup:
--debuginfodcan search servers named byDEBUGINFOD_URLSwhen the build supports it. Treat those servers as an information boundary and prefer local debug files for confidential binaries.
Done means
llvm-symbolizer-18 --versionreports the expected LLVM 18 installation.- The exact object file and matching debug information are identified.
- A test address resolves to the expected function and source location, or the unresolved result has a documented address, build or debug-data explanation.
- Your chosen output mode is deliberate: readable LLVM or pretty output for investigation, JSON for automation.
- Any VMA adjustment and path shortening option is recorded alongside the command that produced the result.