Go 1.16 support branch (#2214)

* proc: misc test fixes for Go 1.16

* proc: fix cgo stacktraces in Go 1.16 with simplified C -> Go call path

* dwarf/line: make dwarf/line correct when '\\' are used

Our code depends heavily on paths being '/' separated because go always
produced '/' separated file paths. The call to filepath.Join will
normalize the paths, on windows, to always be '\\' separated, which
violated our assumptions.

This didn't use to be a problem because the codepath that calls
filepath.Join was never exercised by executable files produced by Go,
but Go 1.16 started producing debug_line sections that use the
directory table with https://go-review.googlesource.com/c/go/+/263017/.

Fix this to always use path.Join after making sure, on windows, to
always normalize paths to use '/' as a separator. Replace the use of
filepath.IsAbs with an operating system independent version.

* goversion: bump supported Go version
This commit is contained in:
Alessandro Arzilli 2021-01-05 19:56:30 +01:00 committed by GitHub
parent 75f00b963c
commit 6dd686ca49
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 41 additions and 16 deletions

@ -3,7 +3,7 @@ package line
import (
"bytes"
"encoding/binary"
"path/filepath"
"path"
"strings"
"github.com/go-delve/delve/pkg/dwarf/util"
@ -193,21 +193,37 @@ func readFileEntry(info *DebugLineInfo, buf *bytes.Buffer, exitOnEmptyPath bool)
}
if info.normalizeBackslash {
entry.Path = strings.Replace(entry.Path, "\\", "/", -1)
entry.Path = strings.ReplaceAll(entry.Path, "\\", "/")
}
entry.DirIdx, _ = util.DecodeULEB128(buf)
entry.LastModTime, _ = util.DecodeULEB128(buf)
entry.Length, _ = util.DecodeULEB128(buf)
if !filepath.IsAbs(entry.Path) {
if !pathIsAbs(entry.Path) {
if entry.DirIdx >= 0 && entry.DirIdx < uint64(len(info.IncludeDirs)) {
entry.Path = filepath.Join(info.IncludeDirs[entry.DirIdx], entry.Path)
entry.Path = path.Join(info.IncludeDirs[entry.DirIdx], entry.Path)
}
}
return entry
}
// pathIsAbs returns true if this is an absolute path.
// We can not use path.IsAbs because it will not recognize windows paths as
// absolute. We also can not use filepath.Abs because we want this
// processing to be independent of the host operating system (we could be
// reading an executable file produced on windows on a unix machine or vice
// versa).
func pathIsAbs(s string) bool {
if len(s) >= 1 && s[0] == '/' {
return true
}
if len(s) >= 2 && s[1] == ':' && (('a' <= s[0] && s[0] <= 'z') || ('A' <= s[0] && s[0] <= 'Z')) {
return true
}
return false
}
// parseFileEntries5 parses the file table for DWARF 5
func parseFileEntries5(info *DebugLineInfo, buf *bytes.Buffer) {
fileEntryFormReader := readEntryFormat(buf, info.Logf)
@ -217,13 +233,13 @@ func parseFileEntries5(info *DebugLineInfo, buf *bytes.Buffer) {
fileEntryFormReader.reset()
for fileEntryFormReader.next(buf) {
entry := new(FileEntry)
var path string
var p string
var diridx int = -1
switch fileEntryFormReader.contentType {
case _DW_LNCT_path:
if fileEntryFormReader.formCode != _DW_FORM_string {
path = fileEntryFormReader.str
p = fileEntryFormReader.str
} else {
//TODO(aarzilli): support debug_string, debug_line_str
info.Logf("unsupported string form %#x", fileEntryFormReader.formCode)
@ -238,10 +254,14 @@ func parseFileEntries5(info *DebugLineInfo, buf *bytes.Buffer) {
// not implemented
}
if diridx >= 0 && !filepath.IsAbs(path) && diridx < len(info.IncludeDirs) {
path = filepath.Join(info.IncludeDirs[diridx], path)
if info.normalizeBackslash {
p = strings.ReplaceAll(p, "\\", "/")
}
entry.Path = path
if diridx >= 0 && !pathIsAbs(p) && diridx < len(info.IncludeDirs) {
p = path.Join(info.IncludeDirs[diridx], p)
}
entry.Path = p
info.FileNames = append(info.FileNames, entry)
info.Lookup[entry.Path] = entry
}

@ -11,12 +11,14 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
"unsafe"
"github.com/go-delve/delve/pkg/dwarf/godwarf"
"github.com/go-delve/delve/pkg/goversion"
)
var userTestFile string
@ -113,8 +115,10 @@ func testDebugLinePrologueParser(p string, t *testing.T) {
}
}
if len(dbl.IncludeDirs) != 1 {
t.Fatal("Include dirs not parsed correctly")
if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 16) {
if len(dbl.IncludeDirs) != 1 {
t.Fatal("Include dirs not parsed correctly")
}
}
for _, ln := range dbl.Lookup {
@ -127,6 +131,7 @@ func testDebugLinePrologueParser(p string, t *testing.T) {
}
for _, n := range dbl.FileNames {
t.Logf("file %s\n", n.Path)
if strings.Contains(n.Path, "/_fixtures/testnextprog.go") {
mainFileFound = true
break

@ -8,7 +8,7 @@ var (
MinSupportedVersionOfGoMajor = 1
MinSupportedVersionOfGoMinor = 13
MaxSupportedVersionOfGoMajor = 1
MaxSupportedVersionOfGoMinor = 15
MaxSupportedVersionOfGoMinor = 16
goTooOldErr = fmt.Errorf("Version of Go is too old for this version of Delve (minimum supported version %d.%d, suppress this error with --check-go-version=false)", MinSupportedVersionOfGoMajor, MinSupportedVersionOfGoMinor)
dlvTooOldErr = fmt.Errorf("Version of Delve is too old for this version of Go (maximum supported version %d.%d, suppress this error with --check-go-version=false)", MaxSupportedVersionOfGoMajor, MaxSupportedVersionOfGoMinor)
)

@ -161,7 +161,7 @@ func amd64SwitchStack(it *stackIterator, _ *op.DwarfRegisters) bool {
it.top = false
return true
case "runtime.cgocallback_gofunc":
case "runtime.cgocallback_gofunc", "runtime.cgocallback":
// For a detailed description of how this works read the long comment at
// the start of $GOROOT/src/runtime/cgocall.go and the source code of
// runtime.cgocallback_gofunc in $GOROOT/src/runtime/asm_amd64.s

@ -139,7 +139,7 @@ const spAlign = 16
func arm64SwitchStack(it *stackIterator, callFrameRegs *op.DwarfRegisters) bool {
if it.frame.Current.Fn != nil {
switch it.frame.Current.Fn.Name {
case "runtime.asmcgocall", "runtime.cgocallback_gofunc", "runtime.sigpanic":
case "runtime.asmcgocall", "runtime.cgocallback_gofunc", "runtime.sigpanic", "runtime.cgocallback":
//do nothing
case "runtime.goexit", "runtime.rt0_go", "runtime.mcall":
// Look for "top of stack" functions.
@ -202,7 +202,7 @@ func arm64SwitchStack(it *stackIterator, callFrameRegs *op.DwarfRegisters) bool
callFrameRegs.Reg(callFrameRegs.SPRegNum).Uint64Val = uint64(int64(newsp))
return false
case "runtime.cgocallback_gofunc":
case "runtime.cgocallback_gofunc", "runtime.cgocallback":
// For a detailed description of how this works read the long comment at
// the start of $GOROOT/src/runtime/cgocall.go and the source code of
// runtime.cgocallback_gofunc in $GOROOT/src/runtime/asm_arm64.s

@ -1709,7 +1709,7 @@ func TestAcceptMulticlient(t *testing.T) {
},
})
if err := server.Run(); err != nil {
t.Fatal(err)
panic(err)
}
<-disconnectChan
server.Stop()