Recover File Names and Lines from Mono Stack Traces
You will finish with a repeatable way to turn a Mono stack trace containing <filename unknown>:0 into one with source file names and line numbers, when matching managed symbols are available. The examples use mono-symbolicate from mono-devel 6.8.0.105+dfsg-3.6ubuntu2, the version installed on this machine.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about fifteen minutes. You need a shell, a stack-trace text file, and the .exe or .dll files that were built with their managed debugging symbols. The normal workflow is unprivileged. Do not use sudo unless your input or symbol files are genuinely unreadable, and do not copy sensitive production traces into a shared temporary directory.
1. Confirm the installed command
Check which executable will run and record the package version. These are read-only commands:
$ command -v mono-symbolicate
/usr/bin/mono-symbolicate
$ dpkg-query -W -f='${Package} ${Version}\n' mono-devel
mono-devel 6.8.0.105+dfsg-3.6ubuntu2
$ mono-symbolicate --help
Usage: symbolicate [options] <msym dir> <input file>
symbolicate [options] store-symbols <msym dir> [<dir>]+
Available options:
-h, --help Show this help
-q Quiet, warnings are not displayed
-v Verbose, log debug messages
The help text calls the operation symbolicate, but the installed command name is mono-symbolicate. Its two modes are easy to confuse: one reads a stack trace, while store-symbols prepares a symbol directory from directories containing managed assemblies.
Checkpoint
If command -v finds another installation, stop and check that its version matches the files that produced the trace. Different installations can have different symbol-reader behaviour.
2. Keep the original trace and inspect its shape
Work on a copy, particularly when a trace may contain customer data, paths or arguments. The tool writes the converted text to standard output, so the input is not modified.
$ cp --preserve=all /path/to/stacktrace.txt /path/to/stacktrace.txt.original
$ sed -n '1,40p' /path/to/stacktrace.txt
at Example.Worker:Run () [0x00000] in <filename unknown>:0
The placeholder is the useful symptom: Mono has a managed frame but cannot show its source location from the information currently available. Keep the trace as text. Do not pass a directory where a file is expected, and do not expect this tool to repair native frames or reconstruct symbols that were never generated.
3. Build a separate symbol directory
Make an empty directory for the symbol store, then add the directories containing the matching managed assemblies:
$ mkdir -p /path/to/msym
$ mono-symbolicate store-symbols /path/to/msym /path/to/release/bin /path/to/release/plugins
$ find /path/to/msym -maxdepth 2 -type f -print
The command accepts one or more source directories after store-symbols. Supply directories, not individual assembly paths. The assemblies must correspond to the build that produced the trace. A same-named .dll from another build can be worse than no symbol because it may lead you to trust an incorrect file and line.
This step writes into /path/to/msym. Treat that directory as disposable derived data. If you need to start again, stop the command first, move the old directory aside, and create a new one rather than mixing releases:
$ mv /path/to/msym /path/to/msym-old
$ mkdir /path/to/msym
That move is reversible until you deliberately remove the old directory. No elevated privilege is needed when the paths belong to you.
Checkpoint
The store command should return to the prompt without an error. An empty source directory can complete successfully but will not provide useful locations, so verify that the intended assembly files are really present before continuing.
4. Symbolicate to standard output
Pass the symbol directory first and the trace file second. Save the output to a new file so a failed or incomplete run cannot overwrite the original:
$ mono-symbolicate /path/to/msym /path/to/stacktrace.txt > /path/to/stacktrace-symbolicated.txt
$ test -s /path/to/stacktrace-symbolicated.txt && sed -n '1,40p' /path/to/stacktrace-symbolicated.txt
at Example.Worker:Run () [0x00000] in /srv/build/Worker.cs:42
The exact frame text depends on the assembly, source paths and symbols. The important change is that a matching frame can now contain a file and line instead of <filename unknown>:0. Frames without a match remain unresolved. A successful process exit is not a promise that every frame was symbolicated.
Compare the two files rather than deleting the source trace:
$ diff -u /path/to/stacktrace.txt /path/to/stacktrace-symbolicated.txt
If the output is useful, archive it with the build identifier and keep the original alongside it. The output file can be removed later with rm, but that is irreversible unless you can regenerate it from the original trace and symbol store.
5. Diagnose unresolved frames
First check the basics without changing anything: the input is readable, the symbol directory exists, and its contents came from the same build:
$ test -r /path/to/stacktrace.txt && echo 'trace readable'
trace readable
$ test -d /path/to/msym && find /path/to/msym -type f | head
$ ls -l /path/to/release/bin
If a file is missing, fix the path rather than adding sudo. On the installed version, a missing input file produces a non-zero status and a .NET file-not-found exception. That is an input problem, not evidence that the symbols are invalid.
Use -v when you need debug messages, or -q when a script must suppress warnings:
$ mono-symbolicate -v /path/to/msym /path/to/stacktrace.txt > /path/to/stacktrace-symbolicated.txt
$ mono-symbolicate -q /path/to/msym /path/to/stacktrace.txt > /path/to/stacktrace-symbolicated.txt
Do not use -q while investigating a failed match, because it hides warnings that may explain what the tool could not load. Do not treat -v output as part of the symbolicated trace: the trace itself is written to standard output, so keep diagnostics separate if you capture both streams.
6. Automate only after a manual match
Once the manual result is correct, a script can process a batch of traces. Use a temporary destination and replace the final report only after the command succeeds:
input=/path/to/stacktrace.txt
output=/path/to/stacktrace-symbolicated.txt
temporary="${output}.new"
if mono-symbolicate /path/to/msym "$input" > "$temporary"; then
mv -- "$temporary" "$output"
else
status=$?
printf 'symbolication failed with status %s\n' "$status" >&2
rm -f -- "$temporary"
exit "$status"
fi
This preserves the previous report if the command fails. The symbol directory is not a configuration file and does not affect Mono or the application at runtime. Do not point the script at a live deployment directory that may change during analysis; copy the exact release assemblies to a controlled, access-restricted location first.
Done means
- You confirmed the installed
mono-symbolicatecommand and package version. - The original trace remains untouched and the output goes to a new file.
- A dedicated symbol directory was built from assemblies matching the trace's build.
- Resolved frames now show source file names and line numbers where symbols support them.
- You understand that unresolved frames and native frames can remain unchanged.
- Any batch script uses a temporary output and leaves the previous report recoverable.