Skip to content
← All posts
· Delta1 Labs .NETDecompilerDebugging

How to debug a .NET app when you don't have the source

No source for that NuGet package or legacy DLL? A .NET decompiler turns the assembly back into readable C# you can navigate, compare across versions and export to a project — so you can find the bug without the original code.

To debug a .NET app when you don’t have the source, open the compiled assembly in a decompiler like Glass.NET: it reads the IL and metadata inside the DLL or EXE and reconstructs readable C# you can navigate, search and export — no source files, PDBs or special build required. That turns “I have no idea what this dependency is doing” into “let me read the method and find out.” Here is how to work through the common no-source scenarios, and where the honest limits are.

Why you end up debugging without source in the first place

It happens to everyone eventually:

  • A bug in a third-party NuGet package. The behavior is wrong, the docs don’t explain it, and the package ships without symbols. You need to see what the method actually does, not what you assume it does.
  • A legacy DLL with no source. An internal library from a team that has moved on, a component whose repository is long gone, or a vendor assembly you depend on and cannot get source for.
  • No symbols for a production build. A crash comes in with a stack trace pointing into an assembly you can’t step into, and you need to understand the code path.

In every case the compiled assembly is right in front of you — and because .NET ships IL plus rich metadata, that assembly carries a near-complete, recoverable description of the program. A decompiler is how you read it.

How does a decompiler get you readable C#?

A .NET assembly is not machine code. The compiler emits Intermediate Language — a stack-based instruction set — plus metadata naming every type, method, field and parameter. The decompiler runs the compiler in reverse: it reads the IL, matches its patterns back to C# constructs (a foreach, an async method, a LINQ query), and pretty-prints the result. For most assemblies the output is accurate and often recompilable. (The full walkthrough is in how to decompile a .NET DLL to C#.)

Open the assembly (in Glass: File ▸ Open Assembly, or drag it onto the window), expand the namespaces ▸ types ▸ members tree, and click the type you care about to decompile it on demand. Within seconds you are reading C# for code you never had the source to.

Reading one method is easy; understanding how it fits together is the real debugging work. This is where a navigation-first decompiler earns its place, and it is what Glass.NET is built around.

Follow the logic with Go to Definition and Find All References

When a method calls into something you don’t understand, Go to Definition (F12) jumps straight to it, and Find All References (Shift+F12) shows every caller — so you can answer “what actually calls this, and with what?” instead of guessing. References resolve from the decompiler’s own syntax tree, so you land on the right symbol, not a text match.

See the shape with call and type hierarchy

Call hierarchy and type hierarchy let you walk up and down the structure — who calls the buggy method, what overrides what, where an interface is implemented. For tracing how a value reaches the code that mishandles it, this is far faster than scrolling.

Jump anywhere with Go To All

Go To All (Ctrl+T) is a fuzzy quick-open across every type and member in the assembly. When a stack trace names a method, type it in and you are there. A namespace ▸ type ▸ member breadcrumb and navigation history keep you oriented as you move.

Drop to IL when the C# looks surprising

Sometimes the reconstructed C# does something you didn’t expect. Switch the same member to the IL view to confirm what the code actually does at the instruction level — invaluable when a decompiler’s pattern-matching produces something that reads oddly but is technically correct.

Finding a regression: compare two versions

One of the most practical no-source debugging moves: a dependency worked in version 2.1 and broke in 2.2, and there is no changelog that explains it. Decompile both assemblies and compare them side by side. Glass has an assembly compare with a line diff, so you can see precisely what changed in the method you suspect — the altered condition, the reordered call, the new null check that isn’t null-safe. Reading the diff of the reconstructed C# often pinpoints a regression in minutes, without any source at all.

Getting the code onto disk

When you want to grep it, annotate it, or open it in Visual Studio, export to a buildable project (in the GUI, typically File ▸ Export to Project), which writes a .csproj plus the reconstructed .cs files. Glass also ships a glass export CLI subcommand so you can script it. A small assembly usually compiles straight back; a large one may need manual fixes for unresolved references or compiler-generated constructs. Once it is a project, all your normal tools — search, static analysis, even setting up a local reproduction — are back on the table. See the Glass.NET features page for the full navigation and export toolset.

The honest limits

A decompiler is powerful, but it is not magic, and pretending otherwise wastes your time.

  • Decompiled ≠ original. You get accurate C# for what the code does, but comments are gone, most local variable names are lost unless a PDB supplies them, and constructs like async state machines and iterators are reconstructed, not recovered verbatim. Names like num2 and flag are common.
  • It is static, not a live debugger. Glass, ILSpy and dotPeek read and reconstruct code; they do not run it. If you need to step through the assembly at runtime, watching values change, that is a different job — dnSpyEx is the community-maintained tool for live managed debugging, or a decompiler with symbol-server / PDB generation (dotPeek) can let Visual Studio step in.
  • Obfuscated code is hard on purpose. If the assembly was protected, renamed identifiers, encrypted strings and flattened control flow all survive decompilation, so you get valid but hard-to-follow C#. You can still trace structure and behavior, but named-symbol navigation loses much of its value.
  • Only analyze what you have the right to. Debugging your own code, or a dependency you have a licence or legal right to inspect, is routine. Decompiling third-party software can be restricted by its EULA or local law — check before you dig.

Which tool should you reach for?

For reading and navigating an assembly you don’t have source for, any capable free decompiler will get you accurate C#. If you specifically need live debugging, use dnSpyEx. If you want a fast, navigation-first reading experience — Go to Definition, Find All References, Go To All, call and type hierarchy, C# and IL views, assembly compare and project export — Glass.NET is built for exactly this workflow. It is built on the same ICSharpCode.Decompiler engine as ILSpy, so the C# accuracy is on par; what Glass adds is the experience around the code, so moving through an unfamiliar assembly feels like navigating your own project.

The bottom line

No source is not a dead end. Decompile the assembly, read the reconstructed C#, follow the logic with real navigation, diff two versions to isolate a regression, and export to a project when you want it on disk — and you can debug a third-party package or a legacy DLL almost as if you had its source. Just keep the limits in mind: you are reading what the code does, not the original file. Download Glass.NET free — no licence, no seats, no sign-up — open the assembly that is giving you trouble, and press F12.

Try Nebula.NET

Harden your .NET code in minutes — start with the free edition.