1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
|
package dap
import (
"bytes"
"errors"
"fmt"
"slices"
"sort"
"strconv"
"strings"
"github.com/go-delve/delve/pkg/config"
"github.com/google/go-dap"
)
func (s *Session) delveCmd(goid, frame int, cmdstr string) (string, error) {
vals := strings.SplitN(strings.TrimSpace(cmdstr), " ", 2)
cmdname := vals[0]
var args string
if len(vals) > 1 {
args = strings.TrimSpace(vals[1])
}
for _, cmd := range debugCommands(s) {
if slices.Contains(cmd.aliases, cmdname) {
return cmd.cmdFn(goid, frame, args)
}
}
return "", errNoCmd
}
type cmdfunc func(goid, frame int, args string) (string, error)
type command struct {
aliases []string
helpMsg string
cmdFn cmdfunc
}
const (
msgHelp = `Prints the help message.
dlv help [command]
Type "help" followed by the name of a command for more information about it.`
msgConfig = `Changes configuration parameters.
dlv config -list
Show all configuration parameters.
dlv config -list <parameter>
Show value of a configuration parameter.
dlv config <parameter> <value>
Changes the value of a configuration parameter.
dlv config substitutePath <from> <to>
dlv config substitutePath <from>
dlv config substitutePath -clear
Adds or removes a path substitution rule. If -clear is used all substitutePath rules are removed.
See also Documentation/cli/substitutepath.md.
dlv config showPprofLabels <label>
dlv config showPprofLabels -clear <label>
dlv config showPprofLabels -clear
Adds or removes a label key to show in the callstack view. If -clear is used without an argument,
all labels are removed.`
msgSources = `Print list of source files.
dlv sources [<regex>]
If regex is specified only the source files matching it will be returned.`
msgTarget = `Manages child process debugging.
target follow-exec [-on [regex]] [-off]
Enables or disables follow exec mode. When follow exec mode Delve will automatically attach to new child processes executed by the target process. An optional regular expression can be passed to 'target follow-exec', only child processes with a command line matching the regular expression will be followed.
target list
List currently attached processes.
target switch [pid]
Switches to the specified process.`
)
// debugCommands returns a list of commands with default commands defined.
func debugCommands(s *Session) []command {
return []command{
{aliases: []string{"help", "h"}, cmdFn: s.helpMessage, helpMsg: msgHelp},
{aliases: []string{"config"}, cmdFn: s.evaluateConfig, helpMsg: msgConfig},
{aliases: []string{"sources", "s"}, cmdFn: s.sources, helpMsg: msgSources},
{aliases: []string{"target"}, cmdFn: s.targetCmd, helpMsg: msgTarget},
}
}
var errNoCmd = errors.New("command not available")
func (s *Session) helpMessage(_, _ int, args string) (string, error) {
var buf bytes.Buffer
if args != "" {
for _, cmd := range debugCommands(s) {
if slices.Contains(cmd.aliases, args) {
return cmd.helpMsg, nil
}
}
return "", errNoCmd
}
fmt.Fprintln(&buf, "The following commands are available:")
for _, cmd := range debugCommands(s) {
h := cmd.helpMsg
if idx := strings.Index(h, "\n"); idx >= 0 {
h = h[:idx]
}
if len(cmd.aliases) > 1 {
fmt.Fprintf(&buf, " dlv %s (alias: %s) \t %s\n", cmd.aliases[0], strings.Join(cmd.aliases[1:], " | "), h)
} else {
fmt.Fprintf(&buf, " dlv %s \t %s\n", cmd.aliases[0], h)
}
}
fmt.Fprintln(&buf)
fmt.Fprintln(&buf, "Type 'dlv help' followed by a command for full documentation.")
return buf.String(), nil
}
func (s *Session) evaluateConfig(_, _ int, expr string) (string, error) {
argv := config.Split2PartsBySpace(expr)
name := argv[0]
if name == "-list" {
if len(argv) > 1 {
return config.ConfigureListByName(&s.args, argv[1], "cfgName"), nil
}
return listConfig(&s.args), nil
}
updated, res, err := configureSet(&s.args, expr)
if err != nil {
return "", err
}
if updated {
// Send invalidated events for areas that are affected by configuration changes.
switch name {
case "showGlobalVariables", "showRegisters":
// Variable data has become invalidated.
s.send(&dap.InvalidatedEvent{
Event: *newEvent("invalidated"),
Body: dap.InvalidatedEventBody{
Areas: []dap.InvalidatedAreas{"variables"},
},
})
case "goroutineFilters", "hideSystemGoroutines", "showPprofLabels":
// Thread related data has become invalidated.
s.send(&dap.InvalidatedEvent{
Event: *newEvent("invalidated"),
Body: dap.InvalidatedEventBody{
Areas: []dap.InvalidatedAreas{"threads"},
},
})
}
res += "\nUpdated"
}
return res, nil
}
func (s *Session) sources(_, _ int, filter string) (string, error) {
sources, err := s.debugger.Sources(filter)
if err != nil {
return "", err
}
sort.Strings(sources)
return strings.Join(sources, "\n"), nil
}
func (s *Session) targetCmd(_, _ int, argstr string) (string, error) {
argv := config.Split2PartsBySpace(argstr)
switch argv[0] {
case "list":
tgrp, unlock := s.debugger.LockTargetGroup()
defer unlock()
curpid := tgrp.Selected.Pid()
tgtListStr := ""
for _, tgt := range tgrp.Targets() {
if _, err := tgt.Valid(); err == nil {
selected := ""
if tgt.Pid() == curpid {
selected = "*"
}
tgtListStr += fmt.Sprintf("%s\t%d\t%s\n", selected, tgt.Pid(), tgt.CmdLine)
}
}
return tgtListStr, nil
case "follow-exec":
if len(argv) == 1 {
if s.debugger.FollowExecEnabled() {
return "Follow exec mode is enabled", nil
} else {
return "Follow exec mode is disabled", nil
}
}
argv = config.Split2PartsBySpace(argv[1])
switch argv[0] {
case "-on":
var regex string
if len(argv) == 2 {
regex = argv[1]
}
if err := s.debugger.FollowExec(true, regex); err != nil {
return "", err
}
if regex != "" {
return fmt.Sprintf("Follow exec mode enabled with regex %q", regex), nil
}
return "Follow exec mode enabled", nil
case "-off":
if len(argv) > 1 {
return "", errors.New("too many arguments")
}
if err := s.debugger.FollowExec(false, ""); err != nil {
return "", err
}
return "Follow exec mode disabled", nil
default:
return "", fmt.Errorf("unknown argument %q to 'target follow-exec'", argv[0])
}
case "switch": // TODO: This may cause inconsistency between debugger and frontend.
tgrp, unlock := s.debugger.LockTargetGroup()
defer unlock()
pid, err := strconv.Atoi(argv[1])
if err != nil {
return "", err
}
found := false
for _, tgt := range tgrp.Targets() {
if _, err = tgt.Valid(); err == nil && tgt.Pid() == pid {
found = true
tgrp.Selected = tgt
tgt.SwitchThread(pid)
}
}
if !found {
return "", fmt.Errorf("could not find target %d", pid)
}
return fmt.Sprintf("Switched to process %d", pid), err
default:
return "", fmt.Errorf("unknown target command")
}
}
|