Delve Debugger for Go

repository·master·Indexed 12 days ago

https://github.com/go-delve/delve

A specialized debugger for the Go programming language designed to be simple, full-featured, and unobtrusive. Delve provides a CLI, a JSON-RPC API (supporting API version 2), and a Debug Adapter Protocol (DAP) interface for integration with IDEs like VS Code. It supports headless mode for remote debugging, multi-client sessions, and advanced breakpoint management including conditional triggers and hitcounts.

Tokens
42.1K
Snippets
120
Records
214
Agent score
95%

What's inside Delve

  1. What is Delve?

    master
    Delve is a debugger specifically designed for the Go programming language. Its primary goal is to provide a simple, full-featured debugging tool that is easy to invoke and stays out of the developer's way during the debugging process.
  2. Overview of Delve debug modes

    master

    Delve provides several different modes of operation depending on whether you are starting a new process, attaching to an existing one, or examining a crash dump. Key modes include:

    • dlv debug: Compiles and begins debugging the main package in the current directory (or a specified package).
    • dlv exec: Executes a precompiled binary and begins a debug session.
    • dlv attach: Attaches to a currently running process.
    • dlv test: Compiles a test binary and begins debugging the program.
    • dlv core: Examines a core dump file.
    • dlv connect: Connects to a headless debug server using a terminal client.
    • dlv dap: Starts a headless TCP server communicating via the Debug Adaptor Protocol (DAP).
    • dlv replay: Replays an rr trace.
    • dlv trace: Compiles and begins tracing a program.
  3. Supported Go expressions in Delve

    master

    Delve supports a subset of the Go expression language for evaluation during debugging. Supported features include:

    • Arithmetic/Logic: All binary and unary operators on basic types (except <-, ++, and --).
    • Comparison: Comparison operators on any type.
    • Type Casting:
      • Between numeric types.
      • Integer constants to/from any pointer type.
      • Between string, []byte, and []rune.
    • Accessors: Struct member access (somevar.memberfield), slicing/indexing for arrays, slices, and strings, and map access.
    • Pointers: Pointer dereferencing.
    • Built-in Functions: cap, len, complex, imag, and real.
    • Interfaces: Type assertion on interface variables (e.g., somevar.(concretetype)).
  4. Control the Delve backend via JSON-RPC

    master

    Delve's API is implemented using the JSON-RPC 1.0 specification. The methods of the service/rpc2.RPCServer are exposed through this connection.

    Every request follows a pattern where you provide args (the input) and receive out (the result). For example, to create a breakpoint, you wrap a CreateBreakpointIn object into a JSON-RPC envelope and send it to the RPCServer.CreateBreakpoint method.

    {
      "method": "RPCServer.CreateBreakpoint",
      "params": [
        {
          "Breakpoint": {
            "file": "/User/you/some/file.go",
            "line": 16
          }
        }
      ],
      "id": 27
    }
  5. Rules for using build tags and runtime checks

    master

    Delve supports cross-platform core file reading (e.g., reading a Linux/arm64 core file on Windows/amd64). Because of this, using runtime.GOOS, runtime.GOARCH, or standard build tags is generally forbidden outside of pkg/proc/native and test files.

    When writing code for other packages, use these abstractions instead:

    • OS: Use proc.BinaryInfo.GOOS instead of runtime.GOOS.
    • Architecture: Use proc.BinaryInfo.Arch.Name instead of runtime.GOARCH.
    • Pointer Size: Use proc.BinaryInfo.Arch.PtrSize() instead of unsafe.Sizeof.
    • Addresses: Use uint64 for address-sized integers instead of uintptr.
    • File Naming: Use amd64_filename.go instead of the build tag version filename_amd64.go.
  6. Caveats when debugging the Go runtime

    master

    Debugging the Go runtime is possible but has specific limitations:

    • Optimizations: The runtime package is compiled with optimizations and inlining. Variables may be unavailable or stale, and line numbers may occasionally be inaccurate.
    • Stepping through runtime functions: Standard next, step, and stepout commands may fail if the runtime function modifies the curg pointer. Use the step-instruction command instead.
    • Viewing g0 stacktraces: When executing a stacktrace from g0, Delve may automatically switch to the goroutine stack. To see the g0 stacktrace specifically, use stack -mode simple.
    • Stepping into private runtime functions: To step into a private runtime function inserted by the compiler into user code, set a breakpoint and use the condition: runtime.curg.goid == <current goroutine id>.
  7. Create custom commands in Starlark

    master

    You can define custom commands in Starlark by defining functions with the prefix command_.

    Argument Handling:

    • Single Argument: If your function takes one argument (e.g., def command_echo(args):), args is received as a single Starlark string containing all arguments passed from the CLI.
    • Expression Arguments: If your function defines multiple parameters (e.g., def command_echo_expr(a, b, c):), Delve will parse the CLI arguments as Starlark expressions, allowing you to pass math or logic (like 2+2) directly.

    To use your commands, load your script using the source command.

    # Single string argument
    def command_echo(args):
    	print(args)
    
    # Multiple expression arguments
    def command_echo_expr(a, b, c):
    	print("a", a, "b", b, "c", c)
  8. Handle simultaneous breakpoints and goroutines

    master

    Because of Go's concurrency, multiple goroutines may hit breakpoints at the same time.

    Best Practices for Clients:

    • Signal all hits: Do not just signal the first breakpoint. Iterate through the Threads array in the DebuggerState and identify all threads where the Breakpoint member is non-nil.
    • Identify the Selected Goroutine: When multiple breakpoints are hit, Delve chooses a SelectedGoroutine randomly among those stopped.
    • Prefer SelectedGoroutine: Always use the SelectedGoroutine field from DebuggerState to identify the active goroutine. Ignore CurrentThread unless SelectedGoroutine is nil.
  9. Understand Single-Client vs Multi-Client DAP modes

    master

    How the Delve server behaves upon disconnection depends on the mode it was started in:

    Single-Client Mode

    Triggered by: dlv dap or dlv --headless --accept-multiclient=false (default).

    • On Disconnect: The DAP server shuts down when the client sends a disconnect request. If the debuggee was launched by Delve, it is also terminated. If attached, the terminateDebuggee option determines if the process is killed.
    • On Program Termination: Delve sends a terminated event. The client is expected to follow up with a disconnect request to shut down the server.
    • On Error/SIGTERM: The server shuts down, taking down launched processes but leaving attached processes running.

    Multi-Client Mode

    Triggered by: dlv --headless --accept-multiclient=true.

    • On Disconnect: If a client disconnects or a connection fails, the server remains running. The debuggee stays in its current state (running or halted), allowing new clients to connect.
    • On Shutdown: The client must explicitly request full shutdown of the server and debuggee using the terminateDebuggee option.
    • On SIGTERM: The server shuts down, taking down launched processes but leaving attached processes running.
  10. Handle nesting and element limits in output

    master

    To prevent massive output, Delve imposes limits on how much data is returned during evaluation:

    Nesting Limit

    By default, Delve limits evaluation to two levels deep. Beyond two levels, only the memory address is returned. To inspect deeper levels, you must explicitly target them:

    • Use a specific index: print c1.sa[0]
    • Use a direct pointer dereference: print *(*main.astruct)(0xc82000a440)

    Elements Limit

    For arrays, slices, strings, and maps, Delve returns a maximum of 64 elements at a time. To view more, use the slice operator:

    • print ba[64:] (to see elements from index 64 onwards).
    • For maps, m[64:] returns the key/value pairs following the first 64 pairs (Delve uses a fixed ordering for map iteration).

    These limits can be adjusted using the max-string-len and max-array-values configuration settings.

    # Example: Accessing elements beyond the 64-element limit
    (dlv) print ba[64:]
  11. Choose between raw_command and dlv_command

    master

    When resuming execution in a Starlark script, you have two options:

    1. dlv_command("command"): Equivalent to typing the command directly into the (dlv) prompt. This is generally preferred as it behaves as expected for standard Delve commands like continue.
    2. raw_command("command"): Maps directly to the underlying RPC Command API. Use this only if you need behavior that diverges from the standard command-line interface.
    # Preferred way to continue execution
    dlv_command("continue")
    
    # Alternative (direct API call)
    raw_command("continue", ...)
  12. Understand Delve API interfaces

    master

    Delve provides two primary API interfaces to allow external frontends (such as IDEs and editors) to interact with the debugger programmatically. The core debugging logic is abstracted from these transport implementations.

    Supported interfaces:

    • JSON-RPC: Used by the built-in terminal client. This interface is updated in lockstep with new Delve features.
    • DAP (Debug Adapter Protocol): A generic, industry-standard protocol used by many modern development tools.