Native vs Managed Debugging

Native vs Managed Debugging

Two completely different mechanisms underlying the same DAP frontend. The choice depends on whether the debuggee is native code (raw machine instructions) or managed (running inside a runtime).

The frontend (Lazydap, VS Code) doesn't care which — DAP looks the same on top. But the DAP Adapter for each language uses radically different machinery underneath.

Native debugging

Languages: C, C++, Rust, Go (compiled, mostly), Zig, Swift native.

Mechanism: the OS-level process debugging API.

The debugger is a separate process that uses these APIs to inspect the debuggee's raw memory and CPU state. It needs DWARF Debug Symbols (or PDB on Windows) to map machine addresses back to source code, variable names, types.

Native adapters: codelldb, lldb-dap (both via LLDB library), GDB, dlv-dap (via delve, which uses ptrace).

Managed debugging

Languages: Python, JavaScript, Java, .NET, Ruby, etc.

Mechanism: runtime-internal debug hooks. The runtime cooperates with the debugger.

The runtime exposes an API or protocol the debugger can connect to. The runtime itself does the actual pausing, inspecting, and resuming — the OS-level ptrace is not involved.

Python — sys.settrace

Python's interpreter has a built-in tracing hook. The debugger registers a Python callback via sys.settrace. The interpreter calls the callback on every line / function entry / exception. The callback can pause execution, inspect frames, evaluate expressions, then return to let the interpreter continue.

debugpy is the standard Python DAP adapter; uses sys.settrace underneath.

JavaScript — V8 Inspector Protocol

V8 (Chrome's JS engine, also Node.js) exposes a WebSocket interface called the V8 Inspector Protocol. Same interface Chrome DevTools uses. The debugger connects via WebSocket, sends commands like "set breakpoint," receives events.

js-debug is the standard Node DAP adapter; bridges DAP to V8 Inspector Protocol.

JVM — JDWP

Java Debug Wire Protocol. The JVM exposes a TCP port; debugger connects, sends commands, gets responses + events. Defined as part of the Java platform.

.NET — ICorDebug

The CLR exposes a C++ COM interface (ICorDebug). Modern .NET also has alternative debug interfaces.

Key differences

Native Managed
Debugger needs special privileges Yes (ptrace, root, capabilities) No (runtime cooperates)
Source of truth for state Raw memory, registers Runtime's own model
Variable inspection Walk DWARF, read memory Ask the runtime
Breakpoints INT3 patching (Software Breakpoints) Runtime-managed
Multi-thread Manage at OS level Runtime usually handles
Performance overhead Low Can be significant (Python's settrace slows execution materially)
Optimisation can hide variables Yes (<optimised out>) Less so

Why this distinction matters

For Lazydap: every adapter we wrap is one of these two types. Adding support for a new language usually means picking up an existing adapter someone else built (rather than writing one from scratch), because the adapter encapsulates this radical mechanism difference. The adapter trait in lazydap (DebugAdapter) is the seam — above it, lazydap doesn't care which mechanism; below it, the adapter does whatever its language's debug story requires.

Hybrid cases

A few languages don't fit cleanly:

See also