The Code That Watches Itself: Building Debuggers From Scratch

Former Microsoft debugger engineer Tim Misiak builds DbgRs in Rust to reveal core mechanisms of process attachment, event loops, and exception handling on Windows. Recent JetBrains architecture changes and persistent industry debugging challenges show why fundamentals still matter. The simple act of watching a program run teaches lessons no high-level tool can match.
The Code That Watches Itself: Building Debuggers From Scratch
Written by Dave Ritchie

Tim Misiak once worked on Microsoft’s debugger platform. Twice he left the team. Each departure led him to start writing his own debugger. The latest effort, a project called DbgRs, aims to teach the fundamentals through clean Rust code. His first post lays out the basics with striking clarity.

TimDbg explains that live debugging centers on an event loop. A debugger attaches to a target process. The operating system then freezes that process whenever something notable happens. It notifies the debugger. The tool examines or changes state. Then it tells the OS to resume. Simple in concept. Complex in practice.

Windows provides the raw tools. Functions such as DebugActiveProcess take a process identifier and establish the connection. DebugActiveProcessStop severs it. Or a developer can launch a fresh program under scrutiny. Call CreateProcessW with specific flags. DEBUG_ONLY_THIS_PROCESS keeps the debug session limited to that one executable. CREATE_NEW_CONSOLE gives it a separate window. The call returns a PROCESS_INFORMATION structure filled with handles and identifiers.

Misiak’s example code shows the pattern. It prepares a STARTUPINFOEXW structure. It builds a command line buffer. It invokes the API with nulls for many optional parameters. Success means the child process sits suspended. No instructions execute until the debugger begins processing events.

And that brings the loop. An infinite one in the sample. It declares a DEBUG_EVENT structure. It calls WaitForDebugEventEx with infinite timeout. The function fills the structure with details. No process identifier needed. One call monitors every attached target. The code then switches on dwDebugEventCode.

CreateProcess. LoadDll. CreateThread. Exception. OutputDebugString. The list goes on. Each type signals a different moment in the target’s life. For many events the debugger simply prints a label. Then it calls ContinueDebugEvent with the process and thread identifiers plus a status code. DBG_EXCEPTION_NOT_HANDLED tells the system to proceed as if the debugger had not intervened.

The loop ends on EXIT_PROCESS_DEBUG_EVENT. Clean. Direct. When Misiak ran his early binary against cmd.exe the output told a story. Multiple LoadDll notifications. Threads created. An early Exception. More libraries loaded and unloaded. Threads exited. The process died. All expected. All visible because the debugger sat in the middle.

That first exception deserves attention. It comes from ntdll.dll. The code at LdrpDoDebuggerBreak executes an int 3 instruction. The breakpoint fires only when the process knows it runs under inspection. A flag inside the Process Environment Block controls the behavior. Set it to zero before the initial break and the exception never appears. Useful knowledge for anyone who wants precise control.

Misiak notes that the Windows APIs he uses remain old. They operate at process granularity rather than thread. Higher-level interfaces such as the DbgEng library add symbols, disassembly, stack unwinding. Those come later. His series focuses on foundations. Future installments will tackle exception handling in depth. The author promises careful treatment there.

His approach resonates. Developers who only consume tools rarely see the machinery. Writing one forces confrontation with operating system conventions, memory layout, thread scheduling. The payoff appears in sharper intuition when production bugs strike.

Industry tools have grown more sophisticated since Misiak’s early Microsoft days. JetBrains recently redesigned its debugger architecture for the 2026.1 release. The company split the component into frontend and backend pieces. JetBrains Blog describes the shift. The frontend now handles rendering of the tool window, stack frames, variable trees, and inline values. The backend owns the active debug session. It talks to the target process and sends data to the frontend over remote procedure calls.

The change targets remote development scenarios. Local interface. Remote execution. The split reduces latency and improves stability. Yet it breaks some older plugin assumptions. The UI now initializes asynchronously. Code that once grabbed the debugger window directly can race. Custom value objects wrapped by frontend proxies no longer cast cleanly to plugin types. Developers must adapt. The post offers migration guidance and urges reporting odd behavior to the issue tracker.

Such evolution shows the distance between hobby implementations and shipping products. A from-scratch debugger in Rust might handle a handful of events. Professional offerings manage remote targets, time-travel replay, scripting, kernel debugging, and massive symbol databases. The gap looks wide. The core ideas stay surprisingly close.

Challenges persist across the board. A February 2026 analysis listed recurring headaches that still plague teams. Developers wrestle with reproducing elusive bugs. They fight incomplete logs. They lose time to environments that differ from production. They struggle to inspect complex state without slowing the system. These problems drive interest in simpler, transparent tools. Sometimes the best way to master complexity is to build the observer yourself.

Other voices echo the theme. Recent discussions on X highlight renewed attention to low-level understanding. One post pointed back to Misiak’s series as it crossed Hacker News thresholds again. Another mentioned open-source alternatives like the Rad Debugger from Epic Games. Its custom UI and Zig support offer different learning angles. Emulation projects incorporate debuggers too. The skills transfer.

Yet the educational value of a ground-up effort remains high. Misiak wrote his code so others could follow even with limited Rust experience. He invites pull requests. He separates the Windows specifics from universal concepts. Readers see how attachment works, how events flow, how control passes back and forth. They gain respect for every breakpoint they set in Visual Studio or GDB.

Memory layout. Register state. Instruction decoding. Each piece builds on the last. A debugger that only prints event names already teaches volumes. Add memory read and write APIs. Add thread context queries. Add software breakpoint insertion by overwriting code with int 3 and remembering the original byte. The project grows. So does the builder’s insight.

Modern processors complicate matters further. Hardware breakpoints. Watchpoints. Precise event delivery under virtualization. Security features that restrict ptrace equivalents. Each layer adds constraints. A hobby debugger sidesteps many by targeting user-mode on a single operating system. Even that subset delivers rich lessons.

Misiak’s series has advanced since the first post. Later parts reach disassembly and symbol handling. The pattern holds. Start small. Explain clearly. Ship working code. The approach contrasts with dense academic treatments or vendor documentation that assumes deep prior knowledge.

Companies still invest heavily in debugging technology. Faster machines. Larger codebases. Distributed systems. The demand for visibility only increases. Yet the fundamental mechanism has not changed in decades. Attach. Wait. Inspect. Continue. Everything else builds atop that loop.

So the next time a production issue stops the line, remember the simple Rust program that launched cmd.exe and printed its life events. The machinery that watches programs run shares DNA with the tools used daily. Understanding one illuminates the other. And sometimes the best way to understand sits at the keyboard with an empty editor and the operating system reference open.

That path demands patience. It rewards persistence. The resulting knowledge travels far beyond any single codebase.

Subscribe for Updates

DevNews Newsletter

The DevNews Email Newsletter is essential for software developers, web developers, programmers, and tech decision-makers. Perfect for professionals driving innovation and building the future of tech.

By signing up for our newsletter you agree to receive content related to ientry.com / webpronews.com and our affiliate partners. For additional information refer to our terms of service.

Notice an error?

Help us improve our content by reporting any issues you find.

Get the WebProNews newsletter delivered to your inbox

Get the free daily newsletter read by decision makers

Subscribe
Advertise with Us

Ready to get started?

Get our media kit

Advertise with Us