gitlab.com/Raven-IO/raven-delve@v1.22.4/Documentation/api/ClientHowto.md (about)

     1  # How to write a Delve client, an informal guide
     2  
     3  ## Spawning the backend
     4  
     5  The `dlv` binary built by our `Makefile` contains both the backend and a
     6  simple command line client. If you are writing your own client you will
     7  probably want to run only the backend, you can do this by specifying the
     8  `--headless` option, for example:
     9  
    10  ```
    11  $ dlv --headless debug
    12  ```
    13  
    14  The rest of the command line remains unchanged. You can use `debug`, `exec`,
    15  `test`, etc... along with `--headless` and they will work. If this project
    16  is part of a larger IDE integration then you probably have your own build
    17  system and do not wish to offload this task to Delve, in that case it's
    18  perfectly fine to always use the `dlv exec` command but do remember that:
    19  1. Delve may not have all the information necessary to properly debug optimized binaries, so it is recommended to disable them via: `-gcflags='all=-N -l`.
    20  2. your users *do want* to debug their tests so you should also provide some way to build the test executable (equivalent to `go test -c --gcflags='all=-N -l'`) and pass it to Delve.
    21  
    22  It would also be nice for your users if you provided a way to attach to a running process, like `dlv attach` does.
    23  
    24  Command line arguments that should be handed to the inferior process should be specified on dlv's command line after a "--" argument:
    25  
    26  ```
    27  dlv exec --headless ./somebinary -- these arguments are for the inferior process
    28  ```
    29  
    30  Specifying a static port number, like in the [README](//github.com/go-delve/Delve/tree/master/Documentation/README.md) example, can be done using `--listen=127.0.0.1:portnumber`. 
    31  
    32  This will, however, cause problems if you actually spawn multiple instances of the debugger. 
    33  
    34  It's probably better to let Delve pick a random unused port number on its own. To do this do not specify any `--listen` option and read one line of output from dlv's stdout. If the first line emitted by dlv starts with "API server listening at: " then dlv started correctly and the rest of the line specifies the address that Delve is listening at.
    35  
    36  The `--log-dest` option can be used to redirect the "API server listening at:" message to a file or to a file descriptor. If the flag is not specified, the message will be output to stdout while other log messages are output to stderr.
    37  
    38  ## Controlling the backend
    39  
    40  Once you have a running headless instance you can connect to it and start sending commands. Delve's protocol is built on top of the [JSON-RPC 1.0 specification](https://www.jsonrpc.org/specification_v1).
    41  
    42  The methods of a `service/rpc2.RPCServer` are exposed through this connection, to find out which requests you can send see the documentation of RPCServer on [godoc](https://godoc.org/github.com/go-delve/Delve/service/rpc2#RPCServer). 
    43  
    44  ### Example
    45  
    46  Let's say you are trying to create a breakpoint. By looking at [godoc](https://godoc.org/github.com/go-delve/Delve/service/rpc2#RPCServer) you'll find that there is a `CreateBreakpoint` method in `RPCServer`.
    47  
    48  This method, like all other methods of RPCServer that you can call through the API, has two arguments: `args` and `out`: `args` contains all the input arguments of `CreateBreakpoint`, while `out` is what `CreateBreakpoint` will return to you.
    49  
    50  The call that you could want to make, in pseudo-code, would be:
    51  
    52  ```
    53  RPCServer.CreateBreakpoint(CreateBreakpointIn{ File: "/User/you/some/file.go", Line: 16 })
    54  ```
    55  
    56  To actually send this request on the JSON-RPC connection you just have to convert the CreateBreakpointIn object to json and then wrap everything into a JSON-RPC envelope:
    57  
    58  ```
    59  {"method":"RPCServer.CreateBreakpoint","params":[{"Breakpoint":{"file":"/User/you/some/file.go","line":16}}],"id":27}
    60  ```
    61  
    62  Delve will respond by sending a response packet that will look like this:
    63  
    64  ```
    65  {"id":27, "result": {"Breakpoint": {"id":3, "name":"", "addr":4538829, "file":"/User/you/some/file.go", "line":16, "functionName":"main.main", "Cond":"", "continue":false, "goroutine":false, "stacktrace":0, "LoadArgs":null, "LoadLocals":null, "hitCount":{}, "totalHitCount":0}}, "error":null}
    66  ```
    67  
    68  ## Selecting the API version
    69  
    70  Delve currently supports two version of its API, APIv1 and APIv2. By default
    71  a headless instance of `dlv` will serve APIv1 for backward-compatibility
    72  with older clients, however new clients should use APIv2 as new features
    73  will only be made available through version 2. The preferred method of
    74  switching to APIv2 is to send the `RPCServer.SetApiVersion` command right
    75  after connecting to the backend.
    76  Alternatively the `--api-version=2` command line option can be used when
    77  spawning the backend.
    78  
    79  ## Diagnostics
    80  
    81  Just like any other program, both Delve and your client have bugs. To help
    82  with determining where the problem is you should log the exchange of
    83  messages between Delve and your client somehow.
    84  
    85  If you don't want to do this yourself you can also pass the options `--log
    86  --log-output=rpc` to Delve. In fact the `--log-output` has many useful
    87  values and you should expose it to users, if possible, so that we can
    88  diagnose problems that are hard to reproduce.
    89  
    90  ## Using RPCServer.Command
    91  
    92  `Command` is probably the most important API entry point. It lets your
    93  client stop (`Name == "halt"`) and resume (`Name == "continue"`) execution
    94  of the inferior process.
    95  
    96  The return value of `Command` is a `DebuggerState` object. If you lose the
    97  DebuggerState object returned by your last call to `Command` you can ask for
    98  a new copy with `RPCServer.State`.
    99  
   100  ### Dealing with simultaneous breakpoints
   101  
   102  Since Go is a programming language with a big emphasis on concurrency and
   103  parallelism it's possible that multiple goroutines will stop at a breakpoint
   104  simultaneously. This may at first seem incredibly unlikely but you must
   105  understand that between the time a breakpoint is triggered and the point
   106  where the debugger finishes stopping all threads of the inferior process
   107  thousands of CPU instructions have to be executed, which make simultaneous
   108  breakpoint triggering not that unlikely.
   109  
   110  You should signal to your user *all* the breakpoints that occur after
   111  executing a command, not just the first one. To do this iterate through the
   112  `Threads` array in `DebuggerState` and note all the threads that have a non
   113  nil `Breakpoint` member.
   114  
   115  ### Special continue commands
   116  
   117  In addition to "halt" and vanilla "continue" `Command` offers a few extra
   118  flavours of continue that automatically set interesting temporary
   119  breakpoints: "next" will continue until the next line of the program,
   120  "stepout" will continue until the function returns, "step" is just like
   121  "next" but it will step into function calls (but skip all calls to
   122  unexported runtime functions).
   123  
   124  All of "next", "step" and "stepout" operate on the selected goroutine. The
   125  selected goroutine is described by the `SelectedGoroutine` field of
   126  `DebuggerState`. Every time `Command` returns the selected goroutine will be
   127  reset to the goroutine that triggered the breakpoint.
   128  
   129  If multiple breakpoints are triggered simultaneously the selected goroutine
   130  will be chosen randomly between the goroutines that are stopped at a
   131  breakpoint. If a breakpoint is hit by a thread that is executing on the
   132  system stack *there will be no selected goroutine*. If the "halt" command is
   133  called *there may not be a selected goroutine*.
   134  
   135  The selected goroutine can be changed using the "switchGoroutine" command.
   136  If "switchGoroutine" is used to switch to a goroutine that's currently
   137  parked SelectedGoroutine and CurrentThread will be mismatched. Always prefer
   138  SelectedGoroutine over CurrentThread, you should ignore CurrentThread
   139  entirely unless SelectedGoroutine is nil.
   140  
   141  ### Special continue commands and asynchronous breakpoints
   142  
   143  Because of the way go internals work it is not possible for a debugger to
   144  resume a single goroutine. Therefore it's possible that after executing a
   145  next/step/stepout a goroutine other than the goroutine the next/step/stepout
   146  was executed on will hit a breakpoint.
   147  
   148  If this happens Delve will return a DebuggerState with NextInProgress set to
   149  true. When this happens your client has two options:
   150  
   151  * You can signal that a different breakpoint was hit and then automatically attempt to complete the next/step/stepout by calling `RPCServer.Command` with `Name == "continue"`
   152  * You can abort the next/step/stepout operation using `RPCServer.CancelNext`.
   153  
   154  It is important to note that while NextInProgress is true it is not possible
   155  to call next/step/stepout again without using CancelNext first. There can
   156  not be multiple next/step/stepout operations in progress at any time.
   157  
   158  ### RPCServer.Command and stale executable files
   159  
   160  It's possible (albeit unfortunate) that your user will decide to change the
   161  source of the program being executed in the debugger, while the debugger is
   162  running. Because of this it would be advisable that your client check that
   163  the executable is not stale every time `Command` returns and notify the user
   164  that the executable being run is stale and line numbers may nor align
   165  properly anymore.
   166  
   167  You can do this bookkeeping yourself, but Delve can also help you with the
   168  `LastModified` call that returns the LastModified time of the executable
   169  file when Delve started it.
   170  
   171  ## Using RPCServer.CreateBreakpoint
   172  
   173  The only two fields you probably want to fill of the Breakpoint argument of
   174  CreateBreakpoint are File and Line. The file name should be the absolute
   175  path to the file as the compiler saw it.
   176  
   177  For example if the compiler saw this path:
   178  
   179  ```
   180  /Users/you/go/src/something/something.go
   181  ```
   182  
   183  But `/Users/you/go/src/something` is a symbolic link to
   184  `/Users/you/projects/golang/something` the path *must* be specified as
   185  `/Users/you/go/src/something/something.go` and
   186  `/Users/you/projects/golang/something/something.go` will not be recognized
   187  as valid.
   188  
   189  If you want to let your users specify a breakpoint on a function selected
   190  from a list of all functions you should specify the name of the function in
   191  the FunctionName field of Breakpoint.
   192  
   193  If you want to support the [same language as dlv's break and trace commands](//github.com/go-delve/Delve/tree/master/Documentation/cli/locspec.md)
   194   you should call RPCServer.FindLocation and
   195  then use the returned slice of Location objects to create Breakpoints to
   196  pass to CreateBreakpoint: just fill each Breakpoint.Addr with the
   197  contents of the corresponding Location.PC.
   198  
   199  ## Looking into variables
   200  
   201  There are several API entry points to evaluate variables in Delve:
   202  
   203  * RPCServer.ListPackageVars returns all global variables in all packages
   204  * PRCServer.ListLocalVars returns all local variables of a stack frame
   205  * RPCServer.ListFunctionArgs returns all function arguments of a stack frame
   206  * RPCServer.Eval evaluates an expression on a given stack frame
   207  
   208  All those API calls take a LoadConfig argument. The LoadConfig specifies how
   209  much of the variable's value should actually be loaded. Because of
   210  LoadConfig a variable could be loaded incompletely, you should always notify
   211  the user of this:
   212  
   213  * For strings, arrays, slices *and structs* the load is incomplete if: `Variable.Len > len(Variable.Children)`. This can happen to structs even if LoadConfig.MaxStructFields is -1 when MaxVariableRecurse is reached.
   214  * For maps the load is incomplete if: `Variable.Len > len(Variable.Children) / 2`
   215  * For interfaces the load is incomplete if the only children has the onlyAddr attribute set to true.
   216  
   217  ### Loading more of a Variable
   218  
   219  You can also give the user an option to continue loading an incompletely
   220  loaded variable. To load a struct that wasn't loaded automatically evaluate
   221  the expression returned by:
   222  
   223  ```
   224  fmt.Sprintf("*(*%q)(%#x)", v.Type, v.Addr)
   225  ```
   226  
   227  where v is the variable that was truncated.
   228  
   229  To load more elements from an array, slice or string:
   230  
   231  ```
   232  fmt.Sprintf("(*(*%q)(%#x))[%d:]", v.Type, v.Addr, len(v.Children))
   233  ```
   234  
   235  To load more elements from a map:
   236  
   237  ```
   238  fmt.Sprintf("(*(*%q)(%#x))[%d:]", v.Type, v.Addr, len(v.Children)/2)
   239  ```
   240  
   241  All the evaluation API calls except ListPackageVars also take a EvalScope
   242  argument, this specifies which stack frame you are interested in. If you
   243  are interested in the topmost stack frame of the current goroutine (or
   244  thread) use: `EvalScope{ GoroutineID: -1, Frame: 0 }`.
   245  
   246  More information on the expression language interpreted by RPCServer.Eval
   247  can be found [here](//github.com/go-delve/Delve/tree/master/Documentation/cli/expr.md).
   248  
   249  ### Variable shadowing
   250  
   251  Let's assume you are debugging a piece of code that looks like this:
   252  
   253  ```
   254  	for i := 0; i < N; i++ {
   255  		for i := 0; i < M; i++ {
   256  			f(i) // <-- debugger is stopped here
   257  		}
   258  	}
   259  ```
   260  
   261  The response to a ListLocalVars request will list two variables named `i`,
   262  because at that point in the code two variables named `i` exist and are in
   263  scope. Only one (the innermost one), however, is visible to the user. The
   264  other one is *shadowed*.
   265  
   266  Delve will tell you which variable is shadowed through the `Flags` field of
   267  the `Variable` object. If `Flags` has the `VariableShadowed` bit set then
   268  the variable in question is shadowed.
   269  
   270  Users of your client should be able to distinguish between shadowed and
   271  non-shadowed variables.
   272  
   273  ## Gracefully ending the debug session
   274  
   275  To ensure that Delve cleans up after itself by deleting the `debug` or `debug.test` binary it creates 
   276  and killing any processes spawned by the program being debugged, the `Detach` command needs to be called.
   277  In case you are disconnecting a running program, ensure to halt the program before trying to detach.
   278  
   279  ## Testing the Client
   280  
   281  A set of [example programs is
   282  available](https://github.com/aarzilli/delve_client_testing) to test corner
   283  cases in handling breakpoints and displaying data structures. Follow the
   284  instructions in the README.txt file.