Repository: go-delve/delve
Pull request #2615 (2021-07-22 22:05:09)

cmd/dlv: dlv version --verbose
With `-v` or `--verbose`, dlv version prints out runtime/debug.BuildInfo 
read from the dlv binary. Users can retrieve the same info using 
`go version -m <path_to_dlv>` but I think it is convenient to have.

runtime/debug.BuildInfo is available since go1.12. I placed built tag
but already the current delve repo requires go1.12, so maybe that isn't
necessary. A different approach is exec `go version -m <self>`, but if
the user's go version in PATH is older than go1.12 this won't work.

If dlv was built from cloned delve repo:

```
$ ./dlv version -v
Delve Debugger
Version: 1.7.0
Build: $Id: e353a65161e6ed74952b96bbb62ebfc56090832b $
Build Details: go1.16.5
 mod    github.com/go-delve/delve       (devel)
 dep    github.com/cosiner/argv v0.1.0  h1:BVDiEL32lwHukgJKP87btEPenzrrHUjajs/8yzaqcXg=
...
```

If dlv was built with `go install github.com/go-delve/delve@latest`
with go1.16+, or
`GO111MODULE=on go get github.com/go-delve/delve@latest`
from a clean main module:

```
$ ./dlv version -v
Delve Debugger
Version: 1.7.0
Build: $Id: e353a65161e6ed74952b96bbb62ebfc56090832b $
Build Details: go1.16.5
 mod    github.com/go-delve/delve       v1.7.0
 dep    github.com/cosiner/argv v0.1.0  h1:BVDiEL32lwHukgJKP87btEPenzrrHUjajs/8yzaqcXg=
...
```

hyangah: Removed the bogus test, and the remaining failures aren't related to this PR.
![Screen Shot 2021-07-23 at 12 10 57 PM](https://user-images.githubusercontent.com/4999471/126810889-a2f71dc1-a34f-4101-b39a-c2562bc132b7.png)


Patch:
diff --git a/Documentation/usage/dlv_version.md b/Documentation/usage/dlv_version.md
index a4f48420..6fec6111 100644
--- a/Documentation/usage/dlv_version.md
+++ b/Documentation/usage/dlv_version.md
@@ -9,7 +9,8 @@ dlv version [flags]
 ### Options
 
 ```
-  -h, --help   help for version
+  -h, --help      help for version
+  -v, --verbose   print verbose version info
 ```
 
 ### Options inherited from parent commands
diff --git a/cmd/dlv/cmds/commands.go b/cmd/dlv/cmds/commands.go
index 3f2a2f1a..9bf1e5b4 100644
--- a/cmd/dlv/cmds/commands.go
+++ b/cmd/dlv/cmds/commands.go
@@ -308,13 +308,18 @@ Currently supports linux/amd64 and linux/arm64 core files, windows/amd64 minidum
 	rootCommand.AddCommand(coreCommand)
 
 	// 'version' subcommand.
+	var versionVerbose = false
 	versionCommand := &cobra.Command{
 		Use:   "version",
 		Short: "Prints version.",
 		Run: func(cmd *cobra.Command, args []string) {
 			fmt.Printf("Delve Debugger\n%s\n", version.DelveVersion)
+			if versionVerbose {
+				fmt.Printf("Build Details: %s\n", version.BuildInfo())
+			}
 		},
 	}
+	versionCommand.Flags().BoolVarP(&versionVerbose, "verbose", "v", false, "print verbose version info")
 	rootCommand.AddCommand(versionCommand)
 
 	if path, _ := exec.LookPath("rr"); path != "" || docCall {
diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go
index c2b1a5cc..f4f57383 100644
--- a/pkg/proc/bininfo.go
+++ b/pkg/proc/bininfo.go
@@ -715,14 +715,28 @@ func (bi *BinaryInfo) AddImage(path string, addr uint64) error {
 
 // moduleDataToImage finds the image corresponding to the given module data object.
 func (bi *BinaryInfo) moduleDataToImage(md *moduleData) *Image {
-	return bi.funcToImage(bi.PCToFunc(uint64(md.text)))
+	fn := bi.PCToFunc(uint64(md.text))
+	if fn != nil {
+		return bi.funcToImage(fn)
+	}
+	// Try searching for the image with the closest address preceding md.text
+	var so *Image
+	for i := range bi.Images {
+		if int64(bi.Images[i].StaticBase) > int64(md.text) {
+			continue
+		}
+		if so == nil || int64(bi.Images[i].StaticBase) > int64(so.StaticBase) {
+			so = bi.Images[i]
+		}
+	}
+	return so
 }
 
 // imageToModuleData finds the module data in mds corresponding to the given image.
 func (bi *BinaryInfo) imageToModuleData(image *Image, mds []moduleData) *moduleData {
 	for _, md := range mds {
 		im2 := bi.moduleDataToImage(&md)
-		if im2.index == image.index {
+		if im2 != nil && im2.index == image.index {
 			return &md
 		}
 	}
diff --git a/pkg/proc/types.go b/pkg/proc/types.go
index d052b61d..deb9c434 100644
--- a/pkg/proc/types.go
+++ b/pkg/proc/types.go
@@ -134,17 +134,19 @@ func runtimeTypeToDIE(_type *Variable, dataAddr uint64) (typ godwarf.Type, kind
 	md := findModuleDataForType(bi, mds, _type.Addr, _type.mem)
 	if md != nil {
 		so := bi.moduleDataToImage(md)
-		if rtdie, ok := so.runtimeTypeToDIE[uint64(_type.Addr-md.types)]; ok {
-			typ, err := godwarf.ReadType(so.dwarf, so.index, rtdie.offset, so.typeCache)
-			if err != nil {
-				return nil, 0, fmt.Errorf("invalid interface type: %v", err)
-			}
-			if rtdie.kind == -1 {
-				if kindField := _type.loadFieldNamed("kind"); kindField != nil && kindField.Value != nil {
-					rtdie.kind, _ = constant.Int64Val(kindField.Value)
+		if so != nil {
+			if rtdie, ok := so.runtimeTypeToDIE[uint64(_type.Addr-md.types)]; ok {
+				typ, err := godwarf.ReadType(so.dwarf, so.index, rtdie.offset, so.typeCache)
+				if err != nil {
+					return nil, 0, fmt.Errorf("invalid interface type: %v", err)
+				}
+				if rtdie.kind == -1 {
+					if kindField := _type.loadFieldNamed("kind"); kindField != nil && kindField.Value != nil {
+						rtdie.kind, _ = constant.Int64Val(kindField.Value)
+					}
 				}
+				return typ, rtdie.kind, nil
 			}
-			return typ, rtdie.kind, nil
 		}
 	}
 
diff --git a/pkg/terminal/command.go b/pkg/terminal/command.go
index 50ab47ee..b0238077 100644
--- a/pkg/terminal/command.go
+++ b/pkg/terminal/command.go
@@ -1446,8 +1446,28 @@ func continueUntilCompleteNext(t *Term, state *api.DebuggerState, op string, sho
 		}
 		return nil
 	}
+	skipBreakpoints := false
 	for {
-		fmt.Printf("\tbreakpoint hit during %s, continuing...\n", op)
+		fmt.Printf("\tbreakpoint hit during %s", op)
+		if !skipBreakpoints {
+			fmt.Printf("\n")
+			answer, err := promptAutoContinue(t, op)
+			switch answer {
+			case "f": // finish next
+				skipBreakpoints = true
+				fallthrough
+			case "c": // continue once
+				fmt.Printf("continuing...\n")
+			case "s": // stop and cancel
+				fallthrough
+			default:
+				t.client.CancelNext()
+				printfile(t, state.CurrentThread.File, state.CurrentThread.Line, true)
+				return err
+			}
+		} else {
+			fmt.Printf(", continuing...\n")
+		}
 		stateChan := t.client.DirectionCongruentContinue()
 		var state *api.DebuggerState
 		for state = range stateChan {
@@ -1464,6 +1484,20 @@ func continueUntilCompleteNext(t *Term, state *api.DebuggerState, op string, sho
 	}
 }
 
+func promptAutoContinue(t *Term, op string) (string, error) {
+	for {
+		answer, err := t.line.Prompt(fmt.Sprintf("[c] continue [s] stop here and cancel %s, [f] finish %s skipping all breakpoints? ", op, op))
+		if err != nil {
+			return "", err
+		}
+		answer = strings.ToLower(strings.TrimSpace(answer))
+		switch answer {
+		case "f", "c", "s":
+			return answer, nil
+		}
+	}
+}
+
 func scopePrefixSwitch(t *Term, ctx callContext) error {
 	if ctx.Scope.GoroutineID > 0 {
 		_, err := t.client.SwitchGoroutine(ctx.Scope.GoroutineID)
diff --git a/pkg/version/buildinfo.go b/pkg/version/buildinfo.go
new file mode 100644
index 00000000..4a34bd56
--- /dev/null
+++ b/pkg/version/buildinfo.go
@@ -0,0 +1,32 @@
+// +build go1.12
+
+package version
+
+import (
+	"bytes"
+	"runtime/debug"
+	"text/template"
+)
+
+func init() {
+	buildInfo = moduleBuildInfo
+}
+
+var buildInfoTmpl = ` mod	{{.Main.Path}}	{{.Main.Version}}	{{.Main.Sum}}
+{{range .Deps}} dep	{{.Path}}	{{.Version}}	{{.Sum}}{{if .Replace}}
+	=> {{.Replace.Path}}	{{.Replace.Version}}	{{.Replace.Sum}}{{end}}
+{{end}}`
+
+func moduleBuildInfo() string {
+	info, ok := debug.ReadBuildInfo()
+	if !ok {
+		return "not built in module mode"
+	}
+
+	buf := new(bytes.Buffer)
+	err := template.Must(template.New("buildinfo").Parse(buildInfoTmpl)).Execute(buf, info)
+	if err != nil {
+		panic(err)
+	}
+	return buf.String()
+}
diff --git a/pkg/version/version.go b/pkg/version/version.go
index e353a651..03c66357 100644
--- a/pkg/version/version.go
+++ b/pkg/version/version.go
@@ -1,6 +1,9 @@
 package version
 
-import "fmt"
+import (
+	"fmt"
+	"runtime"
+)
 
 // Version represents the current version of Delve.
 type Version struct {
@@ -26,3 +29,11 @@ func (v Version) String() string {
 	}
 	return fmt.Sprintf("%s\nBuild: %s", ver, v.Build)
 }
+
+var buildInfo = func() string {
+	return ""
+}
+
+func BuildInfo() string {
+	return fmt.Sprintf("%s\n%s", runtime.Version(), buildInfo())
+}
diff --git a/service/dap/server.go b/service/dap/server.go
index 4a5d5b68..ae87eb53 100644
--- a/service/dap/server.go
+++ b/service/dap/server.go
@@ -772,6 +772,20 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) {
 		return
 	}
 
+	backend, ok := request.Arguments["backend"]
+	if ok {
+		backendParsed, ok := backend.(string)
+		if !ok {
+			s.sendErrorResponse(request.Request,
+				FailedToLaunch, "Failed to launch",
+				fmt.Sprintf("'backend' attribute '%v' in debug configuration is not a string.", backend))
+			return
+		}
+		s.config.Debugger.Backend = backendParsed
+	} else {
+		s.config.Debugger.Backend = "default"
+	}
+
 	if mode == "replay" {
 		traceDirPath, _ := request.Arguments["traceDirPath"].(string)
 
@@ -891,20 +905,6 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) {
 		}
 	}
 
-	backend, ok := request.Arguments["backend"]
-	if ok {
-		backendParsed, ok := backend.(string)
-		if !ok {
-			s.sendErrorResponse(request.Request,
-				FailedToLaunch, "Failed to launch",
-				fmt.Sprintf("'backend' attribute '%v' in debug configuration is not a string.", backend))
-			return
-		}
-		s.config.Debugger.Backend = backendParsed
-	} else {
-		s.config.Debugger.Backend = "default"
-	}
-
 	s.config.ProcessArgs = append([]string{program}, targetArgs...)
 	s.config.Debugger.WorkingDir = filepath.Dir(program)
 


Test patch:
diff --git a/_scripts/latestver.py b/_scripts/latestver.py
index d4138136..cad2f80a 100644
--- a/_scripts/latestver.py
+++ b/_scripts/latestver.py
@@ -2,10 +2,12 @@
 import json
 import urllib
 import sys
+from distutils.version import LooseVersion
 
 ver = sys.argv[1]
 d = json.loads(urllib.urlopen('https://golang.org/dl/?mode=json&include=all').read())
-for x in d:
+ds = sorted(d, reverse=True, key=lambda it: LooseVersion(it['version'][2:]))
+for x in ds:
 	if x['version'][:len(ver)] == ver:
 		print x['version']
 		exit(0)
diff --git a/_scripts/test_linux.sh b/_scripts/test_linux.sh
index 2125af5f..a313c9e1 100755
--- a/_scripts/test_linux.sh
+++ b/_scripts/test_linux.sh
@@ -31,7 +31,7 @@ if [ "$version" = "gotip" ]; then
 	cd -
 else
 	echo Finding latest patch version for $version
-	version=$(curl 'https://golang.org/dl/?mode=json&include=all' | jq '.[].version' --raw-output | egrep ^$version'($|\.|beta|rc)' | head -1)
+	version=$(curl 'https://golang.org/dl/?mode=json&include=all' | jq '.[].version' --raw-output | egrep ^$version'($|\.|beta|rc)' | sort -rV | head -1)
 	echo "Go $version on $arch"
 	getgo $version
 fi
diff --git a/_scripts/test_windows.ps1 b/_scripts/test_windows.ps1
index 7d814b67..27c42751 100644
--- a/_scripts/test_windows.ps1
+++ b/_scripts/test_windows.ps1
@@ -50,9 +50,16 @@ if ($version -eq "gotip") {
 } else {
     # Install Go
     Write-Host "Finding latest patch version for $version"
-    $version = Invoke-WebRequest -Uri 'https://golang.org/dl/?mode=json&include=all' -UseBasicParsing | foreach {$_.Content} | ConvertFrom-Json | foreach {$_.version} | Select-String -Pattern "^$version($|\.|beta|rc)" | Select-Object -First 1 | foreach {$_.Line}
-    Write-Host "Go $version on $arch"
-    GetGo $version
+    $versions = Invoke-WebRequest -Uri 'https://golang.org/dl/?mode=json&include=all' -UseBasicParsing | foreach {$_.Content} | ConvertFrom-Json
+    $v = $versions | foreach {$_.version} | Select-String -Pattern "^$version($|\.)" | Sort-Object -Descending | Select-Object -First 1
+    if ($v -eq $null) {
+      $v = $versions | foreach {$_.version} | Select-String -Pattern "^$version(rc)" | Sort-Object -Descending | Select-Object -First 1
+    }
+    if ($v -eq $null) {
+      $v = $versions | foreach {$_.version} | Select-String -Pattern "^$version(beta)" | Sort-Object -Descending | Select-Object -First 1
+    }
+    Write-Host "Go $v on $arch"
+    GetGo $v
 }
 
 $env:GOPATH = "C:\gopath"
diff --git a/cmd/dlv/dlv_test.go b/cmd/dlv/dlv_test.go
index 479ac40e..63ee20ec 100644
--- a/cmd/dlv/dlv_test.go
+++ b/cmd/dlv/dlv_test.go
@@ -781,3 +781,18 @@ func TestDlvTestChdir(t *testing.T) {
 		t.Errorf("output did not contain expected string %q", tgt)
 	}
 }
+
+func TestVersion(t *testing.T) {
+	dlvbin, tmpdir := getDlvBin(t)
+	defer os.RemoveAll(tmpdir)
+
+	got, err := exec.Command(dlvbin, "version", "-v").CombinedOutput()
+	if err != nil {
+		t.Fatalf("error executing `dlv version`: %v\n%s\n", err, got)
+	}
+	want1 := []byte("mod\tgithub.com/go-delve/delve")
+	want2 := []byte("dep\tgithub.com/google/go-dap")
+	if !bytes.Contains(got, want1) || !bytes.Contains(got, want2) {
+		t.Errorf("got %s\nwant %v and %v in the output", got, want1, want2)
+	}
+}
diff --git a/pkg/terminal/command_test.go b/pkg/terminal/command_test.go
index e23b95d6..4cd90eb9 100644
--- a/pkg/terminal/command_test.go
+++ b/pkg/terminal/command_test.go
@@ -582,47 +582,6 @@ func countOccurrences(s, needle string) int {
 	return count
 }
 
-func TestIssue387(t *testing.T) {
-	if runtime.GOOS == "freebsd" {
-		t.Skip("test is not valid on FreeBSD")
-	}
-	// a breakpoint triggering during a 'next' operation will interrupt it
-	test.AllowRecording(t)
-	withTestTerminal("issue387", t, func(term *FakeTerminal) {
-		breakpointHitCount := 0
-		term.MustExec("break dostuff")
-		for {
-			outstr, err := term.Exec("continue")
-			breakpointHitCount += countOccurrences(outstr, "issue387.go:8")
-			t.Log(outstr)
-			if err != nil {
-				if !strings.Contains(err.Error(), "exited") {
-					t.Fatalf("Unexpected error executing 'continue': %v", err)
-				}
-				break
-			}
-
-			pos := 9
-
-			for {
-				outstr = term.MustExec("next")
-				breakpointHitCount += countOccurrences(outstr, "issue387.go:8")
-				t.Log(outstr)
-				if countOccurrences(outstr, fmt.Sprintf("issue387.go:%d", pos)) == 0 {
-					t.Fatalf("did not continue to expected position %d", pos)
-				}
-				pos++
-				if pos >= 11 {
-					break
-				}
-			}
-		}
-		if breakpointHitCount != 10 {
-			t.Fatalf("Breakpoint hit wrong number of times, expected 10 got %d", breakpointHitCount)
-		}
-	})
-}
-
 func listIsAt(t *testing.T, term *FakeTerminal, listcmd string, cur, start, end int) {
 	t.Helper()
 	outstr := term.MustExec(listcmd)
diff --git a/service/dap/server_test.go b/service/dap/server_test.go
index 63422f38..bb20000c 100644
--- a/service/dap/server_test.go
+++ b/service/dap/server_test.go
@@ -3689,6 +3689,10 @@ func TestPanicBreakpointOnNext(t *testing.T) {
 }
 
 func TestFatalThrowBreakpoint(t *testing.T) {
+	// This is not currently flaky for Go 1.17 see https://github.com/golang/go/issues/46425.
+	if goversion.VersionAfterOrEqual(runtime.Version(), 1, 17) {
+		t.Skip()
+	}
 	runTest(t, "fatalerror", func(client *daptest.Client, fixture protest.Fixture) {
 		runDebugSessionWithBPs(t, client, "launch",
 			// Launch
@@ -4574,7 +4578,6 @@ func TestOptionalNotYetImplementedResponses(t *testing.T) {
 		client.RestartRequest()
 		expectNotYetImplemented("restart")
 
-
 		client.SetExpressionRequest()
 		expectNotYetImplemented("setExpression")
 
@@ -4750,19 +4753,33 @@ func TestBadLaunchRequests(t *testing.T) {
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "noDebug": true, "cwd": "dir/invalid"})
 		checkFailedToLaunch(client.ExpectErrorResponse(t)) // invalid directory, the error message is system-dependent.
 
-		// Bad parameters on "replay" and "core" modes
+		// Bad "replay" parameters
+		// These errors come from dap layer
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "replay", "traceDirPath": ""})
 		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
 			"Failed to launch: The 'traceDirPath' attribute is missing in debug configuration.")
-		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "core", "coreFilePath": ""})
-		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
-			"Failed to launch: The program attribute is missing in debug configuration.")
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "replay", "program": fixture.Source, "traceDirPath": ""})
 		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
 			"Failed to launch: The 'traceDirPath' attribute is missing in debug configuration.")
+		// These errors come from debugger layer
+		if _, err := exec.LookPath("rr"); err != nil {
+			client.LaunchRequestWithArgs(map[string]interface{}{"mode": "replay", "backend": "ignored", "traceDirPath": ".."})
+			checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
+				"Failed to launch: backend unavailable")
+		}
+
+		// Bad "core" parameters
+		// These errors come from dap layer
+		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "core", "coreFilePath": ""})
+		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
+			"Failed to launch: The program attribute is missing in debug configuration.")
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "core", "program": fixture.Source, "coreFilePath": ""})
 		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
 			"Failed to launch: The 'coreFilePath' attribute is missing in debug configuration.")
+		// These errors come from debugger layer
+		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "core", "backend": "ignored", "program": fixture.Source, "coreFilePath": fixture.Source})
+		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
+			"Failed to launch: unrecognized core format")
 
 		// We failed to launch the program. Make sure shutdown still works.
 		client.DisconnectRequest()