未验证 提交 0741d3e5 编写于 作者: A Alessandro Arzilli 提交者: GitHub

*: Go 1.14 support branch (#1727)

* tests: misc test fixes for go1.14

- math.go is now ambiguous due to changes to the go runtime so specify
  that we mean our own math.go in _fixtures
- go list -m requires vendor-mode to be disabled so pass '-mod=' to it
  in case user has GOFLAGS=-mod=vendor
- update version of go/packages, required to work with go 1.14 (and
  executed go mod vendor)
- Increased goroutine migration in one development version of Go 1.14
  revealed a problem with TestCheckpoints in command_test.go and
  rr_test.go. The tests were always wrong because Restart(checkpoint)
  doesn't change the current thread but we can't assume that when the
  checkpoint was taken the current goroutine was running on the same
  thread.

* goversion: update maximum supported version

* Makefile: disable testing lldb-server backend on linux with Go 1.14

There seems to be some incompatibility with lldb-server version 6.0.0
on linux and Go 1.14.

* proc/gdbserial: better handling of signals

- if multiple signals are received simultaneously propagate all of them to the
  target threads instead of only one.
- debugserver will drop an interrupt request if a target thread simultaneously
  receives a signal, handle this situation.

* dwarf/line: normalize backslashes for windows executables

Starting with Go 1.14 the compiler sometimes emits backslashes as well
as forward slashes in debug_line, normalize everything to / for
conformity with the behavior of previous versions.

* proc/native: partial support for Windows async preempt mechanism

See https://github.com/golang/go/issues/36494 for a description of why
full support for 1.14 under windows is problematic.

* proc/native: disable Go 1.14 async preemption on Windows

See https://github.com/golang/go/issues/36494
上级 bc9d95d6
......@@ -63,7 +63,7 @@ func projectRoot() string {
return filepath.Join(curpath, "src", "github.com", "go-delve", "delve")
}
}
val, err := exec.Command("go", "list", "-m", "-f", "{{ .Dir }}").Output()
val, err := exec.Command("go", "list", "-mod=", "-m", "-f", "{{ .Dir }}").Output()
if err != nil {
panic(err) // the Go tool was tested to work earlier
}
......
......@@ -4,6 +4,7 @@ import (
"bytes"
"encoding/binary"
"path/filepath"
"strings"
"github.com/go-delve/delve/pkg/dwarf/util"
)
......@@ -34,9 +35,12 @@ type DebugLineInfo struct {
// lastMachineCache[pc] is a state machine stopped at an address after pc
lastMachineCache map[uint64]*StateMachine
// staticBase is the address at which the executable is loaded, 0 for non-PIEs
staticBase uint64
// if normalizeBackslash is true all backslashes (\) will be converted into forward slashes (/)
normalizeBackslash bool
}
type FileEntry struct {
......@@ -49,7 +53,7 @@ type FileEntry struct {
type DebugLines []*DebugLineInfo
// ParseAll parses all debug_line segments found in data
func ParseAll(data []byte, logfn func(string, ...interface{}), staticBase uint64) DebugLines {
func ParseAll(data []byte, logfn func(string, ...interface{}), staticBase uint64, normalizeBackslash bool) DebugLines {
var (
lines = make(DebugLines, 0)
buf = bytes.NewBuffer(data)
......@@ -57,7 +61,7 @@ func ParseAll(data []byte, logfn func(string, ...interface{}), staticBase uint64
// We have to parse multiple file name tables here.
for buf.Len() > 0 {
lines = append(lines, Parse("", buf, logfn, staticBase))
lines = append(lines, Parse("", buf, logfn, staticBase, normalizeBackslash))
}
return lines
......@@ -65,7 +69,7 @@ func ParseAll(data []byte, logfn func(string, ...interface{}), staticBase uint64
// Parse parses a single debug_line segment from buf. Compdir is the
// DW_AT_comp_dir attribute of the associated compile unit.
func Parse(compdir string, buf *bytes.Buffer, logfn func(string, ...interface{}), staticBase uint64) *DebugLineInfo {
func Parse(compdir string, buf *bytes.Buffer, logfn func(string, ...interface{}), staticBase uint64, normalizeBackslash bool) *DebugLineInfo {
dbl := new(DebugLineInfo)
dbl.Logf = logfn
dbl.staticBase = staticBase
......@@ -76,6 +80,7 @@ func Parse(compdir string, buf *bytes.Buffer, logfn func(string, ...interface{})
dbl.stateMachineCache = make(map[uint64]*StateMachine)
dbl.lastMachineCache = make(map[uint64]*StateMachine)
dbl.normalizeBackslash = normalizeBackslash
parseDebugLinePrologue(dbl, buf)
parseIncludeDirs(dbl, buf)
......@@ -139,6 +144,10 @@ func readFileEntry(info *DebugLineInfo, buf *bytes.Buffer, exitOnEmptyPath bool)
return entry
}
if info.normalizeBackslash {
entry.Path = strings.Replace(entry.Path, "\\", "/", -1)
}
entry.DirIdx, _ = util.DecodeULEB128(buf)
entry.LastModTime, _ = util.DecodeULEB128(buf)
entry.Length, _ = util.DecodeULEB128(buf)
......
......@@ -67,7 +67,7 @@ const (
func testDebugLinePrologueParser(p string, t *testing.T) {
data := grabDebugLineSection(p, t)
debugLines := ParseAll(data, nil, 0)
debugLines := ParseAll(data, nil, 0, true)
mainFileFound := false
......@@ -164,7 +164,7 @@ func BenchmarkLineParser(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = ParseAll(data, nil, 0)
_ = ParseAll(data, nil, 0, true)
}
}
......@@ -179,7 +179,7 @@ func loadBenchmarkData(tb testing.TB) DebugLines {
tb.Fatal("Could not read test data", err)
}
return ParseAll(data, nil, 0)
return ParseAll(data, nil, 0, true)
}
func BenchmarkStateMachine(b *testing.B) {
......
......@@ -80,7 +80,7 @@ func TestGrafana(t *testing.T) {
}
cuname, _ := e.Val(dwarf.AttrName).(string)
lineInfo := Parse(e.Val(dwarf.AttrCompDir).(string), debugLineBuffer, t.Logf, 0)
lineInfo := Parse(e.Val(dwarf.AttrCompDir).(string), debugLineBuffer, t.Logf, 0, false)
sm := newStateMachine(lineInfo, lineInfo.Instructions)
lnrdr, err := data.LineReader(e)
......
......@@ -8,7 +8,7 @@ var (
MinSupportedVersionOfGoMajor = 1
MinSupportedVersionOfGoMinor = 11
MaxSupportedVersionOfGoMajor = 1
MaxSupportedVersionOfGoMinor = 13
MaxSupportedVersionOfGoMinor = 14
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)
)
......
......@@ -1327,7 +1327,7 @@ func (bi *BinaryInfo) loadDebugInfoMaps(image *Image, debugLineBytes []byte, wg
logger.Printf(fmt, args)
}
}
cu.lineInfo = line.Parse(compdir, bytes.NewBuffer(debugLineBytes[lineInfoOffset:]), logfn, image.StaticBase)
cu.lineInfo = line.Parse(compdir, bytes.NewBuffer(debugLineBytes[lineInfoOffset:]), logfn, image.StaticBase, bi.GOOS == "windows")
}
cu.producer, _ = entry.Val(dwarf.AttrProducer).(string)
if cu.isgo && cu.producer != "" {
......
......@@ -216,7 +216,7 @@ func OpenCore(corePath, exePath string, debugInfoDirs []string) (*proc.Target, e
return nil, err
}
return proc.NewTarget(p), nil
return proc.NewTarget(p, false), nil
}
// initialize for core files doesn't do much
......
......@@ -135,7 +135,8 @@ type Thread struct {
regs gdbRegisters
CurrentBreakpoint proc.BreakpointState
p *Process
setbp bool // thread was stopped because of a breakpoint
sig uint8 // signal received by thread after last stop
setbp bool // thread was stopped because of a breakpoint
common proc.CommonThread
}
......@@ -405,7 +406,7 @@ func LLDBLaunch(cmd []string, wd string, foreground bool, debugInfoDirs []string
if err != nil {
return nil, err
}
return proc.NewTarget(p), nil
return proc.NewTarget(p, false), nil
}
// LLDBAttach starts an instance of lldb-server and connects to it, asking
......@@ -457,7 +458,7 @@ func LLDBAttach(pid int, path string, debugInfoDirs []string) (*proc.Target, err
if err != nil {
return nil, err
}
return proc.NewTarget(p), nil
return proc.NewTarget(p, false), nil
}
// EntryPoint will return the process entry point address, useful for
......@@ -646,13 +647,15 @@ func (p *Process) ContinueOnce() (proc.Thread, error) {
// resume all threads
var threadID string
var sig uint8
var trapthread *Thread
var tu = threadUpdater{p: p}
var err error
continueLoop:
for {
var err error
var sig uint8
tu.Reset()
threadID, sig, err = p.conn.resume(sig, threadID, &tu)
//TODO: pass thread list to resume, which should use it to pass the correct signals
threadID, sig, err = p.conn.resume(p.threads, &tu)
if err != nil {
if _, exited := err.(proc.ErrProcessExited); exited {
p.exited = true
......@@ -660,80 +663,147 @@ continueLoop:
return nil, err
}
// For stubs that support qThreadStopInfo updateThreadListNoRegisters will
// find out the reason why each thread stopped.
p.updateThreadListNoRegisters(&tu)
trapthread = p.findThreadByStrID(threadID)
if trapthread != nil && !p.threadStopInfo {
// For stubs that do not support qThreadStopInfo we manually set the
// reason the thread returned by resume() stopped.
trapthread.sig = sig
}
var shouldStop bool
trapthread, shouldStop = p.handleThreadSignals(trapthread)
if shouldStop {
break continueLoop
}
}
if err := p.updateThreadRegisters(); err != nil {
return nil, err
}
if p.BinInfo().GOOS == "linux" {
if err := linutil.ElfUpdateSharedObjects(p); err != nil {
return nil, err
}
}
if err := p.setCurrentBreakpoints(); err != nil {
return nil, err
}
if trapthread == nil {
return nil, fmt.Errorf("could not find thread %s", threadID)
}
var err error
switch trapthread.sig {
case 0x91:
err = errors.New("bad access")
case 0x92:
err = errors.New("bad instruction")
case 0x93:
err = errors.New("arithmetic exception")
case 0x94:
err = errors.New("emulation exception")
case 0x95:
err = errors.New("software exception")
case 0x96:
err = errors.New("breakpoint exception")
}
if err != nil {
// the signals that are reported here can not be propagated back to the target process.
trapthread.sig = 0
}
return trapthread, err
}
func (p *Process) findThreadByStrID(threadID string) *Thread {
for _, thread := range p.threads {
if thread.strID == threadID {
return thread
}
}
return nil
}
// handleThreadSignals looks at the signals received by each thread and
// decides which ones to mask and which ones to propagate back to the target
// and returns true if we should stop execution in response to one of the
// signals and return control to the user.
// Adjusts trapthread to a thread that we actually want to stop at.
func (p *Process) handleThreadSignals(trapthread *Thread) (trapthreadOut *Thread, shouldStop bool) {
var trapthreadCandidate *Thread
for _, th := range p.threads {
isStopSignal := false
// 0x5 is always a breakpoint, a manual stop either manifests as 0x13
// (lldb), 0x11 (debugserver) or 0x2 (gdbserver).
// Since 0x2 could also be produced by the user
// pressing ^C (in which case it should be passed to the inferior) we need
// the ctrlC flag to know that we are the originators.
switch sig {
switch th.sig {
case interruptSignal: // interrupt
if p.getCtrlC() {
break continueLoop
isStopSignal = true
}
case breakpointSignal: // breakpoint
break continueLoop
isStopSignal = true
case childSignal: // stop on debugserver but SIGCHLD on lldb-server/linux
if p.conn.isDebugserver {
break continueLoop
isStopSignal = true
}
case stopSignal: // stop
break continueLoop
isStopSignal = true
// The following are fake BSD-style signals sent by debugserver
// Unfortunately debugserver can not convert them into signals for the
// process so we must stop here.
case debugServerTargetExcBadAccess, debugServerTargetExcBadInstruction, debugServerTargetExcArithmetic, debugServerTargetExcEmulation, debugServerTargetExcSoftware, debugServerTargetExcBreakpoint:
break continueLoop
trapthreadCandidate = th
shouldStop = true
// Signal 0 is returned by rr when it reaches the start of the process
// in backward continue mode.
case 0:
if p.conn.direction == proc.Backward {
break continueLoop
isStopSignal = true
}
default:
// any other signal is always propagated to inferior
}
}
if err := p.updateThreadList(&tu); err != nil {
return nil, err
}
if p.BinInfo().GOOS == "linux" {
if err := linutil.ElfUpdateSharedObjects(p); err != nil {
return nil, err
if isStopSignal {
if trapthreadCandidate == nil {
trapthreadCandidate = th
}
th.sig = 0
shouldStop = true
}
}
if err := p.setCurrentBreakpoints(); err != nil {
return nil, err
if (trapthread == nil || trapthread.sig != 0) && trapthreadCandidate != nil {
// proc.Continue wants us to return one of the threads that we should stop
// at, if the thread returned by vCont received a signal that we want to
// propagate back to the target thread but there were also other threads
// that we wish to stop at we should pick one of those.
trapthread = trapthreadCandidate
}
for _, thread := range p.threads {
if thread.strID == threadID {
var err error
switch sig {
case 0x91:
err = errors.New("bad access")
case 0x92:
err = errors.New("bad instruction")
case 0x93:
err = errors.New("arithmetic exception")
case 0x94:
err = errors.New("emulation exception")
case 0x95:
err = errors.New("software exception")
case 0x96:
err = errors.New("breakpoint exception")
}
return thread, err
}
if p.getCtrlC() || p.getManualStopRequested() {
// If we request an interrupt and a target thread simultaneously receives
// an unrelated singal debugserver will discard our interrupt request and
// report the signal but we should stop anyway.
shouldStop = true
}
return nil, fmt.Errorf("could not find thread %s", threadID)
return trapthread, shouldStop
}
// SetSelectedGoroutine will set internally the goroutine that should be
......@@ -792,6 +862,13 @@ func (p *Process) CheckAndClearManualStopRequest() bool {
return msr
}
func (p *Process) getManualStopRequested() bool {
p.conn.manualStopMutex.Lock()
msr := p.manualStopRequested
p.conn.manualStopMutex.Unlock()
return msr
}
func (p *Process) setCtrlC(v bool) {
p.conn.manualStopMutex.Lock()
p.ctrlC = v
......@@ -854,7 +931,7 @@ func (p *Process) Restart(pos string) error {
// for some reason we have to send a vCont;c after a vRun to make rr behave
// properly, because that's what gdb does.
_, _, err = p.conn.resume(0, "", nil)
_, _, err = p.conn.resume(nil, nil)
if err != nil {
return err
}
......@@ -1081,7 +1158,7 @@ func (tu *threadUpdater) Finish() {
continue
}
delete(tu.p.threads, threadID)
if tu.p.currentThread.ID == threadID {
if tu.p.currentThread != nil && tu.p.currentThread.ID == threadID {
tu.p.currentThread = nil
}
}
......@@ -1099,14 +1176,13 @@ func (tu *threadUpdater) Finish() {
}
}
// updateThreadsList retrieves the list of inferior threads from the stub
// and passes it to the threadUpdater.
// Then it reloads the register information for all running threads.
// updateThreadListNoRegisters retrieves the list of inferior threads from
// the stub and passes it to threadUpdater.
// Some stubs will return the list of running threads in the stop packet, if
// this happens the threadUpdater will know that we have already updated the
// thread list and the first step of updateThreadList will be skipped.
// Registers are always reloaded.
func (p *Process) updateThreadList(tu *threadUpdater) error {
func (p *Process) updateThreadListNoRegisters(tu *threadUpdater) error {
if !tu.done {
first := true
for {
......@@ -1126,8 +1202,8 @@ func (p *Process) updateThreadList(tu *threadUpdater) error {
tu.Finish()
}
if p.threadStopInfo {
for _, th := range p.threads {
for _, th := range p.threads {
if p.threadStopInfo {
sig, reason, err := p.conn.threadStopInfo(th.strID)
if err != nil {
if isProtocolErrorUnsupported(err) {
......@@ -1137,9 +1213,18 @@ func (p *Process) updateThreadList(tu *threadUpdater) error {
return err
}
th.setbp = (reason == "breakpoint" || (reason == "" && sig == breakpointSignal))
th.sig = sig
} else {
th.sig = 0
}
}
return nil
}
// updateThreadRegisters reloads register informations for all running
// threads.
func (p *Process) updateThreadRegisters() error {
for _, thread := range p.threads {
if err := thread.reloadRegisters(); err != nil {
return err
......@@ -1148,6 +1233,24 @@ func (p *Process) updateThreadList(tu *threadUpdater) error {
return nil
}
// updateThreadsList retrieves the list of inferior threads from the stub
// and passes it to the threadUpdater.
// Then it reloads the register information for all running threads.
// Some stubs will return the list of running threads in the stop packet, if
// this happens the threadUpdater will know that we have already updated the
// thread list and the first step of updateThreadList will be skipped.
// Registers are always reloaded.
func (p *Process) updateThreadList(tu *threadUpdater) error {
err := p.updateThreadListNoRegisters(tu)
if err != nil {
return err
}
for _, th := range p.threads {
th.sig = 0
}
return p.updateThreadRegisters()
}
func (p *Process) setCurrentBreakpoints() error {
if p.threadStopInfo {
for _, th := range p.threads {
......
......@@ -538,17 +538,23 @@ func (conn *gdbConn) writeRegister(threadID string, regnum int, data []byte) err
return err
}
// resume executes a 'vCont' command on all threads with action 'c' if sig
// is 0 or 'C' if it isn't. If sig isn't 0 a threadID should be specified as
// the target of the signal.
func (conn *gdbConn) resume(sig uint8, threadID string, tu *threadUpdater) (string, uint8, error) {
// resume execution of the target process.
// If the current direction is proc.Backward this is done with the 'bc' command.
// If the current direction is proc.Forward this is done with the vCont command.
// The threads argument will be used to determine which signal to use to
// resume each thread. If a thread has sig == 0 the 'c' action will be used,
// otherwise the 'C' action will be used and the value of sig will be passed
// to it.
func (conn *gdbConn) resume(threads map[int]*Thread, tu *threadUpdater) (string, uint8, error) {
if conn.direction == proc.Forward {
conn.outbuf.Reset()
if sig == 0 {
fmt.Fprint(&conn.outbuf, "$vCont;c")
} else {
fmt.Fprintf(&conn.outbuf, "$vCont;C%02x:%s;c", sig, threadID)
fmt.Fprintf(&conn.outbuf, "$vCont")
for _, th := range threads {
if th.sig != 0 {
fmt.Fprintf(&conn.outbuf, ";C%02x:%s", th.sig, th.strID)
}
}
fmt.Fprintf(&conn.outbuf, ";c")
} else {
if err := conn.selectThread('c', "p-1.-1", "resume"); err != nil {
return "", 0, err
......
......@@ -96,7 +96,7 @@ func Replay(tracedir string, quiet, deleteOnDetach bool, debugInfoDirs []string)
return nil, err
}
return proc.NewTarget(p), nil
return proc.NewTarget(p, false), nil
}
// ErrPerfEventParanoid is the error returned by Reply and Record if
......
......@@ -197,7 +197,7 @@ func TestCheckpoints(t *testing.T) {
bp := setFunctionBreakpoint(p, t, "main.main")
assertNoError(proc.Continue(p), t, "Continue")
when0, loc0 := getPosition(p, t)
t.Logf("when0: %q (%#x)", when0, loc0.PC)
t.Logf("when0: %q (%#x) %x", when0, loc0.PC, p.CurrentThread().ThreadID())
// Create a checkpoint and check that the list of checkpoints reflects this
cpid, err := p.Checkpoint("checkpoint1")
......@@ -215,7 +215,7 @@ func TestCheckpoints(t *testing.T) {
assertNoError(proc.Next(p), t, "First Next")
assertNoError(proc.Next(p), t, "Second Next")
when1, loc1 := getPosition(p, t)
t.Logf("when1: %q (%#x)", when1, loc1.PC)
t.Logf("when1: %q (%#x) %x", when1, loc1.PC, p.CurrentThread().ThreadID())
if loc0.PC == loc1.PC {
t.Fatalf("next did not move process %#x", loc0.PC)
}
......@@ -226,8 +226,10 @@ func TestCheckpoints(t *testing.T) {
// Move back to checkpoint, check that the output of 'when' is the same as
// what it was when we set the breakpoint
p.Restart(fmt.Sprintf("c%d", cpid))
g, _ := proc.FindGoroutine(p, 1)
p.SwitchGoroutine(g)
when2, loc2 := getPosition(p, t)
t.Logf("when2: %q (%#x)", when2, loc2.PC)
t.Logf("when2: %q (%#x) %x", when2, loc2.PC, p.CurrentThread().ThreadID())
if loc2.PC != loc0.PC {
t.Fatalf("PC address mismatch %#x != %#x", loc0.PC, loc2.PC)
}
......@@ -252,6 +254,8 @@ func TestCheckpoints(t *testing.T) {
_, err = p.ClearBreakpoint(bp.Addr)
assertNoError(err, t, "ClearBreakpoint")
p.Restart(fmt.Sprintf("c%d", cpid))
g, _ = proc.FindGoroutine(p, 1)
p.SwitchGoroutine(g)
assertNoError(proc.Next(p), t, "First Next")
assertNoError(proc.Next(p), t, "Second Next")
when4, loc4 := getPosition(p, t)
......
......@@ -126,7 +126,7 @@ func Launch(cmd []string, wd string, foreground bool, _ []string) (*proc.Target,
return nil, err
}
return proc.NewTarget(dbp), err
return proc.NewTarget(dbp, false), err
}
// Attach to an existing process with the given PID.
......@@ -158,7 +158,7 @@ func Attach(pid int, _ []string) (*proc.Target, error) {
dbp.Detach(false)
return nil, err
}
return proc.NewTarget(dbp), nil
return proc.NewTarget(dbp, false), nil
}
// Kill kills the process.
......
......@@ -87,7 +87,7 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (*
if err = dbp.initialize(cmd[0], debugInfoDirs); err != nil {
return nil, err
}
return proc.NewTarget(dbp), nil
return proc.NewTarget(dbp, false), nil
}
// Attach to an existing process with the given PID. Once attached, if
......@@ -111,7 +111,7 @@ func Attach(pid int, debugInfoDirs []string) (*proc.Target, error) {
dbp.Detach(false)
return nil, err
}
return proc.NewTarget(dbp), nil
return proc.NewTarget(dbp, false), nil
}
func initialize(dbp *Process) error {
......
......@@ -92,7 +92,7 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (*
if err = dbp.initialize(cmd[0], debugInfoDirs); err != nil {
return nil, err
}
return proc.NewTarget(dbp), nil
return proc.NewTarget(dbp, false), nil
}
// Attach to an existing process with the given PID. Once attached, if
......@@ -123,7 +123,7 @@ func Attach(pid int, debugInfoDirs []string) (*proc.Target, error) {
if err != nil {
return nil, err
}
return proc.NewTarget(dbp), nil
return proc.NewTarget(dbp, false), nil
}
func initialize(dbp *Process) error {
......
......@@ -7,6 +7,7 @@ import (
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"
"unsafe"
......@@ -55,6 +56,15 @@ func Launch(cmd []string, wd string, foreground bool, _ []string) (*proc.Target,
}
closer.Close()
env := os.Environ()
for i := range env {
if strings.HasPrefix(env[i], "GODEBUG=") {
// Go 1.14 asynchronous preemption mechanism is incompatible with
// debuggers, see: https://github.com/golang/go/issues/36494
env[i] += ",asyncpreemptoff=1"
}
}
var p *os.Process
dbp := New(0)
dbp.execPtraceFunc(func() {
......@@ -64,6 +74,7 @@ func Launch(cmd []string, wd string, foreground bool, _ []string) (*proc.Target,
Sys: &syscall.SysProcAttr{
CreationFlags: _DEBUG_ONLY_THIS_PROCESS,
},
Env: env,
}
p, err = os.StartProcess(argv0Go, cmd, attr)
})
......@@ -79,7 +90,7 @@ func Launch(cmd []string, wd string, foreground bool, _ []string) (*proc.Target,
dbp.Detach(true)
return nil, err
}
return proc.NewTarget(dbp), nil
return proc.NewTarget(dbp, true), nil
}
func initialize(dbp *Process) error {
......@@ -168,7 +179,7 @@ func Attach(pid int, _ []string) (*proc.Target, error) {
dbp.Detach(true)
return nil, err
}
return proc.NewTarget(dbp), nil
return proc.NewTarget(dbp, true), nil
}
// kill kills the process.
......@@ -472,6 +483,8 @@ func (dbp *Process) stop(trapthread *Thread) (err error) {
func (dbp *Process) detach(kill bool) error {
if !kill {
//TODO(aarzilli): when debug.Target exist Detach should be moved to
// debug.Target and the call to RestoreAsyncPreempt should be moved there.
for _, thread := range dbp.threads {
_, err := _ResumeThread(thread.os.hThread)
if err != nil {
......
......@@ -36,9 +36,22 @@ func (t *Thread) singleStep() error {
return err
}
_, err = _ResumeThread(t.os.hThread)
if err != nil {
return err
suspendcnt := 0
// If a thread simultaneously hits a breakpoint and is suspended by the Go
// runtime it will have a suspend count greater than 1 and to actually take
// a single step we have to resume it multiple times here.
// We keep a counter of how many times it was suspended so that after
// single-stepping we can re-suspend it the corrent number of times.
for {
n, err := _ResumeThread(t.os.hThread)
if err != nil {
return err
}
suspendcnt++
if n == 1 {
break
}
}
for {
......@@ -63,9 +76,11 @@ func (t *Thread) singleStep() error {
})
}
_, err = _SuspendThread(t.os.hThread)
if err != nil {
return err
for i := 0; i < suspendcnt; i++ {
_, err = _SuspendThread(t.os.hThread)
if err != nil {
return err
}
}
t.dbp.execPtraceFunc(func() {
......
......@@ -5,9 +5,12 @@ import (
"errors"
"fmt"
"go/ast"
"go/constant"
"go/token"
"path/filepath"
"strconv"
"github.com/go-delve/delve/pkg/goversion"
)
// ErrNotExecutable is returned after attempting to execute a non-executable file
......@@ -814,3 +817,31 @@ func FirstPCAfterPrologue(p Process, fn *Function, sameline bool) (uint64, error
return pc, nil
}
func setAsyncPreemptOff(p *Target, v int64) {
logger := p.BinInfo().logger
if producer := p.BinInfo().Producer(); producer == "" || !goversion.ProducerAfterOrEqual(producer, 1, 14) {
return
}
scope := globalScope(p.BinInfo(), p.BinInfo().Images[0], p.CurrentThread())
debugv, err := scope.findGlobal("runtime", "debug")
if err != nil || debugv.Unreadable != nil {
logger.Warnf("could not find runtime/debug variable (or unreadable): %v %v", err, debugv.Unreadable)
return
}
asyncpreemptoffv, err := debugv.structMember("asyncpreemptoff")
if err != nil {
logger.Warnf("could not find asyncpreemptoff field: %v", err)
return
}
asyncpreemptoffv.loadValue(loadFullValue)
if asyncpreemptoffv.Unreadable != nil {
logger.Warnf("asyncpreemptoff field unreadable: %v", asyncpreemptoffv.Unreadable)
return
}
p.asyncPreemptChanged = true
p.asyncPreemptOff, _ = constant.Int64Val(asyncpreemptoffv.Value)
err = scope.setValue(asyncpreemptoffv, newConstant(constant.MakeInt64(v), scope.Mem), "")
logger.Warnf("could not set asyncpreemptoff %v", err)
}
......@@ -1244,7 +1244,7 @@ func TestFrameEvaluation(t *testing.T) {
t.Logf("could not stacktrace goroutine %d: %v\n", g.ID, err)
continue
}
t.Logf("Goroutine %d", g.ID)
t.Logf("Goroutine %d %#v", g.ID, g.Thread)
logStacktrace(t, p.BinInfo(), frames)
for i := range frames {
if frames[i].Call.Fn != nil && frames[i].Call.Fn.Name == "main.agoroutine" {
......
......@@ -7,6 +7,9 @@ type Target struct {
// fncallForG stores a mapping of current active function calls.
fncallForG map[int]*callInjection
asyncPreemptChanged bool // runtime/debug.asyncpreemptoff was changed
asyncPreemptOff int64 // cached value of runtime/debug.asyncpreemptoff
// gcache is a cache for Goroutines that we
// have read and parsed from the targets memory.
// This must be cleared whenever the target is resumed.
......@@ -14,12 +17,17 @@ type Target struct {
}
// NewTarget returns an initialized Target object.
func NewTarget(p Process) *Target {
func NewTarget(p Process, disableAsyncPreempt bool) *Target {
t := &Target{
Process: p,
fncallForG: make(map[int]*callInjection),
}
t.gcache.init(p.BinInfo())
if disableAsyncPreempt {
setAsyncPreemptOff(t, 1)
}
return t
}
......@@ -45,3 +53,10 @@ func (t *Target) Restart(from string) error {
t.ClearAllGCache()
return t.Process.Restart(from)
}
func (t *Target) Detach(kill bool) error {
if !kill && t.asyncPreemptChanged {
setAsyncPreemptOff(t, t.asyncPreemptOff)
}
return t.Process.Detach(kill)
}
......@@ -271,8 +271,8 @@ func TestIssue411(t *testing.T) {
}
test.AllowRecording(t)
withTestTerminal("math", t, func(term *FakeTerminal) {
term.MustExec("break math.go:8")
term.MustExec("trace math.go:9")
term.MustExec("break _fixtures/math.go:8")
term.MustExec("trace _fixtures/math.go:9")
term.MustExec("continue")
out := term.MustExec("next")
if !strings.HasPrefix(out, "> main.main()") {
......@@ -683,7 +683,9 @@ func TestCheckpoints(t *testing.T) {
term.MustExec("checkpoints")
listIsAt(t, term, "next", 17, -1, -1)
listIsAt(t, term, "next", 18, -1, -1)
listIsAt(t, term, "restart c1", 16, -1, -1)
term.MustExec("restart c1")
term.MustExec("goroutine 1")
listIsAt(t, term, "list", 16, -1, -1)
})
}
......
......@@ -10,6 +10,7 @@ import (
"sort"
"strings"
"github.com/go-delve/delve/pkg/goversion"
"github.com/spf13/cobra"
)
......@@ -71,7 +72,7 @@ func NewMakeCommands() *cobra.Command {
Use the flags -s, -r and -b to specify which tests to run. Specifying nothing is equivalent to:
go run scripts/make.go test -s all -b default
go run scripts/make.go test -s basic -b lldb # if lldb-server is installed
go run scripts/make.go test -s basic -b lldb # if lldb-server is installed and Go < 1.14
go run scripts/make.go test -s basic -b rr # if rr is installed
go run scripts/make.go test -s basic -m pie # only on linux
......@@ -295,7 +296,7 @@ func testCmd(cmd *cobra.Command, args []string) {
fmt.Println("Testing default backend")
testCmdIntl("all", "", "default", "normal")
if inpath("lldb-server") {
if inpath("lldb-server") && !goversion.VersionAfterOrEqual(runtime.Version(), 1, 14) {
fmt.Println("\nTesting LLDB backend")
testCmdIntl("basic", "", "lldb", "normal")
}
......
......@@ -1205,7 +1205,6 @@ func TestCallFunction(t *testing.T) {
// support calling optimized functions
{`strings.Join(nil, "")`, []string{`:string:""`}, nil},
{`strings.Join(stringslice, comma)`, []string{`:string:"one,two,three"`}, nil},
{`strings.Join(s1, comma)`, nil, errors.New(`error evaluating "s1" as argument a in function strings.Join: could not find symbol value for s1`)},
{`strings.Join(intslice, comma)`, nil, errors.New("can not convert value of type []int to []string")},
{`strings.Join(stringslice, ",")`, []string{`:string:"one,two,three"`}, nil},
{`strings.LastIndexByte(stringslice[1], 'w')`, []string{":int:1"}, nil},
......@@ -1230,6 +1229,14 @@ func TestCallFunction(t *testing.T) {
{`getVRcvrableFromAStructPtr(6).VRcvr(5)`, []string{`:string:"5 + 6 = 11"`}, nil}, // indirect call of method on interface / containing pointer with pointer method
}
var testcasesBefore114After112 = []testCaseCallFunction{
{`strings.Join(s1, comma)`, nil, errors.New(`error evaluating "s1" as argument a in function strings.Join: could not find symbol value for s1`)},
}
var testcases114 = []testCaseCallFunction{
{`strings.Join(s1, comma)`, nil, errors.New(`error evaluating "s1" as argument elems in function strings.Join: could not find symbol value for s1`)},
}
withTestProcess("fncall", t, func(p *proc.Target, fixture protest.Fixture) {
_, err := proc.FindFunctionLocation(p, "runtime.debugCallV1", 0)
if err != nil {
......@@ -1244,6 +1251,11 @@ func TestCallFunction(t *testing.T) {
for _, tc := range testcases112 {
testCallFunction(t, p, tc)
}
if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 14) {
for _, tc := range testcasesBefore114After112 {
testCallFunction(t, p, tc)
}
}
}
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 13) {
......@@ -1252,6 +1264,12 @@ func TestCallFunction(t *testing.T) {
}
}
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 14) {
for _, tc := range testcases114 {
testCallFunction(t, p, tc)
}
}
// LEAVE THIS AS THE LAST ITEM, IT BREAKS THE TARGET PROCESS!!!
testCallFunction(t, p, testCaseCallFunction{"-unsafe escapeArg(&a2)", nil, nil})
})
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册