Skip to content

Commit

Permalink
service,terminal,cmd/dlv: automatically guessing substitute-path config
Browse files Browse the repository at this point in the history
Add command, API calls and launch.json option to automatically guess
substitute-path configuration.
  • Loading branch information
aarzilli committed Jul 15, 2024
1 parent c1366e9 commit d41a9b9
Show file tree
Hide file tree
Showing 19 changed files with 521 additions and 44 deletions.
3 changes: 3 additions & 0 deletions Documentation/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,10 +256,13 @@ Changes the value of a configuration parameter.
config substitute-path <from> <to>
config substitute-path <from>
config substitute-path -clear
config substitute-path -guess

Adds or removes a path substitution rule, if -clear is used all
substitute-path rules are removed. Without arguments shows the current list
of substitute-path rules.
The -guess option causes Delve to try to guess your substitute-path
configuration automatically.
See also [Documentation/cli/substitutepath.md](//github.com/go-delve/delve/tree/master/Documentation/cli/substitutepath.md) for how the rules are applied.

config alias <command> <alias>
Expand Down
1 change: 1 addition & 0 deletions Documentation/cli/starlark.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ function_return_locations(FnName) | Equivalent to API call [FunctionReturnLocati
get_breakpoint(Id, Name) | Equivalent to API call [GetBreakpoint](https://pkg.go.dev/github.com/go-delve/delve/service/rpc2#RPCServer.GetBreakpoint)
get_buffered_tracepoints() | Equivalent to API call [GetBufferedTracepoints](https://pkg.go.dev/github.com/go-delve/delve/service/rpc2#RPCServer.GetBufferedTracepoints)
get_thread(Id) | Equivalent to API call [GetThread](https://pkg.go.dev/github.com/go-delve/delve/service/rpc2#RPCServer.GetThread)
guess_substitute_path(ModuleDirectories) | Equivalent to API call [GuessSubstitutePath](https://pkg.go.dev/github.com/go-delve/delve/service/rpc2#RPCServer.GuessSubstitutePath)
is_multiclient() | Equivalent to API call [IsMulticlient](https://pkg.go.dev/github.com/go-delve/delve/service/rpc2#RPCServer.IsMulticlient)
last_modified() | Equivalent to API call [LastModified](https://pkg.go.dev/github.com/go-delve/delve/service/rpc2#RPCServer.LastModified)
breakpoints(All) | Equivalent to API call [ListBreakpoints](https://pkg.go.dev/github.com/go-delve/delve/service/rpc2#RPCServer.ListBreakpoints)
Expand Down
39 changes: 39 additions & 0 deletions Documentation/usage/dlv_substitute-path-guess-helper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
## dlv substitute-path-guess-helper



```
dlv substitute-path-guess-helper [flags]
```

### Options

```
-h, --help help for substitute-path-guess-helper
```

### Options inherited from parent commands

```
--accept-multiclient Allows a headless server to accept multiple client connections via JSON-RPC or DAP.
--allow-non-terminal-interactive Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr
--api-version int Selects JSON-RPC API version when headless. New clients should use v2. Can be reset via RPCServer.SetApiVersion. See Documentation/api/json-rpc/README.md. (default 1)
--backend string Backend selection (see 'dlv help backend'). (default "default")
--build-flags string Build flags, to be passed to the compiler. For example: --build-flags="-tags=integration -mod=vendor -cover -v"
--check-go-version Exits if the version of Go in use is not compatible (too old or too new) with the version of Delve. (default true)
--disable-aslr Disables address space randomization
--headless Run debug server only, in headless mode. Server will accept both JSON-RPC or DAP client connections.
--init string Init file, executed by the terminal client.
-l, --listen string Debugging server listen address. Prefix with 'unix:' to use a unix domain socket. (default "127.0.0.1:0")
--log Enable debugging server logging.
--log-dest string Writes logs to the specified file or file descriptor (see 'dlv help log').
--log-output string Comma separated list of components that should produce debug output (see 'dlv help log')
--only-same-user Only connections from the same user that started this instance of Delve are allowed to connect. (default true)
-r, --redirect stringArray Specifies redirect rules for target process (see 'dlv help redirect')
--wd string Working directory for running the program.
```

### SEE ALSO

* [dlv](dlv.md) - Delve is a debugger for the Go programming language.

5 changes: 4 additions & 1 deletion _scripts/make.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,9 @@ This option can only be specified if testset is basic or a single package.`)
}

func checkCert() bool {
if os.Getenv("NOCERT") != "" {
return false
}
// If we're on OSX make sure the proper CERT env var is set.
if runtime.GOOS != "darwin" || os.Getenv("CERT") != "" {
return true
Expand Down Expand Up @@ -321,7 +324,7 @@ func buildFlags() []string {
} else {
ldFlags = "-X main.Build=" + buildSHA
}
if runtime.GOOS == "darwin" {
if runtime.GOOS == "darwin" && os.Getenv("CERT") != "" {
ldFlags = "-s " + ldFlags
}
return []string{fmt.Sprintf("-ldflags=%s", ldFlags)}
Expand Down
18 changes: 18 additions & 0 deletions cmd/dlv/cmds/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,24 @@ File redirects can also be changed using the 'restart' command.
`,
})

rootCommand.AddCommand(&cobra.Command{
Use: "substitute-path-guess-helper",
Hidden: true,
Run: func(cmd *cobra.Command, args []string) {
gsp, err := rpc2.MakeGuessSusbtitutePathIn()
if err != nil {
fmt.Printf("ERROR: %v\n", err)
os.Exit(1)
}
err = json.NewEncoder(os.Stdout).Encode(gsp)
if err != nil {
fmt.Printf("ERROR: %v\n", err)
os.Exit(1)
}
os.Exit(0)
},
})

rootCommand.DisableAutoGenTag = true

configUsageFunc(rootCommand)
Expand Down
52 changes: 9 additions & 43 deletions cmd/dlv/dlv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,45 +61,18 @@ func assertNoError(err error, t testing.TB, s string) {
}
}

func projectRoot() string {
wd, err := os.Getwd()
if err != nil {
panic(err)
}

gopaths := strings.FieldsFunc(os.Getenv("GOPATH"), func(r rune) bool { return r == os.PathListSeparator })
for _, curpath := range gopaths {
// Detects "gopath mode" when GOPATH contains several paths ex. "d:\\dir\\gopath;f:\\dir\\gopath2"
if strings.Contains(wd, curpath) {
return filepath.Join(curpath, "src", "github.com", "go-delve", "delve")
}
}
val, err := exec.Command("go", "list", "-mod=", "-m", "-f", "{{ .Dir }}").Output()
if err != nil {
panic(err) // the Go tool was tested to work earlier
}
return strings.TrimSuffix(string(val), "\n")
}

func TestBuild(t *testing.T) {
const listenAddr = "127.0.0.1:40573"
var err error

cmd := exec.Command("go", "run", "_scripts/make.go", "build")
cmd.Dir = projectRoot()
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("makefile error: %v\noutput %s\n", err, string(out))
}

dlvbin := filepath.Join(cmd.Dir, "dlv")
dlvbin := protest.GetDlvBinary(t)
defer os.Remove(dlvbin)

fixtures := protest.FindFixturesDir()

buildtestdir := filepath.Join(fixtures, "buildtest")

cmd = exec.Command(dlvbin, "debug", "--headless=true", "--listen="+listenAddr, "--api-version=2", "--backend="+testBackend, "--log", "--log-output=debugger,rpc")
cmd := exec.Command(dlvbin, "debug", "--headless=true", "--listen="+listenAddr, "--api-version=2", "--backend="+testBackend, "--log", "--log-output=debugger,rpc")
cmd.Dir = buildtestdir
stderr, err := cmd.StderrPipe()
assertNoError(err, t, "stderr pipe")
Expand Down Expand Up @@ -344,7 +317,7 @@ func TestRedirect(t *testing.T) {
const checkAutogenDocLongOutput = false

func checkAutogenDoc(t *testing.T, filename, gencommand string, generated []byte) {
saved := slurpFile(t, filepath.Join(projectRoot(), filename))
saved := slurpFile(t, filepath.Join(protest.ProjectRoot(), filename))

saved = bytes.ReplaceAll(saved, []byte("\r\n"), []byte{'\n'})
generated = bytes.ReplaceAll(generated, []byte("\r\n"), []byte{'\n'})
Expand Down Expand Up @@ -382,7 +355,7 @@ func diffMaybe(t *testing.T, filename string, generated []byte) {
return
}
cmd := exec.Command("diff", filename, "-")
cmd.Dir = projectRoot()
cmd.Dir = protest.ProjectRoot()
stdin, _ := cmd.StdinPipe()
go func() {
stdin.Write(generated)
Expand Down Expand Up @@ -412,7 +385,7 @@ func TestGeneratedDoc(t *testing.T) {
// Checks gen-usage-docs.go
tempDir := t.TempDir()
cmd := exec.Command("go", "run", "_scripts/gen-usage-docs.go", tempDir)
cmd.Dir = projectRoot()
cmd.Dir = protest.ProjectRoot()
err := cmd.Run()
assertNoError(err, t, "go run _scripts/gen-usage-docs.go")
entries, err := os.ReadDir(tempDir)
Expand All @@ -426,7 +399,7 @@ func TestGeneratedDoc(t *testing.T) {
a := []string{"run"}
a = append(a, args...)
cmd := exec.Command("go", a...)
cmd.Dir = projectRoot()
cmd.Dir = protest.ProjectRoot()
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("could not run script %v: %v (output: %q)", args, err, string(out))
Expand Down Expand Up @@ -1383,7 +1356,7 @@ func TestStaticcheck(t *testing.T) {
// where we don't do this it is a deliberate style choice.
// * ST1023 "Redundant type in variable declaration" same as S1021.
cmd := exec.Command("staticcheck", args...)
cmd.Dir = projectRoot()
cmd.Dir = protest.ProjectRoot()
cmd.Env = append(os.Environ(), "GOOS=linux", "GOARCH=amd64")
out, _ := cmd.CombinedOutput()
checkAutogenDoc(t, "_scripts/staticcheck-out.txt", fmt.Sprintf("staticcheck %s > _scripts/staticcheck-out.txt", strings.Join(args, " ")), out)
Expand Down Expand Up @@ -1435,21 +1408,14 @@ func TestUnixDomainSocket(t *testing.T) {

var err error

cmd := exec.Command("go", "run", "_scripts/make.go", "build")
cmd.Dir = projectRoot()
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("makefile error: %v\noutput %s\n", err, string(out))
}

dlvbin := filepath.Join(cmd.Dir, "dlv")
dlvbin := protest.GetDlvBinary(t)
defer os.Remove(dlvbin)

fixtures := protest.FindFixturesDir()

buildtestdir := filepath.Join(fixtures, "buildtest")

cmd = exec.Command(dlvbin, "debug", "--headless=true", "--listen=unix:"+listenPath, "--api-version=2", "--backend="+testBackend, "--log", "--log-output=debugger,rpc")
cmd := exec.Command(dlvbin, "debug", "--headless=true", "--listen=unix:"+listenPath, "--api-version=2", "--backend="+testBackend, "--log", "--log-output=debugger,rpc")
cmd.Dir = buildtestdir
stderr, err := cmd.StderrPipe()
assertNoError(err, t, "stderr pipe")
Expand Down
7 changes: 7 additions & 0 deletions pkg/proc/bininfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,13 @@ func (fn *Function) privateRuntime() bool {
return len(name) > n && name[:n] == "runtime." && !('A' <= name[n] && name[n] <= 'Z')
}

func (fn *Function) CompileUnitName() string {
if fn.cu == nil {
return ""
}
return fn.cu.name
}

func rangeParentName(fnname string) int {
const rangeSuffix = "-range"
ridx := strings.Index(fnname, rangeSuffix)
Expand Down
34 changes: 34 additions & 0 deletions pkg/proc/test/support.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,3 +389,37 @@ func RegabiSupported() bool {
return false
}
}

func ProjectRoot() string {
wd, err := os.Getwd()
if err != nil {
panic(err)
}

gopaths := strings.FieldsFunc(os.Getenv("GOPATH"), func(r rune) bool { return r == os.PathListSeparator })
for _, curpath := range gopaths {
// Detects "gopath mode" when GOPATH contains several paths ex. "d:\\dir\\gopath;f:\\dir\\gopath2"
if strings.Contains(wd, curpath) {
return filepath.Join(curpath, "src", "github.com", "go-delve", "delve")
}
}
val, err := exec.Command("go", "list", "-mod=", "-m", "-f", "{{ .Dir }}").Output()
if err != nil {
panic(err) // the Go tool was tested to work earlier
}
return strings.TrimSuffix(string(val), "\n")
}

func GetDlvBinary(t *testing.T) string {
cmd := exec.Command("go", "run", "_scripts/make.go", "build")
cmd.Dir = ProjectRoot()
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("makefile error: %v\noutput %s\n", err, string(out))
}

if runtime.GOOS == "windows" {
return filepath.Join(cmd.Dir, "dlv.exe")
}
return filepath.Join(cmd.Dir, "dlv")
}
3 changes: 3 additions & 0 deletions pkg/terminal/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -555,10 +555,13 @@ Changes the value of a configuration parameter.
config substitute-path <from> <to>
config substitute-path <from>
config substitute-path -clear
config substitute-path -guess
Adds or removes a path substitution rule, if -clear is used all
substitute-path rules are removed. Without arguments shows the current list
of substitute-path rules.
The -guess option causes Delve to try to guess your substitute-path
configuration automatically.
See also Documentation/cli/substitutepath.md for how the rules are applied.
config alias <command> <alias>
Expand Down
11 changes: 11 additions & 0 deletions pkg/terminal/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,17 @@ func configureSetSubstitutePath(t *Term, rest string) error {
t.conf.SubstitutePath = t.conf.SubstitutePath[:0]
return nil
}
if strings.TrimSpace(rest) == "-guess" {
rules, err := t.client.GuessSubstitutePath()
if err != nil {
return err
}
t.conf.SubstitutePath = t.conf.SubstitutePath[:0]
for _, rule := range rules {
t.conf.SubstitutePath = append(t.conf.SubstitutePath, config.SubstitutePathRule{From: rule[0], To: rule[1]})
}
return nil
}
argv := config.SplitQuotedFields(rest, '"')
if len(argv) == 2 && argv[0] == "-clear" {
argv = argv[1:]
Expand Down
31 changes: 31 additions & 0 deletions pkg/terminal/starbind/starlark_mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -956,6 +956,37 @@ func (env *Env) starlarkPredeclare() (starlark.StringDict, map[string]string) {
return env.interfaceToStarlarkValue(rpcRet), nil
})
doc["get_thread"] = "builtin get_thread(Id)\n\nget_thread gets a thread by its ID."
r["guess_substitute_path"] = starlark.NewBuiltin("guess_substitute_path", func(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
if err := isCancelled(thread); err != nil {
return starlark.None, decorateError(thread, err)
}
var rpcArgs rpc2.GuessSubstitutePathIn
var rpcRet rpc2.GuessSubstitutePathOut
if len(args) > 0 && args[0] != starlark.None {
err := unmarshalStarlarkValue(args[0], &rpcArgs.ModuleDirectories, "ModuleDirectories")
if err != nil {
return starlark.None, decorateError(thread, err)
}
}
for _, kv := range kwargs {
var err error
switch kv[0].(starlark.String) {
case "ModuleDirectories":
err = unmarshalStarlarkValue(kv[1], &rpcArgs.ModuleDirectories, "ModuleDirectories")
default:
err = fmt.Errorf("unknown argument %q", kv[0])
}
if err != nil {
return starlark.None, decorateError(thread, err)
}
}
err := env.ctx.Client().CallAPI("GuessSubstitutePath", &rpcArgs, &rpcRet)
if err != nil {
return starlark.None, err
}
return env.interfaceToStarlarkValue(rpcRet), nil
})
doc["guess_substitute_path"] = "builtin guess_substitute_path(ModuleDirectories)"
r["is_multiclient"] = starlark.NewBuiltin("is_multiclient", func(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
if err := isCancelled(thread); err != nil {
return starlark.None, decorateError(thread, err)
Expand Down
3 changes: 3 additions & 0 deletions service/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,9 @@ type Client interface {
// GetDebugInfoDirectories returns the list of directories used to search for debug symbols
GetDebugInfoDirectories() ([]string, error)

// GuessSubstitutePath tries to guess a substitute-path configuration for the client
GuessSubstitutePath() ([][2]string, error)

// CallAPI allows calling an arbitrary rpc method (used by starlark bindings)
CallAPI(method string, args, reply interface{}) error
}
12 changes: 12 additions & 0 deletions service/dap/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1975,6 +1975,18 @@ func (s *Session) onAttachRequest(request *dap.AttachRequest) {

s.setLaunchAttachArgs(args.LaunchAttachCommonConfig)

if len(args.LaunchAttachCommonConfig.SubstitutePath) == 0 && len(args.ClientMod2Dir) > 0 && s.debugger != nil {
server2Client := s.debugger.GuessSubstitutePath(args.ClientMod2Dir)
clientToServer := make([][2]string, 0, len(server2Client))
serverToClient := make([][2]string, 0, len(server2Client))
for serverDir, clientDir := range server2Client {
serverToClient = append(serverToClient, [2]string{serverDir, clientDir})
clientToServer = append(clientToServer, [2]string{clientDir, serverDir})
}
s.args.substitutePathClientToServer = clientToServer
s.args.substitutePathServerToClient = serverToClient
}

// Notify the client that the debugger is ready to start accepting
// configuration requests for setting breakpoints, etc. The client
// will end the configuration sequence with 'configurationDone'.
Expand Down
4 changes: 4 additions & 0 deletions service/dap/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,10 @@ type AttachConfig struct {
// Wait for a process with a name beginning with this prefix.
AttachWaitFor string `json:"waitFor,omitempty"`

// ClientMod2Dir is a map from module paths to client directories, used to
// automatically guess SubstitutePath if it is not specified explicitly.
ClientMod2Dir map[string]string

LaunchAttachCommonConfig
}

Expand Down
Loading

0 comments on commit d41a9b9

Please sign in to comment.