Documentation/cli/starlark.md
Passing a file with the .star extension to the source command will cause delve to interpret it as a starlark script.
Starlark is a dialect of python, a specification of its syntax can be found here.
In addition to the normal starlark built-ins delve defines a number of global functions that can be used to interact with the debugger.
After the file has been evaluated delve will bind any function starting with command_ to a command-line command: for example command_goroutines_wait_reason will be bound to goroutines_wait_reason.
Then if a function named main exists it will be executed.
Global functions with a name that begins with a capital letter will be available to other scripts.
| Function | API Call |
|---|---|
| amend_breakpoint(Breakpoint) | Equivalent to API call AmendBreakpoint |
| ancestors(GoroutineID, NumAncestors, Depth) | Equivalent to API call Ancestors |
| attached_to_existing_process() | Equivalent to API call AttachedToExistingProcess |
| build_id() | Equivalent to API call BuildID |
| cancel_next() | Equivalent to API call CancelNext |
| checkpoint(Where) | Equivalent to API call Checkpoint |
| clear_breakpoint(Id, Name) | Equivalent to API call ClearBreakpoint |
| clear_checkpoint(ID) | Equivalent to API call ClearCheckpoint |
| raw_command(Name, ThreadID, GoroutineID, ReturnInfoLoadConfig, Expr, WithEvents, UnsafeCall) | Equivalent to API call Command |
| create_breakpoint(Breakpoint, LocExpr, SubstitutePathRules, Suspended) | Equivalent to API call CreateBreakpoint |
| create_ebpf_tracepoint(FunctionName) | Equivalent to API call CreateEBPFTracepoint |
| create_watchpoint(Scope, Expr, Type) | Equivalent to API call CreateWatchpoint |
| debug_info_directories(Set, List) | Equivalent to API call DebugInfoDirectories |
| detach(Kill) | Equivalent to API call Detach |
| disassemble(Scope, StartPC, EndPC, Flavour) | Equivalent to API call Disassemble |
| dump_cancel() | Equivalent to API call DumpCancel |
| dump_start(Destination) | Equivalent to API call DumpStart |
| dump_wait(Wait) | Equivalent to API call DumpWait |
| eval(Scope, Expr, Cfg) | Equivalent to API call Eval |
| examine_memory(Address, Length) | Equivalent to API call ExamineMemory |
| find_location(Scope, Loc, IncludeNonExecutableLines, SubstitutePathRules) | Equivalent to API call FindLocation |
| follow_exec(Enable, Regex) | Equivalent to API call FollowExec |
| follow_exec_enabled() | Equivalent to API call FollowExecEnabled |
| function_return_locations(FnName) | Equivalent to API call FunctionReturnLocations |
| get_breakpoint(Id, Name) | Equivalent to API call GetBreakpoint |
| get_buffered_tracepoints() | Equivalent to API call GetBufferedTracepoints |
| get_thread(Id) | Equivalent to API call GetThread |
| guess_substitute_path(Args) | Equivalent to API call GuessSubstitutePath |
| is_multiclient() | Equivalent to API call IsMulticlient |
| last_modified() | Equivalent to API call LastModified |
| breakpoints(All) | Equivalent to API call ListBreakpoints |
| checkpoints() | Equivalent to API call ListCheckpoints |
| dynamic_libraries() | Equivalent to API call ListDynamicLibraries |
| function_args(Scope, Cfg) | Equivalent to API call ListFunctionArgs |
| functions(Filter, FollowCalls) | Equivalent to API call ListFunctions |
| goroutines(Start, Count, Filters, GoroutineGroupingOptions, EvalScope) | Equivalent to API call ListGoroutines |
| local_vars(Scope, Cfg) | Equivalent to API call ListLocalVars |
| package_vars(Filter, Cfg) | Equivalent to API call ListPackageVars |
| packages_build_info(IncludeFiles, Filter) | Equivalent to API call ListPackagesBuildInfo |
| registers(ThreadID, IncludeFp, Scope) | Equivalent to API call ListRegisters |
| sources(Filter) | Equivalent to API call ListSources |
| targets() | Equivalent to API call ListTargets |
| threads() | Equivalent to API call ListThreads |
| types(Filter) | Equivalent to API call ListTypes |
| process_pid() | Equivalent to API call ProcessPid |
| recorded() | Equivalent to API call Recorded |
| restart(Position, ResetArgs, NewArgs, Rerecord, Rebuild, NewRedirects) | Equivalent to API call Restart |
| set_expr(Scope, Symbol, Value) | Equivalent to API call Set |
| stacktrace(Id, Depth, Full, Defers, Opts, Cfg, Skip) | Equivalent to API call Stacktrace |
| state(NonBlocking) | Equivalent to API call State |
| toggle_breakpoint(Id, Name) | Equivalent to API call ToggleBreakpoint |
| type_info(Name) | Equivalent to API call TypeInfo |
| dlv_command(command) | Executes the specified command as if typed at the dlv_prompt |
| read_file(path) | Reads the file as a string |
| write_file(path, contents) | Writes string to a file |
| cur_scope() | Returns the current evaluation scope |
| default_load_config() | Returns the current default load configuration |
In addition to these built-ins, the time library from the starlark-go project is also available to scripts.
There are two ways to resume the execution of the target program:
raw_command("continue")
dlv_command("continue")
The first one maps to the API call Command. As such all the caveats explained in the Client HowTo.
The latter is equivalent to typing continue to the (dlv) command line and should do what you expect.
In general dlv_command("continue") should be preferred, unless the behavior you wish to produces diverges significantly from that of the command line's continue.
Any global function with a name starting with command_ will be made available as a command line command. If the function has a single argument named args all arguments passed on the command line will be passed to the function as a single string.
Otherwise arguments passed on the command line are interpreted as starlark expressions. See the expression arguments example.
If the command function has a doc string it will be used as a help message.
on prefixAll custom starlark commands can be used with the on command (see help on) to execute when a breakpoint is hit:
def command_my_trace(args):
"""Custom trace command that executes when a breakpoint is hit."""
print("Tracing:", args)
You can use this command like this:
(dlv) break main.go:10
(dlv) on 1 my_trace
Variables of the target program can be accessed using local_vars, function_args or the eval functions. Each variable will be returned as a Variable struct, with one special field: Value.
As a convenience a special global object exists, called tgt: evaluating tgt.varname is equivalent to evaluating eval(None, "varname").Variable.Value.
The Value field will return the value of the target variable converted to a starlark value:
For example, given this variable in the target program:
type astruct struct {
A int
B int
}
s2 := []astruct{{1, 2}, {3, 4}, {5, 6}, {7, 8}, {9, 10}, {11, 12}, {13, 14}, {15, 16}}
The following is possible:
>>> s2 = eval(None, "s2").Variable
>>> s2.Value[0] # access of a slice item by index
main.astruct {A: 1, B: 2}
>>> a = s2.Value[1]
>>> a.Value.A # access to a struct field
3
>>> a.Value.A + 10 # arithmetic on the value of s2[1].X
13
>>> a.Value["B"] # access to a struct field, using dictionary syntax
4
For more examples see the linked list example below.
Create a goroutine_start_line command that prints the starting line of each goroutine, sets gsl as an alias:
def command_goroutine_start_line(args):
gs = goroutines().Goroutines
for g in gs:
line = read_file(g.StartLoc.File).splitlines()[g.StartLoc.Line-1].strip()
print(g.ID, "\t", g.StartLoc.File + ":" + str(g.StartLoc.Line), "\t", line)
def main():
dlv_command("config alias goroutine_start_line gsl")
Use it like this:
(dlv) source goroutine_start_line.star
(dlv) goroutine_start_line
1 /usr/local/go/src/runtime/proc.go:110 func main() {
2 /usr/local/go/src/runtime/proc.go:242 func forcegchelper() {
17 /usr/local/go/src/runtime/mgcsweep.go:64 func bgsweep(c chan int) {
18 /usr/local/go/src/runtime/mfinal.go:161 func runfinq() {
(dlv) gsl
1 /usr/local/go/src/runtime/proc.go:110 func main() {
2 /usr/local/go/src/runtime/proc.go:242 func forcegchelper() {
17 /usr/local/go/src/runtime/mgcsweep.go:64 func bgsweep(c chan int) {
18 /usr/local/go/src/runtime/mfinal.go:161 func runfinq() {
After evaluating this script:
def command_echo(args):
print(args)
def command_echo_expr(a, b, c):
print("a", a, "b", b, "c", c)
The first command, echo, takes its arguments as a single string, while for echo_expr it will be possible to pass starlark expression as arguments:
(dlv) echo 2+2, 2-1, 2*3
"2+2, 2-1, 2*3"
(dlv) echo_expr 2+2, 2-1, 2*3
a 4 b 1 c 6
Set a breakpoint on all private methods of package main:
def main():
for f in functions().Funcs:
v = f.split('.')
if len(v) != 2:
continue
if v[0] != "main":
continue
if v[1][0] >= 'a' and v[1][0] <= 'z':
create_breakpoint({ "FunctionName": f, "Line": -1 }) # see documentation of RPCServer.CreateBreakpoint
Create a command, switch_to_main_goroutine, that searches for a goroutine running a function in the main package and switches to it:
def command_switch_to_main_goroutine(args):
for g in goroutines().Goroutines:
if g.currentLoc.function != None and g.currentLoc.function.name.startswith("main."):
print("switching to:", g.id)
raw_command("switchGoroutine", GoroutineID=g.id)
break
Create a command, "goexcl", that lists all goroutines excluding the ones stopped on a specified function.
def command_goexcl(args):
"""Prints all goroutines not stopped in the function passed as argument."""
excluded = 0
start = 0
while start >= 0:
gr = goroutines(start, 10)
start = gr.Nextg
for g in gr.Goroutines:
fn = g.UserCurrentLoc.Function
if fn == None:
print("Goroutine", g.ID, "User:", g.UserCurrentLoc.File, g.UserCurrentLoc.Line)
elif fn.Name_ != args:
print("Goroutine", g.ID, "User:", g.UserCurrentLoc.File, g.UserCurrentLoc.Line, fn.Name_)
else:
excluded = excluded + 1
print("Excluded", excluded, "goroutines")
Usage:
(dlv) goexcl main.somefunc
prints all goroutines that are not stopped inside main.somefunc.
Repeatedly call continue and restart until the target hits a breakpoint.
def command_flaky(args):
"Repeatedly runs program until a breakpoint is hit"
while True:
if dlv_command("continue") == None:
break
dlv_command("restart")
def command_linked_list(args):
"""Prints the contents of a linked list.
linked_list <var_name> <next_field_name> <max_depth>
Prints up to max_depth elements of the linked list variable 'var_name' using 'next_field_name' as the name of the link field.
"""
var_name, next_field_name, max_depth = args.split(" ")
max_depth = int(max_depth)
next_name = var_name
v = eval(None, var_name).Variable.Value
for i in range(0, max_depth):
print(str(i)+":",v)
if v[0] == None:
break
v = v[next_field_name]
def command_find_array(arr, pred):
"""Calls pred for each element of the array or slice 'arr' returns the index of the first element for which pred returns true.
find_arr <arr> <pred>
Example use (find the first element of slice 's2' with field A equal to 5):
find_arr "s2", lambda x: x.A == 5
"""
arrv = eval(None, arr).Variable
for i in range(0, arrv.Len):
v = arrv.Value[i]
if pred(v):
print("found", i)
return
print("not found")
def command_flaky(args):
"Continues and restarts the target program repeatedly (re-recording it on the rr backend), until a breakpoint is hit"
count = 1
while True:
if dlv_command("continue") == None:
break
print("restarting", count, "...")
count = count+1
restart(Rerecord=True)
Struct literals can be passed to built-ins as Starlark dictionaries. For example, the following snippet passes
in an api.EvalScope
and api.LoadConfig
to the eval built-in. None can be passed for optional arguments, and
trailing optional arguments can be elided completely.
var = eval(
{"GoroutineID": 42, "Frame": 5},
"myVar",
{"FollowPointers":True, "MaxVariableRecurse":2, "MaxStringLen":100, "MaxArrayValues":10, "MaxStructFields":100}
)
Chain a number of breakpoints such that breakpoint n+1 is only hit after breakpoint n is hit:
def command_breakchain(*args):
v = args.split(" ")
bp = get_breakpoint(int(v[0]), "").Breakpoint
bp.HitCond = "== 1"
amend_breakpoint(bp)
for i in range(1, len(v)):
bp = get_breakpoint(int(v[i]), "").Breakpoint
if i != len(v)-1:
bp.HitCond = "== 1"
bp.Cond = "delve.bphitcount[" + v[i-1] + "] > 0"
amend_breakpoint(bp)
To be used as chain 1 2 3 where 1, 2, and 3 are IDs of breakpoints to chain together.