Trace a Crash Address Back to C Source with addr2line

A crash log hands you a bare hex address like 0x401136 and nothing else useful, and addr2line turns that into a source file and line number. This also covers making it useful for real logs: function names, demangling, standard input and inline-frame output. Allow about fifteen minutes.

You need GNU addr2line, an executable or relocatable object, and matching debugging information. The examples were checked with GNU Binutils 2.42, package version 2.42-4ubuntu2.10, on this machine. This is a read-only workflow: it does not modify the binary, source tree or core dump, and no command below needs sudo. The cross-prefixed commands aarch64-linux-gnu-addr2line and x86_64-linux-gnu-addr2line expose the same installed manual and are useful when you need to choose a target format explicitly.

1. Check the installed command

Confirm which binary your shell will run and record its version:

$ command -v addr2line
/usr/bin/addr2line
$ addr2line --version
GNU addr2line (GNU Binutils for Ubuntu) 2.42

Checkpoint: if the command is missing, install the distribution's Binutils package through your normal system-management process. Do not copy a random executable into a production debugging environment, and keep the tool version in your incident notes because output details can vary between releases.

2. Build or locate a binary with debug information

addr2line can only report useful source locations when the object contains, or can find, debugging information. For a small reproducible test, create this source file:

#include <stdio.h>

static int add_one(int value)
{
    return value + 1;
}

int main(void)
{
    printf("%d\n", add_one(41));
    return 0;
}

Compile it with symbols and without position-independent executable layout, so the demonstration address stays stable for this build:

$ gcc -g -O0 -fno-pie -no-pie -o /tmp/addr2line-demo /tmp/addr2line-demo.c
$ nm -n /tmp/addr2line-demo | grep -E ' add_one$| main$'
0000000000401136 t add_one
0000000000401149 T main

The address is not universal. Optimisation, compiler version, link order and PIE can all change it: treat 0x401136 as a value from this particular build, not one to paste into a different executable.

3. Translate one address

Pass the executable with -e, followed by the hexadecimal address:

$ addr2line -e /tmp/addr2line-demo 0x401136
/tmp/addr2line-demo.c:4

The normal output shape is FILENAME:LINENO. The address points into add_one, so the result identifies the return statement's source line in this build. You can pass several addresses and get one result per input:

$ addr2line -e /tmp/addr2line-demo 0x401136 0x401149
/tmp/addr2line-demo.c:4
/tmp/addr2line-demo.c:9

Checkpoint: compare the file path and line with the exact source revision used to build the crashing program. A matching line number from a different checkout is not proof you have the right source.

4. Add the function name and keep the address visible

Use -f for the containing function and -a to print the input address before the other fields:

$ addr2line -e /tmp/addr2line-demo -a -f 0x401136
0x0000000000401136
add_one
/tmp/addr2line-demo.c:4

For logs and terminals, -p is often easier to scan because it puts each result on one line:

$ addr2line -e /tmp/addr2line-demo -p -f 0x401136
add_one at /tmp/addr2line-demo.c:4

Add -s when long build paths are distracting and only the source basename matters:

$ addr2line -e /tmp/addr2line-demo -s -p 0x401136
addr2line-demo.c:4

Do not use -s when two different directories hold files with the same name: the removed path may be exactly the clue that distinguishes generated code from the real source.

5. Feed crash addresses through standard input

If addresses arrive from a log parser or another command, omit them from the command line and send them on standard input:

$ printf '%s\n' 0x401136 0x401149 | addr2line -e /tmp/addr2line-demo -f -p
add_one at /tmp/addr2line-demo.c:4
main at /tmp/addr2line-demo.c:9

This mode also handles symbol-plus-offset input, useful when a log identifies a symbol and a position within it:

$ addr2line -e /tmp/addr2line-demo -a -f -p 'add_one+0x4'
0x000000000040113a: add_one at /tmp/addr2line-demo.c:4

Quote an input containing + when it comes from a shell variable or external text, and validate log data before passing it into a larger command. addr2line interprets its own options; do not concatenate untrusted input into the option area.

6. Interpret missing or incomplete results

Test an address that is not in the sample program:

$ addr2line -e /tmp/addr2line-demo -f -p 0xdeadbeef
?? at ??:0

For a PIE executable or shared library, the address in a crash report may be a runtime address. Establish the module's load mapping and calculate the address relative to the file before using addr2line; do not guess an offset. If the crash log names a module, use the matching build of that module with -e.

The default executable is a.out if you omit -e, which is an easy distraction: always name the file explicitly in incident commands. For a relocatable object, use -j SECTION when the value is an offset relative to a named section rather than an absolute address, and confirm the section and offset with objdump -h before translating.

7. Inspect C++ names and inline frames

Use -C to demangle compiler-generated C++ function names. It only affects demangling, so it cannot restore source information already removed from the binary:

$ addr2line -C -f -p -e /path/to/program 0xADDRESS

Use -i when optimisation has inlined one function into another: the tool prints the direct location and then the enclosing inline scopes. With -p, inline entries print on separate lines marked as inlined. Keep the compiler's debug settings consistent with the binary under investigation.

Warning: demangling has a recursion limit enabled by default. The manual documents -R as the explicit limited form and -r as the unlimited form. Avoid -r unless you have a specific, trusted symbol that needs it: disabling the limit can allow stack exhaustion while processing a crafted or unusually deep name.

Done means