Trace MinGW-W64 Addresses Back to Source with addr2line
addr2line turns a raw hex address from a crash report into a source file and line number, or tells you there is not enough debug data to do that. It also accepts symbol plus offset, reads addresses from standard input, and can show demangled function names.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about fifteen minutes for a first investigation. You need the exact executable or relocatable object that produced the address, a shell, and the binutils-mingw-w64-x86-64 package. No elevated privileges are normally needed: addr2line only reads the object and writes to standard output.
This guide uses GNU Binutils 2.41.90.20240122, installed here as package version 2.41.90.20240122-1ubuntu1+11.4. The x86_64-w64-mingw32ucrt-addr2line alias has the same installed manual page and purpose.
1. Check the installed command
Confirm the binary and its version before you trust any output. These are ordinary, read-only commands:
$ command -v x86_64-w64-mingw32-addr2line
/usr/bin/x86_64-w64-mingw32-addr2line
$ x86_64-w64-mingw32-addr2line --version
GNU addr2line (GNU Binutils) 2.41.90.20240122
$ x86_64-w64-mingw32-addr2line --help
Usage: x86_64-w64-mingw32-addr2line [option(s)] [addr(s)]
Tip
Keep the command name and version beside the crash report. If the report came from a different toolchain or build, matching the executable matters more than matching the utility version.
2. Resolve one address against the executable
Pass the executable with -e, then give a hexadecimal address. Use the address exactly as reported by the debugger, apart from any notation the tool will not accept:
$ x86_64-w64-mingw32-addr2line -e /path/to/program.exe 0x401234
src/worker.c:87
The normal output shape is FILENAME:LINENO. This example location is illustrative, not a promised result. Your executable must contain matching debugging information for a useful path and line.
Omit -e and the installed command falls back to looking for a.out. That default is easy to miss when a command gets copied from a generic Unix example, so name the executable explicitly in scripts and incident notes.
Checkpoint
On an object without a matching location, the installed command reports question marks and line zero:
$ x86_64-w64-mingw32-addr2line -e /usr/bin/x86_64-w64-mingw32-addr2line 0x0
??:0
That result is not a source location. It usually means the address is wrong for that file, the symbols or debug data are missing, or the address needs interpreting relative to a loaded image rather than used as a raw file address.
3. Add the function name and keep output on one line
For crash logs, a function name makes a result far easier to scan. Use -f for the containing function and -C to demangle C++ names. Add -p when another program will consume one location per line:
$ x86_64-w64-mingw32-addr2line -f -C -p -e /path/to/program.exe 0x401234
process_item at src/worker.c:87
With an unknown location, the formatting stays just as predictable:
$ x86_64-w64-mingw32-addr2line -f -C -p -e /usr/bin/x86_64-w64-mingw32-addr2line 0x0
?? ??:0
Do not treat a readable function name as proof the line number is trustworthy. Check the executable and build actually correspond to the crash, and that the reported address has been adjusted correctly for the loader's relocation.
4. Process addresses from a pipe
Give it no addresses on the command line and addr2line reads hexadecimal addresses or symbol plus offset from standard input instead. Handy for a debugger export or a list of return addresses:
$ printf '%s\n' '0x401234' '0x4012a0' |
> x86_64-w64-mingw32-addr2line -f -C -p -e /path/to/program.exe
process_item at src/worker.c:87
flush_queue at src/queue.c:142
Each input address produces output in the same order. Add -a when you need the original address echoed before the decoded location:
$ printf '%s\n' '0x0' | x86_64-w64-mingw32-addr2line -a -f -C -p -e /usr/bin/x86_64-w64-mingw32-addr2line
0x0000000000000000: ?? ??:0
Keep the pipe input controlled. addr2line treats each line as data, but the shell expansions that build that input can still execute before it even starts.
5. Account for sections and inlined calls
Addresses in a relocatable object may be offsets within a section rather than absolute addresses. Supply the section name with -j:
$ x86_64-w64-mingw32-addr2line -j .text -e /path/to/object.o 0x120
src/worker.c:87
Use -i when compiler inlining may hide the real call chain; it prints the location for the containing inline scopes after the main location. Combine it with -f if you need each function name too. Without suitable inline debug data, there may be nothing extra to show.
Use -s to strip directory names when a report needs to travel between build machines. That shortens the output, but it also removes context you might need to identify the right source tree.
6. Handle missing data without damaging the evidence
Warning
Do not overwrite the executable or its separate debug file while investigating. The address-to-line result depends on the exact build. Copy the files to a working directory if you need to experiment, and record their checksums before analysis:
$ sha256sum /path/to/program.exe
$ x86_64-w64-mingw32-addr2line -e /path/to/program.exe -f -C -p 0x401234
If every address gives ??:0, verify the path and format first, then check whether the executable was stripped or built without debug information. The command can list its supported object formats with --help; this installed build covers PE, PEI, ELF and several others. Use -b only when automatic format detection is unsuitable, and pass the BFD target name that matches the file.
Do not reach for -r casually. Demangling has a recursion limit enabled by default to reduce the risk of stack exhaustion from hostile or unusually complex names. -r disables that limit; -R explicitly enables it. Both options only matter alongside -C.
Done means
- Recorded the toolchain. The utility version and the exact executable were noted.
- Matched the context. Addresses were interpreted in the correct address or section context.
- Got readable output.
-f -C -pproduced function-aware results where the data allowed it. - Treated gaps as gaps. Unknown results like
??:0were treated as missing or mismatched evidence, not as source locations. - Kept the evidence intact. The executable and debug files remain unchanged and can be rechecked from their recorded checksums.