mirror of
				https://github.com/go-delve/delve.git
				synced 2025-11-04 14:36:47 +08:00 
			
		
		
		
	* service/dap: Implement suspended breakpoints This enables support for 'suspended' breakpoints, for example for a plugin that hasn't been loaded yet. Fixes #4074 * Address comments * Cleanup
		
			
				
	
	
		
			5790 lines
		
	
	
		
			188 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			5790 lines
		
	
	
		
			188 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package proc_test
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"encoding/binary"
 | 
						|
	"errors"
 | 
						|
	"flag"
 | 
						|
	"fmt"
 | 
						|
	"go/ast"
 | 
						|
	"go/constant"
 | 
						|
	"go/parser"
 | 
						|
	"go/token"
 | 
						|
	"io"
 | 
						|
	"math/rand"
 | 
						|
	"net"
 | 
						|
	"net/http"
 | 
						|
	"os"
 | 
						|
	"os/exec"
 | 
						|
	"path/filepath"
 | 
						|
	"reflect"
 | 
						|
	"runtime"
 | 
						|
	"slices"
 | 
						|
	"sort"
 | 
						|
	"strings"
 | 
						|
	"sync"
 | 
						|
	"testing"
 | 
						|
	"text/tabwriter"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/go-delve/delve/pkg/dwarf/frame"
 | 
						|
	"github.com/go-delve/delve/pkg/dwarf/op"
 | 
						|
	"github.com/go-delve/delve/pkg/goversion"
 | 
						|
	"github.com/go-delve/delve/pkg/logflags"
 | 
						|
	"github.com/go-delve/delve/pkg/proc"
 | 
						|
	"github.com/go-delve/delve/pkg/proc/core"
 | 
						|
	"github.com/go-delve/delve/pkg/proc/gdbserial"
 | 
						|
	"github.com/go-delve/delve/pkg/proc/native"
 | 
						|
	protest "github.com/go-delve/delve/pkg/proc/test"
 | 
						|
	"github.com/go-delve/delve/service/api"
 | 
						|
)
 | 
						|
 | 
						|
var normalLoadConfig = proc.LoadConfig{true, 1, 64, 64, -1, 0}
 | 
						|
var testBackend, buildMode string
 | 
						|
 | 
						|
func init() {
 | 
						|
	runtime.GOMAXPROCS(4)
 | 
						|
	os.Setenv("GOMAXPROCS", "4")
 | 
						|
}
 | 
						|
 | 
						|
func TestMain(m *testing.M) {
 | 
						|
	flag.StringVar(&testBackend, "backend", "", "selects backend")
 | 
						|
	flag.StringVar(&buildMode, "test-buildmode", "", "selects build mode")
 | 
						|
	var logConf string
 | 
						|
	flag.StringVar(&logConf, "log", "", "configures logging")
 | 
						|
	flag.Parse()
 | 
						|
	protest.DefaultTestBackend(&testBackend)
 | 
						|
	if buildMode != "" && buildMode != "pie" {
 | 
						|
		fmt.Fprintf(os.Stderr, "unknown build mode %q", buildMode)
 | 
						|
		os.Exit(1)
 | 
						|
	}
 | 
						|
	logflags.Setup(logConf != "", logConf, "")
 | 
						|
	protest.RunTestsWithFixtures(m)
 | 
						|
}
 | 
						|
 | 
						|
func matchSkipConditions(conditions ...string) bool {
 | 
						|
	for _, cond := range conditions {
 | 
						|
		if !slices.Contains([]string{runtime.GOOS, runtime.GOARCH, testBackend, buildMode}, cond) {
 | 
						|
			return false
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return true
 | 
						|
}
 | 
						|
 | 
						|
func skipOn(t testing.TB, reason string, conditions ...string) {
 | 
						|
	if matchSkipConditions(conditions...) {
 | 
						|
		t.Skipf("skipped on %s: %s", strings.Join(conditions, "/"), reason)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func skipUnlessOn(t testing.TB, reason string, conditions ...string) {
 | 
						|
	if !matchSkipConditions(conditions...) {
 | 
						|
		t.Skipf("skipped on %s: %s", strings.Join(conditions, "/"), reason)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func withTestProcess(name string, t testing.TB, fn func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture)) {
 | 
						|
	withTestProcessArgs(name, t, ".", []string{}, 0, fn)
 | 
						|
}
 | 
						|
 | 
						|
func withTestProcessArgs(name string, t testing.TB, wd string, args []string, buildFlags protest.BuildFlags, fn func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture)) {
 | 
						|
	t.Helper()
 | 
						|
	if buildMode == "pie" {
 | 
						|
		buildFlags |= protest.BuildModePIE
 | 
						|
	}
 | 
						|
	fixture := protest.BuildFixture(t, name, buildFlags)
 | 
						|
 | 
						|
	grp := startTestProcessArgs(fixture, t, wd, args)
 | 
						|
 | 
						|
	defer func() {
 | 
						|
		grp.Detach(true)
 | 
						|
	}()
 | 
						|
 | 
						|
	fn(grp.Selected, grp, fixture)
 | 
						|
}
 | 
						|
 | 
						|
func startTestProcessArgs(fixture protest.Fixture, t testing.TB, wd string, args []string) *proc.TargetGroup {
 | 
						|
	var grp *proc.TargetGroup
 | 
						|
	var err error
 | 
						|
	var tracedir string
 | 
						|
 | 
						|
	switch testBackend {
 | 
						|
	case "native":
 | 
						|
		grp, err = native.Launch(append([]string{fixture.Path}, args...), wd, 0, []string{}, "", "", proc.OutputRedirect{}, proc.OutputRedirect{})
 | 
						|
	case "lldb":
 | 
						|
		grp, err = gdbserial.LLDBLaunch(append([]string{fixture.Path}, args...), wd, 0, []string{}, "", [3]string{})
 | 
						|
	case "rr":
 | 
						|
		protest.MustHaveRecordingAllowed(t)
 | 
						|
		t.Log("recording")
 | 
						|
		grp, tracedir, err = gdbserial.RecordAndReplay(append([]string{fixture.Path}, args...), wd, true, []string{}, "", proc.OutputRedirect{}, proc.OutputRedirect{})
 | 
						|
		t.Logf("replaying %q", tracedir)
 | 
						|
	default:
 | 
						|
		t.Fatal("unknown backend")
 | 
						|
	}
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal("Launch():", err)
 | 
						|
	}
 | 
						|
	return grp
 | 
						|
}
 | 
						|
 | 
						|
func getRegisters(p *proc.Target, t *testing.T) proc.Registers {
 | 
						|
	regs, err := p.CurrentThread().Registers()
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal("Registers():", err)
 | 
						|
	}
 | 
						|
 | 
						|
	return regs
 | 
						|
}
 | 
						|
 | 
						|
func dataAtAddr(thread proc.MemoryReadWriter, addr uint64) ([]byte, error) {
 | 
						|
	data := make([]byte, 1)
 | 
						|
	_, err := thread.ReadMemory(data, addr)
 | 
						|
	return data, err
 | 
						|
}
 | 
						|
 | 
						|
func assertNoError(err error, t testing.TB, s string) {
 | 
						|
	t.Helper()
 | 
						|
	if err != nil {
 | 
						|
		_, file, line, _ := runtime.Caller(1)
 | 
						|
		fname := filepath.Base(file)
 | 
						|
		t.Fatalf("failed assertion at %s:%d: %s - %s\n", fname, line, s, err)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func currentPC(p *proc.Target, t *testing.T) uint64 {
 | 
						|
	regs, err := p.CurrentThread().Registers()
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	return regs.PC()
 | 
						|
}
 | 
						|
 | 
						|
func currentLineNumber(p *proc.Target, t *testing.T) (string, int) {
 | 
						|
	pc := currentPC(p, t)
 | 
						|
	f, l, _ := p.BinInfo().PCToLine(pc)
 | 
						|
	return f, l
 | 
						|
}
 | 
						|
 | 
						|
func assertLineNumber(p *proc.Target, t *testing.T, lineno int, descr string) (string, int) {
 | 
						|
	f, l := currentLineNumber(p, t)
 | 
						|
	if l != lineno {
 | 
						|
		_, callerFile, callerLine, _ := runtime.Caller(1)
 | 
						|
		t.Fatalf("%s expected line :%d got %s:%d\n\tat %s:%d", descr, lineno, f, l, callerFile, callerLine)
 | 
						|
	}
 | 
						|
	return f, l
 | 
						|
}
 | 
						|
 | 
						|
func assertLineNumberIn(p *proc.Target, t *testing.T, linenos []int, descr string) (string, int) {
 | 
						|
	f, l := currentLineNumber(p, t)
 | 
						|
	if !slices.Contains(linenos, l) {
 | 
						|
		_, callerFile, callerLine, _ := runtime.Caller(1)
 | 
						|
		t.Fatalf("%s expected lines :%#v got %s:%d\n\tat %s:%d", descr, linenos, f, l, callerFile, callerLine)
 | 
						|
	}
 | 
						|
	return f, l
 | 
						|
}
 | 
						|
 | 
						|
func assertFunctionName(p *proc.Target, t *testing.T, fnname string, descr string) {
 | 
						|
	pc := currentPC(p, t)
 | 
						|
	f, l, fn := p.BinInfo().PCToLine(pc)
 | 
						|
	if fn == nil {
 | 
						|
		t.Fatalf("%s expected function %s got %s:%d", descr, fnname, f, l)
 | 
						|
	}
 | 
						|
	if fn.Name != fnname {
 | 
						|
		t.Fatalf("%s expected function %s got %s %s:%d", descr, fnname, fn.Name, f, l)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestExit(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("continuetestprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		err := grp.Continue()
 | 
						|
		pe, ok := err.(proc.ErrProcessExited)
 | 
						|
		if !ok {
 | 
						|
			t.Fatalf("Continue() returned unexpected error type %s", err)
 | 
						|
		}
 | 
						|
		if pe.Status != 0 {
 | 
						|
			t.Errorf("Unexpected error status: %d", pe.Status)
 | 
						|
		}
 | 
						|
		if pe.Pid != p.Pid() {
 | 
						|
			t.Errorf("Unexpected process id: %d", pe.Pid)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestExitAfterContinue(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("continuetestprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFunctionBreakpoint(p, t, "main.sayhi")
 | 
						|
		assertNoError(grp.Continue(), t, "First Continue()")
 | 
						|
		err := grp.Continue()
 | 
						|
		pe, ok := err.(proc.ErrProcessExited)
 | 
						|
		if !ok {
 | 
						|
			t.Fatalf("Continue() returned unexpected error type %s", pe)
 | 
						|
		}
 | 
						|
		if pe.Status != 0 {
 | 
						|
			t.Errorf("Unexpected error status: %d", pe.Status)
 | 
						|
		}
 | 
						|
		if pe.Pid != p.Pid() {
 | 
						|
			t.Errorf("Unexpected process id: %d", pe.Pid)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func setFunctionBreakpoint(p *proc.Target, t testing.TB, fname string) *proc.Breakpoint {
 | 
						|
	t.Helper()
 | 
						|
	addrs, err := proc.FindFunctionLocation(p, fname, 0)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("FindFunctionLocation(%s): %v", fname, err)
 | 
						|
	}
 | 
						|
	if len(addrs) != 1 {
 | 
						|
		t.Fatalf("setFunctionBreakpoint(%s): too many results %v", fname, addrs)
 | 
						|
	}
 | 
						|
	bp, err := p.SetBreakpoint(int(addrs[0]), addrs[0], proc.UserBreakpoint, nil)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("FindFunctionLocation(%s): %v", fname, err)
 | 
						|
	}
 | 
						|
	bp.Logical.Set.FunctionName = fname
 | 
						|
	return bp
 | 
						|
}
 | 
						|
 | 
						|
func setFunctionBreakpointAll(p *proc.Target, t testing.TB, fname string) {
 | 
						|
	t.Helper()
 | 
						|
	addrs, err := proc.FindFunctionLocation(p, fname, 0)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("FindFunctionLocation(%s): %v", fname, err)
 | 
						|
	}
 | 
						|
	for _, addr := range addrs {
 | 
						|
		_, err := p.SetBreakpoint(int(addr), addr, proc.UserBreakpoint, nil)
 | 
						|
		if err != nil {
 | 
						|
			t.Fatalf("FindFunctionLocation(%s): %v", fname, err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func setFileBreakpoint(p *proc.Target, t testing.TB, path string, lineno int) *proc.Breakpoint {
 | 
						|
	_, f, l, _ := runtime.Caller(1)
 | 
						|
	f = filepath.Base(f)
 | 
						|
 | 
						|
	addrs, err := proc.FindFileLocation(p, path, lineno)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("%s:%d: FindFileLocation(%s, %d): %v", f, l, path, lineno, err)
 | 
						|
	}
 | 
						|
	if len(addrs) != 1 {
 | 
						|
		t.Fatalf("%s:%d: setFileLineBreakpoint(%s, %d): too many (or not enough) results %v", f, l, path, lineno, addrs)
 | 
						|
	}
 | 
						|
	bp, err := p.SetBreakpoint(int(addrs[0]), addrs[0], proc.UserBreakpoint, nil)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("%s:%d: SetBreakpoint: %v", f, l, err)
 | 
						|
	}
 | 
						|
	return bp
 | 
						|
}
 | 
						|
 | 
						|
func findFunctionLocation(p *proc.Target, t *testing.T, fnname string) uint64 {
 | 
						|
	_, f, l, _ := runtime.Caller(1)
 | 
						|
	f = filepath.Base(f)
 | 
						|
	addrs, err := proc.FindFunctionLocation(p, fnname, 0)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("%s:%d: FindFunctionLocation(%s): %v", f, l, fnname, err)
 | 
						|
	}
 | 
						|
	if len(addrs) != 1 {
 | 
						|
		t.Fatalf("%s:%d: FindFunctionLocation(%s): too many results %v", f, l, fnname, addrs)
 | 
						|
	}
 | 
						|
	return addrs[0]
 | 
						|
}
 | 
						|
 | 
						|
func findFileLocation(p *proc.Target, t *testing.T, file string, lineno int) uint64 {
 | 
						|
	_, f, l, _ := runtime.Caller(1)
 | 
						|
	f = filepath.Base(f)
 | 
						|
	addrs, err := proc.FindFileLocation(p, file, lineno)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("%s:%d: FindFileLocation(%s, %d): %v", f, l, file, lineno, err)
 | 
						|
	}
 | 
						|
	if len(addrs) != 1 {
 | 
						|
		t.Fatalf("%s:%d: FindFileLocation(%s, %d): too many results %v", f, l, file, lineno, addrs)
 | 
						|
	}
 | 
						|
	return addrs[0]
 | 
						|
}
 | 
						|
 | 
						|
func TestHalt(t *testing.T) {
 | 
						|
	stopChan := make(chan any, 1)
 | 
						|
	withTestProcess("loopprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFunctionBreakpoint(p, t, "main.loop")
 | 
						|
		assertNoError(grp.Continue(), t, "Continue")
 | 
						|
		resumeChan := make(chan struct{}, 1)
 | 
						|
		go func() {
 | 
						|
			<-resumeChan
 | 
						|
			time.Sleep(100 * time.Millisecond)
 | 
						|
			stopChan <- grp.RequestManualStop()
 | 
						|
		}()
 | 
						|
		grp.ResumeNotify(resumeChan)
 | 
						|
		assertNoError(grp.Continue(), t, "Continue")
 | 
						|
		retVal := <-stopChan
 | 
						|
 | 
						|
		if err, ok := retVal.(error); ok && err != nil {
 | 
						|
			t.Fatal()
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestStepInstruction(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("testprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFunctionBreakpoint(p, t, "main.helloworld")
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
 | 
						|
		regs := getRegisters(p, t)
 | 
						|
		rip := regs.PC()
 | 
						|
 | 
						|
		err := grp.StepInstruction(false)
 | 
						|
		assertNoError(err, t, "Step()")
 | 
						|
 | 
						|
		regs = getRegisters(p, t)
 | 
						|
		if rip >= regs.PC() {
 | 
						|
			t.Errorf("Expected %#v to be greater than %#v", regs.PC(), rip)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestNextInstruction(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("testprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 19)
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
 | 
						|
		err := grp.StepInstruction(true)
 | 
						|
		assertNoError(err, t, "Step()")
 | 
						|
 | 
						|
		assertLineNumber(p, t, 20, "next-instruction did not step over call")
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestBreakpoint(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("testprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		bp := setFunctionBreakpoint(p, t, "main.helloworld")
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
 | 
						|
		regs, err := p.CurrentThread().Registers()
 | 
						|
		assertNoError(err, t, "Registers")
 | 
						|
		pc := regs.PC()
 | 
						|
 | 
						|
		if bp.Logical.TotalHitCount != 1 {
 | 
						|
			t.Fatalf("Breakpoint should be hit once, got %d\n", bp.Logical.TotalHitCount)
 | 
						|
		}
 | 
						|
 | 
						|
		if pc-1 != bp.Addr && pc != bp.Addr {
 | 
						|
			f, l, _ := p.BinInfo().PCToLine(pc)
 | 
						|
			t.Fatalf("Break not respected:\nPC:%#v %s:%d\nFN:%#v \n", pc, f, l, bp.Addr)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestBreakpointInSeparateGoRoutine(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("testthreads", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFunctionBreakpoint(p, t, "main.anotherthread")
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue")
 | 
						|
 | 
						|
		regs, err := p.CurrentThread().Registers()
 | 
						|
		assertNoError(err, t, "Registers")
 | 
						|
		pc := regs.PC()
 | 
						|
 | 
						|
		f, l, _ := p.BinInfo().PCToLine(pc)
 | 
						|
		if f != "testthreads.go" && l != 8 {
 | 
						|
			t.Fatal("Program did not hit breakpoint")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestBreakpointWithNonExistentFunction(t *testing.T) {
 | 
						|
	withTestProcess("testprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		_, err := p.SetBreakpoint(0, 0, proc.UserBreakpoint, nil)
 | 
						|
		if err == nil {
 | 
						|
			t.Fatal("Should not be able to break at non existent function")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestClearBreakpointBreakpoint(t *testing.T) {
 | 
						|
	withTestProcess("testprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		bp := setFunctionBreakpoint(p, t, "main.sleepytime")
 | 
						|
 | 
						|
		err := p.ClearBreakpoint(bp.Addr)
 | 
						|
		assertNoError(err, t, "ClearBreakpoint()")
 | 
						|
 | 
						|
		data, err := dataAtAddr(p.Memory(), bp.Addr)
 | 
						|
		assertNoError(err, t, "dataAtAddr")
 | 
						|
 | 
						|
		int3 := []byte{0xcc}
 | 
						|
		if bytes.Equal(data, int3) {
 | 
						|
			t.Fatalf("Breakpoint was not cleared data: %#v, int3: %#v", data, int3)
 | 
						|
		}
 | 
						|
 | 
						|
		if countBreakpoints(p) != 0 {
 | 
						|
			t.Fatal("Breakpoint not removed internally")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func countBreakpoints(p *proc.Target) int {
 | 
						|
	bpcount := 0
 | 
						|
	for _, bp := range p.Breakpoints().M {
 | 
						|
		if bp.LogicalID() >= 0 {
 | 
						|
			bpcount++
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return bpcount
 | 
						|
}
 | 
						|
 | 
						|
func TestNextConcurrent(t *testing.T) {
 | 
						|
	skipOn(t, "broken", "windows", "arm64")
 | 
						|
	testcases := []nextTest{
 | 
						|
		{8, 9},
 | 
						|
		{9, 10},
 | 
						|
		{10, 11},
 | 
						|
	}
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("parallel_next", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		bp := setFunctionBreakpoint(p, t, "main.sayhi")
 | 
						|
		assertNoError(grp.Continue(), t, "Continue")
 | 
						|
		f, ln := currentLineNumber(p, t)
 | 
						|
		initV := evalVariable(p, t, "n")
 | 
						|
		initVval, _ := constant.Int64Val(initV.Value)
 | 
						|
		err := p.ClearBreakpoint(bp.Addr)
 | 
						|
		assertNoError(err, t, "ClearBreakpoint()")
 | 
						|
		for _, tc := range testcases {
 | 
						|
			g, err := proc.GetG(p.CurrentThread())
 | 
						|
			assertNoError(err, t, "GetG()")
 | 
						|
			if p.SelectedGoroutine().ID != g.ID {
 | 
						|
				t.Fatalf("SelectedGoroutine not CurrentThread's goroutine: %d %d", g.ID, p.SelectedGoroutine().ID)
 | 
						|
			}
 | 
						|
			if ln != tc.begin {
 | 
						|
				t.Fatalf("Program not stopped at correct spot expected %d was %s:%d", tc.begin, filepath.Base(f), ln)
 | 
						|
			}
 | 
						|
			assertNoError(grp.Next(), t, "Next() returned an error")
 | 
						|
			f, ln = assertLineNumber(p, t, tc.end, "Program did not continue to the expected location")
 | 
						|
			v := evalVariable(p, t, "n")
 | 
						|
			vval, _ := constant.Int64Val(v.Value)
 | 
						|
			if vval != initVval {
 | 
						|
				t.Fatal("Did not end up on same goroutine")
 | 
						|
			}
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestNextConcurrentVariant2(t *testing.T) {
 | 
						|
	skipOn(t, "broken", "windows", "arm64")
 | 
						|
	// Just like TestNextConcurrent but instead of removing the initial breakpoint we check that when it happens is for other goroutines
 | 
						|
	testcases := []nextTest{
 | 
						|
		{8, 9},
 | 
						|
		{9, 10},
 | 
						|
		{10, 11},
 | 
						|
	}
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("parallel_next", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFunctionBreakpoint(p, t, "main.sayhi")
 | 
						|
		assertNoError(grp.Continue(), t, "Continue")
 | 
						|
		f, ln := currentLineNumber(p, t)
 | 
						|
		initV := evalVariable(p, t, "n")
 | 
						|
		initVval, _ := constant.Int64Val(initV.Value)
 | 
						|
		for _, tc := range testcases {
 | 
						|
			t.Logf("test case %v", tc)
 | 
						|
			g, err := proc.GetG(p.CurrentThread())
 | 
						|
			assertNoError(err, t, "GetG()")
 | 
						|
			if p.SelectedGoroutine().ID != g.ID {
 | 
						|
				t.Fatalf("SelectedGoroutine not CurrentThread's goroutine: %d %d", g.ID, p.SelectedGoroutine().ID)
 | 
						|
			}
 | 
						|
			if ln != tc.begin {
 | 
						|
				t.Fatalf("Program not stopped at correct spot expected %d was %s:%d", tc.begin, filepath.Base(f), ln)
 | 
						|
			}
 | 
						|
			assertNoError(grp.Next(), t, "Next() returned an error")
 | 
						|
			var vval int64
 | 
						|
			for {
 | 
						|
				v := evalVariable(p, t, "n")
 | 
						|
				for _, thread := range p.ThreadList() {
 | 
						|
					proc.GetG(thread)
 | 
						|
				}
 | 
						|
				vval, _ = constant.Int64Val(v.Value)
 | 
						|
				if bpstate := p.CurrentThread().Breakpoint(); bpstate.Breakpoint == nil {
 | 
						|
					if vval != initVval {
 | 
						|
						t.Fatal("Did not end up on same goroutine")
 | 
						|
					}
 | 
						|
					break
 | 
						|
				} else {
 | 
						|
					if vval == initVval {
 | 
						|
						t.Fatal("Initial breakpoint triggered twice for the same goroutine")
 | 
						|
					}
 | 
						|
					assertNoError(grp.Continue(), t, "Continue 2")
 | 
						|
				}
 | 
						|
			}
 | 
						|
			f, ln = assertLineNumber(p, t, tc.end, "Program did not continue to the expected location")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestNextNetHTTP(t *testing.T) {
 | 
						|
	testcases := []nextTest{
 | 
						|
		{11, 12},
 | 
						|
		{12, 13},
 | 
						|
	}
 | 
						|
	withTestProcess("testnextnethttp", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		go func() {
 | 
						|
			// Wait for program to start listening.
 | 
						|
			for {
 | 
						|
				conn, err := net.Dial("tcp", "127.0.0.1:9191")
 | 
						|
				if err == nil {
 | 
						|
					conn.Close()
 | 
						|
					break
 | 
						|
				}
 | 
						|
				time.Sleep(50 * time.Millisecond)
 | 
						|
			}
 | 
						|
			resp, err := http.Get("http://127.0.0.1:9191")
 | 
						|
			if err == nil {
 | 
						|
				resp.Body.Close()
 | 
						|
			}
 | 
						|
		}()
 | 
						|
		if err := grp.Continue(); err != nil {
 | 
						|
			t.Fatal(err)
 | 
						|
		}
 | 
						|
		f, ln := currentLineNumber(p, t)
 | 
						|
		for _, tc := range testcases {
 | 
						|
			if ln != tc.begin {
 | 
						|
				t.Fatalf("Program not stopped at correct spot expected %d was %s:%d", tc.begin, filepath.Base(f), ln)
 | 
						|
			}
 | 
						|
 | 
						|
			assertNoError(grp.Next(), t, "Next() returned an error")
 | 
						|
 | 
						|
			f, ln = assertLineNumber(p, t, tc.end, "Program did not continue to correct next location")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestRuntimeBreakpoint(t *testing.T) {
 | 
						|
	withTestProcess("testruntimebreakpoint", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		err := grp.Continue()
 | 
						|
		if err != nil {
 | 
						|
			t.Fatal(err)
 | 
						|
		}
 | 
						|
		regs, err := p.CurrentThread().Registers()
 | 
						|
		assertNoError(err, t, "Registers")
 | 
						|
		pc := regs.PC()
 | 
						|
		f, l, _ := p.BinInfo().PCToLine(pc)
 | 
						|
		if l != 10 {
 | 
						|
			t.Fatalf("did not respect breakpoint %s:%d", f, l)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func returnAddress(tgt *proc.Target, thread proc.Thread) (uint64, error) {
 | 
						|
	locations, err := proc.ThreadStacktrace(tgt, thread, 2)
 | 
						|
	if err != nil {
 | 
						|
		return 0, err
 | 
						|
	}
 | 
						|
	if len(locations) < 2 {
 | 
						|
		return 0, fmt.Errorf("no return address for function: %s", locations[0].Current.Fn.BaseName())
 | 
						|
	}
 | 
						|
	return locations[1].Current.PC, nil
 | 
						|
}
 | 
						|
 | 
						|
func TestFindReturnAddress(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("testnextprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 24)
 | 
						|
		err := grp.Continue()
 | 
						|
		if err != nil {
 | 
						|
			t.Fatal(err)
 | 
						|
		}
 | 
						|
		addr, err := returnAddress(p, p.CurrentThread())
 | 
						|
		if err != nil {
 | 
						|
			t.Fatal(err)
 | 
						|
		}
 | 
						|
		_, l, _ := p.BinInfo().PCToLine(addr)
 | 
						|
		if l != 40 {
 | 
						|
			t.Fatalf("return address not found correctly, expected line 40")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestFindReturnAddressTopOfStackFn(t *testing.T) {
 | 
						|
	skipOn(t, "broken in linux ppc64le", "linux", "ppc64le", "native")
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("testreturnaddress", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		fnName := "runtime.rt0_go"
 | 
						|
		setFunctionBreakpoint(p, t, fnName)
 | 
						|
		if err := grp.Continue(); err != nil {
 | 
						|
			t.Fatal(err)
 | 
						|
		}
 | 
						|
		if _, err := returnAddress(p, p.CurrentThread()); err == nil {
 | 
						|
			t.Fatal("expected error to be returned")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestSwitchThread(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("testnextprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		// With invalid thread id
 | 
						|
		err := p.SwitchThread(-1)
 | 
						|
		if err == nil {
 | 
						|
			t.Fatal("Expected error for invalid thread id")
 | 
						|
		}
 | 
						|
		setFunctionBreakpoint(p, t, "main.main")
 | 
						|
		err = grp.Continue()
 | 
						|
		if err != nil {
 | 
						|
			t.Fatal(err)
 | 
						|
		}
 | 
						|
		var nt int
 | 
						|
		ct := p.CurrentThread().ThreadID()
 | 
						|
		for _, thread := range p.ThreadList() {
 | 
						|
			if thread.ThreadID() != ct {
 | 
						|
				nt = thread.ThreadID()
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if nt == 0 {
 | 
						|
			t.Fatal("could not find thread to switch to")
 | 
						|
		}
 | 
						|
		// With valid thread id
 | 
						|
		err = p.SwitchThread(nt)
 | 
						|
		if err != nil {
 | 
						|
			t.Fatal(err)
 | 
						|
		}
 | 
						|
		if p.CurrentThread().ThreadID() != nt {
 | 
						|
			t.Fatal("Did not switch threads")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestCGONext(t *testing.T) {
 | 
						|
	// Test if one can do 'next' in a cgo binary
 | 
						|
	// On OSX with Go < 1.5 CGO is not supported due to: https://github.com/golang/go/issues/8973
 | 
						|
	if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 5) {
 | 
						|
		skipOn(t, "upstream issue", "darwin")
 | 
						|
	}
 | 
						|
	protest.MustHaveCgo(t)
 | 
						|
 | 
						|
	skipOn(t, "broken - see https://github.com/go-delve/delve/issues/3158", "darwin", "amd64")
 | 
						|
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("cgotest", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFunctionBreakpoint(p, t, "main.main")
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		assertNoError(grp.Next(), t, "Next()")
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestCGOBreakpointLocation(t *testing.T) {
 | 
						|
	protest.MustHaveCgo(t)
 | 
						|
	protest.AllowRecording(t)
 | 
						|
 | 
						|
	withTestProcess("cgotest", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		bp := setFunctionBreakpoint(p, t, "C.foo")
 | 
						|
		if !strings.Contains(bp.File, "cgotest.go") {
 | 
						|
			t.Fatalf("incorrect breakpoint location, expected cgotest.go got %s", bp.File)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
type loc struct {
 | 
						|
	line int
 | 
						|
	fn   string
 | 
						|
}
 | 
						|
 | 
						|
func (l1 *loc) match(l2 proc.Stackframe) bool {
 | 
						|
	if l1.line >= 0 {
 | 
						|
		if l1.line != l2.Call.Line {
 | 
						|
			return false
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return l1.fn == l2.Call.Fn.Name
 | 
						|
}
 | 
						|
 | 
						|
func TestStacktrace(t *testing.T) {
 | 
						|
	stacks := [][]loc{
 | 
						|
		{{4, "main.stacktraceme"}, {8, "main.func1"}, {16, "main.main"}},
 | 
						|
		{{4, "main.stacktraceme"}, {8, "main.func1"}, {12, "main.func2"}, {17, "main.main"}},
 | 
						|
	}
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("stacktraceprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		bp := setFunctionBreakpoint(p, t, "main.stacktraceme")
 | 
						|
 | 
						|
		for i := range stacks {
 | 
						|
			assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
			locations, err := proc.ThreadStacktrace(p, p.CurrentThread(), 40)
 | 
						|
			assertNoError(err, t, "Stacktrace()")
 | 
						|
 | 
						|
			t.Logf("Stacktrace %d:\n", i)
 | 
						|
			for i := range locations {
 | 
						|
				t.Logf("\t%s (%#x) %s:%d\n", locations[i].Call.Fn.Name, locations[i].Call.PC, locations[i].Call.File, locations[i].Call.Line)
 | 
						|
			}
 | 
						|
 | 
						|
			if len(locations) != len(stacks[i])+2 {
 | 
						|
				t.Fatalf("Wrong stack trace size %d %d\n", len(locations), len(stacks[i])+2)
 | 
						|
			}
 | 
						|
 | 
						|
			for j := range stacks[i] {
 | 
						|
				if !stacks[i][j].match(locations[j]) {
 | 
						|
					t.Fatalf("Wrong stack trace pos %d\n", j)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		p.ClearBreakpoint(bp.Addr)
 | 
						|
		grp.Continue()
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestStacktrace2(t *testing.T) {
 | 
						|
	withTestProcess("retstack", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
 | 
						|
		locations, err := proc.ThreadStacktrace(p, p.CurrentThread(), 40)
 | 
						|
		assertNoError(err, t, "Stacktrace()")
 | 
						|
		if !stackMatch([]loc{{-1, "main.f"}, {16, "main.main"}}, locations, false) {
 | 
						|
			for i := range locations {
 | 
						|
				t.Logf("\t%s:%d [%s]\n", locations[i].Call.File, locations[i].Call.Line, locations[i].Call.Fn.Name)
 | 
						|
			}
 | 
						|
			t.Fatalf("Stack error at main.f()\n%v\n", locations)
 | 
						|
		}
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		locations, err = proc.ThreadStacktrace(p, p.CurrentThread(), 40)
 | 
						|
		assertNoError(err, t, "Stacktrace()")
 | 
						|
		if !stackMatch([]loc{{-1, "main.g"}, {17, "main.main"}}, locations, false) {
 | 
						|
			for i := range locations {
 | 
						|
				t.Logf("\t%s:%d [%s]\n", locations[i].Call.File, locations[i].Call.Line, locations[i].Call.Fn.Name)
 | 
						|
			}
 | 
						|
			t.Fatalf("Stack error at main.g()\n%v\n", locations)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func stackMatch(stack []loc, locations []proc.Stackframe, skipRuntime bool) bool {
 | 
						|
	if len(stack) > len(locations) {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	i := 0
 | 
						|
	for j := range locations {
 | 
						|
		if i >= len(stack) {
 | 
						|
			break
 | 
						|
		}
 | 
						|
		if skipRuntime {
 | 
						|
			if locations[j].Call.Fn == nil || strings.HasPrefix(locations[j].Call.Fn.Name, "runtime.") {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if !stack[i].match(locations[j]) {
 | 
						|
			return false
 | 
						|
		}
 | 
						|
		i++
 | 
						|
	}
 | 
						|
	return i >= len(stack)
 | 
						|
}
 | 
						|
 | 
						|
func TestStacktraceGoroutine(t *testing.T) {
 | 
						|
	mainStack := []loc{{14, "main.stacktraceme"}, {29, "main.main"}}
 | 
						|
	if goversion.VersionAfterOrEqual(runtime.Version(), 1, 11) {
 | 
						|
		mainStack[0].line = 15
 | 
						|
	}
 | 
						|
	agoroutineStacks := [][]loc{
 | 
						|
		{{8, "main.agoroutine"}},
 | 
						|
		{{9, "main.agoroutine"}},
 | 
						|
		{{10, "main.agoroutine"}},
 | 
						|
	}
 | 
						|
 | 
						|
	lenient := 0
 | 
						|
	if runtime.GOOS == "windows" {
 | 
						|
		lenient = 1
 | 
						|
	}
 | 
						|
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("goroutinestackprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		bp := setFunctionBreakpoint(p, t, "main.stacktraceme")
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
 | 
						|
		gs, _, err := proc.GoroutinesInfo(p, 0, 0)
 | 
						|
		assertNoError(err, t, "GoroutinesInfo")
 | 
						|
 | 
						|
		agoroutineCount := 0
 | 
						|
		mainCount := 0
 | 
						|
 | 
						|
		for i, g := range gs {
 | 
						|
			locations, err := proc.GoroutineStacktrace(p, g, 40, 0)
 | 
						|
			if err != nil {
 | 
						|
				// On windows we do not have frame information for goroutines doing system calls.
 | 
						|
				t.Logf("Could not retrieve goroutine stack for goid=%d: %v", g.ID, err)
 | 
						|
				continue
 | 
						|
			}
 | 
						|
 | 
						|
			if stackMatch(mainStack, locations, false) {
 | 
						|
				mainCount++
 | 
						|
			}
 | 
						|
 | 
						|
			found := false
 | 
						|
			for _, agoroutineStack := range agoroutineStacks {
 | 
						|
				if stackMatch(agoroutineStack, locations, true) {
 | 
						|
					found = true
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			if found {
 | 
						|
				agoroutineCount++
 | 
						|
			} else {
 | 
						|
				t.Logf("Non-goroutine stack: %d (%d)", i, len(locations))
 | 
						|
				for i := range locations {
 | 
						|
					name := ""
 | 
						|
					if locations[i].Call.Fn != nil {
 | 
						|
						name = locations[i].Call.Fn.Name
 | 
						|
					}
 | 
						|
					t.Logf("\t%s:%d %s (%#x) %x %v\n", locations[i].Call.File, locations[i].Call.Line, name, locations[i].Current.PC, locations[i].FrameOffset(), locations[i].SystemStack)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if mainCount != 1 {
 | 
						|
			t.Fatalf("Main goroutine stack not found %d", mainCount)
 | 
						|
		}
 | 
						|
 | 
						|
		if agoroutineCount < 10-lenient {
 | 
						|
			t.Fatalf("Goroutine stacks not found (%d)", agoroutineCount)
 | 
						|
		}
 | 
						|
 | 
						|
		p.ClearBreakpoint(bp.Addr)
 | 
						|
		grp.Continue()
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestKill(t *testing.T) {
 | 
						|
	withTestProcess("testprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		if err := grp.Detach(true); err != nil {
 | 
						|
			t.Fatal(err)
 | 
						|
		}
 | 
						|
		if valid, _ := p.Valid(); valid {
 | 
						|
			t.Fatal("expected process to have exited")
 | 
						|
		}
 | 
						|
		if runtime.GOOS == "linux" {
 | 
						|
			if runtime.GOARCH == "arm64" {
 | 
						|
				// there is no any sync between signal sended(tracee handled) and open /proc/%d/. It may fail on arm64
 | 
						|
				return
 | 
						|
			}
 | 
						|
			_, err := os.Open(fmt.Sprintf("/proc/%d/", p.Pid()))
 | 
						|
			if err == nil {
 | 
						|
				t.Fatal("process has not exited", p.Pid())
 | 
						|
			}
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func testGSupportFunc(name string, t *testing.T, p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
	bp := setFunctionBreakpoint(p, t, "main.main")
 | 
						|
 | 
						|
	assertNoError(grp.Continue(), t, name+": Continue()")
 | 
						|
 | 
						|
	g, err := proc.GetG(p.CurrentThread())
 | 
						|
	assertNoError(err, t, name+": GetG()")
 | 
						|
 | 
						|
	if g == nil {
 | 
						|
		t.Fatal(name + ": g was nil")
 | 
						|
	}
 | 
						|
 | 
						|
	t.Logf(name+": g is: %v", g)
 | 
						|
 | 
						|
	p.ClearBreakpoint(bp.Addr)
 | 
						|
}
 | 
						|
 | 
						|
func TestGetG(t *testing.T) {
 | 
						|
	withTestProcess("testprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		testGSupportFunc("nocgo", t, p, grp, fixture)
 | 
						|
	})
 | 
						|
 | 
						|
	// On OSX with Go < 1.5 CGO is not supported due to: https://github.com/golang/go/issues/8973
 | 
						|
	if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 5) {
 | 
						|
		skipOn(t, "upstream issue", "darwin")
 | 
						|
	}
 | 
						|
	protest.MustHaveCgo(t)
 | 
						|
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("cgotest", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		testGSupportFunc("cgo", t, p, grp, fixture)
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestContinueMulti(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("integrationprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		bp1 := setFunctionBreakpoint(p, t, "main.main")
 | 
						|
		bp2 := setFunctionBreakpoint(p, t, "main.sayhi")
 | 
						|
 | 
						|
		mainCount := 0
 | 
						|
		sayhiCount := 0
 | 
						|
		for {
 | 
						|
			err := grp.Continue()
 | 
						|
			if valid, _ := p.Valid(); !valid {
 | 
						|
				break
 | 
						|
			}
 | 
						|
			assertNoError(err, t, "Continue()")
 | 
						|
 | 
						|
			if bp := p.CurrentThread().Breakpoint(); bp.LogicalID() == bp1.LogicalID() {
 | 
						|
				mainCount++
 | 
						|
			}
 | 
						|
 | 
						|
			if bp := p.CurrentThread().Breakpoint(); bp.LogicalID() == bp2.LogicalID() {
 | 
						|
				sayhiCount++
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if mainCount != 1 {
 | 
						|
			t.Fatalf("Main breakpoint hit wrong number of times: %d\n", mainCount)
 | 
						|
		}
 | 
						|
 | 
						|
		if sayhiCount != 3 {
 | 
						|
			t.Fatalf("Sayhi breakpoint hit wrong number of times: %d\n", sayhiCount)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestBreakpointOnFunctionEntry(t *testing.T) {
 | 
						|
	testseq2(t, "testprog", "main.main", []seqTest{{contContinue, 17}})
 | 
						|
}
 | 
						|
 | 
						|
func TestProcessReceivesSIGCHLD(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("sigchldprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		err := grp.Continue()
 | 
						|
		_, ok := err.(proc.ErrProcessExited)
 | 
						|
		if !ok {
 | 
						|
			t.Fatalf("Continue() returned unexpected error type %v", err)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue239(t *testing.T) {
 | 
						|
	withTestProcess("is sue239", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 17)
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func findFirstNonRuntimeFrame(p *proc.Target) (proc.Stackframe, error) {
 | 
						|
	frames, err := proc.ThreadStacktrace(p, p.CurrentThread(), 10)
 | 
						|
	if err != nil {
 | 
						|
		return proc.Stackframe{}, err
 | 
						|
	}
 | 
						|
 | 
						|
	for _, frame := range frames {
 | 
						|
		if frame.Current.Fn != nil && !strings.HasPrefix(frame.Current.Fn.Name, "runtime.") {
 | 
						|
			return frame, nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return proc.Stackframe{}, errors.New("non-runtime frame not found")
 | 
						|
}
 | 
						|
 | 
						|
func evalVariableOrError(p *proc.Target, symbol string) (*proc.Variable, error) {
 | 
						|
	var scope *proc.EvalScope
 | 
						|
	var err error
 | 
						|
 | 
						|
	if testBackend == "rr" {
 | 
						|
		var frame proc.Stackframe
 | 
						|
		frame, err = findFirstNonRuntimeFrame(p)
 | 
						|
		if err == nil {
 | 
						|
			scope = proc.FrameToScope(p, p.Memory(), nil, 0, frame)
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		scope, err = proc.GoroutineScope(p, p.CurrentThread())
 | 
						|
	}
 | 
						|
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return scope.EvalExpression(symbol, normalLoadConfig)
 | 
						|
}
 | 
						|
 | 
						|
func evalVariable(p *proc.Target, t testing.TB, symbol string) *proc.Variable {
 | 
						|
	t.Helper()
 | 
						|
	v, err := evalVariableOrError(p, symbol)
 | 
						|
	if err != nil {
 | 
						|
		_, file, line, _ := runtime.Caller(1)
 | 
						|
		fname := filepath.Base(file)
 | 
						|
		t.Fatalf("%s:%d: EvalVariable(%q): %v", fname, line, symbol, err)
 | 
						|
	}
 | 
						|
	return v
 | 
						|
}
 | 
						|
 | 
						|
func TestFrameEvaluation(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	lenient := false
 | 
						|
	if runtime.GOOS == "windows" {
 | 
						|
		lenient = true
 | 
						|
	}
 | 
						|
	withTestProcess("goroutinestackprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFunctionBreakpoint(p, t, "main.stacktraceme")
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
 | 
						|
		t.Logf("stopped on thread %d, goroutine: %#v", p.CurrentThread().ThreadID(), p.SelectedGoroutine())
 | 
						|
 | 
						|
		// Testing evaluation on goroutines
 | 
						|
		gs, _, err := proc.GoroutinesInfo(p, 0, 0)
 | 
						|
		assertNoError(err, t, "GoroutinesInfo")
 | 
						|
		found := make([]bool, 10)
 | 
						|
		for _, g := range gs {
 | 
						|
			frame := -1
 | 
						|
			frames, err := proc.GoroutineStacktrace(p, g, 40, 0)
 | 
						|
			if err != nil {
 | 
						|
				t.Logf("could not stacktrace goroutine %d: %v\n", g.ID, err)
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			t.Logf("Goroutine %d %#v", g.ID, g.Thread)
 | 
						|
			logStacktrace(t, p, frames)
 | 
						|
			for i := range frames {
 | 
						|
				if frames[i].Call.Fn != nil && frames[i].Call.Fn.Name == "main.agoroutine" {
 | 
						|
					frame = i
 | 
						|
					break
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			if frame < 0 {
 | 
						|
				t.Logf("Goroutine %d: could not find correct frame", g.ID)
 | 
						|
				continue
 | 
						|
			}
 | 
						|
 | 
						|
			scope, err := proc.ConvertEvalScope(p, g.ID, frame, 0)
 | 
						|
			assertNoError(err, t, "ConvertEvalScope()")
 | 
						|
			t.Logf("scope = %v", scope)
 | 
						|
			v, err := scope.EvalExpression("i", normalLoadConfig)
 | 
						|
			t.Logf("v = %v", v)
 | 
						|
			if err != nil {
 | 
						|
				t.Logf("Goroutine %d: %v\n", g.ID, err)
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			vval, _ := constant.Int64Val(v.Value)
 | 
						|
			found[vval] = true
 | 
						|
		}
 | 
						|
 | 
						|
		for i := range found {
 | 
						|
			if !found[i] {
 | 
						|
				if lenient {
 | 
						|
					lenient = false
 | 
						|
				} else {
 | 
						|
					t.Fatalf("Goroutine %d not found\n", i)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// Testing evaluation on frames
 | 
						|
		assertNoError(grp.Continue(), t, "Continue() 2")
 | 
						|
		g, err := proc.GetG(p.CurrentThread())
 | 
						|
		assertNoError(err, t, "GetG()")
 | 
						|
 | 
						|
		frames, err := proc.GoroutineStacktrace(p, g, 40, 0)
 | 
						|
		assertNoError(err, t, "Stacktrace()")
 | 
						|
		t.Logf("Goroutine %d %#v", g.ID, g.Thread)
 | 
						|
		logStacktrace(t, p, frames)
 | 
						|
 | 
						|
		for i := 0; i <= 3; i++ {
 | 
						|
			scope, err := proc.ConvertEvalScope(p, g.ID, i+1, 0)
 | 
						|
			assertNoError(err, t, fmt.Sprintf("ConvertEvalScope() on frame %d", i+1))
 | 
						|
			v, err := scope.EvalExpression("n", normalLoadConfig)
 | 
						|
			assertNoError(err, t, fmt.Sprintf("EvalVariable() on frame %d", i+1))
 | 
						|
			n, _ := constant.Int64Val(v.Value)
 | 
						|
			t.Logf("frame %d n %d\n", i+1, n)
 | 
						|
			if n != int64(3-i) {
 | 
						|
				t.Fatalf("On frame %d value of n is %d (not %d)", i+1, n, 3-i)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestThreadFrameEvaluation(t *testing.T) {
 | 
						|
	skipOn(t, "upstream issue - https://github.com/golang/go/issues/29322", "pie")
 | 
						|
	deadlockBp := proc.FatalThrow
 | 
						|
	if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 11) {
 | 
						|
		t.SkipNow()
 | 
						|
	}
 | 
						|
	withTestProcess("testdeadlock", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
 | 
						|
		bp := p.CurrentThread().Breakpoint()
 | 
						|
		if bp.Breakpoint == nil || bp.Logical.Name != deadlockBp {
 | 
						|
			t.Fatalf("did not stop at deadlock breakpoint %v", bp)
 | 
						|
		}
 | 
						|
 | 
						|
		// There is no selected goroutine during a deadlock, so the scope will
 | 
						|
		// be a thread scope.
 | 
						|
		scope, err := proc.ConvertEvalScope(p, 0, 0, 0)
 | 
						|
		assertNoError(err, t, "ConvertEvalScope() on frame 0")
 | 
						|
		_, err = scope.EvalExpression("s", normalLoadConfig)
 | 
						|
		assertNoError(err, t, "EvalVariable(\"s\") on frame 0")
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestPointerSetting(t *testing.T) {
 | 
						|
	withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), t, "Continue() returned an error")
 | 
						|
 | 
						|
		pval := func(n int64) {
 | 
						|
			variable := evalVariable(p, t, "p1")
 | 
						|
			c0val, _ := constant.Int64Val(variable.Children[0].Value)
 | 
						|
			if c0val != n {
 | 
						|
				t.Fatalf("Wrong value of p1, *%d expected *%d", c0val, n)
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		pval(1)
 | 
						|
 | 
						|
		// change p1 to point to i2
 | 
						|
		scope, err := proc.GoroutineScope(p, p.CurrentThread())
 | 
						|
		assertNoError(err, t, "Scope()")
 | 
						|
		i2addr, err := scope.EvalExpression("i2", normalLoadConfig)
 | 
						|
		assertNoError(err, t, "EvalExpression()")
 | 
						|
		assertNoError(setVariable(p, "p1", fmt.Sprintf("(*int)(0x%x)", i2addr.Addr)), t, "SetVariable()")
 | 
						|
		pval(2)
 | 
						|
 | 
						|
		// change the value of i2 check that p1 also changes
 | 
						|
		assertNoError(setVariable(p, "i2", "5"), t, "SetVariable()")
 | 
						|
		pval(5)
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestVariableFunctionScoping(t *testing.T) {
 | 
						|
	withTestProcess("testvariables", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		err := grp.Continue()
 | 
						|
		assertNoError(err, t, "Continue() returned an error")
 | 
						|
 | 
						|
		evalVariable(p, t, "a1")
 | 
						|
		evalVariable(p, t, "a2")
 | 
						|
 | 
						|
		// Move scopes, a1 exists here by a2 does not
 | 
						|
		err = grp.Continue()
 | 
						|
		assertNoError(err, t, "Continue() returned an error")
 | 
						|
 | 
						|
		evalVariable(p, t, "a1")
 | 
						|
 | 
						|
		_, err = evalVariableOrError(p, "a2")
 | 
						|
		if err == nil {
 | 
						|
			t.Fatalf("Can eval out of scope variable a2")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestRecursiveStructure(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		v := evalVariable(p, t, "aas")
 | 
						|
		t.Logf("v: %v\n", v)
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue316(t *testing.T) {
 | 
						|
	// A pointer loop that includes one interface should not send dlv into an infinite loop
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		evalVariable(p, t, "iface5")
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue325(t *testing.T) {
 | 
						|
	// nil pointer dereference when evaluating interfaces to function pointers
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		iface2fn1v := evalVariable(p, t, "iface2fn1")
 | 
						|
		t.Logf("iface2fn1: %v\n", iface2fn1v)
 | 
						|
 | 
						|
		iface2fn2v := evalVariable(p, t, "iface2fn2")
 | 
						|
		t.Logf("iface2fn2: %v\n", iface2fn2v)
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestBreakpointCounts(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("bpcountstest", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		bp := setFileBreakpoint(p, t, fixture.Source, 12)
 | 
						|
 | 
						|
		for {
 | 
						|
			err := grp.Continue()
 | 
						|
			if errors.As(err, &proc.ErrProcessExited{}) {
 | 
						|
				break
 | 
						|
			}
 | 
						|
			assertNoError(err, t, "Continue()")
 | 
						|
		}
 | 
						|
 | 
						|
		t.Logf("TotalHitCount: %d", bp.Logical.TotalHitCount)
 | 
						|
		if bp.Logical.TotalHitCount != 200 {
 | 
						|
			t.Fatalf("Wrong TotalHitCount for the breakpoint (%d)", bp.Logical.TotalHitCount)
 | 
						|
		}
 | 
						|
 | 
						|
		if len(bp.Logical.HitCount) != 2 {
 | 
						|
			t.Fatalf("Wrong number of goroutines for breakpoint (%d)", len(bp.Logical.HitCount))
 | 
						|
		}
 | 
						|
 | 
						|
		for _, v := range bp.Logical.HitCount {
 | 
						|
			if v != 100 {
 | 
						|
				t.Fatalf("Wrong HitCount for breakpoint (%v)", bp.Logical.HitCount)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestHardcodedBreakpointCounts(t *testing.T) {
 | 
						|
	skipOn(t, "broken", "windows", "arm64")
 | 
						|
	withTestProcess("hcbpcountstest", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		counts := map[int64]int{}
 | 
						|
		for {
 | 
						|
			err := grp.Continue()
 | 
						|
			if errors.As(err, &proc.ErrProcessExited{}) {
 | 
						|
				break
 | 
						|
			}
 | 
						|
			assertNoError(err, t, "Continue()")
 | 
						|
 | 
						|
			for _, th := range p.ThreadList() {
 | 
						|
				bp := th.Breakpoint().Breakpoint
 | 
						|
				if bp == nil {
 | 
						|
					continue
 | 
						|
				}
 | 
						|
				if bp.Logical.Name != proc.HardcodedBreakpoint {
 | 
						|
					t.Fatalf("wrong breakpoint name %s", bp.Logical.Name)
 | 
						|
				}
 | 
						|
				g, err := proc.GetG(th)
 | 
						|
				assertNoError(err, t, "GetG")
 | 
						|
				counts[g.ID]++
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if len(counts) != 2 {
 | 
						|
			t.Fatalf("Wrong number of goroutines for hardcoded breakpoint (%d)", len(counts))
 | 
						|
		}
 | 
						|
 | 
						|
		for goid, count := range counts {
 | 
						|
			if count != 100 {
 | 
						|
				t.Fatalf("Wrong hit count for hardcoded breakpoint (%d) on goroutine %d", count, goid)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func BenchmarkArray(b *testing.B) {
 | 
						|
	// each bencharr struct is 128 bytes, bencharr is 64 elements long
 | 
						|
	b.SetBytes(int64(64 * 128))
 | 
						|
	withTestProcess("testvariables2", b, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), b, "Continue()")
 | 
						|
		b.ResetTimer()
 | 
						|
		for i := 0; i < b.N; i++ {
 | 
						|
			evalVariable(p, b, "bencharr")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
const doTestBreakpointCountsWithDetection = false
 | 
						|
 | 
						|
func TestBreakpointCountsWithDetection(t *testing.T) {
 | 
						|
	if !doTestBreakpointCountsWithDetection {
 | 
						|
		return
 | 
						|
	}
 | 
						|
	m := map[int64]int64{}
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("bpcountstest", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		bp := setFileBreakpoint(p, t, fixture.Source, 12)
 | 
						|
 | 
						|
		for {
 | 
						|
			err := grp.Continue()
 | 
						|
			if errors.As(err, &proc.ErrProcessExited{}) {
 | 
						|
				break
 | 
						|
			}
 | 
						|
			assertNoError(err, t, "Continue()")
 | 
						|
			for _, th := range p.ThreadList() {
 | 
						|
				if bp := th.Breakpoint(); bp.Breakpoint == nil {
 | 
						|
					continue
 | 
						|
				}
 | 
						|
				scope, err := proc.GoroutineScope(p, th)
 | 
						|
				assertNoError(err, t, "Scope()")
 | 
						|
				v, err := scope.EvalExpression("i", normalLoadConfig)
 | 
						|
				assertNoError(err, t, "evalVariable")
 | 
						|
				i, _ := constant.Int64Val(v.Value)
 | 
						|
				v, err = scope.EvalExpression("id", normalLoadConfig)
 | 
						|
				assertNoError(err, t, "evalVariable")
 | 
						|
				id, _ := constant.Int64Val(v.Value)
 | 
						|
				m[id] = i
 | 
						|
			}
 | 
						|
 | 
						|
			total := int64(0)
 | 
						|
			for i := range m {
 | 
						|
				total += m[i] + 1
 | 
						|
			}
 | 
						|
 | 
						|
			if uint64(total) != bp.Logical.TotalHitCount {
 | 
						|
				t.Fatalf("Mismatched total count %d %d\n", total, bp.Logical.TotalHitCount)
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		t.Logf("TotalHitCount: %d", bp.Logical.TotalHitCount)
 | 
						|
		if bp.Logical.TotalHitCount != 200 {
 | 
						|
			t.Fatalf("Wrong TotalHitCount for the breakpoint (%d)", bp.Logical.TotalHitCount)
 | 
						|
		}
 | 
						|
 | 
						|
		if len(bp.Logical.HitCount) != 2 {
 | 
						|
			t.Fatalf("Wrong number of goroutines for breakpoint (%d)", len(bp.Logical.HitCount))
 | 
						|
		}
 | 
						|
 | 
						|
		for _, v := range bp.Logical.HitCount {
 | 
						|
			if v != 100 {
 | 
						|
				t.Fatalf("Wrong HitCount for breakpoint (%v)", bp.Logical.HitCount)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func BenchmarkArrayPointer(b *testing.B) {
 | 
						|
	// each bencharr struct is 128 bytes, benchparr is an array of 64 pointers to bencharr
 | 
						|
	// each read will read 64 bencharr structs plus the 64 pointers of benchparr
 | 
						|
	b.SetBytes(int64(64*128 + 64*8))
 | 
						|
	withTestProcess("testvariables2", b, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), b, "Continue()")
 | 
						|
		b.ResetTimer()
 | 
						|
		for i := 0; i < b.N; i++ {
 | 
						|
			evalVariable(p, b, "bencharr")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func BenchmarkMap(b *testing.B) {
 | 
						|
	// m1 contains 41 entries, each one has a value that's 2 int values (2* 8 bytes) and a string key
 | 
						|
	// each string key has an average of 9 character
 | 
						|
	// reading strings and the map structure imposes a overhead that we ignore here
 | 
						|
	b.SetBytes(int64(41 * (2*8 + 9)))
 | 
						|
	withTestProcess("testvariables2", b, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), b, "Continue()")
 | 
						|
		b.ResetTimer()
 | 
						|
		for i := 0; i < b.N; i++ {
 | 
						|
			evalVariable(p, b, "m1")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func BenchmarkGoroutinesInfo(b *testing.B) {
 | 
						|
	withTestProcess("testvariables2", b, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), b, "Continue()")
 | 
						|
		b.ResetTimer()
 | 
						|
		for i := 0; i < b.N; i++ {
 | 
						|
			p.ClearCaches()
 | 
						|
			_, _, err := proc.GoroutinesInfo(p, 0, 0)
 | 
						|
			assertNoError(err, b, "GoroutinesInfo")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue262(t *testing.T) {
 | 
						|
	// Continue does not work when the current breakpoint is set on a NOP instruction
 | 
						|
	skipOn(t, "N/A", "386")
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("issue262", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 11)
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		err := grp.Continue()
 | 
						|
		if err == nil {
 | 
						|
			t.Fatalf("No error on second continue")
 | 
						|
		}
 | 
						|
		_, exited := err.(proc.ErrProcessExited)
 | 
						|
		if !exited {
 | 
						|
			t.Fatalf("Process did not exit after second continue: %v", err)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue305(t *testing.T) {
 | 
						|
	// If 'next' hits a breakpoint on the goroutine it's stepping through
 | 
						|
	// the internal breakpoints aren't cleared preventing further use of
 | 
						|
	// 'next' command
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("issue305", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 5)
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
 | 
						|
		assertNoError(grp.Next(), t, "Next() 1")
 | 
						|
		assertNoError(grp.Next(), t, "Next() 2")
 | 
						|
		assertNoError(grp.Next(), t, "Next() 3")
 | 
						|
		assertNoError(grp.Next(), t, "Next() 4")
 | 
						|
		assertNoError(grp.Next(), t, "Next() 5")
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestPointerLoops(t *testing.T) {
 | 
						|
	// Pointer loops through map entries, pointers and slices
 | 
						|
	// Regression test for issue #341
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		for _, expr := range []string{"mapinf", "ptrinf", "sliceinf"} {
 | 
						|
			t.Logf("requesting %s", expr)
 | 
						|
			v := evalVariable(p, t, expr)
 | 
						|
			t.Logf("%s: %v\n", expr, v)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func BenchmarkLocalVariables(b *testing.B) {
 | 
						|
	withTestProcess("testvariables", b, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), b, "Continue() returned an error")
 | 
						|
		scope, err := proc.GoroutineScope(p, p.CurrentThread())
 | 
						|
		assertNoError(err, b, "Scope()")
 | 
						|
		b.ResetTimer()
 | 
						|
		for i := 0; i < b.N; i++ {
 | 
						|
			_, err := scope.LocalVariables(normalLoadConfig)
 | 
						|
			assertNoError(err, b, "LocalVariables()")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestCondBreakpoint(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("parallel_next", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		bp := setFileBreakpoint(p, t, fixture.Source, 9)
 | 
						|
		bp.UserBreaklet().Cond = &ast.BinaryExpr{
 | 
						|
			Op: token.EQL,
 | 
						|
			X:  &ast.Ident{Name: "n"},
 | 
						|
			Y:  &ast.BasicLit{Kind: token.INT, Value: "7"},
 | 
						|
		}
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
 | 
						|
		nvar := evalVariable(p, t, "n")
 | 
						|
 | 
						|
		n, _ := constant.Int64Val(nvar.Value)
 | 
						|
		if n != 7 {
 | 
						|
			t.Fatalf("Stopped on wrong goroutine %d\n", n)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestCondBreakpointWithFrame(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("condframe", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		bp := setFileBreakpoint(p, t, fixture.Source, 12)
 | 
						|
		parsed, err := parser.ParseExpr("runtime.frame(1).i == 3")
 | 
						|
		if err != nil {
 | 
						|
			t.Fatalf("failed to parse expression: %v", err)
 | 
						|
		}
 | 
						|
		bp.UserBreaklet().Cond = parsed
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
 | 
						|
		g := p.SelectedGoroutine()
 | 
						|
		scope, err := proc.ConvertEvalScope(p, g.ID, 1, 0)
 | 
						|
		if err != nil {
 | 
						|
			t.Fatal(err)
 | 
						|
		}
 | 
						|
 | 
						|
		v, err := scope.EvalExpression("i", normalLoadConfig)
 | 
						|
		if err != nil {
 | 
						|
			t.Fatal(err)
 | 
						|
		}
 | 
						|
 | 
						|
		vval, _ := constant.Int64Val(v.Value)
 | 
						|
		if vval != 3 {
 | 
						|
			t.Fatalf("Incorrect value for frame variable: %v", vval)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestCondBreakpointError(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("parallel_next", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		bp := setFileBreakpoint(p, t, fixture.Source, 9)
 | 
						|
		bp.UserBreaklet().Cond = &ast.BinaryExpr{
 | 
						|
			Op: token.EQL,
 | 
						|
			X:  &ast.Ident{Name: "nonexistentvariable"},
 | 
						|
			Y:  &ast.BasicLit{Kind: token.INT, Value: "7"},
 | 
						|
		}
 | 
						|
 | 
						|
		err := grp.Continue()
 | 
						|
		if err == nil {
 | 
						|
			t.Fatalf("No error on first Continue()")
 | 
						|
		}
 | 
						|
 | 
						|
		if err.Error() != "error evaluating expression: could not find symbol value for nonexistentvariable" && err.Error() != "multiple errors evaluating conditions" {
 | 
						|
			t.Fatalf("Unexpected error on first Continue(): %v", err)
 | 
						|
		}
 | 
						|
 | 
						|
		bp.UserBreaklet().Cond = &ast.BinaryExpr{
 | 
						|
			Op: token.EQL,
 | 
						|
			X:  &ast.Ident{Name: "n"},
 | 
						|
			Y:  &ast.BasicLit{Kind: token.INT, Value: "7"},
 | 
						|
		}
 | 
						|
 | 
						|
		err = grp.Continue()
 | 
						|
		if err != nil {
 | 
						|
			if !errors.As(err, &proc.ErrProcessExited{}) {
 | 
						|
				t.Fatalf("Unexpected error on second Continue(): %v", err)
 | 
						|
			}
 | 
						|
			return
 | 
						|
		}
 | 
						|
		nvar := evalVariable(p, t, "n")
 | 
						|
 | 
						|
		n, _ := constant.Int64Val(nvar.Value)
 | 
						|
		if n != 7 {
 | 
						|
			t.Fatalf("Stopped on wrong goroutine %d\n", n)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestHitCondBreakpointEQ(t *testing.T) {
 | 
						|
	withTestProcess("break", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		bp := setFileBreakpoint(p, t, fixture.Source, 7)
 | 
						|
		grp.ChangeBreakpointCondition(bp.Logical, "", "== 3", false)
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		ivar := evalVariable(p, t, "i")
 | 
						|
 | 
						|
		i, _ := constant.Int64Val(ivar.Value)
 | 
						|
		if i != 3 {
 | 
						|
			t.Fatalf("Stopped on wrong hitcount %d\n", i)
 | 
						|
		}
 | 
						|
 | 
						|
		err := grp.Continue()
 | 
						|
		if _, exited := err.(proc.ErrProcessExited); !exited {
 | 
						|
			t.Fatalf("Unexpected error on Continue(): %v", err)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestHitCondBreakpointGEQ(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("break", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		bp := setFileBreakpoint(p, t, fixture.Source, 7)
 | 
						|
		grp.ChangeBreakpointCondition(bp.Logical, "", ">= 3", false)
 | 
						|
 | 
						|
		for it := 3; it <= 10; it++ {
 | 
						|
			assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
			ivar := evalVariable(p, t, "i")
 | 
						|
 | 
						|
			i, _ := constant.Int64Val(ivar.Value)
 | 
						|
			if int(i) != it {
 | 
						|
				t.Fatalf("Stopped on wrong hitcount %d\n", i)
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestHitCondBreakpointREM(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("break", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		bp := setFileBreakpoint(p, t, fixture.Source, 7)
 | 
						|
		grp.ChangeBreakpointCondition(bp.Logical, "", "% 2", false)
 | 
						|
 | 
						|
		for it := 2; it <= 10; it += 2 {
 | 
						|
			assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
			ivar := evalVariable(p, t, "i")
 | 
						|
 | 
						|
			i, _ := constant.Int64Val(ivar.Value)
 | 
						|
			if int(i) != it {
 | 
						|
				t.Fatalf("Stopped on wrong hitcount %d\n", i)
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		err := grp.Continue()
 | 
						|
		if _, exited := err.(proc.ErrProcessExited); !exited {
 | 
						|
			t.Fatalf("Unexpected error on Continue(): %v", err)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue356(t *testing.T) {
 | 
						|
	// slice with a typedef does not get printed correctly
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), t, "Continue() returned an error")
 | 
						|
		mmvar := evalVariable(p, t, "mainMenu")
 | 
						|
		if mmvar.Kind != reflect.Slice {
 | 
						|
			t.Fatalf("Wrong kind for mainMenu: %v\n", mmvar.Kind)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestStepIntoFunction(t *testing.T) {
 | 
						|
	withTestProcess("teststep", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		// Continue until breakpoint
 | 
						|
		assertNoError(grp.Continue(), t, "Continue() returned an error")
 | 
						|
		// Step into function
 | 
						|
		assertNoError(grp.Step(), t, "Step() returned an error")
 | 
						|
		// We should now be inside the function.
 | 
						|
		loc, err := proc.ThreadLocation(p.CurrentThread())
 | 
						|
		if err != nil {
 | 
						|
			t.Fatal(err)
 | 
						|
		}
 | 
						|
		if loc.Fn.Name != "main.callme" {
 | 
						|
			t.Fatalf("expected to be within the 'callme' function, was in %s instead", loc.Fn.Name)
 | 
						|
		}
 | 
						|
		if !strings.Contains(loc.File, "teststep") {
 | 
						|
			t.Fatalf("debugger stopped at incorrect location: %s:%d", loc.File, loc.Line)
 | 
						|
		}
 | 
						|
		if loc.Line != 8 {
 | 
						|
			t.Fatalf("debugger stopped at incorrect line: %d", loc.Line)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue332_Part1(t *testing.T) {
 | 
						|
	// Next shouldn't step inside a function call
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("issue332", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 8)
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		assertNoError(grp.Next(), t, "first Next()")
 | 
						|
		locations, err := proc.ThreadStacktrace(p, p.CurrentThread(), 2)
 | 
						|
		assertNoError(err, t, "Stacktrace()")
 | 
						|
		if locations[0].Call.Fn == nil {
 | 
						|
			t.Fatalf("Not on a function")
 | 
						|
		}
 | 
						|
		if locations[0].Call.Fn.Name != "main.main" {
 | 
						|
			t.Fatalf("Not on main.main after Next: %s (%s:%d)", locations[0].Call.Fn.Name, locations[0].Call.File, locations[0].Call.Line)
 | 
						|
		}
 | 
						|
		if locations[0].Call.Line != 9 {
 | 
						|
			t.Fatalf("Not on line 9 after Next: %s (%s:%d)", locations[0].Call.Fn.Name, locations[0].Call.File, locations[0].Call.Line)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue332_Part2(t *testing.T) {
 | 
						|
	// Step should skip a function's prologue
 | 
						|
	// In some parts of the prologue, for some functions, the FDE data is incorrect
 | 
						|
	// which leads to 'next' and 'stack' failing with error "could not find FDE for PC: <garbage>"
 | 
						|
	// because the incorrect FDE data leads to reading the wrong stack address as the return address
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("issue332", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 8)
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
 | 
						|
		// step until we enter changeMe
 | 
						|
		for {
 | 
						|
			assertNoError(grp.Step(), t, "Step()")
 | 
						|
			locations, err := proc.ThreadStacktrace(p, p.CurrentThread(), 2)
 | 
						|
			assertNoError(err, t, "Stacktrace()")
 | 
						|
			if locations[0].Call.Fn == nil {
 | 
						|
				t.Fatalf("Not on a function")
 | 
						|
			}
 | 
						|
			if locations[0].Call.Fn.Name == "main.changeMe" {
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		regs, err := p.CurrentThread().Registers()
 | 
						|
		assertNoError(err, t, "Registers()")
 | 
						|
		pc := regs.PC()
 | 
						|
		pcAfterPrologue := findFunctionLocation(p, t, "main.changeMe")
 | 
						|
		if pcAfterPrologue == p.BinInfo().LookupFunc()["main.changeMe"][0].Entry {
 | 
						|
			t.Fatalf("main.changeMe and main.changeMe:0 are the same (%x)", pcAfterPrologue)
 | 
						|
		}
 | 
						|
		if pc != pcAfterPrologue {
 | 
						|
			t.Fatalf("Step did not skip the prologue: current pc: %x, first instruction after prologue: %x", pc, pcAfterPrologue)
 | 
						|
		}
 | 
						|
 | 
						|
		assertNoError(grp.Next(), t, "first Next()")
 | 
						|
		assertNoError(grp.Next(), t, "second Next()")
 | 
						|
		assertNoError(grp.Next(), t, "third Next()")
 | 
						|
		err = grp.Continue()
 | 
						|
		if _, exited := err.(proc.ErrProcessExited); !exited {
 | 
						|
			assertNoError(err, t, "final Continue()")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue414(t *testing.T) {
 | 
						|
	skipOn(t, "broken", "linux", "386", "pie") // test occasionally hangs on linux/386/pie
 | 
						|
	// Stepping until the program exits
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("math", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 9)
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		for {
 | 
						|
			pc := currentPC(p, t)
 | 
						|
			f, ln := currentLineNumber(p, t)
 | 
						|
			t.Logf("at %s:%d %#x\n", f, ln, pc)
 | 
						|
			var err error
 | 
						|
			// Stepping through the runtime is not generally safe so after we are out
 | 
						|
			// of main.main just use Next.
 | 
						|
			// See: https://github.com/go-delve/delve/pull/2082
 | 
						|
			if f == fixture.Source {
 | 
						|
				err = grp.Step()
 | 
						|
			} else {
 | 
						|
				err = grp.Next()
 | 
						|
			}
 | 
						|
			if errors.As(err, &proc.ErrProcessExited{}) {
 | 
						|
				break
 | 
						|
			}
 | 
						|
			assertNoError(err, t, "Step()")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestPackageVariables(t *testing.T) {
 | 
						|
	var skippedVariable = map[string]bool{
 | 
						|
		"runtime.uint16Eface": true,
 | 
						|
		"runtime.uint32Eface": true,
 | 
						|
		"runtime.uint64Eface": true,
 | 
						|
		"runtime.stringEface": true,
 | 
						|
		"runtime.sliceEface":  true,
 | 
						|
	}
 | 
						|
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("testvariables", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		err := grp.Continue()
 | 
						|
		assertNoError(err, t, "Continue()")
 | 
						|
		scope, err := proc.GoroutineScope(p, p.CurrentThread())
 | 
						|
		assertNoError(err, t, "Scope()")
 | 
						|
		vars, err := scope.PackageVariables(normalLoadConfig)
 | 
						|
		assertNoError(err, t, "PackageVariables()")
 | 
						|
		failed := false
 | 
						|
		for _, v := range vars {
 | 
						|
			if skippedVariable[v.Name] {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			if v.Unreadable != nil && v.Unreadable.Error() != "no location attribute Location" {
 | 
						|
				failed = true
 | 
						|
				t.Logf("Unreadable variable %s: %v", v.Name, v.Unreadable)
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if failed {
 | 
						|
			t.Fatalf("previous errors")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue149(t *testing.T) {
 | 
						|
	ver, _ := goversion.Parse(runtime.Version())
 | 
						|
	if ver.Major > 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 7, Rev: -1}) {
 | 
						|
		return
 | 
						|
	}
 | 
						|
	// setting breakpoint on break statement
 | 
						|
	withTestProcess("break", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		findFileLocation(p, t, fixture.Source, 8)
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestPanicBreakpoint(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("panic", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		bp := p.CurrentThread().Breakpoint()
 | 
						|
		if bp.Breakpoint == nil || bp.Logical.Name != proc.UnrecoveredPanic {
 | 
						|
			t.Fatalf("not on unrecovered-panic breakpoint: %v", bp)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestCmdLineArgs(t *testing.T) {
 | 
						|
	expectSuccess := func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		err := grp.Continue()
 | 
						|
		bp := p.CurrentThread().Breakpoint()
 | 
						|
		if bp.Breakpoint != nil && bp.Logical.Name == proc.UnrecoveredPanic {
 | 
						|
			t.Fatalf("testing args failed on unrecovered-panic breakpoint: %v", bp)
 | 
						|
		}
 | 
						|
		var exit proc.ErrProcessExited
 | 
						|
		if !errors.As(err, &exit) {
 | 
						|
			t.Fatalf("Process did not exit: %v", err)
 | 
						|
		}
 | 
						|
		if exit.Status != 0 {
 | 
						|
			t.Fatalf("process exited with invalid status %d", exit.Status)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	expectPanic := func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		grp.Continue()
 | 
						|
		bp := p.CurrentThread().Breakpoint()
 | 
						|
		if bp.Breakpoint == nil || bp.Logical.Name != proc.UnrecoveredPanic {
 | 
						|
			t.Fatalf("not on unrecovered-panic breakpoint: %v", bp)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// make sure multiple arguments (including one with spaces) are passed to the binary correctly
 | 
						|
	withTestProcessArgs("testargs", t, ".", []string{"test"}, 0, expectSuccess)
 | 
						|
	withTestProcessArgs("testargs", t, ".", []string{"-test"}, 0, expectPanic)
 | 
						|
	withTestProcessArgs("testargs", t, ".", []string{"test", "pass flag"}, 0, expectSuccess)
 | 
						|
	// check that arguments with spaces are *only* passed correctly when correctly called
 | 
						|
	withTestProcessArgs("testargs", t, ".", []string{"test pass", "flag"}, 0, expectPanic)
 | 
						|
	withTestProcessArgs("testargs", t, ".", []string{"test", "pass", "flag"}, 0, expectPanic)
 | 
						|
	withTestProcessArgs("testargs", t, ".", []string{"test pass flag"}, 0, expectPanic)
 | 
						|
	// and that invalid cases (wrong arguments or no arguments) panic
 | 
						|
	withTestProcess("testargs", t, expectPanic)
 | 
						|
	withTestProcessArgs("testargs", t, ".", []string{"invalid"}, 0, expectPanic)
 | 
						|
	withTestProcessArgs("testargs", t, ".", []string{"test", "invalid"}, 0, expectPanic)
 | 
						|
	withTestProcessArgs("testargs", t, ".", []string{"invalid", "pass flag"}, 0, expectPanic)
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue462(t *testing.T) {
 | 
						|
	skipOn(t, "broken", "windows") // Stacktrace of Goroutine 0 fails with an error
 | 
						|
	withTestProcess("testnextnethttp", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		go func() {
 | 
						|
			// Wait for program to start listening.
 | 
						|
			for {
 | 
						|
				conn, err := net.Dial("tcp", "127.0.0.1:9191")
 | 
						|
				if err == nil {
 | 
						|
					conn.Close()
 | 
						|
					break
 | 
						|
				}
 | 
						|
				time.Sleep(50 * time.Millisecond)
 | 
						|
			}
 | 
						|
 | 
						|
			grp.RequestManualStop()
 | 
						|
		}()
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		_, err := proc.ThreadStacktrace(p, p.CurrentThread(), 40)
 | 
						|
		assertNoError(err, t, "Stacktrace()")
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestNextParked(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("parallel_next", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		bp := setFunctionBreakpoint(p, t, "main.sayhi")
 | 
						|
 | 
						|
		// continue until a parked goroutine exists
 | 
						|
		var parkedg *proc.G
 | 
						|
		for parkedg == nil {
 | 
						|
			err := grp.Continue()
 | 
						|
			if _, exited := err.(proc.ErrProcessExited); exited {
 | 
						|
				t.Log("could not find parked goroutine")
 | 
						|
				return
 | 
						|
			}
 | 
						|
			assertNoError(err, t, "Continue()")
 | 
						|
 | 
						|
			gs, _, err := proc.GoroutinesInfo(p, 0, 0)
 | 
						|
			assertNoError(err, t, "GoroutinesInfo()")
 | 
						|
 | 
						|
			// Search for a parked goroutine that we know for sure will have to be
 | 
						|
			// resumed before the program can exit. This is a parked goroutine that:
 | 
						|
			// 1. is executing main.sayhi
 | 
						|
			// 2. hasn't called wg.Done yet
 | 
						|
			for _, g := range gs {
 | 
						|
				if g.Thread != nil {
 | 
						|
					continue
 | 
						|
				}
 | 
						|
				frames, _ := proc.GoroutineStacktrace(p, g, 5, 0)
 | 
						|
				for _, frame := range frames {
 | 
						|
					// line 11 is the line where wg.Done is called
 | 
						|
					if frame.Current.Fn != nil && frame.Current.Fn.Name == "main.sayhi" && frame.Current.Line < 11 {
 | 
						|
						parkedg = g
 | 
						|
						break
 | 
						|
					}
 | 
						|
				}
 | 
						|
				if parkedg != nil {
 | 
						|
					break
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		assertNoError(p.SwitchGoroutine(parkedg), t, "SwitchGoroutine()")
 | 
						|
		p.ClearBreakpoint(bp.Addr)
 | 
						|
		assertNoError(grp.Next(), t, "Next()")
 | 
						|
 | 
						|
		if p.SelectedGoroutine().ID != parkedg.ID {
 | 
						|
			t.Fatalf("Next did not continue on the selected goroutine, expected %d got %d", parkedg.ID, p.SelectedGoroutine().ID)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestStepParked(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("parallel_next", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		bp := setFunctionBreakpoint(p, t, "main.sayhi")
 | 
						|
 | 
						|
		// continue until a parked goroutine exists
 | 
						|
		var parkedg *proc.G
 | 
						|
	LookForParkedG:
 | 
						|
		for {
 | 
						|
			err := grp.Continue()
 | 
						|
			if _, exited := err.(proc.ErrProcessExited); exited {
 | 
						|
				t.Log("could not find parked goroutine")
 | 
						|
				return
 | 
						|
			}
 | 
						|
			assertNoError(err, t, "Continue()")
 | 
						|
 | 
						|
			gs, _, err := proc.GoroutinesInfo(p, 0, 0)
 | 
						|
			assertNoError(err, t, "GoroutinesInfo()")
 | 
						|
 | 
						|
			for _, g := range gs {
 | 
						|
				if g.Thread == nil && g.CurrentLoc.Fn != nil && g.CurrentLoc.Fn.Name == "main.sayhi" {
 | 
						|
					parkedg = g
 | 
						|
					break LookForParkedG
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		t.Logf("Parked g is: %v\n", parkedg)
 | 
						|
		frames, _ := proc.GoroutineStacktrace(p, parkedg, 20, 0)
 | 
						|
		for _, frame := range frames {
 | 
						|
			name := ""
 | 
						|
			if frame.Call.Fn != nil {
 | 
						|
				name = frame.Call.Fn.Name
 | 
						|
			}
 | 
						|
			t.Logf("\t%s:%d in %s (%#x)", frame.Call.File, frame.Call.Line, name, frame.Current.PC)
 | 
						|
		}
 | 
						|
 | 
						|
		assertNoError(p.SwitchGoroutine(parkedg), t, "SwitchGoroutine()")
 | 
						|
		p.ClearBreakpoint(bp.Addr)
 | 
						|
		assertNoError(grp.Step(), t, "Step()")
 | 
						|
 | 
						|
		if p.SelectedGoroutine().ID != parkedg.ID {
 | 
						|
			t.Fatalf("Step did not continue on the selected goroutine, expected %d got %d", parkedg.ID, p.SelectedGoroutine().ID)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestUnsupportedArch(t *testing.T) {
 | 
						|
	ver, _ := goversion.Parse(runtime.Version())
 | 
						|
	if ver.Major < 0 || !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 6, Rev: -1}) || ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 7, Rev: -1}) {
 | 
						|
		// cross compile (with -N?) works only on select versions of go
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	fixturesDir := protest.FindFixturesDir()
 | 
						|
	infile := filepath.Join(fixturesDir, "math.go")
 | 
						|
	outfile := filepath.Join(fixturesDir, "_math_debug_386")
 | 
						|
 | 
						|
	cmd := exec.Command("go", "build", "-gcflags=-N -l", "-o", outfile, infile)
 | 
						|
	for _, v := range os.Environ() {
 | 
						|
		if !strings.HasPrefix(v, "GOARCH=") {
 | 
						|
			cmd.Env = append(cmd.Env, v)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	cmd.Env = append(cmd.Env, "GOARCH=386")
 | 
						|
	out, err := cmd.CombinedOutput()
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("go build failed: %v: %v", err, string(out))
 | 
						|
	}
 | 
						|
	defer os.Remove(outfile)
 | 
						|
 | 
						|
	var p *proc.TargetGroup
 | 
						|
 | 
						|
	switch testBackend {
 | 
						|
	case "native":
 | 
						|
		p, err = native.Launch([]string{outfile}, ".", 0, []string{}, "", "", proc.OutputRedirect{}, proc.OutputRedirect{})
 | 
						|
	case "lldb":
 | 
						|
		p, err = gdbserial.LLDBLaunch([]string{outfile}, ".", 0, []string{}, "", [3]string{})
 | 
						|
	default:
 | 
						|
		t.Skip("test not valid for this backend")
 | 
						|
	}
 | 
						|
 | 
						|
	if err == nil {
 | 
						|
		p.Detach(true)
 | 
						|
		t.Fatal("Launch is expected to fail, but succeeded")
 | 
						|
	}
 | 
						|
 | 
						|
	if _, ok := err.(*proc.ErrUnsupportedArch); ok {
 | 
						|
		// all good
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	t.Fatal(err)
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue573(t *testing.T) {
 | 
						|
	// calls to runtime.duffzero and runtime.duffcopy jump directly into the middle
 | 
						|
	// of the function and the internal breakpoint set by StepInto may be missed.
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("issue573", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFunctionBreakpoint(p, t, "main.foo")
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		assertNoError(grp.Step(), t, "Step() #1")
 | 
						|
		assertNoError(grp.Step(), t, "Step() #2") // Bug exits here.
 | 
						|
		assertNoError(grp.Step(), t, "Step() #3") // Third step ought to be possible; program ought not have exited.
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestTestvariables2Prologue(t *testing.T) {
 | 
						|
	withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		addrEntry := p.BinInfo().LookupFunc()["main.main"][0].Entry
 | 
						|
		addrPrologue := findFunctionLocation(p, t, "main.main")
 | 
						|
		if addrEntry == addrPrologue {
 | 
						|
			t.Fatalf("Prologue detection failed on testvariables2.go/main.main")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue561(t *testing.T) {
 | 
						|
	// Step fails to make progress when PC is at a CALL instruction
 | 
						|
	// where a breakpoint is also set.
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("issue561", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 10)
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		assertNoError(grp.Step(), t, "Step()")
 | 
						|
		assertLineNumber(p, t, 5, "wrong line number after Step,")
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestGoroutineLabels(t *testing.T) {
 | 
						|
	withTestProcess("goroutineLabels", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		g, err := proc.GetG(p.CurrentThread())
 | 
						|
		assertNoError(err, t, "GetG()")
 | 
						|
		if len(g.Labels()) != 0 {
 | 
						|
			t.Fatalf("No labels expected")
 | 
						|
		}
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		g, err = proc.GetG(p.CurrentThread())
 | 
						|
		assertNoError(err, t, "GetG()")
 | 
						|
		labels := g.Labels()
 | 
						|
		if v := labels["k1"]; v != "v1" {
 | 
						|
			t.Errorf("Unexpected label value k1=%v", v)
 | 
						|
		}
 | 
						|
		if v := labels["k2"]; v != "v2" {
 | 
						|
			t.Errorf("Unexpected label value k2=%v", v)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestStepOut(t *testing.T) {
 | 
						|
	testseq2(t, "testnextprog", "main.helloworld", []seqTest{{contContinue, 13}, {contStepout, 35}})
 | 
						|
}
 | 
						|
 | 
						|
func TestStepConcurrentDirect(t *testing.T) {
 | 
						|
	skipOn(t, "broken - step concurrent", "windows", "arm64")
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("teststepconcurrent", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		bp := setFileBreakpoint(p, t, fixture.Source, 37)
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		err := p.ClearBreakpoint(bp.Addr)
 | 
						|
		assertNoError(err, t, "ClearBreakpoint()")
 | 
						|
 | 
						|
		for _, b := range p.Breakpoints().M {
 | 
						|
			if b.Logical.Name == proc.UnrecoveredPanic {
 | 
						|
				err := p.ClearBreakpoint(b.Addr)
 | 
						|
				assertNoError(err, t, "ClearBreakpoint(unrecovered-panic)")
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		gid := p.SelectedGoroutine().ID
 | 
						|
 | 
						|
		seq := []int{37, 38, 13, 15, 16, 38}
 | 
						|
 | 
						|
		i := 0
 | 
						|
		count := 0
 | 
						|
		for {
 | 
						|
			anyerr := false
 | 
						|
			if p.SelectedGoroutine().ID != gid {
 | 
						|
				t.Errorf("Step switched to different goroutine %d %d\n", gid, p.SelectedGoroutine().ID)
 | 
						|
				anyerr = true
 | 
						|
			}
 | 
						|
			f, ln := currentLineNumber(p, t)
 | 
						|
			if ln != seq[i] {
 | 
						|
				if i == 1 && ln == 40 {
 | 
						|
					// loop exited
 | 
						|
					break
 | 
						|
				}
 | 
						|
				frames, err := proc.ThreadStacktrace(p, p.CurrentThread(), 20)
 | 
						|
				if err != nil {
 | 
						|
					t.Errorf("Could not get stacktrace of goroutine %d\n", p.SelectedGoroutine().ID)
 | 
						|
				} else {
 | 
						|
					t.Logf("Goroutine %d (thread: %d):", p.SelectedGoroutine().ID, p.CurrentThread().ThreadID())
 | 
						|
					for _, frame := range frames {
 | 
						|
						t.Logf("\t%s:%d (%#x)", frame.Call.File, frame.Call.Line, frame.Current.PC)
 | 
						|
					}
 | 
						|
				}
 | 
						|
				t.Errorf("Program did not continue at expected location (%d) %s:%d [i %d count %d]", seq[i], f, ln, i, count)
 | 
						|
				anyerr = true
 | 
						|
			}
 | 
						|
			if anyerr {
 | 
						|
				t.FailNow()
 | 
						|
			}
 | 
						|
			i = (i + 1) % len(seq)
 | 
						|
			if i == 0 {
 | 
						|
				count++
 | 
						|
			}
 | 
						|
			assertNoError(grp.Step(), t, "Step()")
 | 
						|
		}
 | 
						|
 | 
						|
		if count != 100 {
 | 
						|
			t.Fatalf("Program did not loop expected number of times: %d", count)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestStepConcurrentPtr(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("teststepconcurrent", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 24)
 | 
						|
 | 
						|
		for _, b := range p.Breakpoints().M {
 | 
						|
			if b.Logical.Name == proc.UnrecoveredPanic {
 | 
						|
				err := p.ClearBreakpoint(b.Addr)
 | 
						|
				assertNoError(err, t, "ClearBreakpoint(unrecovered-panic)")
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		kvals := map[int64]int64{}
 | 
						|
		count := 0
 | 
						|
		for {
 | 
						|
			err := grp.Continue()
 | 
						|
			_, exited := err.(proc.ErrProcessExited)
 | 
						|
			if exited {
 | 
						|
				break
 | 
						|
			}
 | 
						|
			assertNoError(err, t, "Continue()")
 | 
						|
 | 
						|
			f, ln := currentLineNumber(p, t)
 | 
						|
			if ln != 24 {
 | 
						|
				for _, th := range p.ThreadList() {
 | 
						|
					t.Logf("thread %d stopped on breakpoint %v", th.ThreadID(), th.Breakpoint())
 | 
						|
				}
 | 
						|
				curbp := p.CurrentThread().Breakpoint()
 | 
						|
				t.Fatalf("Program did not continue at expected location (24): %s:%d %#x [%v] (gid %d count %d)", f, ln, currentPC(p, t), curbp, p.SelectedGoroutine().ID, count)
 | 
						|
			}
 | 
						|
 | 
						|
			gid := p.SelectedGoroutine().ID
 | 
						|
 | 
						|
			kvar := evalVariable(p, t, "k")
 | 
						|
			k, _ := constant.Int64Val(kvar.Value)
 | 
						|
 | 
						|
			if oldk, ok := kvals[gid]; ok {
 | 
						|
				if oldk >= k {
 | 
						|
					t.Fatalf("Goroutine %d did not make progress?", gid)
 | 
						|
				}
 | 
						|
			}
 | 
						|
			kvals[gid] = k
 | 
						|
 | 
						|
			assertNoError(grp.Step(), t, "Step()")
 | 
						|
			for p.Breakpoints().HasSteppingBreakpoints() {
 | 
						|
				if p.SelectedGoroutine().ID == gid {
 | 
						|
					t.Fatalf("step did not step into function call (but internal breakpoints still active?) (%d %d)", gid, p.SelectedGoroutine().ID)
 | 
						|
				}
 | 
						|
				assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
			}
 | 
						|
 | 
						|
			if p.SelectedGoroutine().ID != gid {
 | 
						|
				t.Fatalf("Step switched goroutines (wanted: %d got: %d)", gid, p.SelectedGoroutine().ID)
 | 
						|
			}
 | 
						|
 | 
						|
			f, ln = assertLineNumber(p, t, 13, "Step did not step into function call")
 | 
						|
 | 
						|
			count++
 | 
						|
			if count > 50 {
 | 
						|
				// this test could potentially go on for 10000 cycles, since that's
 | 
						|
				// too slow we cut the execution after 50 cycles
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if count == 0 {
 | 
						|
			t.Fatalf("Breakpoint never hit")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestStepOutBreakpoint(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("testnextprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		bp := setFileBreakpoint(p, t, fixture.Source, 13)
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		p.ClearBreakpoint(bp.Addr)
 | 
						|
 | 
						|
		// StepOut should be interrupted by a breakpoint on the same goroutine.
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 14)
 | 
						|
		assertNoError(grp.StepOut(), t, "StepOut()")
 | 
						|
		assertLineNumber(p, t, 14, "wrong line number")
 | 
						|
		if p.Breakpoints().HasSteppingBreakpoints() {
 | 
						|
			t.Fatal("has internal breakpoints after hitting breakpoint on same goroutine")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestNextBreakpoint(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("testnextprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		bp := setFileBreakpoint(p, t, fixture.Source, 34)
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		p.ClearBreakpoint(bp.Addr)
 | 
						|
 | 
						|
		// Next should be interrupted by a breakpoint on the same goroutine.
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 14)
 | 
						|
		assertNoError(grp.Next(), t, "Next()")
 | 
						|
		assertLineNumber(p, t, 14, "wrong line number")
 | 
						|
		if p.Breakpoints().HasSteppingBreakpoints() {
 | 
						|
			t.Fatal("has internal breakpoints after hitting breakpoint on same goroutine")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestNextBreakpointKeepsSteppingBreakpoints(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("testnextprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		grp.KeepSteppingBreakpoints = proc.TracepointKeepsSteppingBreakpoints
 | 
						|
		bp := setFileBreakpoint(p, t, fixture.Source, 34)
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		p.ClearBreakpoint(bp.Addr)
 | 
						|
 | 
						|
		// Next should be interrupted by a tracepoint on the same goroutine.
 | 
						|
		bp = setFileBreakpoint(p, t, fixture.Source, 14)
 | 
						|
		bp.Logical.Tracepoint = true
 | 
						|
		assertNoError(grp.Next(), t, "Next()")
 | 
						|
		assertLineNumber(p, t, 14, "wrong line number")
 | 
						|
		if !p.Breakpoints().HasSteppingBreakpoints() {
 | 
						|
			t.Fatal("does not have internal breakpoints after hitting tracepoint on same goroutine")
 | 
						|
		}
 | 
						|
 | 
						|
		// Continue to complete next.
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		assertLineNumber(p, t, 35, "wrong line number")
 | 
						|
		if p.Breakpoints().HasSteppingBreakpoints() {
 | 
						|
			t.Fatal("has internal breakpoints after completing next")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestStepOutDefer(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("testnextdefer", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		bp := setFileBreakpoint(p, t, fixture.Source, 9)
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		p.ClearBreakpoint(bp.Addr)
 | 
						|
 | 
						|
		assertLineNumber(p, t, 9, "wrong line number")
 | 
						|
 | 
						|
		assertNoError(grp.StepOut(), t, "StepOut()")
 | 
						|
 | 
						|
		f, l, _ := p.BinInfo().PCToLine(currentPC(p, t))
 | 
						|
		if f == fixture.Source || l == 6 {
 | 
						|
			t.Fatalf("wrong location %s:%d, expected to end somewhere in runtime", f, l)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestStepOutDeferReturnAndDirectCall(t *testing.T) {
 | 
						|
	// StepOut should not step into a deferred function if it is called
 | 
						|
	// directly, only if it is called through a panic.
 | 
						|
	// Here we test the case where the function is called by a deferreturn
 | 
						|
	testseq2(t, "defercall", "", []seqTest{
 | 
						|
		{contContinue, 11},
 | 
						|
		{contStepout, 28}})
 | 
						|
}
 | 
						|
 | 
						|
func TestStepOnCallPtrInstr(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("teststepprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 10)
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
 | 
						|
		found := false
 | 
						|
 | 
						|
		for {
 | 
						|
			_, ln := currentLineNumber(p, t)
 | 
						|
			if ln != 10 {
 | 
						|
				break
 | 
						|
			}
 | 
						|
			regs, err := p.CurrentThread().Registers()
 | 
						|
			assertNoError(err, t, "Registers()")
 | 
						|
			pc := regs.PC()
 | 
						|
			text, err := proc.Disassemble(p.Memory(), regs, p.Breakpoints(), p.BinInfo(), pc, pc+uint64(p.BinInfo().Arch.MaxInstructionLength()))
 | 
						|
			assertNoError(err, t, "Disassemble()")
 | 
						|
			if text[0].IsCall() {
 | 
						|
				found = true
 | 
						|
				break
 | 
						|
			}
 | 
						|
			assertNoError(grp.StepInstruction(false), t, "StepInstruction()")
 | 
						|
		}
 | 
						|
 | 
						|
		if !found {
 | 
						|
			t.Fatal("Could not find CALL instruction")
 | 
						|
		}
 | 
						|
 | 
						|
		assertNoError(grp.Step(), t, "Step()")
 | 
						|
 | 
						|
		if goversion.VersionAfterOrEqual(runtime.Version(), 1, 11) && !protest.RegabiSupported() {
 | 
						|
			assertLineNumber(p, t, 6, "Step continued to wrong line,")
 | 
						|
		} else {
 | 
						|
			assertLineNumber(p, t, 5, "Step continued to wrong line,")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue594(t *testing.T) {
 | 
						|
	skipOn(t, "upstream issue", "darwin", "lldb")
 | 
						|
	// debugserver will receive an EXC_BAD_ACCESS for this, at that point
 | 
						|
	// there is no way to reconvert this exception into a unix signal and send
 | 
						|
	// it to the process.
 | 
						|
	// This is a bug in debugserver/lldb:
 | 
						|
	//  https://bugs.llvm.org//show_bug.cgi?id=22868
 | 
						|
 | 
						|
	// Exceptions that aren't caused by breakpoints should be propagated
 | 
						|
	// back to the target.
 | 
						|
	// In particular the target should be able to cause a nil pointer
 | 
						|
	// dereference panic and recover from it.
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("issue594", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		var f string
 | 
						|
		var ln int
 | 
						|
		if testBackend == "rr" {
 | 
						|
			frame, err := findFirstNonRuntimeFrame(p)
 | 
						|
			assertNoError(err, t, "findFirstNonRuntimeFrame")
 | 
						|
			f, ln = frame.Current.File, frame.Current.Line
 | 
						|
		} else {
 | 
						|
			f, ln = currentLineNumber(p, t)
 | 
						|
		}
 | 
						|
		if ln != 21 {
 | 
						|
			t.Fatalf("Program stopped at %s:%d, expected :21", f, ln)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestStepOutPanicAndDirectCall(t *testing.T) {
 | 
						|
	// StepOut should not step into a deferred function if it is called
 | 
						|
	// directly, only if it is called through a panic.
 | 
						|
	// Here we test the case where the function is called by a panic
 | 
						|
	testseq2(t, "defercall", "", []seqTest{
 | 
						|
		{contContinue, 17},
 | 
						|
		{contStepout, 6}})
 | 
						|
}
 | 
						|
 | 
						|
func TestWorkDir(t *testing.T) {
 | 
						|
	wd := os.TempDir()
 | 
						|
	// For Darwin `os.TempDir()` returns `/tmp` which is symlink to `/private/tmp`.
 | 
						|
	if runtime.GOOS == "darwin" {
 | 
						|
		wd = "/private/tmp"
 | 
						|
	}
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcessArgs("workdir", t, wd, []string{}, 0, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 14)
 | 
						|
		grp.Continue()
 | 
						|
		v := evalVariable(p, t, "pwd")
 | 
						|
		str := constant.StringVal(v.Value)
 | 
						|
		if wd != str {
 | 
						|
			t.Fatalf("Expected %s got %s\n", wd, str)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestNegativeIntEvaluation(t *testing.T) {
 | 
						|
	testcases := []struct {
 | 
						|
		name  string
 | 
						|
		typ   string
 | 
						|
		value any
 | 
						|
	}{
 | 
						|
		{"ni8", "int8", int64(-5)},
 | 
						|
		{"ni16", "int16", int64(-5)},
 | 
						|
		{"ni32", "int32", int64(-5)},
 | 
						|
	}
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		for _, tc := range testcases {
 | 
						|
			v := evalVariable(p, t, tc.name)
 | 
						|
			if typ := v.RealType.String(); typ != tc.typ {
 | 
						|
				t.Fatalf("Wrong type for variable %q: %q (expected: %q)", tc.name, typ, tc.typ)
 | 
						|
			}
 | 
						|
			if val, _ := constant.Int64Val(v.Value); val != tc.value {
 | 
						|
				t.Fatalf("Wrong value for variable %q: %v (expected: %v)", tc.name, val, tc.value)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue683(t *testing.T) {
 | 
						|
	// Step panics when source file can not be found
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("issue683", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFunctionBreakpoint(p, t, "main.main")
 | 
						|
		assertNoError(grp.Continue(), t, "First Continue()")
 | 
						|
		for range 20 {
 | 
						|
			// eventually an error about the source file not being found will be
 | 
						|
			// returned, the important thing is that we shouldn't panic
 | 
						|
			err := grp.Step()
 | 
						|
			if err != nil {
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue664(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("issue664", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 4)
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		assertNoError(grp.Next(), t, "Next()")
 | 
						|
		assertLineNumber(p, t, 5, "Did not continue to correct location,")
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
// Benchmarks (*Process).Continue + (*Scope).FunctionArguments
 | 
						|
func BenchmarkTrace(b *testing.B) {
 | 
						|
	withTestProcess("traceperf", b, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFunctionBreakpoint(p, b, "main.PerfCheck")
 | 
						|
		b.ResetTimer()
 | 
						|
		for i := 0; i < b.N; i++ {
 | 
						|
			assertNoError(grp.Continue(), b, "Continue()")
 | 
						|
			s, err := proc.GoroutineScope(p, p.CurrentThread())
 | 
						|
			assertNoError(err, b, "Scope()")
 | 
						|
			_, err = s.FunctionArguments(proc.LoadConfig{false, 0, 64, 0, 3, 0})
 | 
						|
			assertNoError(err, b, "FunctionArguments()")
 | 
						|
		}
 | 
						|
		b.StopTimer()
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestNextInDeferReturn(t *testing.T) {
 | 
						|
	// runtime.deferreturn updates the G struct in a way that for one
 | 
						|
	// instruction leaves the curg._defer field non-nil but with curg._defer.fn
 | 
						|
	// field being nil.
 | 
						|
	// We need to deal with this without panicking.
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("defercall", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFunctionBreakpoint(p, t, "runtime.deferreturn")
 | 
						|
		assertNoError(grp.Continue(), t, "First Continue()")
 | 
						|
 | 
						|
		// Set a breakpoint on the deferred function so that the following loop
 | 
						|
		// can not step out of the runtime.deferreturn and all the way to the
 | 
						|
		// point where the target program panics.
 | 
						|
		setFunctionBreakpoint(p, t, "main.sampleFunction")
 | 
						|
		for i := range 20 {
 | 
						|
			loc, err := proc.ThreadLocation(p.CurrentThread())
 | 
						|
			assertNoError(err, t, "CurrentThread().Location()")
 | 
						|
			t.Logf("at %#x %s:%d", loc.PC, loc.File, loc.Line)
 | 
						|
			if loc.Fn != nil && loc.Fn.Name == "main.sampleFunction" {
 | 
						|
				break
 | 
						|
			}
 | 
						|
			assertNoError(grp.Next(), t, fmt.Sprintf("Next() %d", i))
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestAttachDetach(t *testing.T) {
 | 
						|
	if testBackend == "lldb" && runtime.GOOS == "linux" {
 | 
						|
		bs, _ := os.ReadFile("/proc/sys/kernel/yama/ptrace_scope")
 | 
						|
		if bs == nil || strings.TrimSpace(string(bs)) != "0" {
 | 
						|
			t.Logf("can not run TestAttachDetach: %v\n", bs)
 | 
						|
			return
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if testBackend == "rr" {
 | 
						|
		return
 | 
						|
	}
 | 
						|
	var buildFlags protest.BuildFlags
 | 
						|
	if buildMode == "pie" {
 | 
						|
		buildFlags |= protest.BuildModePIE
 | 
						|
	}
 | 
						|
	fixture := protest.BuildFixture(t, "testnextnethttp", buildFlags)
 | 
						|
	cmd := exec.Command(fixture.Path)
 | 
						|
	cmd.Stdout = os.Stdout
 | 
						|
	cmd.Stderr = os.Stderr
 | 
						|
	assertNoError(cmd.Start(), t, "starting fixture")
 | 
						|
 | 
						|
	// wait for testnextnethttp to start listening
 | 
						|
	t0 := time.Now()
 | 
						|
	for {
 | 
						|
		conn, err := net.Dial("tcp", "127.0.0.1:9191")
 | 
						|
		if err == nil {
 | 
						|
			conn.Close()
 | 
						|
			break
 | 
						|
		}
 | 
						|
		time.Sleep(50 * time.Millisecond)
 | 
						|
		if time.Since(t0) > 10*time.Second {
 | 
						|
			t.Fatal("fixture did not start")
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	var p *proc.TargetGroup
 | 
						|
	var err error
 | 
						|
 | 
						|
	switch testBackend {
 | 
						|
	case "native":
 | 
						|
		p, err = native.Attach(cmd.Process.Pid, nil, []string{})
 | 
						|
	case "lldb":
 | 
						|
		path := ""
 | 
						|
		if runtime.GOOS == "darwin" {
 | 
						|
			path = fixture.Path
 | 
						|
		}
 | 
						|
		p, err = gdbserial.LLDBAttach(cmd.Process.Pid, path, nil, []string{})
 | 
						|
	default:
 | 
						|
		err = fmt.Errorf("unknown backend %q", testBackend)
 | 
						|
	}
 | 
						|
 | 
						|
	assertNoError(err, t, "Attach")
 | 
						|
	go func() {
 | 
						|
		time.Sleep(1 * time.Second)
 | 
						|
		resp, err := http.Get("http://127.0.0.1:9191")
 | 
						|
		if err == nil {
 | 
						|
			resp.Body.Close()
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	assertNoError(p.Continue(), t, "Continue")
 | 
						|
	assertLineNumber(p.Selected, t, 11, "Did not continue to correct location,")
 | 
						|
 | 
						|
	assertNoError(p.Detach(false), t, "Detach")
 | 
						|
 | 
						|
	if runtime.GOOS != "darwin" {
 | 
						|
		// Debugserver sometimes will leave a zombie process after detaching, this
 | 
						|
		// seems to be a bug with debugserver.
 | 
						|
		resp, err := http.Get("http://127.0.0.1:9191/nobp")
 | 
						|
		assertNoError(err, t, "Page request after detach")
 | 
						|
		bs, err := io.ReadAll(resp.Body)
 | 
						|
		assertNoError(err, t, "Reading /nobp page")
 | 
						|
		defer resp.Body.Close()
 | 
						|
		if out := string(bs); !strings.Contains(out, "hello, world!") {
 | 
						|
			t.Fatalf("/nobp page does not contain \"hello, world!\": %q", out)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	cmd.Process.Kill()
 | 
						|
}
 | 
						|
 | 
						|
func TestVarSum(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		sumvar := evalVariable(p, t, "s1[0] + s1[1]")
 | 
						|
		sumvarstr := constant.StringVal(sumvar.Value)
 | 
						|
		if sumvarstr != "onetwo" {
 | 
						|
			t.Fatalf("s1[0] + s1[1] == %q (expected \"onetwo\")", sumvarstr)
 | 
						|
		}
 | 
						|
		if sumvar.Len != int64(len(sumvarstr)) {
 | 
						|
			t.Fatalf("sumvar.Len == %d (expected %d)", sumvar.Len, len(sumvarstr))
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestPackageWithPathVar(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("pkgrenames", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		evalVariable(p, t, "pkg.SomeVar")
 | 
						|
		evalVariable(p, t, "pkg.SomeVar.X")
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestEnvironment(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	t.Setenv("SOMEVAR", "bah")
 | 
						|
	withTestProcess("testenv", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		v := evalVariable(p, t, "x")
 | 
						|
		vv := constant.StringVal(v.Value)
 | 
						|
		t.Logf("v = %q", vv)
 | 
						|
		if vv != "bah" {
 | 
						|
			t.Fatalf("value of v is %q (expected \"bah\")", vv)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func getFrameOff(p *proc.Target, t *testing.T) int64 {
 | 
						|
	frameoffvar := evalVariable(p, t, "runtime.frameoff")
 | 
						|
	frameoff, _ := constant.Int64Val(frameoffvar.Value)
 | 
						|
	return frameoff
 | 
						|
}
 | 
						|
 | 
						|
func TestRecursiveNext(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	testcases := []nextTest{
 | 
						|
		{6, 7},
 | 
						|
		{7, 10},
 | 
						|
		{10, 11},
 | 
						|
		{11, 17},
 | 
						|
	}
 | 
						|
	testseq("increment", contNext, testcases, "main.Increment", t)
 | 
						|
 | 
						|
	withTestProcess("increment", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		bp := setFunctionBreakpoint(p, t, "main.Increment")
 | 
						|
		assertNoError(grp.Continue(), t, "Continue")
 | 
						|
		err := p.ClearBreakpoint(bp.Addr)
 | 
						|
		assertNoError(err, t, "ClearBreakpoint")
 | 
						|
		assertNoError(grp.Next(), t, "Next 1")
 | 
						|
		assertNoError(grp.Next(), t, "Next 2")
 | 
						|
		assertNoError(grp.Next(), t, "Next 3")
 | 
						|
		frameoff0 := getFrameOff(p, t)
 | 
						|
		assertNoError(grp.Step(), t, "Step")
 | 
						|
		frameoff1 := getFrameOff(p, t)
 | 
						|
		if frameoff0 == frameoff1 {
 | 
						|
			t.Fatalf("did not step into function?")
 | 
						|
		}
 | 
						|
		assertLineNumber(p, t, 6, "program did not continue to expected location,")
 | 
						|
		assertNoError(grp.Next(), t, "Next 4")
 | 
						|
		assertLineNumber(p, t, 7, "program did not continue to expected location,")
 | 
						|
		assertNoError(grp.StepOut(), t, "StepOut")
 | 
						|
		assertLineNumber(p, t, 11, "program did not continue to expected location,")
 | 
						|
		frameoff2 := getFrameOff(p, t)
 | 
						|
		if frameoff0 != frameoff2 {
 | 
						|
			t.Fatalf("frame offset mismatch %x != %x", frameoff0, frameoff2)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
// TestIssue877 ensures that the environment variables starting with DYLD_ and LD_
 | 
						|
// are passed when executing the binary on OSX via debugserver
 | 
						|
func TestIssue877(t *testing.T) {
 | 
						|
	if runtime.GOOS != "darwin" && testBackend == "lldb" {
 | 
						|
		return
 | 
						|
	}
 | 
						|
	const envval = "/usr/local/lib"
 | 
						|
	t.Setenv("DYLD_LIBRARY_PATH", envval)
 | 
						|
	withTestProcess("issue877", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		v := evalVariable(p, t, "dyldenv")
 | 
						|
		vv := constant.StringVal(v.Value)
 | 
						|
		t.Logf("v = %q", vv)
 | 
						|
		if vv != envval {
 | 
						|
			t.Fatalf("value of v is %q (expected %q)", vv, envval)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue893(t *testing.T) {
 | 
						|
	// Test what happens when next is called immediately after launching the
 | 
						|
	// executable, acceptable behaviors are: (a) no error, (b) no source at PC
 | 
						|
	// error, (c) program runs to completion
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("increment", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		err := grp.Next()
 | 
						|
		if err == nil {
 | 
						|
			return
 | 
						|
		}
 | 
						|
		if _, ok := err.(*frame.ErrNoFDEForPC); ok {
 | 
						|
			return
 | 
						|
		}
 | 
						|
		if _, ok := err.(*proc.ErrNoSourceForPC); ok {
 | 
						|
			return
 | 
						|
		}
 | 
						|
		if _, ok := err.(proc.ErrProcessExited); ok {
 | 
						|
			return
 | 
						|
		}
 | 
						|
		assertNoError(err, t, "Next")
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestStepInstructionNoGoroutine(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("increment", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		// Call StepInstruction immediately after launching the program, it should
 | 
						|
		// work even though no goroutine is selected.
 | 
						|
		assertNoError(grp.StepInstruction(false), t, "StepInstruction")
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue871(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcess("issue871", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), t, "Continue")
 | 
						|
 | 
						|
		var scope *proc.EvalScope
 | 
						|
		var err error
 | 
						|
		if testBackend == "rr" {
 | 
						|
			var frame proc.Stackframe
 | 
						|
			frame, err = findFirstNonRuntimeFrame(p)
 | 
						|
			if err == nil {
 | 
						|
				scope = proc.FrameToScope(p, p.Memory(), nil, 0, frame)
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			scope, err = proc.GoroutineScope(p, p.CurrentThread())
 | 
						|
		}
 | 
						|
		assertNoError(err, t, "scope")
 | 
						|
 | 
						|
		locals, err := scope.LocalVariables(normalLoadConfig)
 | 
						|
		assertNoError(err, t, "LocalVariables")
 | 
						|
 | 
						|
		foundA, foundB := false, false
 | 
						|
 | 
						|
		for _, v := range locals {
 | 
						|
			t.Logf("local %v", v)
 | 
						|
			switch v.Name {
 | 
						|
			case "a":
 | 
						|
				foundA = true
 | 
						|
				if v.Flags&proc.VariableEscaped == 0 {
 | 
						|
					t.Errorf("variable a not flagged as escaped")
 | 
						|
				}
 | 
						|
			case "b":
 | 
						|
				foundB = true
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if !foundA {
 | 
						|
			t.Errorf("variable a not found")
 | 
						|
		}
 | 
						|
 | 
						|
		if !foundB {
 | 
						|
			t.Errorf("variable b not found")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestShadowedFlag(t *testing.T) {
 | 
						|
	if ver, _ := goversion.Parse(runtime.Version()); ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 9, Rev: -1}) {
 | 
						|
		return
 | 
						|
	}
 | 
						|
	withTestProcess("testshadow", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), t, "Continue")
 | 
						|
		scope, err := proc.GoroutineScope(p, p.CurrentThread())
 | 
						|
		assertNoError(err, t, "GoroutineScope")
 | 
						|
		locals, err := scope.LocalVariables(normalLoadConfig)
 | 
						|
		assertNoError(err, t, "LocalVariables")
 | 
						|
		foundShadowed := false
 | 
						|
		foundNonShadowed := false
 | 
						|
		for _, v := range locals {
 | 
						|
			if v.Flags&proc.VariableShadowed != 0 {
 | 
						|
				if v.Name != "a" {
 | 
						|
					t.Errorf("wrong shadowed variable %s", v.Name)
 | 
						|
				}
 | 
						|
				foundShadowed = true
 | 
						|
				if n, _ := constant.Int64Val(v.Value); n != 0 {
 | 
						|
					t.Errorf("wrong value for shadowed variable a: %d", n)
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				if v.Name != "a" {
 | 
						|
					t.Errorf("wrong non-shadowed variable %s", v.Name)
 | 
						|
				}
 | 
						|
				foundNonShadowed = true
 | 
						|
				if n, _ := constant.Int64Val(v.Value); n != 1 {
 | 
						|
					t.Errorf("wrong value for non-shadowed variable a: %d", n)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if !foundShadowed {
 | 
						|
			t.Error("could not find any shadowed variable")
 | 
						|
		}
 | 
						|
		if !foundNonShadowed {
 | 
						|
			t.Error("could not find any non-shadowed variable")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestDebugStripped(t *testing.T) {
 | 
						|
	// Currently only implemented for Linux ELF and macOS Mach-O executables.
 | 
						|
	// TODO(derekparker): Add support for PE.
 | 
						|
	skipOn(t, "not working on windows", "windows")
 | 
						|
	skipOn(t, "not working on freebsd", "freebsd")
 | 
						|
	skipOn(t, "not working on linux/386", "linux", "386")
 | 
						|
	skipOn(t, "not working on linux/ppc64le when -gcflags=-N -l is passed", "linux", "ppc64le")
 | 
						|
	ver, _ := goversion.Parse(runtime.Version())
 | 
						|
	if ver.IsDevelBuild() {
 | 
						|
		t.Skip("not supported")
 | 
						|
	}
 | 
						|
	withTestProcessArgs("testnextprog", t, "", []string{}, protest.LinkStrip, func(p *proc.Target, grp *proc.TargetGroup, f protest.Fixture) {
 | 
						|
		setFunctionBreakpoint(p, t, "main.main")
 | 
						|
 | 
						|
		loc, err := proc.FindFunctionLocation(p, "main.main", 0)
 | 
						|
		assertNoError(err, t, "main.main")
 | 
						|
		filename, lineno, _ := p.BinInfo().PCToLine(loc[0])
 | 
						|
		_, err = proc.FindFileLocation(p, filename, lineno)
 | 
						|
		t.Logf("stripped breakpoint by line %s:%d: %v", filename, lineno, err)
 | 
						|
		if !strings.Contains(err.Error(), "binary is stripped") {
 | 
						|
			t.Errorf("wrong error, expected reference to binary being stripped")
 | 
						|
		}
 | 
						|
 | 
						|
		_, err = proc.FindFunctionLocation(p, "main.main", 1)
 | 
						|
		t.Logf("stripped breakpoint by function and line main.main:1: %v", err)
 | 
						|
		if !strings.Contains(err.Error(), "binary is stripped") {
 | 
						|
			t.Errorf("wrong error, expected reference to binary being stripped")
 | 
						|
		}
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue")
 | 
						|
		assertCurrentLocationFunction(p, t, "main.main")
 | 
						|
		assertLineNumber(p, t, 37, "first continue")
 | 
						|
		assertNoError(grp.Next(), t, "Next")
 | 
						|
		assertLineNumber(p, t, 38, "after next")
 | 
						|
 | 
						|
		// Assert that commands like "args", "locals", etc... will
 | 
						|
		// return an error instead of panic.
 | 
						|
		s, err := proc.ThreadScope(p, p.CurrentThread())
 | 
						|
		assertNoError(err, t, "ThreadScope")
 | 
						|
		_, err = s.Locals(0, "")
 | 
						|
		if err == nil {
 | 
						|
			t.Error("expected an error to be returned from scope.Locals in stripped binary")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestDebugStripped2(t *testing.T) {
 | 
						|
	// TODO(derekparker): Add support for PE.
 | 
						|
	skipOn(t, "not working on windows", "windows")
 | 
						|
	skipOn(t, "not working on freebsd", "freebsd")
 | 
						|
	skipOn(t, "not working on linux/386", "linux", "386")
 | 
						|
	skipOn(t, "not working on linux/ppc64le when -gcflags=-N -l is passed", "linux", "ppc64le")
 | 
						|
	skipOn(t, "not working on linux/riscv64", "linux", "riscv64")
 | 
						|
	skipOn(t, "not working on linux/loong64", "linux", "loong64")
 | 
						|
	ver, _ := goversion.Parse(runtime.Version())
 | 
						|
	if ver.IsDevelBuild() {
 | 
						|
		t.Skip("not supported")
 | 
						|
	}
 | 
						|
	if ver.Major > 0 && ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 22, Rev: -1}) {
 | 
						|
		skipOn(t, "not working on linux/arm64 with Go 1.22", "linux", "arm64")
 | 
						|
	}
 | 
						|
	withTestProcessArgs("inlinestripped", t, "", []string{}, protest.EnableInlining|protest.LinkStrip|protest.LinkDisableDWARF, func(p *proc.Target, grp *proc.TargetGroup, f protest.Fixture) {
 | 
						|
		setFunctionBreakpointAll(p, t, "fmt.Println")
 | 
						|
 | 
						|
		for i, line := range []int{12, 13, 14} {
 | 
						|
			assertNoError(grp.Continue(), t, "Continue")
 | 
						|
			assertCurrentLocationFunction(p, t, "main.main")
 | 
						|
			assertLineNumber(p, t, line, fmt.Sprintf("continue %d", i))
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue844(t *testing.T) {
 | 
						|
	// Conditional breakpoints should not prevent next from working if their
 | 
						|
	// condition isn't met.
 | 
						|
	withTestProcess("nextcond", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 9)
 | 
						|
		condbp := setFileBreakpoint(p, t, fixture.Source, 10)
 | 
						|
		condbp.UserBreaklet().Cond = &ast.BinaryExpr{
 | 
						|
			Op: token.EQL,
 | 
						|
			X:  &ast.Ident{Name: "n"},
 | 
						|
			Y:  &ast.BasicLit{Kind: token.INT, Value: "11"},
 | 
						|
		}
 | 
						|
		assertNoError(grp.Continue(), t, "Continue")
 | 
						|
		assertNoError(grp.Next(), t, "Next")
 | 
						|
		assertLineNumber(p, t, 10, "continued to wrong location,")
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func logStacktrace(t *testing.T, p *proc.Target, frames []proc.Stackframe) {
 | 
						|
	w := tabwriter.NewWriter(os.Stderr, 0, 0, 3, ' ', 0)
 | 
						|
	fmt.Fprintf(w, "\n%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t\n", "Call PC", "Frame Offset", "Frame Pointer Offset", "PC", "Return", "Function", "Location", "Top Defer", "Defers")
 | 
						|
	for j := range frames {
 | 
						|
		name := "?"
 | 
						|
		if frames[j].Current.Fn != nil {
 | 
						|
			name = frames[j].Current.Fn.Name
 | 
						|
		}
 | 
						|
		if frames[j].Call.Fn != nil && frames[j].Current.Fn != frames[j].Call.Fn {
 | 
						|
			name = fmt.Sprintf("%s inlined in %s", frames[j].Call.Fn.Name, frames[j].Current.Fn.Name)
 | 
						|
		}
 | 
						|
 | 
						|
		topmostdefer := ""
 | 
						|
		if frames[j].TopmostDefer != nil {
 | 
						|
			_, _, fn := frames[j].TopmostDefer.DeferredFunc(p)
 | 
						|
			fnname := ""
 | 
						|
			if fn != nil {
 | 
						|
				fnname = fn.Name
 | 
						|
			}
 | 
						|
			topmostdefer = fmt.Sprintf("%#x %s", frames[j].TopmostDefer.DwrapPC, fnname)
 | 
						|
		}
 | 
						|
 | 
						|
		defers := ""
 | 
						|
		for deferIdx, _defer := range frames[j].Defers {
 | 
						|
			_, _, fn := _defer.DeferredFunc(p)
 | 
						|
			fnname := ""
 | 
						|
			if fn != nil {
 | 
						|
				fnname = fn.Name
 | 
						|
			}
 | 
						|
			defers += fmt.Sprintf("%d %#x %s |", deferIdx, _defer.DwrapPC, fnname)
 | 
						|
		}
 | 
						|
 | 
						|
		frame := frames[j]
 | 
						|
		fmt.Fprintf(w, "%#x\t%#x\t%#x\t%#x\t%#x\t%s\t%s:%d\t%s\t%s\t\n",
 | 
						|
			frame.Call.PC, frame.FrameOffset(), frame.FramePointerOffset(), frame.Current.PC, frame.Ret,
 | 
						|
			name, filepath.Base(frame.Call.File), frame.Call.Line, topmostdefer, defers)
 | 
						|
	}
 | 
						|
	w.Flush()
 | 
						|
}
 | 
						|
 | 
						|
// stacktraceCheck checks that all the functions listed in tc appear in
 | 
						|
// frames in the same order.
 | 
						|
// Checks that all the functions in tc starting with "C." or with "!" are in
 | 
						|
// a systemstack frame.
 | 
						|
// Returns a slice m where m[i] is the index in frames of the function tc[i]
 | 
						|
// or nil if any check fails.
 | 
						|
func stacktraceCheck(t *testing.T, tc []string, frames []proc.Stackframe) []int {
 | 
						|
	m := make([]int, len(tc))
 | 
						|
	i, j := 0, 0
 | 
						|
	for i < len(tc) {
 | 
						|
		tcname := tc[i]
 | 
						|
		tcsystem := strings.HasPrefix(tcname, "C.")
 | 
						|
		if tcname[0] == '!' {
 | 
						|
			tcsystem = true
 | 
						|
			tcname = tcname[1:]
 | 
						|
		}
 | 
						|
		for j < len(frames) {
 | 
						|
			name := "?"
 | 
						|
			if frames[j].Current.Fn != nil {
 | 
						|
				name = frames[j].Current.Fn.Name
 | 
						|
			}
 | 
						|
			if name == tcname {
 | 
						|
				m[i] = j
 | 
						|
				if tcsystem != frames[j].SystemStack {
 | 
						|
					t.Logf("system stack check failed for frame %d (expected %v got %v)", j, tcsystem, frames[j].SystemStack)
 | 
						|
					t.Logf("expected: %v\n", tc)
 | 
						|
					return nil
 | 
						|
				}
 | 
						|
				break
 | 
						|
			}
 | 
						|
 | 
						|
			j++
 | 
						|
		}
 | 
						|
		if j >= len(frames) {
 | 
						|
			t.Logf("couldn't find frame %d %s", i, tc)
 | 
						|
			t.Logf("expected: %v\n", tc)
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
 | 
						|
		i++
 | 
						|
	}
 | 
						|
	return m
 | 
						|
}
 | 
						|
 | 
						|
func frameInFile(frame proc.Stackframe, file string) bool {
 | 
						|
	for _, loc := range []proc.Location{frame.Current, frame.Call} {
 | 
						|
		if !strings.HasSuffix(loc.File, file) && !strings.HasSuffix(loc.File, "/"+file) && !strings.HasSuffix(loc.File, "\\"+file) {
 | 
						|
			return false
 | 
						|
		}
 | 
						|
		if loc.Line <= 0 {
 | 
						|
			return false
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return true
 | 
						|
}
 | 
						|
 | 
						|
func TestCgoStacktrace(t *testing.T) {
 | 
						|
	if runtime.GOOS == "windows" {
 | 
						|
		ver, _ := goversion.Parse(runtime.Version())
 | 
						|
		if ver.Major > 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 9, Rev: -1}) {
 | 
						|
			t.Skip("disabled on windows with go before version 1.9")
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if runtime.GOOS == "darwin" {
 | 
						|
		ver, _ := goversion.Parse(runtime.Version())
 | 
						|
		if ver.Major > 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 8, Rev: -1}) {
 | 
						|
			t.Skip("disabled on macOS with go before version 1.8")
 | 
						|
		}
 | 
						|
	}
 | 
						|
	skipOn(t, "broken - cgo stacktraces", "386")
 | 
						|
	skipOn(t, "broken - cgo stacktraces", "windows", "arm64")
 | 
						|
	skipOn(t, "broken - cgo stacktraces", "linux", "ppc64le")
 | 
						|
	skipOn(t, "broken - cgo stacktraces", "linux", "riscv64")
 | 
						|
	if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 21) {
 | 
						|
		skipOn(t, "broken - cgo stacktraces", "windows", "arm64")
 | 
						|
	}
 | 
						|
	protest.MustHaveCgo(t)
 | 
						|
 | 
						|
	// Tests that:
 | 
						|
	// a) we correctly identify the goroutine while we are executing cgo code
 | 
						|
	// b) that we can stitch together the system stack (where cgo code
 | 
						|
	// executes) and the normal goroutine stack
 | 
						|
 | 
						|
	// Each test case describes how the stack trace should appear after a
 | 
						|
	// continue. The first function on each test case is the topmost function
 | 
						|
	// that should be found on the stack, the actual stack trace can have more
 | 
						|
	// frame than those listed here but all the frames listed must appear in
 | 
						|
	// the specified order.
 | 
						|
	testCases := [][]string{
 | 
						|
		{"main.main"},
 | 
						|
		{"C.helloworld_pt2", "C.helloworld", "main.main"},
 | 
						|
		{"main.helloWorldS", "main.helloWorld", "C.helloworld_pt2", "C.helloworld", "main.main"},
 | 
						|
		{"C.helloworld_pt4", "C.helloworld_pt3", "main.helloWorldS", "main.helloWorld", "C.helloworld_pt2", "C.helloworld", "main.main"},
 | 
						|
		{"main.helloWorld2", "C.helloworld_pt4", "C.helloworld_pt3", "main.helloWorldS", "main.helloWorld", "C.helloworld_pt2", "C.helloworld", "main.main"}}
 | 
						|
 | 
						|
	var gid int64
 | 
						|
 | 
						|
	frameOffs := map[string]int64{}
 | 
						|
	framePointerOffs := map[string]int64{}
 | 
						|
 | 
						|
	withTestProcess("cgostacktest/", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		for itidx, tc := range testCases {
 | 
						|
			t.Logf("iteration step %d", itidx)
 | 
						|
 | 
						|
			assertNoError(grp.Continue(), t, fmt.Sprintf("Continue at iteration step %d", itidx))
 | 
						|
 | 
						|
			g, err := proc.GetG(p.CurrentThread())
 | 
						|
			assertNoError(err, t, fmt.Sprintf("GetG at iteration step %d", itidx))
 | 
						|
 | 
						|
			if itidx == 0 {
 | 
						|
				gid = g.ID
 | 
						|
			} else {
 | 
						|
				if gid != g.ID {
 | 
						|
					t.Fatalf("wrong goroutine id at iteration step %d (expected %d got %d)", itidx, gid, g.ID)
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			frames, err := proc.GoroutineStacktrace(p, g, 100, 0)
 | 
						|
			assertNoError(err, t, fmt.Sprintf("Stacktrace at iteration step %d", itidx))
 | 
						|
 | 
						|
			logStacktrace(t, p, frames)
 | 
						|
 | 
						|
			m := stacktraceCheck(t, tc, frames)
 | 
						|
			mismatch := m == nil
 | 
						|
 | 
						|
			for i, j := range m {
 | 
						|
				if strings.HasPrefix(tc[i], "C.hellow") {
 | 
						|
					if !frameInFile(frames[j], "hello.c") {
 | 
						|
						t.Logf("position in %q is %s:%d (call %s:%d)", tc[i], frames[j].Current.File, frames[j].Current.Line, frames[j].Call.File, frames[j].Call.Line)
 | 
						|
						mismatch = true
 | 
						|
						break
 | 
						|
					}
 | 
						|
				}
 | 
						|
				if frameOff, ok := frameOffs[tc[i]]; ok {
 | 
						|
					if frameOff != frames[j].FrameOffset() {
 | 
						|
						t.Logf("frame %s offset mismatch", tc[i])
 | 
						|
					}
 | 
						|
					if framePointerOffs[tc[i]] != frames[j].FramePointerOffset() {
 | 
						|
						t.Logf("frame %s pointer offset mismatch, expected: %#v actual: %#v", tc[i], framePointerOffs[tc[i]], frames[j].FramePointerOffset())
 | 
						|
					}
 | 
						|
				} else {
 | 
						|
					frameOffs[tc[i]] = frames[j].FrameOffset()
 | 
						|
					framePointerOffs[tc[i]] = frames[j].FramePointerOffset()
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			// also check that ThreadStacktrace produces the same list of frames
 | 
						|
			threadFrames, err := proc.ThreadStacktrace(p, p.CurrentThread(), 100)
 | 
						|
			assertNoError(err, t, fmt.Sprintf("ThreadStacktrace at iteration step %d", itidx))
 | 
						|
 | 
						|
			if len(threadFrames) != len(frames) {
 | 
						|
				mismatch = true
 | 
						|
			} else {
 | 
						|
				for j := range frames {
 | 
						|
					if frames[j].Current.File != threadFrames[j].Current.File || frames[j].Current.Line != threadFrames[j].Current.Line {
 | 
						|
						t.Logf("stack mismatch between goroutine stacktrace and thread stacktrace")
 | 
						|
						t.Logf("thread stacktrace:")
 | 
						|
						logStacktrace(t, p, threadFrames)
 | 
						|
						mismatch = true
 | 
						|
						break
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
			if mismatch {
 | 
						|
				t.Fatal("see previous loglines")
 | 
						|
			}
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestCgoSources(t *testing.T) {
 | 
						|
	if runtime.GOOS == "windows" {
 | 
						|
		ver, _ := goversion.Parse(runtime.Version())
 | 
						|
		if ver.Major > 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 9, Rev: -1}) {
 | 
						|
			t.Skip("disabled on windows with go before version 1.9")
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if runtime.GOARCH == "386" {
 | 
						|
		t.Skip("cgo stacktraces not supported on i386 for now")
 | 
						|
	}
 | 
						|
 | 
						|
	protest.MustHaveCgo(t)
 | 
						|
 | 
						|
	withTestProcess("cgostacktest/", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		sources := p.BinInfo().Sources
 | 
						|
		for _, needle := range []string{"main.go", "hello.c"} {
 | 
						|
			found := false
 | 
						|
			for _, k := range sources {
 | 
						|
				if strings.HasSuffix(k, needle) || strings.HasSuffix(k, "/"+needle) || strings.HasSuffix(k, "\\"+needle) {
 | 
						|
					found = true
 | 
						|
					break
 | 
						|
				}
 | 
						|
			}
 | 
						|
			if !found {
 | 
						|
				t.Errorf("File %s not found", needle)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestSystemstackStacktrace(t *testing.T) {
 | 
						|
	skipOn(t, "broken", "ppc64le")
 | 
						|
	// check that we can follow a stack switch initiated by runtime.systemstack()
 | 
						|
	withTestProcess("panic", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFunctionBreakpoint(p, t, "runtime.startpanic_m")
 | 
						|
		assertNoError(grp.Continue(), t, "first continue")
 | 
						|
		assertNoError(grp.Continue(), t, "second continue")
 | 
						|
		g, err := proc.GetG(p.CurrentThread())
 | 
						|
		assertNoError(err, t, "GetG")
 | 
						|
		frames, err := proc.GoroutineStacktrace(p, g, 100, 0)
 | 
						|
		assertNoError(err, t, "stacktrace")
 | 
						|
		logStacktrace(t, p, frames)
 | 
						|
		m := stacktraceCheck(t, []string{"!runtime.startpanic_m", "runtime.gopanic", "main.main"}, frames)
 | 
						|
		if m == nil {
 | 
						|
			t.Fatal("see previous loglines")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestSystemstackOnRuntimeNewstack(t *testing.T) {
 | 
						|
	skipOn(t, "broken", "ppc64le")
 | 
						|
	// The bug being tested here manifests as follows:
 | 
						|
	// - set a breakpoint somewhere or interrupt the program with Ctrl-C
 | 
						|
	// - try to look at stacktraces of other goroutines
 | 
						|
	// If one of the other goroutines is resizing its own stack the stack
 | 
						|
	// command won't work for it.
 | 
						|
	withTestProcess("binarytrees", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFunctionBreakpoint(p, t, "main.main")
 | 
						|
		assertNoError(grp.Continue(), t, "first continue")
 | 
						|
 | 
						|
		g, err := proc.GetG(p.CurrentThread())
 | 
						|
		assertNoError(err, t, "GetG")
 | 
						|
		mainGoroutineID := g.ID
 | 
						|
 | 
						|
		setFunctionBreakpointAll(p, t, "runtime.newstack")
 | 
						|
		for {
 | 
						|
			assertNoError(grp.Continue(), t, "second continue")
 | 
						|
			g, err = proc.GetG(p.CurrentThread())
 | 
						|
			assertNoError(err, t, "GetG")
 | 
						|
			if g.ID == mainGoroutineID {
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
		frames, err := proc.GoroutineStacktrace(p, g, 100, 0)
 | 
						|
		assertNoError(err, t, "stacktrace")
 | 
						|
		logStacktrace(t, p, frames)
 | 
						|
		m := stacktraceCheck(t, []string{"!runtime.newstack", "main.main"}, frames)
 | 
						|
		if m == nil {
 | 
						|
			t.Fatal("see previous loglines")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue1034(t *testing.T) {
 | 
						|
	skipOn(t, "broken - cgo stacktraces", "386")
 | 
						|
	protest.MustHaveCgo(t)
 | 
						|
 | 
						|
	// The external linker on macOS produces an abbrev for DW_TAG_subprogram
 | 
						|
	// without the "has children" flag, we should support this.
 | 
						|
	withTestProcess("cgostacktest/", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFunctionBreakpoint(p, t, "main.main")
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		frames, err := proc.GoroutineStacktrace(p, p.SelectedGoroutine(), 10, 0)
 | 
						|
		assertNoError(err, t, "Stacktrace")
 | 
						|
		scope := proc.FrameToScope(p, p.Memory(), nil, 0, frames[2:]...)
 | 
						|
		args, _ := scope.FunctionArguments(normalLoadConfig)
 | 
						|
		assertNoError(err, t, "FunctionArguments()")
 | 
						|
		if len(args) > 0 {
 | 
						|
			t.Fatalf("wrong number of arguments for frame %v (%d)", frames[2], len(args))
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue1008(t *testing.T) {
 | 
						|
	skipOn(t, "broken - cgo stacktraces", "386")
 | 
						|
	protest.MustHaveCgo(t)
 | 
						|
 | 
						|
	// The external linker on macOS inserts "end of sequence" extended opcodes
 | 
						|
	// in debug_line. which we should support correctly.
 | 
						|
	withTestProcess("cgostacktest/", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFunctionBreakpoint(p, t, "main.main")
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		loc, err := proc.ThreadLocation(p.CurrentThread())
 | 
						|
		assertNoError(err, t, "CurrentThread().Location()")
 | 
						|
		t.Logf("location %v\n", loc)
 | 
						|
		if !strings.HasSuffix(loc.File, "/main.go") {
 | 
						|
			t.Errorf("unexpected location %s:%d\n", loc.File, loc.Line)
 | 
						|
		}
 | 
						|
		if loc.Line > 35 {
 | 
						|
			t.Errorf("unexpected location %s:%d (file only has 34 lines)\n", loc.File, loc.Line)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func testDeclLineCount(t *testing.T, p *proc.Target, lineno int, tgtvars []string) {
 | 
						|
	sort.Strings(tgtvars)
 | 
						|
 | 
						|
	assertLineNumber(p, t, lineno, "Program did not continue to correct next location")
 | 
						|
	scope, err := proc.GoroutineScope(p, p.CurrentThread())
 | 
						|
	t.Logf("scope PC: %#x\n", scope.PC)
 | 
						|
	assertNoError(err, t, fmt.Sprintf("GoroutineScope (:%d)", lineno))
 | 
						|
	vars, err := scope.Locals(0, "")
 | 
						|
	assertNoError(err, t, fmt.Sprintf("Locals (:%d)", lineno))
 | 
						|
	if len(vars) != len(tgtvars) {
 | 
						|
		t.Fatalf("wrong number of variables %d (:%d)", len(vars), lineno)
 | 
						|
	}
 | 
						|
	outvars := make([]string, len(vars))
 | 
						|
	for i, v := range vars {
 | 
						|
		outvars[i] = v.Name
 | 
						|
	}
 | 
						|
	sort.Strings(outvars)
 | 
						|
 | 
						|
	for i := range outvars {
 | 
						|
		if tgtvars[i] != outvars[i] {
 | 
						|
			t.Fatalf("wrong variables, got: %q expected %q\n", outvars, tgtvars)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestDeclLine(t *testing.T) {
 | 
						|
	ver, _ := goversion.Parse(runtime.Version())
 | 
						|
	if ver.Major > 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 10, Rev: -1}) {
 | 
						|
		t.Skip("go 1.9 and prior versions do not emit DW_AT_decl_line")
 | 
						|
	}
 | 
						|
 | 
						|
	withTestProcess("decllinetest", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 8)
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 9)
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 10)
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 11)
 | 
						|
		b := setFunctionBreakpoint(p, t, "main.f1")
 | 
						|
		if b.Line != 14 {
 | 
						|
			// Line 14 is hard-coded below.
 | 
						|
			t.Fatalf("expected \"main.f1\" breakpoint to be set on line 14, but got line: %d", b.Line)
 | 
						|
		}
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue 1")
 | 
						|
		if goversion.VersionAfterOrEqual(runtime.Version(), 1, 15) {
 | 
						|
			testDeclLineCount(t, p, 8, []string{})
 | 
						|
		} else {
 | 
						|
			testDeclLineCount(t, p, 8, []string{"a"})
 | 
						|
		}
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue 2")
 | 
						|
		testDeclLineCount(t, p, 9, []string{"a"})
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue 3")
 | 
						|
		if goversion.VersionAfterOrEqual(runtime.Version(), 1, 15) {
 | 
						|
			testDeclLineCount(t, p, 10, []string{"a"})
 | 
						|
		} else {
 | 
						|
			testDeclLineCount(t, p, 10, []string{"a", "b"})
 | 
						|
		}
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue 4")
 | 
						|
		testDeclLineCount(t, p, 11, []string{"a", "b"})
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue 5")
 | 
						|
		// On line 14, we expect the function's arguments to be available, even
 | 
						|
		// though their DW_AT_decl_line declares higher line numbers. The decl_line
 | 
						|
		// is supposed to be ignored for the visibility of arguments.
 | 
						|
		testDeclLineCount(t, p, 14, []string{"a", "b"})
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue1137(t *testing.T) {
 | 
						|
	withTestProcess("dotpackagesiface", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		v := evalVariable(p, t, "iface")
 | 
						|
		assertNoError(v.Unreadable, t, "iface unreadable")
 | 
						|
		v2 := evalVariable(p, t, "iface2")
 | 
						|
		assertNoError(v2.Unreadable, t, "iface2 unreadable")
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue1101(t *testing.T) {
 | 
						|
	// If a breakpoint is hit close to process death on a thread that isn't the
 | 
						|
	// group leader the process could die while we are trying to stop it.
 | 
						|
	//
 | 
						|
	// This can be easily reproduced by having the goroutine that's executing
 | 
						|
	// main.main (which will almost always run on the thread group leader) wait
 | 
						|
	// for a second goroutine before exiting, then setting a breakpoint on the
 | 
						|
	// second goroutine and stepping through it (see TestIssue1101 in
 | 
						|
	// proc_test.go).
 | 
						|
	//
 | 
						|
	// When stepping over the return instruction of main.f the deferred
 | 
						|
	// wg.Done() call will be executed which will cause the main goroutine to
 | 
						|
	// resume and proceed to exit. Both the temporary breakpoint on wg.Done and
 | 
						|
	// the temporary breakpoint on the return address of main.f will be in
 | 
						|
	// close proximity to main.main calling os.Exit() and causing the death of
 | 
						|
	// the thread group leader.
 | 
						|
 | 
						|
	withTestProcess("issue1101", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFunctionBreakpoint(p, t, "main.f")
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		assertNoError(grp.Next(), t, "Next() 1")
 | 
						|
		assertNoError(grp.Next(), t, "Next() 2")
 | 
						|
		lastCmd := "Next() 3"
 | 
						|
		exitErr := grp.Next()
 | 
						|
		if exitErr == nil {
 | 
						|
			lastCmd = "final Continue()"
 | 
						|
			exitErr = grp.Continue()
 | 
						|
		}
 | 
						|
		if pexit, exited := exitErr.(proc.ErrProcessExited); exited {
 | 
						|
			if pexit.Status != 2 && testBackend != "lldb" && (runtime.GOOS != "linux" || runtime.GOARCH != "386") {
 | 
						|
				// Looks like there's a bug with debugserver on macOS that sometimes
 | 
						|
				// will report exit status 0 instead of the proper exit status.
 | 
						|
				//
 | 
						|
				// Also it seems that sometimes on linux/386 we will not receive the
 | 
						|
				// exit status. This happens if the process exits at the same time as it
 | 
						|
				// receives a signal.
 | 
						|
				t.Fatalf("process exited status %d (expected 2) (last command = %s) (%#v)", pexit.Status, lastCmd, pexit)
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			assertNoError(exitErr, t, lastCmd)
 | 
						|
			t.Fatalf("process did not exit after %s", lastCmd)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue1145(t *testing.T) {
 | 
						|
	withTestProcess("sleep", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 18)
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		resumeChan := make(chan struct{}, 1)
 | 
						|
		grp.ResumeNotify(resumeChan)
 | 
						|
		go func() {
 | 
						|
			<-resumeChan
 | 
						|
			time.Sleep(100 * time.Millisecond)
 | 
						|
			grp.RequestManualStop()
 | 
						|
		}()
 | 
						|
 | 
						|
		assertNoError(grp.Next(), t, "Next()")
 | 
						|
		if p.Breakpoints().HasSteppingBreakpoints() {
 | 
						|
			t.Fatal("has internal breakpoints after manual stop request")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestHaltKeepsSteppingBreakpoints(t *testing.T) {
 | 
						|
	withTestProcess("sleep", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		grp.KeepSteppingBreakpoints = proc.HaltKeepsSteppingBreakpoints
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 18)
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		resumeChan := make(chan struct{}, 1)
 | 
						|
		grp.ResumeNotify(resumeChan)
 | 
						|
		go func() {
 | 
						|
			<-resumeChan
 | 
						|
			time.Sleep(100 * time.Millisecond)
 | 
						|
			grp.RequestManualStop()
 | 
						|
		}()
 | 
						|
 | 
						|
		assertNoError(grp.Next(), t, "Next()")
 | 
						|
		if !p.Breakpoints().HasSteppingBreakpoints() {
 | 
						|
			t.Fatal("does not have internal breakpoints after manual stop request")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestDisassembleGlobalVars(t *testing.T) {
 | 
						|
	skipOn(t, "broken - global variable symbolication", "arm64")   // On ARM64 symLookup can't look up variables due to how they are loaded, see issue #1778
 | 
						|
	skipOn(t, "broken - global variable symbolication", "ppc64le") // See comment on ARM64 above.
 | 
						|
	skipOn(t, "broken - global variable symbolication", "riscv64")
 | 
						|
	skipOn(t, "broken - global variable symbolication", "loong64")
 | 
						|
	// On 386 linux when pie, the generated code use __x86.get_pc_thunk to ensure position-independent.
 | 
						|
	// Locate global variable by
 | 
						|
	//    `CALL __x86.get_pc_thunk.ax(SB) 0xb0f7f
 | 
						|
	//     LEAL 0xc0a19(AX), AX`
 | 
						|
	// dynamically.
 | 
						|
	if runtime.GOARCH == "386" && runtime.GOOS == "linux" && buildMode == "pie" {
 | 
						|
		t.Skip("On 386 linux when pie, symLookup can't look up global variables")
 | 
						|
	}
 | 
						|
	withTestProcess("teststepconcurrent", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		mainfn := p.BinInfo().LookupFunc()["main.main"][0]
 | 
						|
		regs, _ := p.CurrentThread().Registers()
 | 
						|
		text, err := proc.Disassemble(p.Memory(), regs, p.Breakpoints(), p.BinInfo(), mainfn.Entry, mainfn.End)
 | 
						|
		assertNoError(err, t, "Disassemble")
 | 
						|
		found := false
 | 
						|
		for i := range text {
 | 
						|
			if strings.Index(text[i].Text(proc.IntelFlavour, p.BinInfo()), "main.v") > 0 {
 | 
						|
				found = true
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if !found {
 | 
						|
			t.Fatalf("could not find main.v reference in disassembly")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func checkFrame(frame proc.Stackframe, fnname, file string, line int, inlined bool) error {
 | 
						|
	if frame.Call.Fn == nil || frame.Call.Fn.Name != fnname {
 | 
						|
		return fmt.Errorf("wrong function name: %s", fnname)
 | 
						|
	}
 | 
						|
	if file != "" {
 | 
						|
		if frame.Call.File != file || frame.Call.Line != line {
 | 
						|
			return fmt.Errorf("wrong file:line %s:%d", frame.Call.File, frame.Call.Line)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if frame.Inlined != inlined {
 | 
						|
		if inlined {
 | 
						|
			return errors.New("not inlined")
 | 
						|
		}
 | 
						|
		return errors.New("inlined")
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func TestAllPCsForFileLines(t *testing.T) {
 | 
						|
	if ver, _ := goversion.Parse(runtime.Version()); ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 10, Rev: -1}) {
 | 
						|
		// Versions of go before 1.10 do not have DWARF information for inlined calls
 | 
						|
		t.Skip("inlining not supported")
 | 
						|
	}
 | 
						|
	withTestProcessArgs("testinline", t, ".", []string{}, protest.EnableInlining, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		l2pcs := p.BinInfo().AllPCsForFileLines(fixture.Source, []int{7, 20})
 | 
						|
		if len(l2pcs) != 2 {
 | 
						|
			t.Fatalf("expected two map entries for %s:{%d,%d} (got %d: %v)", fixture.Source, 7, 20, len(l2pcs), l2pcs)
 | 
						|
		}
 | 
						|
		pcs := l2pcs[20]
 | 
						|
		if len(pcs) < 1 {
 | 
						|
			t.Fatalf("expected at least one location for %s:%d (got %d: %#x)", fixture.Source, 20, len(pcs), pcs)
 | 
						|
		}
 | 
						|
		pcs = l2pcs[7]
 | 
						|
		if len(pcs) < 2 {
 | 
						|
			t.Fatalf("expected at least two locations for %s:%d (got %d: %#x)", fixture.Source, 7, len(pcs), pcs)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestInlinedStacktraceAndVariables(t *testing.T) {
 | 
						|
	if ver, _ := goversion.Parse(runtime.Version()); ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 10, Rev: -1}) {
 | 
						|
		// Versions of go before 1.10 do not have DWARF information for inlined calls
 | 
						|
		t.Skip("inlining not supported")
 | 
						|
	}
 | 
						|
 | 
						|
	firstCallCheck := &scopeCheck{
 | 
						|
		line: 7,
 | 
						|
		ok:   false,
 | 
						|
		varChecks: []varCheck{
 | 
						|
			{
 | 
						|
				name:   "a",
 | 
						|
				typ:    "int",
 | 
						|
				kind:   reflect.Int,
 | 
						|
				hasVal: true,
 | 
						|
				intVal: 3,
 | 
						|
			},
 | 
						|
			{
 | 
						|
				name:   "z",
 | 
						|
				typ:    "int",
 | 
						|
				kind:   reflect.Int,
 | 
						|
				hasVal: true,
 | 
						|
				intVal: 9,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	secondCallCheck := &scopeCheck{
 | 
						|
		line: 7,
 | 
						|
		ok:   false,
 | 
						|
		varChecks: []varCheck{
 | 
						|
			{
 | 
						|
				name:   "a",
 | 
						|
				typ:    "int",
 | 
						|
				kind:   reflect.Int,
 | 
						|
				hasVal: true,
 | 
						|
				intVal: 4,
 | 
						|
			},
 | 
						|
			{
 | 
						|
				name:   "z",
 | 
						|
				typ:    "int",
 | 
						|
				kind:   reflect.Int,
 | 
						|
				hasVal: true,
 | 
						|
				intVal: 16,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	withTestProcessArgs("testinline", t, ".", []string{}, protest.EnableInlining, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		pcs, err := proc.FindFileLocation(p, fixture.Source, 7)
 | 
						|
		assertNoError(err, t, "LineToPC")
 | 
						|
		if len(pcs) < 2 {
 | 
						|
			t.Fatalf("expected at least two locations for %s:%d (got %d: %#x)", fixture.Source, 7, len(pcs), pcs)
 | 
						|
		}
 | 
						|
		for _, pc := range pcs {
 | 
						|
			t.Logf("setting breakpoint at %#x\n", pc)
 | 
						|
			_, err := p.SetBreakpoint(0, pc, proc.UserBreakpoint, nil)
 | 
						|
			assertNoError(err, t, fmt.Sprintf("SetBreakpoint(%#x)", pc))
 | 
						|
		}
 | 
						|
 | 
						|
		// first inlined call
 | 
						|
		assertNoError(grp.Continue(), t, "Continue")
 | 
						|
		frames, err := proc.ThreadStacktrace(p, p.CurrentThread(), 20)
 | 
						|
		assertNoError(err, t, "ThreadStacktrace")
 | 
						|
		t.Logf("Stacktrace:\n")
 | 
						|
		for i := range frames {
 | 
						|
			t.Logf("\t%s at %s:%d (%#x)\n", frames[i].Call.Fn.Name, frames[i].Call.File, frames[i].Call.Line, frames[i].Current.PC)
 | 
						|
		}
 | 
						|
 | 
						|
		if err := checkFrame(frames[0], "main.inlineThis", fixture.Source, 7, true); err != nil {
 | 
						|
			t.Fatalf("Wrong frame 0: %v", err)
 | 
						|
		}
 | 
						|
		if err := checkFrame(frames[1], "main.main", fixture.Source, 18, false); err != nil {
 | 
						|
			t.Fatalf("Wrong frame 1: %v", err)
 | 
						|
		}
 | 
						|
 | 
						|
		if avar, _ := constant.Int64Val(evalVariable(p, t, "a").Value); avar != 3 {
 | 
						|
			t.Fatalf("value of 'a' variable is not 3 (%d)", avar)
 | 
						|
		}
 | 
						|
		if zvar, _ := constant.Int64Val(evalVariable(p, t, "z").Value); zvar != 9 {
 | 
						|
			t.Fatalf("value of 'z' variable is not 9 (%d)", zvar)
 | 
						|
		}
 | 
						|
 | 
						|
		if _, ok := firstCallCheck.checkLocalsAndArgs(p, t); !ok {
 | 
						|
			t.Fatalf("exiting for past errors")
 | 
						|
		}
 | 
						|
 | 
						|
		// second inlined call
 | 
						|
		assertNoError(grp.Continue(), t, "Continue")
 | 
						|
		frames, err = proc.ThreadStacktrace(p, p.CurrentThread(), 20)
 | 
						|
		assertNoError(err, t, "ThreadStacktrace (2)")
 | 
						|
		t.Logf("Stacktrace 2:\n")
 | 
						|
		for i := range frames {
 | 
						|
			t.Logf("\t%s at %s:%d (%#x)\n", frames[i].Call.Fn.Name, frames[i].Call.File, frames[i].Call.Line, frames[i].Current.PC)
 | 
						|
		}
 | 
						|
 | 
						|
		if err := checkFrame(frames[0], "main.inlineThis", fixture.Source, 7, true); err != nil {
 | 
						|
			t.Fatalf("Wrong frame 0: %v", err)
 | 
						|
		}
 | 
						|
		if err := checkFrame(frames[1], "main.main", fixture.Source, 19, false); err != nil {
 | 
						|
			t.Fatalf("Wrong frame 1: %v", err)
 | 
						|
		}
 | 
						|
 | 
						|
		if avar, _ := constant.Int64Val(evalVariable(p, t, "a").Value); avar != 4 {
 | 
						|
			t.Fatalf("value of 'a' variable is not 3 (%d)", avar)
 | 
						|
		}
 | 
						|
		if zvar, _ := constant.Int64Val(evalVariable(p, t, "z").Value); zvar != 16 {
 | 
						|
			t.Fatalf("value of 'z' variable is not 9 (%d)", zvar)
 | 
						|
		}
 | 
						|
		if bvar, err := evalVariableOrError(p, "b"); err == nil {
 | 
						|
			t.Fatalf("expected error evaluating 'b', but it succeeded instead: %v", bvar)
 | 
						|
		}
 | 
						|
 | 
						|
		if _, ok := secondCallCheck.checkLocalsAndArgs(p, t); !ok {
 | 
						|
			t.Fatalf("exiting for past errors")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestInlineFunctionList(t *testing.T) {
 | 
						|
	// We should be able to list all functions, even inlined ones.
 | 
						|
	ver, _ := goversion.Parse(runtime.Version())
 | 
						|
	if ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 10, Rev: -1}) {
 | 
						|
		// Versions of go before 1.10 do not have DWARF information for inlined calls
 | 
						|
		t.Skip("inlining not supported")
 | 
						|
	}
 | 
						|
	if runtime.GOOS == "windows" && runtime.GOARCH == "arm64" {
 | 
						|
		// TODO(qmuntal): seems to be an upstream issue, investigate.
 | 
						|
		t.Skip("inlining not supported")
 | 
						|
	}
 | 
						|
	withTestProcessArgs("testinline", t, ".", []string{}, protest.EnableInlining|protest.EnableOptimization, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		var found bool
 | 
						|
		for _, fn := range p.BinInfo().Functions {
 | 
						|
			if strings.Contains(fn.Name, "inlineThis") {
 | 
						|
				found = true
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if !found {
 | 
						|
			t.Fatal("inline function not returned")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestInlineBreakpoint(t *testing.T) {
 | 
						|
	// We should be able to set a breakpoint on the call site of an inlined function.
 | 
						|
	if ver, _ := goversion.Parse(runtime.Version()); ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 10, Rev: -1}) {
 | 
						|
		// Versions of go before 1.10 do not have DWARF information for inlined calls
 | 
						|
		t.Skip("inlining not supported")
 | 
						|
	}
 | 
						|
	withTestProcessArgs("testinline", t, ".", []string{}, protest.EnableInlining|protest.EnableOptimization, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		pcs, err := proc.FindFileLocation(p, fixture.Source, 17)
 | 
						|
		if err != nil {
 | 
						|
			t.Fatal(err)
 | 
						|
		}
 | 
						|
		t.Logf("%#v\n", pcs)
 | 
						|
		if len(pcs) != 1 {
 | 
						|
			t.Fatalf("unable to get PC for inlined function call: %v", pcs)
 | 
						|
		}
 | 
						|
		fn := p.BinInfo().PCToFunc(pcs[0])
 | 
						|
		expectedFn := "main.main"
 | 
						|
		if fn.Name != expectedFn {
 | 
						|
			t.Fatalf("incorrect function returned, expected %s, got %s", expectedFn, fn.Name)
 | 
						|
		}
 | 
						|
		_, err = p.SetBreakpoint(0, pcs[0], proc.UserBreakpoint, nil)
 | 
						|
		if err != nil {
 | 
						|
			t.Fatalf("unable to set breakpoint: %v", err)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestDoubleInlineBreakpoint(t *testing.T) {
 | 
						|
	// We should be able to set a breakpoint on an inlined function that
 | 
						|
	// has been inlined within an inlined function.
 | 
						|
	if ver, _ := goversion.Parse(runtime.Version()); ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 10, Rev: -1}) {
 | 
						|
		// Versions of go before 1.10 do not have DWARF information for inlined calls
 | 
						|
		t.Skip("inlining not supported")
 | 
						|
	}
 | 
						|
	withTestProcessArgs("doubleinline", t, ".", []string{}, protest.EnableInlining|protest.EnableOptimization, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		fns, err := p.BinInfo().FindFunction("main.(*Rectangle).Height")
 | 
						|
		if err != nil {
 | 
						|
			t.Fatal(err)
 | 
						|
		}
 | 
						|
		if len(fns) != 1 {
 | 
						|
			t.Fatalf("expected one function for Height, got %d", len(fns))
 | 
						|
		}
 | 
						|
		if len(fns[0].InlinedCalls) != 1 {
 | 
						|
			t.Fatalf("expected one inlined call for Height, got %d", len(fns[0].InlinedCalls))
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue951(t *testing.T) {
 | 
						|
	if ver, _ := goversion.Parse(runtime.Version()); ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 9, Rev: -1}) {
 | 
						|
		t.Skip("scopes not implemented in <=go1.8")
 | 
						|
	}
 | 
						|
 | 
						|
	withTestProcess("issue951", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		scope, err := proc.GoroutineScope(p, p.CurrentThread())
 | 
						|
		assertNoError(err, t, "GoroutineScope")
 | 
						|
		args, err := scope.FunctionArguments(normalLoadConfig)
 | 
						|
		assertNoError(err, t, "FunctionArguments")
 | 
						|
		t.Logf("%#v", args[0])
 | 
						|
		if args[0].Flags&proc.VariableShadowed == 0 {
 | 
						|
			t.Error("argument is not shadowed")
 | 
						|
		}
 | 
						|
		vars, err := scope.LocalVariables(normalLoadConfig)
 | 
						|
		assertNoError(err, t, "LocalVariables")
 | 
						|
		shadowed, notShadowed := 0, 0
 | 
						|
		for i := range vars {
 | 
						|
			t.Logf("var %d: %#v\n", i, vars[i])
 | 
						|
			if vars[i].Flags&proc.VariableShadowed != 0 {
 | 
						|
				shadowed++
 | 
						|
			} else {
 | 
						|
				notShadowed++
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if shadowed != 1 || notShadowed != 1 {
 | 
						|
			t.Errorf("Wrong number of shadowed/non-shadowed local variables: %d %d", shadowed, notShadowed)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestDWZCompression(t *testing.T) {
 | 
						|
	skipOn(t, "broken", "ppc64le")
 | 
						|
	skipOn(t, "broken", "riscv64")
 | 
						|
	skipOn(t, "broken", "loong64")
 | 
						|
	// If dwz is not available in the system, skip this test
 | 
						|
	if _, err := exec.LookPath("dwz"); err != nil {
 | 
						|
		t.Skip("dwz not installed")
 | 
						|
	}
 | 
						|
 | 
						|
	withTestProcessArgs("dwzcompression", t, ".", []string{}, protest.EnableDWZCompression, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFunctionBreakpoint(p, t, "C.fortytwo")
 | 
						|
		assertNoError(grp.Continue(), t, "first Continue()")
 | 
						|
		val := evalVariable(p, t, "stdin")
 | 
						|
		if val.RealType == nil {
 | 
						|
			t.Errorf("Can't find type for \"stdin\" global variable")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestMapLoadConfigWithReslice(t *testing.T) {
 | 
						|
	// Check that load configuration is respected for resliced maps.
 | 
						|
	withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		zolotovLoadCfg := proc.LoadConfig{FollowPointers: true, MaxStructFields: -1, MaxVariableRecurse: 3, MaxStringLen: 10, MaxArrayValues: 10}
 | 
						|
		assertNoError(grp.Continue(), t, "First Continue()")
 | 
						|
		scope, err := proc.GoroutineScope(p, p.CurrentThread())
 | 
						|
		assertNoError(err, t, "GoroutineScope")
 | 
						|
		m1, err := scope.EvalExpression("m1", zolotovLoadCfg)
 | 
						|
		assertNoError(err, t, "EvalVariable")
 | 
						|
		t.Logf("m1 returned children %d (%d)", len(m1.Children)/2, m1.Len)
 | 
						|
 | 
						|
		expr := fmt.Sprintf("(*(*%q)(%d))[10:]", m1.DwarfType.String(), m1.Addr)
 | 
						|
		t.Logf("expr %q\n", expr)
 | 
						|
 | 
						|
		m1cont, err := scope.EvalExpression(expr, zolotovLoadCfg)
 | 
						|
		assertNoError(err, t, "EvalVariable")
 | 
						|
 | 
						|
		t.Logf("m1cont returned children %d", len(m1cont.Children)/2)
 | 
						|
 | 
						|
		if len(m1cont.Children) != 20 {
 | 
						|
			t.Fatalf("wrong number of children returned %d\n", len(m1cont.Children)/2)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestStepOutReturn(t *testing.T) {
 | 
						|
	ver, _ := goversion.Parse(runtime.Version())
 | 
						|
	if ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 10, Rev: -1}) {
 | 
						|
		t.Skip("return variables aren't marked on 1.9 or earlier")
 | 
						|
	}
 | 
						|
	withTestProcess("stepoutret", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFunctionBreakpoint(p, t, "main.stepout")
 | 
						|
		assertNoError(grp.Continue(), t, "Continue")
 | 
						|
		assertNoError(grp.StepOut(), t, "StepOut")
 | 
						|
		ret := p.CurrentThread().Common().ReturnValues(normalLoadConfig)
 | 
						|
		if len(ret) != 2 {
 | 
						|
			t.Fatalf("wrong number of return values %v", ret)
 | 
						|
		}
 | 
						|
 | 
						|
		stridx := 0
 | 
						|
		numidx := 1
 | 
						|
 | 
						|
		if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 12) {
 | 
						|
			// in 1.11 and earlier the order of return values in DWARF is
 | 
						|
			// unspecified, in 1.11 and later it follows the order of definition
 | 
						|
			// specified by the user
 | 
						|
			for i := range ret {
 | 
						|
				if ret[i].Name == "str" {
 | 
						|
					stridx = i
 | 
						|
					numidx = 1 - i
 | 
						|
					break
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if ret[stridx].Name != "str" {
 | 
						|
			t.Fatalf("(str) bad return value name %s", ret[stridx].Name)
 | 
						|
		}
 | 
						|
		if ret[stridx].Kind != reflect.String {
 | 
						|
			t.Fatalf("(str) bad return value kind %v", ret[stridx].Kind)
 | 
						|
		}
 | 
						|
		if s := constant.StringVal(ret[stridx].Value); s != "return 47" {
 | 
						|
			t.Fatalf("(str) bad return value %q", s)
 | 
						|
		}
 | 
						|
 | 
						|
		if ret[numidx].Name != "num" {
 | 
						|
			t.Fatalf("(num) bad return value name %s", ret[numidx].Name)
 | 
						|
		}
 | 
						|
		if ret[numidx].Kind != reflect.Int {
 | 
						|
			t.Fatalf("(num) bad return value kind %v", ret[numidx].Kind)
 | 
						|
		}
 | 
						|
		if n, _ := constant.Int64Val(ret[numidx].Value); n != 48 {
 | 
						|
			t.Fatalf("(num) bad return value %d", n)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestOptimizationCheck(t *testing.T) {
 | 
						|
	withTestProcess("continuetestprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		fn := p.BinInfo().LookupFunc()["main.main"][0]
 | 
						|
		if fn.Optimized() {
 | 
						|
			t.Fatalf("main.main is optimized")
 | 
						|
		}
 | 
						|
	})
 | 
						|
 | 
						|
	if goversion.VersionAfterOrEqual(runtime.Version(), 1, 10) {
 | 
						|
		withTestProcessArgs("continuetestprog", t, ".", []string{}, protest.EnableOptimization|protest.EnableInlining, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
			fn := p.BinInfo().LookupFunc()["main.main"][0]
 | 
						|
			if !fn.Optimized() {
 | 
						|
				t.Fatalf("main.main is not optimized")
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue1264(t *testing.T) {
 | 
						|
	// It should be possible to set a breakpoint condition that consists only
 | 
						|
	// of evaluating a single boolean variable.
 | 
						|
	withTestProcess("issue1264", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		bp := setFileBreakpoint(p, t, fixture.Source, 8)
 | 
						|
		bp.UserBreaklet().Cond = &ast.Ident{Name: "equalsTwo"}
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		assertLineNumber(p, t, 8, "after continue")
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestReadDefer(t *testing.T) {
 | 
						|
	withTestProcess("deferstack", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), t, "Continue")
 | 
						|
		frames, err := proc.GoroutineStacktrace(p, p.SelectedGoroutine(), 10, proc.StacktraceReadDefers)
 | 
						|
		assertNoError(err, t, "Stacktrace")
 | 
						|
 | 
						|
		logStacktrace(t, p, frames)
 | 
						|
 | 
						|
		examples := []struct {
 | 
						|
			frameIdx     int
 | 
						|
			topmostDefer string
 | 
						|
			defers       []string
 | 
						|
		}{
 | 
						|
			// main.call3 (defers nothing, topmost defer main.f2)
 | 
						|
			{0, "main.f2", []string{}},
 | 
						|
 | 
						|
			// main.call2 (defers main.f2, main.f3, topmost defer main.f2)
 | 
						|
			{1, "main.f2", []string{"main.f2", "main.f3"}},
 | 
						|
 | 
						|
			// main.call1 (defers main.f1, main.f2, topmost defer main.f1)
 | 
						|
			{2, "main.f1", []string{"main.f1", "main.f2"}},
 | 
						|
 | 
						|
			// main.main (defers nothing)
 | 
						|
			{3, "", []string{}}}
 | 
						|
 | 
						|
		defercheck := func(d *proc.Defer, deferName, tgt string, frameIdx int) {
 | 
						|
			if d == nil {
 | 
						|
				t.Fatalf("expected %q as %s of frame %d, got nothing", tgt, deferName, frameIdx)
 | 
						|
			}
 | 
						|
			if d.Unreadable != nil {
 | 
						|
				t.Fatalf("expected %q as %s of frame %d, got unreadable defer: %v", tgt, deferName, frameIdx, d.Unreadable)
 | 
						|
			}
 | 
						|
			_, _, dfn := d.DeferredFunc(p)
 | 
						|
			if dfn == nil {
 | 
						|
				t.Fatalf("expected %q as %s of frame %d, got %#x", tgt, deferName, frameIdx, d.DwrapPC)
 | 
						|
			}
 | 
						|
			if dfn.Name != tgt {
 | 
						|
				t.Fatalf("expected %q as %s of frame %d, got %q", tgt, deferName, frameIdx, dfn.Name)
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		for _, example := range examples {
 | 
						|
			frame := &frames[example.frameIdx]
 | 
						|
 | 
						|
			if example.topmostDefer != "" {
 | 
						|
				defercheck(frame.TopmostDefer, "topmost defer", example.topmostDefer, example.frameIdx)
 | 
						|
			}
 | 
						|
 | 
						|
			if len(example.defers) != len(frames[example.frameIdx].Defers) {
 | 
						|
				t.Fatalf("expected %d defers for %d, got %v", len(example.defers), example.frameIdx, frame.Defers)
 | 
						|
			}
 | 
						|
 | 
						|
			for deferIdx := range example.defers {
 | 
						|
				defercheck(frame.Defers[deferIdx], fmt.Sprintf("defer %d", deferIdx), example.defers[deferIdx], example.frameIdx)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestReadDeferArgs(t *testing.T) {
 | 
						|
	if goversion.VersionAfterOrEqual(runtime.Version(), 1, 17) {
 | 
						|
		// When regabi is enabled in Go 1.17 and later, reading arguments of
 | 
						|
		// deferred functions becomes significantly more complicated because of
 | 
						|
		// the autogenerated code used to unpack the argument frame stored in
 | 
						|
		// runtime._defer into registers.
 | 
						|
		// We either need to know how to do the translation, implementing the ABI1
 | 
						|
		// rules in Delve, or have some assistance from the compiler (for example
 | 
						|
		// have the dwrap function contain entries for each of the captured
 | 
						|
		// variables with a location describing their offset from DX).
 | 
						|
		// Ultimately this feature is unimportant enough that we can leave it
 | 
						|
		// disabled for now.
 | 
						|
		t.Skip("unsupported")
 | 
						|
	}
 | 
						|
	var tests = []struct {
 | 
						|
		frame, deferCall int
 | 
						|
		a, b             int64
 | 
						|
	}{
 | 
						|
		{1, 1, 42, 61},
 | 
						|
		{2, 2, 1, -1},
 | 
						|
	}
 | 
						|
 | 
						|
	withTestProcess("deferstack", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
 | 
						|
		for _, test := range tests {
 | 
						|
			scope, err := proc.ConvertEvalScope(p, -1, test.frame, test.deferCall)
 | 
						|
			assertNoError(err, t, fmt.Sprintf("ConvertEvalScope(-1, %d, %d)", test.frame, test.deferCall))
 | 
						|
 | 
						|
			if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 17) {
 | 
						|
				// In Go 1.17 deferred function calls can end up inside a wrapper, and
 | 
						|
				// the scope for this evaluation needs to be the wrapper.
 | 
						|
				if scope.Fn.Name != "main.f2" {
 | 
						|
					t.Fatalf("expected function \"main.f2\" got %q", scope.Fn.Name)
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			avar, err := scope.EvalExpression("a", normalLoadConfig)
 | 
						|
			if err != nil {
 | 
						|
				t.Fatal(err)
 | 
						|
			}
 | 
						|
			bvar, err := scope.EvalExpression("b", normalLoadConfig)
 | 
						|
			if err != nil {
 | 
						|
				t.Fatal(err)
 | 
						|
			}
 | 
						|
 | 
						|
			a, _ := constant.Int64Val(avar.Value)
 | 
						|
			b, _ := constant.Int64Val(bvar.Value)
 | 
						|
 | 
						|
			if a != test.a {
 | 
						|
				t.Errorf("value of argument 'a' at frame %d, deferred call %d: %d (expected %d)", test.frame, test.deferCall, a, test.a)
 | 
						|
			}
 | 
						|
 | 
						|
			if b != test.b {
 | 
						|
				t.Errorf("value of argument 'b' at frame %d, deferred call %d: %d (expected %d)", test.frame, test.deferCall, b, test.b)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue1374(t *testing.T) {
 | 
						|
	// Continue did not work when stopped at a breakpoint immediately after calling CallFunction.
 | 
						|
	skipOn(t, "broken - pie mode", "linux", "ppc64le", "native", "pie")
 | 
						|
 | 
						|
	protest.MustSupportFunctionCalls(t, testBackend)
 | 
						|
	withTestProcess("issue1374", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 7)
 | 
						|
		assertNoError(grp.Continue(), t, "First Continue")
 | 
						|
		assertLineNumber(p, t, 7, "Did not continue to correct location (first continue),")
 | 
						|
		assertNoError(proc.EvalExpressionWithCalls(grp, p.SelectedGoroutine(), "getNum()", normalLoadConfig, true), t, "Call")
 | 
						|
		err := grp.Continue()
 | 
						|
		if _, isexited := err.(proc.ErrProcessExited); !isexited {
 | 
						|
			regs, _ := p.CurrentThread().Registers()
 | 
						|
			f, l, _ := p.BinInfo().PCToLine(regs.PC())
 | 
						|
			t.Fatalf("expected process exited error got %v at %s:%d", err, f, l)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue1432(t *testing.T) {
 | 
						|
	// Check that taking the address of a struct, casting it into a pointer to
 | 
						|
	// the struct's type and then accessing a member field will still:
 | 
						|
	// - perform auto-dereferencing on struct member access
 | 
						|
	// - yield a Variable that's ultimately assignable (i.e. has an address)
 | 
						|
	withTestProcess("issue1432", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), t, "Continue")
 | 
						|
		svar := evalVariable(p, t, "s")
 | 
						|
		t.Logf("%#x", svar.Addr)
 | 
						|
 | 
						|
		scope, err := proc.GoroutineScope(p, p.CurrentThread())
 | 
						|
		assertNoError(err, t, "GoroutineScope()")
 | 
						|
 | 
						|
		err = scope.SetVariable(fmt.Sprintf("(*\"main.s\")(%#x).i", svar.Addr), "10")
 | 
						|
		assertNoError(err, t, "SetVariable")
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestGoroutinesInfoLimit(t *testing.T) {
 | 
						|
	withTestProcess("teststepconcurrent", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 37)
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
 | 
						|
		gcount := 0
 | 
						|
		nextg := 0
 | 
						|
		const goroutinesInfoLimit = 10
 | 
						|
		for nextg >= 0 {
 | 
						|
			oldnextg := nextg
 | 
						|
			var gs []*proc.G
 | 
						|
			var err error
 | 
						|
			gs, nextg, err = proc.GoroutinesInfo(p, nextg, goroutinesInfoLimit)
 | 
						|
			assertNoError(err, t, fmt.Sprintf("GoroutinesInfo(%d, %d)", oldnextg, goroutinesInfoLimit))
 | 
						|
			gcount += len(gs)
 | 
						|
			t.Logf("got %d goroutines\n", len(gs))
 | 
						|
		}
 | 
						|
 | 
						|
		t.Logf("number of goroutines: %d\n", gcount)
 | 
						|
 | 
						|
		gs, _, err := proc.GoroutinesInfo(p, 0, 0)
 | 
						|
		assertNoError(err, t, "GoroutinesInfo(0, 0)")
 | 
						|
		t.Logf("number of goroutines (full scan): %d\n", gcount)
 | 
						|
		if len(gs) != gcount {
 | 
						|
			t.Fatalf("mismatch in the number of goroutines %d %d\n", gcount, len(gs))
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue1469(t *testing.T) {
 | 
						|
	withTestProcess("issue1469", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 13)
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
 | 
						|
		gid2thread := make(map[int64][]proc.Thread)
 | 
						|
		for _, thread := range p.ThreadList() {
 | 
						|
			g, _ := proc.GetG(thread)
 | 
						|
			if g == nil {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			gid2thread[g.ID] = append(gid2thread[g.ID], thread)
 | 
						|
		}
 | 
						|
 | 
						|
		for gid := range gid2thread {
 | 
						|
			if len(gid2thread[gid]) > 1 {
 | 
						|
				t.Logf("too many threads running goroutine %d", gid)
 | 
						|
				for _, thread := range gid2thread[gid] {
 | 
						|
					t.Logf("\tThread %d", thread.ThreadID())
 | 
						|
					frames, err := proc.ThreadStacktrace(p, thread, 20)
 | 
						|
					if err != nil {
 | 
						|
						t.Logf("\t\tcould not get stacktrace %v", err)
 | 
						|
					}
 | 
						|
					for _, frame := range frames {
 | 
						|
						t.Logf("\t\t%#x at %s:%d (systemstack: %v)", frame.Call.PC, frame.Call.File, frame.Call.Line, frame.SystemStack)
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestDeadlockBreakpoint(t *testing.T) {
 | 
						|
	skipOn(t, "upstream issue - https://github.com/golang/go/issues/29322", "pie")
 | 
						|
	deadlockBp := proc.FatalThrow
 | 
						|
	if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 11) {
 | 
						|
		deadlockBp = proc.UnrecoveredPanic
 | 
						|
	}
 | 
						|
	withTestProcess("testdeadlock", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
 | 
						|
		bp := p.CurrentThread().Breakpoint()
 | 
						|
		if bp.Breakpoint == nil || bp.Logical.Name != deadlockBp {
 | 
						|
			t.Fatalf("did not stop at deadlock breakpoint %v", bp)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestListImages(t *testing.T) {
 | 
						|
	protest.MustHaveCgo(t)
 | 
						|
	pluginFixtures := protest.WithPlugins(t, protest.AllNonOptimized, "plugin1/", "plugin2/")
 | 
						|
 | 
						|
	withTestProcessArgs("plugintest", t, ".", []string{pluginFixtures[0].Path, pluginFixtures[1].Path}, protest.AllNonOptimized, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		if !slices.Contains(p.BinInfo().Sources, fixture.Source) {
 | 
						|
			t.Fatalf("could not find %s in sources: %q\n", fixture.Source, p.BinInfo().Sources)
 | 
						|
		}
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "first continue")
 | 
						|
		f, l := currentLineNumber(p, t)
 | 
						|
		plugin1Found := false
 | 
						|
		t.Logf("Libraries before %s:%d:", f, l)
 | 
						|
		for _, image := range p.BinInfo().Images {
 | 
						|
			t.Logf("\t%#x %q err:%v", image.StaticBase, image.Path, image.LoadError())
 | 
						|
			if image.Path == pluginFixtures[0].Path {
 | 
						|
				plugin1Found = true
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if !plugin1Found {
 | 
						|
			t.Fatalf("Could not find plugin1")
 | 
						|
		}
 | 
						|
		if !slices.Contains(p.BinInfo().Sources, fixture.Source) {
 | 
						|
			// Source files for the base program must be available even after a plugin is loaded. Issue #2074.
 | 
						|
			t.Fatalf("could not find %s in sources (after loading plugin): %q\n", fixture.Source, p.BinInfo().Sources)
 | 
						|
		}
 | 
						|
		assertNoError(grp.Continue(), t, "second continue")
 | 
						|
		f, l = currentLineNumber(p, t)
 | 
						|
		plugin1Found, plugin2Found := false, false
 | 
						|
		t.Logf("Libraries after %s:%d:", f, l)
 | 
						|
		for _, image := range p.BinInfo().Images {
 | 
						|
			t.Logf("\t%#x %q err:%v", image.StaticBase, image.Path, image.LoadError())
 | 
						|
			switch image.Path {
 | 
						|
			case pluginFixtures[0].Path:
 | 
						|
				plugin1Found = true
 | 
						|
			case pluginFixtures[1].Path:
 | 
						|
				plugin2Found = true
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if !plugin1Found {
 | 
						|
			t.Fatalf("Could not find plugin1")
 | 
						|
		}
 | 
						|
		if !plugin2Found {
 | 
						|
			t.Fatalf("Could not find plugin2")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestAncestors(t *testing.T) {
 | 
						|
	t.Setenv("GODEBUG", "tracebackancestors=100")
 | 
						|
	withTestProcess("testnextprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFunctionBreakpoint(p, t, "main.testgoroutine")
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		as, err := proc.Ancestors(p, p.SelectedGoroutine(), 1000)
 | 
						|
		assertNoError(err, t, "Ancestors")
 | 
						|
		t.Logf("ancestors: %#v\n", as)
 | 
						|
		if len(as) != 1 {
 | 
						|
			t.Fatalf("expected only one ancestor got %d", len(as))
 | 
						|
		}
 | 
						|
		mainFound := false
 | 
						|
		for i, a := range as {
 | 
						|
			astack, err := a.Stack(100)
 | 
						|
			assertNoError(err, t, fmt.Sprintf("Ancestor %d stack", i))
 | 
						|
			t.Logf("ancestor %d\n", i)
 | 
						|
			logStacktrace(t, p, astack)
 | 
						|
			for _, frame := range astack {
 | 
						|
				if frame.Current.Fn != nil && frame.Current.Fn.Name == "main.main" {
 | 
						|
					mainFound = true
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if !mainFound {
 | 
						|
			t.Fatal("could not find main.main function in ancestors")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func testCallConcurrentCheckReturns(p *proc.Target, t *testing.T, gid1, gid2 int64) int {
 | 
						|
	found := 0
 | 
						|
	for _, thread := range p.ThreadList() {
 | 
						|
		g, _ := proc.GetG(thread)
 | 
						|
		if g == nil || (g.ID != gid1 && g.ID != gid2) {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		retvals := thread.Common().ReturnValues(normalLoadConfig)
 | 
						|
		if len(retvals) == 0 {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		n, _ := constant.Int64Val(retvals[0].Value)
 | 
						|
		t.Logf("injection on goroutine %d (thread %d) returned %v\n", g.ID, thread.ThreadID(), n)
 | 
						|
		switch g.ID {
 | 
						|
		case gid1:
 | 
						|
			if n != 11 {
 | 
						|
				t.Errorf("wrong return value for goroutine %d", g.ID)
 | 
						|
			}
 | 
						|
			found++
 | 
						|
		case gid2:
 | 
						|
			if n != 12 {
 | 
						|
				t.Errorf("wrong return value for goroutine %d", g.ID)
 | 
						|
			}
 | 
						|
			found++
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return found
 | 
						|
}
 | 
						|
 | 
						|
func TestCallConcurrent(t *testing.T) {
 | 
						|
	skipOn(t, "broken - pie mode", "linux", "ppc64le", "native", "pie")
 | 
						|
 | 
						|
	protest.MustSupportFunctionCalls(t, testBackend)
 | 
						|
	withTestProcess("teststepconcurrent", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		bp := setFileBreakpoint(p, t, fixture.Source, 24)
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		//_, err := p.ClearBreakpoint(bp.Addr)
 | 
						|
		//assertNoError(err, t, "ClearBreakpoint() returned an error")
 | 
						|
 | 
						|
		gid1 := p.SelectedGoroutine().ID
 | 
						|
		t.Logf("starting injection in %d / %d", p.SelectedGoroutine().ID, p.CurrentThread().ThreadID())
 | 
						|
		assertNoError(proc.EvalExpressionWithCalls(grp, p.SelectedGoroutine(), "Foo(10, 1)", normalLoadConfig, false), t, "EvalExpressionWithCalls()")
 | 
						|
 | 
						|
		returned := testCallConcurrentCheckReturns(p, t, gid1, -1)
 | 
						|
 | 
						|
		curthread := p.CurrentThread()
 | 
						|
		if curbp := curthread.Breakpoint(); curbp.Breakpoint == nil || curbp.LogicalID() != bp.LogicalID() || returned > 0 {
 | 
						|
			t.Logf("skipping test, the call injection terminated before we hit a breakpoint in a different thread")
 | 
						|
			return
 | 
						|
		}
 | 
						|
 | 
						|
		err := p.ClearBreakpoint(bp.Addr)
 | 
						|
		assertNoError(err, t, "ClearBreakpoint() returned an error")
 | 
						|
 | 
						|
		gid2 := p.SelectedGoroutine().ID
 | 
						|
		t.Logf("starting second injection in %d / %d", p.SelectedGoroutine().ID, p.CurrentThread().ThreadID())
 | 
						|
		assertNoError(proc.EvalExpressionWithCalls(grp, p.SelectedGoroutine(), "Foo(10, 2)", normalLoadConfig, false), t, "EvalExpressionWithCalls")
 | 
						|
 | 
						|
		for {
 | 
						|
			returned += testCallConcurrentCheckReturns(p, t, gid1, gid2)
 | 
						|
			if returned >= 2 {
 | 
						|
				break
 | 
						|
			}
 | 
						|
			t.Logf("Continuing... %d", returned)
 | 
						|
			assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		}
 | 
						|
 | 
						|
		grp.Continue()
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestPluginStepping(t *testing.T) {
 | 
						|
	protest.MustHaveCgo(t)
 | 
						|
	pluginFixtures := protest.WithPlugins(t, protest.AllNonOptimized, "plugin1/", "plugin2/")
 | 
						|
 | 
						|
	testseq2Args(".", []string{pluginFixtures[0].Path, pluginFixtures[1].Path}, protest.AllNonOptimized, t, "plugintest2", "", []seqTest{
 | 
						|
		{contContinue, 41},
 | 
						|
		{contStep, "plugin1.go:9"},
 | 
						|
		{contStep, "plugin1.go:10"},
 | 
						|
		{contStep, "plugin1.go:11"},
 | 
						|
		{contNext, "plugin1.go:12"},
 | 
						|
		{contNext, "plugintest2.go:41"},
 | 
						|
		{contNext, "plugintest2.go:42"},
 | 
						|
		{contStep, "plugin2.go:22"},
 | 
						|
		{contNext, "plugin2.go:23"},
 | 
						|
		{contNext, "plugin2.go:26"},
 | 
						|
		{contNext, "plugintest2.go:42"}})
 | 
						|
}
 | 
						|
 | 
						|
func TestBreakpointMaterializedEvent(t *testing.T) {
 | 
						|
	protest.MustHaveCgo(t)
 | 
						|
	pluginFixtures := protest.WithPlugins(t, protest.AllNonOptimized, "plugin1/", "plugin2/")
 | 
						|
 | 
						|
	withTestProcessArgs("plugintest2", t, ".", []string{pluginFixtures[0].Path, pluginFixtures[1].Path}, protest.AllNonOptimized, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		path := filepath.Join(fixture.BuildDir, "plugin2", "plugin2.go")
 | 
						|
		const lineno = 23
 | 
						|
 | 
						|
		// Set a suspended breakpoint.
 | 
						|
		bp2 := &proc.LogicalBreakpoint{
 | 
						|
			LogicalID: 2,
 | 
						|
			HitCount:  make(map[int64]uint64),
 | 
						|
			Set:       proc.SetBreakpoint{File: path, Line: lineno},
 | 
						|
		}
 | 
						|
		grp.LogicalBreakpoints[2] = bp2
 | 
						|
		err := grp.SetBreakpointEnabled(bp2, true)
 | 
						|
		if !errors.As(err, new(*proc.ErrCouldNotFindLine)) {
 | 
						|
			if err == nil {
 | 
						|
				t.Fatal("Expected not to be able to find the breakpoint")
 | 
						|
			} else {
 | 
						|
				t.Fatal(err)
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// Collect events
 | 
						|
		var events []*proc.Event
 | 
						|
		grp.SetEventsFn(func(e *proc.Event) { events = append(events, e) })
 | 
						|
 | 
						|
		// Continue past the plugin load.
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 35)
 | 
						|
		assertNoError(grp.Continue(), t, "Continue")
 | 
						|
 | 
						|
		// The breakpoint should be enabled now
 | 
						|
		if !bp2.Enabled() || len(events) == 0 {
 | 
						|
			t.Fatal("Breakpoint did not materialize")
 | 
						|
		}
 | 
						|
		e := events[0]
 | 
						|
		if e.Kind != proc.EventBreakpointMaterialized {
 | 
						|
			t.Fatalf("Wrong event kind: want breakpoint materialized (%v), got %v", proc.EventBreakpointMaterialized, e.Kind)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue1601(t *testing.T) {
 | 
						|
	protest.MustHaveCgo(t)
 | 
						|
	// Tests that recursive types involving C qualifiers and typedefs are parsed correctly
 | 
						|
	withTestProcess("issue1601", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), t, "Continue")
 | 
						|
		evalVariable(p, t, "C.globalq")
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue1615(t *testing.T) {
 | 
						|
	// A breakpoint condition that tests for string equality with a constant string shouldn't fail with 'string too long for comparison' error
 | 
						|
 | 
						|
	withTestProcess("issue1615", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		bp := setFileBreakpoint(p, t, fixture.Source, 19)
 | 
						|
		bp.UserBreaklet().Cond = &ast.BinaryExpr{
 | 
						|
			Op: token.EQL,
 | 
						|
			X:  &ast.Ident{Name: "s"},
 | 
						|
			Y:  &ast.BasicLit{Kind: token.STRING, Value: `"projects/my-gcp-project-id-string/locations/us-central1/queues/my-task-queue-name"`},
 | 
						|
		}
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue")
 | 
						|
		assertLineNumber(p, t, 19, "")
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestCgoStacktrace2(t *testing.T) {
 | 
						|
	if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 21) {
 | 
						|
		skipOn(t, "upstream issue", "windows")
 | 
						|
		skipOn(t, "broken", "arm64")
 | 
						|
	}
 | 
						|
	skipOn(t, "broken", "386")
 | 
						|
	skipOn(t, "broken - cgo stacktraces", "darwin", "arm64")
 | 
						|
	skipOn(t, "broken", "ppc64le")
 | 
						|
	skipOn(t, "broken", "riscv64")
 | 
						|
	protest.MustHaveCgo(t)
 | 
						|
	// If a panic happens during cgo execution the stacktrace should show the C
 | 
						|
	// function that caused the problem.
 | 
						|
	withTestProcess("cgosigsegvstack", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		err := grp.Continue()
 | 
						|
		if _, exited := err.(proc.ErrProcessExited); exited {
 | 
						|
			t.Fatal("process exited")
 | 
						|
		}
 | 
						|
		frames, err := proc.ThreadStacktrace(p, p.CurrentThread(), 100)
 | 
						|
		assertNoError(err, t, "Stacktrace()")
 | 
						|
		logStacktrace(t, p, frames)
 | 
						|
		m := stacktraceCheck(t, []string{"C.sigsegv", "C.testfn", "main.main"}, frames)
 | 
						|
		if m == nil {
 | 
						|
			t.Fatal("see previous loglines")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue1736(t *testing.T) {
 | 
						|
	withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		ch1BufVar := evalVariable(p, t, "*(ch1.buf)")
 | 
						|
		q := fmt.Sprintf("*(*%q)(%d)", ch1BufVar.DwarfType.Common().Name, ch1BufVar.Addr)
 | 
						|
		t.Logf("%s", q)
 | 
						|
		ch1BufVar2 := evalVariable(p, t, q)
 | 
						|
		if ch1BufVar2.Unreadable != nil {
 | 
						|
			t.Fatal(ch1BufVar2.Unreadable)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue1817(t *testing.T) {
 | 
						|
	// Setting a breakpoint on a line that doesn't have any PC addresses marked
 | 
						|
	// is_stmt should work.
 | 
						|
	withTestProcess("issue1817", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 16)
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestListPackagesBuildInfo(t *testing.T) {
 | 
						|
	withTestProcess("pkgrenames", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		pkgs := p.BinInfo().ListPackagesBuildInfo(true)
 | 
						|
		t.Logf("returned %d", len(pkgs))
 | 
						|
		if len(pkgs) < 10 {
 | 
						|
			t.Errorf("very few packages returned")
 | 
						|
		}
 | 
						|
		for _, pkg := range pkgs {
 | 
						|
			t.Logf("%q %q", pkg.ImportPath, pkg.DirectoryPath)
 | 
						|
			const _fixtures = "_fixtures"
 | 
						|
			fidx := strings.Index(pkg.ImportPath, _fixtures)
 | 
						|
			if fidx < 0 {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			if !strings.HasSuffix(strings.ReplaceAll(pkg.DirectoryPath, "\\", "/"), pkg.ImportPath[fidx:]) {
 | 
						|
				t.Errorf("unexpected suffix: %q %q", pkg.ImportPath, pkg.DirectoryPath)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue1795(t *testing.T) {
 | 
						|
	// When doing midstack inlining the Go compiler sometimes (always?) emits
 | 
						|
	// the toplevel inlined call with ranges that do not cover the inlining of
 | 
						|
	// other nested inlined calls.
 | 
						|
	// For example if a function A calls B which calls C and both the calls to
 | 
						|
	// B and C are inlined the DW_AT_inlined_subroutine entry for A might have
 | 
						|
	// ranges that do not cover the ranges of the inlined call to C.
 | 
						|
	// This is probably a violation of the DWARF standard (it's unclear) but we
 | 
						|
	// might as well support it as best as possible anyway.
 | 
						|
	if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 13) {
 | 
						|
		t.Skip("Test not relevant to Go < 1.13")
 | 
						|
	}
 | 
						|
	skipOn(t, "broken", "ppc64le")
 | 
						|
	withTestProcessArgs("issue1795", t, ".", []string{}, protest.EnableInlining|protest.EnableOptimization, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		assertLineNumber(p, t, 12, "wrong line number after Continue,")
 | 
						|
		assertNoError(grp.Next(), t, "Next()")
 | 
						|
		assertLineNumber(p, t, 13, "wrong line number after Next,")
 | 
						|
	})
 | 
						|
	withTestProcessArgs("issue1795", t, ".", []string{}, protest.EnableInlining|protest.EnableOptimization, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFunctionBreakpoint(p, t, "regexp.(*Regexp).doExecute")
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		assertLineNumber(p, t, 12, "wrong line number after Continue (1),")
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		frames, err := proc.ThreadStacktrace(p, p.CurrentThread(), 40)
 | 
						|
		assertNoError(err, t, "ThreadStacktrace()")
 | 
						|
		logStacktrace(t, p, frames)
 | 
						|
		if err := checkFrame(frames[0], "regexp.(*Regexp).doExecute", "", 0, false); err != nil {
 | 
						|
			t.Errorf("Wrong frame 0: %v", err)
 | 
						|
		}
 | 
						|
		if err := checkFrame(frames[1], "regexp.(*Regexp).doMatch", "", 0, true); err != nil {
 | 
						|
			t.Errorf("Wrong frame 1: %v", err)
 | 
						|
		}
 | 
						|
		if err := checkFrame(frames[2], "regexp.(*Regexp).MatchString", "", 0, true); err != nil {
 | 
						|
			t.Errorf("Wrong frame 2: %v", err)
 | 
						|
		}
 | 
						|
		if err := checkFrame(frames[3], "main.main", fixture.Source, 12, false); err != nil {
 | 
						|
			t.Errorf("Wrong frame 3: %v", err)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func BenchmarkConditionalBreakpoints(b *testing.B) {
 | 
						|
	b.N = 1
 | 
						|
	withTestProcess("issue1549", b, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		bp := setFileBreakpoint(p, b, fixture.Source, 12)
 | 
						|
		bp.UserBreaklet().Cond = &ast.BinaryExpr{
 | 
						|
			Op: token.EQL,
 | 
						|
			X:  &ast.Ident{Name: "value"},
 | 
						|
			Y:  &ast.BasicLit{Kind: token.INT, Value: "-1"},
 | 
						|
		}
 | 
						|
		err := grp.Continue()
 | 
						|
		if _, exited := err.(proc.ErrProcessExited); !exited {
 | 
						|
			b.Fatalf("Unexpected error on Continue(): %v", err)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue1925(t *testing.T) {
 | 
						|
	// Calling a function should not leave cached goroutine information in an
 | 
						|
	// inconsistent state.
 | 
						|
	// In particular the stepInstructionOut function called at the end of a
 | 
						|
	// 'call' procedure should clean the G cache like every other function
 | 
						|
	// altering the state of the target process.
 | 
						|
	skipOn(t, "broken - pie mode", "linux", "ppc64le", "native", "pie")
 | 
						|
	protest.MustSupportFunctionCalls(t, testBackend)
 | 
						|
	withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		assertNoError(proc.EvalExpressionWithCalls(grp, p.SelectedGoroutine(), "afunc(2)", normalLoadConfig, true), t, "Call")
 | 
						|
		t.Logf("%v\n", p.SelectedGoroutine().CurrentLoc)
 | 
						|
		if loc := p.SelectedGoroutine().CurrentLoc; loc.File != fixture.Source {
 | 
						|
			t.Errorf("wrong location for selected goroutine after call: %s:%d", loc.File, loc.Line)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestStepoutOneliner(t *testing.T) {
 | 
						|
	// The heuristic detecting autogenerated wrappers when stepping out should
 | 
						|
	// not skip oneliner functions.
 | 
						|
	withTestProcess("issue2086", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		assertLineNumber(p, t, 15, "after first continue")
 | 
						|
		assertNoError(grp.StepOut(), t, "StepOut()")
 | 
						|
		if fn := p.BinInfo().PCToFunc(currentPC(p, t)); fn == nil || fn.Name != "main.T.m" {
 | 
						|
			t.Fatalf("wrong function after stepout %#v", fn)
 | 
						|
		}
 | 
						|
		assertNoError(grp.StepOut(), t, "second StepOut()")
 | 
						|
		if fn := p.BinInfo().PCToFunc(currentPC(p, t)); fn == nil || fn.Name != "main.main" {
 | 
						|
			t.Fatalf("wrong fnuction after second stepout %#v", fn)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestRequestManualStopWhileStopped(t *testing.T) {
 | 
						|
	// Requesting a manual stop while stopped shouldn't cause problems (issue #2138).
 | 
						|
	withTestProcess("issue2138", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		resumed := make(chan struct{})
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 8)
 | 
						|
		assertNoError(grp.Continue(), t, "Continue() 1")
 | 
						|
		grp.ResumeNotify(resumed)
 | 
						|
		go func() {
 | 
						|
			<-resumed
 | 
						|
			time.Sleep(1 * time.Second)
 | 
						|
			grp.RequestManualStop()
 | 
						|
		}()
 | 
						|
		t.Logf("at time.Sleep call")
 | 
						|
		assertNoError(grp.Continue(), t, "Continue() 2")
 | 
						|
		t.Logf("manually stopped")
 | 
						|
		grp.RequestManualStop()
 | 
						|
		grp.RequestManualStop()
 | 
						|
		grp.RequestManualStop()
 | 
						|
 | 
						|
		resumed = make(chan struct{})
 | 
						|
		grp.ResumeNotify(resumed)
 | 
						|
		go func() {
 | 
						|
			<-resumed
 | 
						|
			time.Sleep(1 * time.Second)
 | 
						|
			grp.RequestManualStop()
 | 
						|
		}()
 | 
						|
		t.Logf("resuming sleep")
 | 
						|
		assertNoError(grp.Continue(), t, "Continue() 3")
 | 
						|
		t.Logf("done")
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestStepOutPreservesGoroutine(t *testing.T) {
 | 
						|
	// Checks that StepOut preserves the currently selected goroutine.
 | 
						|
	rand.Seed(time.Now().Unix())
 | 
						|
	withTestProcess("issue2113", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
 | 
						|
		logState := func() {
 | 
						|
			g := p.SelectedGoroutine()
 | 
						|
			var goid int64 = -42
 | 
						|
			if g != nil {
 | 
						|
				goid = g.ID
 | 
						|
			}
 | 
						|
			pc := currentPC(p, t)
 | 
						|
			f, l, fn := p.BinInfo().PCToLine(pc)
 | 
						|
			var fnname = "???"
 | 
						|
			if fn != nil {
 | 
						|
				fnname = fn.Name
 | 
						|
			}
 | 
						|
			t.Logf("goroutine %d at %s:%d in %s", goid, f, l, fnname)
 | 
						|
		}
 | 
						|
 | 
						|
		logState()
 | 
						|
 | 
						|
		gs, _, err := proc.GoroutinesInfo(p, 0, 0)
 | 
						|
		assertNoError(err, t, "GoroutinesInfo")
 | 
						|
		candg := []*proc.G{}
 | 
						|
		bestg := []*proc.G{}
 | 
						|
		for _, g := range gs {
 | 
						|
			t.Logf("stacktracing goroutine %d (%v)\n", g.ID, g.CurrentLoc)
 | 
						|
			frames, err := proc.GoroutineStacktrace(p, g, 20, 0)
 | 
						|
			assertNoError(err, t, "Stacktrace")
 | 
						|
			for _, frame := range frames {
 | 
						|
				if frame.Call.Fn != nil && frame.Call.Fn.Name == "main.coroutine" {
 | 
						|
					candg = append(candg, g)
 | 
						|
					if g.Thread != nil && frames[0].Call.Fn != nil && strings.HasPrefix(frames[0].Call.Fn.Name, "runtime.") {
 | 
						|
						bestg = append(bestg, g)
 | 
						|
					}
 | 
						|
					break
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		var pickg *proc.G
 | 
						|
		if len(bestg) > 0 {
 | 
						|
			pickg = bestg[rand.Intn(len(bestg))]
 | 
						|
			t.Logf("selected goroutine %d (best)\n", pickg.ID)
 | 
						|
		} else {
 | 
						|
			pickg = candg[rand.Intn(len(candg))]
 | 
						|
			t.Logf("selected goroutine %d\n", pickg.ID)
 | 
						|
		}
 | 
						|
		goid := pickg.ID
 | 
						|
		assertNoError(p.SwitchGoroutine(pickg), t, "SwitchGoroutine")
 | 
						|
 | 
						|
		logState()
 | 
						|
 | 
						|
		err = grp.StepOut()
 | 
						|
		if errors.As(err, &proc.ErrProcessExited{}) {
 | 
						|
			return
 | 
						|
		}
 | 
						|
		assertNoError(err, t, "StepOut()")
 | 
						|
 | 
						|
		logState()
 | 
						|
 | 
						|
		g2 := p.SelectedGoroutine()
 | 
						|
		if g2 == nil {
 | 
						|
			t.Fatalf("no selected goroutine after stepout")
 | 
						|
		} else if g2.ID != goid {
 | 
						|
			t.Fatalf("unexpected selected goroutine %d", g2.ID)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue2319(t *testing.T) {
 | 
						|
	// Check to make sure we don't crash on startup when the target is
 | 
						|
	// a binary with a mix of DWARF-5 C++ compilation units and
 | 
						|
	// DWARF-4 Go compilation units.
 | 
						|
 | 
						|
	// Require CGO, since we need to use the external linker for this test.
 | 
						|
	protest.MustHaveCgo(t)
 | 
						|
 | 
						|
	// The test fixture uses linux/amd64 assembly and a *.syso file
 | 
						|
	// that is linux/amd64, so skip for other architectures.
 | 
						|
	if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
 | 
						|
		t.Skipf("skipping since not linux/amd64")
 | 
						|
	}
 | 
						|
 | 
						|
	// Skip unless on 1.14 or later. The test fixture uses a *.syso
 | 
						|
	// file, which in 1.13 is not loaded unless we're in internal
 | 
						|
	// linking mode (we need external linking here).
 | 
						|
	if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 14) {
 | 
						|
		t.Skip("test contains fixture that is specific to go 1.14+")
 | 
						|
	}
 | 
						|
 | 
						|
	fixture := protest.BuildFixture(t, "issue2319/", protest.BuildModeExternalLinker)
 | 
						|
 | 
						|
	// Load up the binary and make sure there are no crashes.
 | 
						|
	bi := proc.NewBinaryInfo("linux", "amd64")
 | 
						|
	assertNoError(bi.LoadBinaryInfo(fixture.Path, 0, nil), t, "LoadBinaryInfo")
 | 
						|
}
 | 
						|
 | 
						|
func TestDump(t *testing.T) {
 | 
						|
	if (runtime.GOOS == "darwin" && testBackend == "native") || (runtime.GOOS == "windows" && runtime.GOARCH != "amd64") {
 | 
						|
		t.Skip("not supported")
 | 
						|
	}
 | 
						|
	skipOn(t, "not implemented", "ppc64le")
 | 
						|
 | 
						|
	convertRegisters := func(arch *proc.Arch, dregs op.DwarfRegisters) string {
 | 
						|
		dregs.Reg(^uint64(0))
 | 
						|
		buf := new(bytes.Buffer)
 | 
						|
		for i := 0; i < dregs.CurrentSize(); i++ {
 | 
						|
			reg := dregs.Reg(uint64(i))
 | 
						|
			if reg == nil {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			name, _, repr := arch.DwarfRegisterToString(i, reg)
 | 
						|
			fmt.Fprintf(buf, " %s=%s", name, repr)
 | 
						|
		}
 | 
						|
		return buf.String()
 | 
						|
	}
 | 
						|
 | 
						|
	convertThread := func(thread proc.Thread) string {
 | 
						|
		regs, err := thread.Registers()
 | 
						|
		assertNoError(err, t, fmt.Sprintf("Thread registers %d", thread.ThreadID()))
 | 
						|
		arch := thread.BinInfo().Arch
 | 
						|
		dregs := arch.RegistersToDwarfRegisters(0, regs)
 | 
						|
		return fmt.Sprintf("%08d %s", thread.ThreadID(), convertRegisters(arch, *dregs))
 | 
						|
	}
 | 
						|
 | 
						|
	convertThreads := func(threads []proc.Thread) []string {
 | 
						|
		r := make([]string, len(threads))
 | 
						|
		for i := range threads {
 | 
						|
			r[i] = convertThread(threads[i])
 | 
						|
		}
 | 
						|
		sort.Strings(r)
 | 
						|
		return r
 | 
						|
	}
 | 
						|
 | 
						|
	convertGoroutine := func(g *proc.G) string {
 | 
						|
		threadID := 0
 | 
						|
		if g.Thread != nil {
 | 
						|
			threadID = g.Thread.ThreadID()
 | 
						|
		}
 | 
						|
		return fmt.Sprintf("%d pc=%#x sp=%#x bp=%#x lr=%#x gopc=%#x startpc=%#x systemstack=%v thread=%d", g.ID, g.PC, g.SP, g.BP, g.LR, g.GoPC, g.StartPC, g.SystemStack, threadID)
 | 
						|
	}
 | 
						|
 | 
						|
	convertFrame := func(arch *proc.Arch, frame *proc.Stackframe) string {
 | 
						|
		return fmt.Sprintf("currentPC=%#x callPC=%#x frameOff=%#x\n", frame.Current.PC, frame.Call.PC, frame.FrameOffset())
 | 
						|
	}
 | 
						|
 | 
						|
	makeDump := func(p *proc.Target, corePath, exePath string, flags proc.DumpFlags) *proc.Target {
 | 
						|
		fh, err := os.Create(corePath)
 | 
						|
		assertNoError(err, t, "Create()")
 | 
						|
		var state proc.DumpState
 | 
						|
		p.Dump(fh, flags, &state)
 | 
						|
		assertNoError(state.Err, t, "Dump()")
 | 
						|
		if state.ThreadsDone != state.ThreadsTotal || state.MemDone != state.MemTotal || !state.AllDone || state.Dumping || state.Canceled {
 | 
						|
			t.Fatalf("bad DumpState %#v", &state)
 | 
						|
		}
 | 
						|
		c, err := core.OpenCore(corePath, exePath, nil)
 | 
						|
		assertNoError(err, t, "OpenCore()")
 | 
						|
		return c.Selected
 | 
						|
	}
 | 
						|
 | 
						|
	testDump := func(p, c *proc.Target) {
 | 
						|
		if p.Pid() != c.Pid() {
 | 
						|
			t.Errorf("Pid mismatch %x %x", p.Pid(), c.Pid())
 | 
						|
		}
 | 
						|
 | 
						|
		threads := convertThreads(p.ThreadList())
 | 
						|
		cthreads := convertThreads(c.ThreadList())
 | 
						|
 | 
						|
		if len(threads) != len(cthreads) {
 | 
						|
			t.Errorf("Thread number mismatch %d %d", len(threads), len(cthreads))
 | 
						|
		}
 | 
						|
 | 
						|
		for i := range threads {
 | 
						|
			if threads[i] != cthreads[i] {
 | 
						|
				t.Errorf("Thread mismatch\nlive:\t%s\ncore:\t%s", threads[i], cthreads[i])
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		gos, _, err := proc.GoroutinesInfo(p, 0, 0)
 | 
						|
		assertNoError(err, t, "GoroutinesInfo() - live process")
 | 
						|
		cgos, _, err := proc.GoroutinesInfo(c, 0, 0)
 | 
						|
		assertNoError(err, t, "GoroutinesInfo() - core dump")
 | 
						|
 | 
						|
		if len(gos) != len(cgos) {
 | 
						|
			t.Errorf("Goroutine number mismatch %d %d", len(gos), len(cgos))
 | 
						|
		}
 | 
						|
 | 
						|
		var scope, cscope *proc.EvalScope
 | 
						|
 | 
						|
		for i := range gos {
 | 
						|
			if convertGoroutine(gos[i]) != convertGoroutine(cgos[i]) {
 | 
						|
				t.Errorf("Goroutine mismatch\nlive:\t%s\ncore:\t%s", convertGoroutine(gos[i]), convertGoroutine(cgos[i]))
 | 
						|
			}
 | 
						|
 | 
						|
			frames, err := proc.GoroutineStacktrace(p, gos[i], 20, 0)
 | 
						|
			assertNoError(err, t, fmt.Sprintf("Stacktrace for goroutine %d - live process", gos[i].ID))
 | 
						|
			cframes, err := proc.GoroutineStacktrace(c, cgos[i], 20, 0)
 | 
						|
			assertNoError(err, t, fmt.Sprintf("Stacktrace for goroutine %d - core dump", gos[i].ID))
 | 
						|
 | 
						|
			if len(frames) != len(cframes) {
 | 
						|
				t.Errorf("Frame number mismatch for goroutine %d: %d %d", gos[i].ID, len(frames), len(cframes))
 | 
						|
			}
 | 
						|
 | 
						|
			for j := range frames {
 | 
						|
				if convertFrame(p.BinInfo().Arch, &frames[j]) != convertFrame(p.BinInfo().Arch, &cframes[j]) {
 | 
						|
					t.Errorf("Frame mismatch %d.%d\nlive:\t%s\ncore:\t%s", gos[i].ID, j, convertFrame(p.BinInfo().Arch, &frames[j]), convertFrame(p.BinInfo().Arch, &cframes[j]))
 | 
						|
				}
 | 
						|
				if frames[j].Call.Fn != nil && frames[j].Call.Fn.Name == "main.main" {
 | 
						|
					scope = proc.FrameToScope(p, p.Memory(), gos[i], 0, frames[j:]...)
 | 
						|
					cscope = proc.FrameToScope(c, c.Memory(), cgos[i], 0, cframes[j:]...)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		vars, err := scope.LocalVariables(normalLoadConfig)
 | 
						|
		assertNoError(err, t, "LocalVariables - live process")
 | 
						|
		cvars, err := cscope.LocalVariables(normalLoadConfig)
 | 
						|
		assertNoError(err, t, "LocalVariables - core dump")
 | 
						|
 | 
						|
		if len(vars) != len(cvars) {
 | 
						|
			t.Errorf("Variable number mismatch %d %d", len(vars), len(cvars))
 | 
						|
		}
 | 
						|
 | 
						|
		for i := range vars {
 | 
						|
			varstr := vars[i].Name + "=" + api.ConvertVar(vars[i]).SinglelineString()
 | 
						|
			cvarstr := cvars[i].Name + "=" + api.ConvertVar(cvars[i]).SinglelineString()
 | 
						|
			if strings.Contains(varstr, "(unreadable") {
 | 
						|
				// errors reading from unmapped memory differ between live process and core
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			if varstr != cvarstr {
 | 
						|
				t.Errorf("Variable mismatch %s %s", varstr, cvarstr)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		corePath := filepath.Join(fixture.BuildDir, "coredump")
 | 
						|
		corePathPlatIndep := filepath.Join(fixture.BuildDir, "coredump-indep")
 | 
						|
 | 
						|
		t.Logf("testing normal dump")
 | 
						|
 | 
						|
		c := makeDump(p, corePath, fixture.Path, 0)
 | 
						|
		defer os.Remove(corePath)
 | 
						|
		testDump(p, c)
 | 
						|
 | 
						|
		if runtime.GOOS == "linux" && runtime.GOARCH == "amd64" {
 | 
						|
			// No reason to do this test on other goos/goarch because they use the
 | 
						|
			// platform-independent format anyway.
 | 
						|
			t.Logf("testing platform-independent dump")
 | 
						|
			c2 := makeDump(p, corePathPlatIndep, fixture.Path, proc.DumpPlatformIndependent)
 | 
						|
			defer os.Remove(corePathPlatIndep)
 | 
						|
			testDump(p, c2)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestCompositeMemoryWrite(t *testing.T) {
 | 
						|
	if runtime.GOARCH != "amd64" {
 | 
						|
		t.Skip("only valid on amd64")
 | 
						|
	}
 | 
						|
	skipOn(t, "not implemented", "freebsd")
 | 
						|
	withTestProcess("fputest/", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		getregs := func() (pc, rax, xmm1 uint64) {
 | 
						|
			regs, err := p.CurrentThread().Registers()
 | 
						|
			assertNoError(err, t, "Registers")
 | 
						|
			fmtregs, err := regs.Slice(true)
 | 
						|
			assertNoError(err, t, "register slice")
 | 
						|
 | 
						|
			var xmm1buf []byte
 | 
						|
 | 
						|
			for _, reg := range fmtregs {
 | 
						|
				switch strings.ToLower(reg.Name) {
 | 
						|
				case "rax":
 | 
						|
					rax = reg.Reg.Uint64Val
 | 
						|
				case "xmm1":
 | 
						|
					xmm1buf = reg.Reg.Bytes
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			xmm1 = binary.LittleEndian.Uint64(xmm1buf[:8])
 | 
						|
 | 
						|
			return regs.PC(), rax, xmm1
 | 
						|
		}
 | 
						|
 | 
						|
		const fakeAddress = 0xbeef0000
 | 
						|
 | 
						|
		getmem := func(mem proc.MemoryReader) uint64 {
 | 
						|
			buf := make([]byte, 8)
 | 
						|
			_, err := mem.ReadMemory(buf, fakeAddress)
 | 
						|
			assertNoError(err, t, "ReadMemory")
 | 
						|
			return binary.LittleEndian.Uint64(buf)
 | 
						|
		}
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		oldPc, oldRax, oldXmm1 := getregs()
 | 
						|
		t.Logf("PC %#x AX %#x XMM1 %#x", oldPc, oldRax, oldXmm1)
 | 
						|
 | 
						|
		memRax, err := proc.NewCompositeMemory(p, []op.Piece{{Size: 0, Val: 0, Kind: op.RegPiece}}, fakeAddress)
 | 
						|
		assertNoError(err, t, "NewCompositeMemory (rax)")
 | 
						|
		memXmm1, err := proc.NewCompositeMemory(p, []op.Piece{{Size: 0, Val: 18, Kind: op.RegPiece}}, fakeAddress)
 | 
						|
		assertNoError(err, t, "NewCompositeMemory (xmm1)")
 | 
						|
 | 
						|
		if memRax := getmem(memRax); memRax != oldRax {
 | 
						|
			t.Errorf("reading rax memory, expected %#x got %#x", oldRax, memRax)
 | 
						|
		}
 | 
						|
		if memXmm1 := getmem(memXmm1); memXmm1 != oldXmm1 {
 | 
						|
			t.Errorf("reading xmm1 memory, expected %#x got %#x", oldXmm1, memXmm1)
 | 
						|
		}
 | 
						|
 | 
						|
		_, err = memRax.WriteMemory(0xbeef0000, []byte{0xef, 0xbe, 0x0d, 0xf0, 0xef, 0xbe, 0x0d, 0xf0})
 | 
						|
		assertNoError(err, t, "WriteMemory (rax)")
 | 
						|
		_, err = memXmm1.WriteMemory(0xbeef0000, []byte{0xef, 0xbe, 0x0d, 0xf0, 0xef, 0xbe, 0x0d, 0xf0})
 | 
						|
		assertNoError(err, t, "WriteMemory (xmm1)")
 | 
						|
 | 
						|
		newPc, newRax, newXmm1 := getregs()
 | 
						|
		t.Logf("PC %#x AX %#x XMM1 %#x", newPc, newRax, newXmm1)
 | 
						|
 | 
						|
		const tgt = 0xf00dbeeff00dbeef
 | 
						|
		if newRax != tgt {
 | 
						|
			t.Errorf("reading rax register, expected %#x, got %#x", uint64(tgt), newRax)
 | 
						|
		}
 | 
						|
		if newXmm1 != tgt {
 | 
						|
			t.Errorf("reading xmm1 register, expected %#x, got %#x", uint64(tgt), newXmm1)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestVariablesWithExternalLinking(t *testing.T) {
 | 
						|
	protest.MustHaveCgo(t)
 | 
						|
	// Tests that macOSDebugFrameBugWorkaround works.
 | 
						|
	// See:
 | 
						|
	//  https://github.com/golang/go/issues/25841
 | 
						|
	//  https://github.com/go-delve/delve/issues/2346
 | 
						|
	withTestProcessArgs("testvariables2", t, ".", []string{}, protest.BuildModeExternalLinker, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		str1Var := evalVariable(p, t, "str1")
 | 
						|
		if str1Var.Unreadable != nil {
 | 
						|
			t.Fatalf("variable str1 is unreadable: %v", str1Var.Unreadable)
 | 
						|
		}
 | 
						|
		t.Logf("%#v", str1Var)
 | 
						|
		if constant.StringVal(str1Var.Value) != "01234567890" {
 | 
						|
			t.Fatalf("wrong value for str1: %v", str1Var.Value)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestWatchpointsBasic(t *testing.T) {
 | 
						|
	skipOn(t, "not implemented", "freebsd")
 | 
						|
	skipOn(t, "not implemented", "386")
 | 
						|
	skipOn(t, "not implemented", "ppc64le")
 | 
						|
	skipOn(t, "not implemented", "riscv64")
 | 
						|
	skipOn(t, "not implemented", "loong64")
 | 
						|
	skipOn(t, "see https://github.com/go-delve/delve/issues/2768", "windows")
 | 
						|
	protest.AllowRecording(t)
 | 
						|
 | 
						|
	position1 := []int{18, 19}
 | 
						|
	position5 := []int{40, 41}
 | 
						|
 | 
						|
	withTestProcess("databpeasy", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFunctionBreakpoint(p, t, "main.main")
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 21) // Position 2 breakpoint
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 27) // Position 4 breakpoint
 | 
						|
		assertNoError(grp.Continue(), t, "Continue 0")
 | 
						|
		assertLineNumber(p, t, 13, "Continue 0") // Position 0
 | 
						|
 | 
						|
		scope, err := proc.GoroutineScope(p, p.CurrentThread())
 | 
						|
		assertNoError(err, t, "GoroutineScope")
 | 
						|
 | 
						|
		bp, err := p.SetWatchpoint(0, scope, "globalvar1", proc.WatchWrite, nil)
 | 
						|
		assertNoError(err, t, "SetDataBreakpoint(write-only)")
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue 1")
 | 
						|
		assertLineNumberIn(p, t, position1, "Continue 1") // Position 1
 | 
						|
 | 
						|
		if curbp := p.CurrentThread().Breakpoint().Breakpoint; curbp == nil || (curbp.LogicalID() != bp.LogicalID()) {
 | 
						|
			t.Fatal("breakpoint not set")
 | 
						|
		}
 | 
						|
 | 
						|
		assertNoError(p.ClearBreakpoint(bp.Addr), t, "ClearBreakpoint")
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue 2")
 | 
						|
		assertLineNumber(p, t, 21, "Continue 2") // Position 2
 | 
						|
 | 
						|
		_, err = p.SetWatchpoint(0, scope, "globalvar1", proc.WatchWrite|proc.WatchRead, nil)
 | 
						|
		assertNoError(err, t, "SetDataBreakpoint(read-write)")
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue 3")
 | 
						|
		assertLineNumber(p, t, 22, "Continue 3") // Position 3
 | 
						|
 | 
						|
		p.ClearBreakpoint(bp.Addr)
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue 4")
 | 
						|
		assertLineNumber(p, t, 27, "Continue 4") // Position 4
 | 
						|
 | 
						|
		t.Logf("setting final breakpoint")
 | 
						|
		_, err = p.SetWatchpoint(0, scope, "globalvar1", proc.WatchWrite, nil)
 | 
						|
		assertNoError(err, t, "SetDataBreakpoint(write-only, again)")
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue 5")
 | 
						|
		assertLineNumberIn(p, t, position5, "Continue 5") // Position 5
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestWatchpointCounts(t *testing.T) {
 | 
						|
	skipOn(t, "not implemented", "freebsd")
 | 
						|
	skipOn(t, "not implemented", "386")
 | 
						|
	skipOn(t, "see https://github.com/go-delve/delve/issues/2768", "windows")
 | 
						|
	skipOn(t, "not implemented", "ppc64le")
 | 
						|
	skipOn(t, "not implemented", "riscv64")
 | 
						|
	skipOn(t, "not implemented", "loong64")
 | 
						|
	if _, isTeamCityTest := os.LookupEnv("TEAMCITY_VERSION"); isTeamCityTest {
 | 
						|
		skipOn(t, "CI is running a version of macOS that is too old (11.2)", "darwin", "arm64")
 | 
						|
	}
 | 
						|
	protest.AllowRecording(t)
 | 
						|
 | 
						|
	withTestProcess("databpcountstest", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFunctionBreakpoint(p, t, "main.main")
 | 
						|
		assertNoError(grp.Continue(), t, "Continue 0")
 | 
						|
 | 
						|
		scope, err := proc.GoroutineScope(p, p.CurrentThread())
 | 
						|
		assertNoError(err, t, "GoroutineScope")
 | 
						|
 | 
						|
		bp, err := p.SetWatchpoint(0, scope, "globalvar1", proc.WatchWrite, nil)
 | 
						|
		assertNoError(err, t, "SetWatchpoint(write-only)")
 | 
						|
 | 
						|
		for {
 | 
						|
			err := grp.Continue()
 | 
						|
			if errors.As(err, &proc.ErrProcessExited{}) {
 | 
						|
				break
 | 
						|
			}
 | 
						|
			assertNoError(err, t, "Continue()")
 | 
						|
		}
 | 
						|
 | 
						|
		t.Logf("TotalHitCount: %d", bp.Logical.TotalHitCount)
 | 
						|
		if bp.Logical.TotalHitCount != 200 {
 | 
						|
			t.Fatalf("Wrong TotalHitCount for the breakpoint (%d)", bp.Logical.TotalHitCount)
 | 
						|
		}
 | 
						|
 | 
						|
		if len(bp.Logical.HitCount) != 2 {
 | 
						|
			t.Fatalf("Wrong number of goroutines for breakpoint (%d)", len(bp.Logical.HitCount))
 | 
						|
		}
 | 
						|
 | 
						|
		for _, v := range bp.Logical.HitCount {
 | 
						|
			if v != 100 {
 | 
						|
				t.Fatalf("Wrong HitCount for breakpoint (%v)", bp.Logical.HitCount)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestManualStopWhileStopped(t *testing.T) {
 | 
						|
	// Checks that RequestManualStop sent to a stopped thread does not cause the target process to die.
 | 
						|
	withTestProcess("loopprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		asyncCont := func(done chan struct{}) {
 | 
						|
			defer close(done)
 | 
						|
			err := grp.Continue()
 | 
						|
			t.Logf("%v\n", err)
 | 
						|
			if err != nil {
 | 
						|
				panic(err)
 | 
						|
			}
 | 
						|
			for _, th := range p.ThreadList() {
 | 
						|
				if th.Breakpoint().Breakpoint != nil {
 | 
						|
					t.Logf("unexpected stop at breakpoint: %v", th.Breakpoint().Breakpoint)
 | 
						|
					panic("unexpected stop at breakpoint")
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		const (
 | 
						|
			repeatsSlow = 3
 | 
						|
			repeatsFast = 5
 | 
						|
		)
 | 
						|
 | 
						|
		for i := range repeatsSlow {
 | 
						|
			t.Logf("Continue %d (slow)", i)
 | 
						|
			done := make(chan struct{})
 | 
						|
			go asyncCont(done)
 | 
						|
			time.Sleep(1 * time.Second)
 | 
						|
			grp.RequestManualStop()
 | 
						|
			time.Sleep(1 * time.Second)
 | 
						|
			grp.RequestManualStop()
 | 
						|
			time.Sleep(1 * time.Second)
 | 
						|
			<-done
 | 
						|
		}
 | 
						|
		for i := range repeatsFast {
 | 
						|
			t.Logf("Continue %d (fast)", i)
 | 
						|
			rch := make(chan struct{})
 | 
						|
			done := make(chan struct{})
 | 
						|
			grp.ResumeNotify(rch)
 | 
						|
			go asyncCont(done)
 | 
						|
			<-rch
 | 
						|
			grp.RequestManualStop()
 | 
						|
			grp.RequestManualStop()
 | 
						|
			<-done
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestDwrapStartLocation(t *testing.T) {
 | 
						|
	// Tests that the start location of a goroutine is unwrapped in Go 1.17 and later.
 | 
						|
	withTestProcess("goroutinestackprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFunctionBreakpoint(p, t, "main.stacktraceme")
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		gs, _, err := proc.GoroutinesInfo(p, 0, 0)
 | 
						|
		assertNoError(err, t, "GoroutinesInfo")
 | 
						|
		found := false
 | 
						|
		for _, g := range gs {
 | 
						|
			startLoc := g.StartLoc(p)
 | 
						|
			if startLoc.Fn == nil {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			t.Logf("%#v\n", startLoc.Fn.Name)
 | 
						|
			if startLoc.Fn.Name == "main.agoroutine" {
 | 
						|
				found = true
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if !found {
 | 
						|
			t.Errorf("could not find any goroutine with a start location of main.agoroutine")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestWatchpointStack(t *testing.T) {
 | 
						|
	skipOn(t, "not implemented", "freebsd")
 | 
						|
	skipOn(t, "not implemented", "386")
 | 
						|
	skipOn(t, "not implemented", "ppc64le")
 | 
						|
	skipOn(t, "not implemented", "riscv64")
 | 
						|
	skipOn(t, "not implemented", "loong64")
 | 
						|
	skipOn(t, "see https://github.com/go-delve/delve/issues/2768", "windows")
 | 
						|
	if _, isTeamCityTest := os.LookupEnv("TEAMCITY_VERSION"); isTeamCityTest {
 | 
						|
		skipOn(t, "CI is running a version of macOS that is too old (11.2)", "darwin", "arm64")
 | 
						|
	}
 | 
						|
	protest.AllowRecording(t)
 | 
						|
 | 
						|
	position1 := []int{16, 17}
 | 
						|
 | 
						|
	withTestProcess("databpstack", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 11) // Position 0 breakpoint
 | 
						|
		clearlen := len(p.Breakpoints().M)
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue 0")
 | 
						|
		assertLineNumber(p, t, 11, "Continue 0") // Position 0
 | 
						|
 | 
						|
		scope, err := proc.GoroutineScope(p, p.CurrentThread())
 | 
						|
		assertNoError(err, t, "GoroutineScope")
 | 
						|
 | 
						|
		_, err = p.SetWatchpoint(0, scope, "w", proc.WatchWrite, nil)
 | 
						|
		assertNoError(err, t, "SetDataBreakpoint(write-only)")
 | 
						|
 | 
						|
		watchbpnum := 3
 | 
						|
		if recorded, _ := grp.Recorded(); recorded {
 | 
						|
			watchbpnum = 4
 | 
						|
		}
 | 
						|
 | 
						|
		if len(p.Breakpoints().M) != clearlen+watchbpnum {
 | 
						|
			// want 1 watchpoint, 1 stack resize breakpoint, 1 out of scope sentinel (2 if recorded)
 | 
						|
			t.Errorf("wrong number of breakpoints after setting watchpoint: %d", len(p.Breakpoints().M)-clearlen)
 | 
						|
		}
 | 
						|
 | 
						|
		var retaddr uint64
 | 
						|
		for _, bp := range p.Breakpoints().M {
 | 
						|
			for _, breaklet := range bp.Breaklets {
 | 
						|
				if breaklet.Kind&proc.WatchOutOfScopeBreakpoint != 0 {
 | 
						|
					retaddr = bp.Addr
 | 
						|
					break
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// Note: for recorded processes retaddr will not always be the return
 | 
						|
		// address, ~50% of the times it will be the address of the CALL
 | 
						|
		// instruction preceding the return address, this does not matter for this
 | 
						|
		// test.
 | 
						|
 | 
						|
		_, err = p.SetBreakpoint(0, retaddr, proc.UserBreakpoint, nil)
 | 
						|
		assertNoError(err, t, "SetBreakpoint")
 | 
						|
 | 
						|
		if len(p.Breakpoints().M) != clearlen+watchbpnum {
 | 
						|
			// want 1 watchpoint, 1 stack resize breakpoint, 1 out of scope sentinel (which is also a user breakpoint) (and another out of scope sentinel if recorded)
 | 
						|
			t.Errorf("wrong number of breakpoints after setting watchpoint: %d", len(p.Breakpoints().M)-clearlen)
 | 
						|
		}
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue 1")
 | 
						|
		assertLineNumberIn(p, t, position1, "Continue 1") // Position 1
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue 2")
 | 
						|
		t.Logf("%#v", p.CurrentThread().Breakpoint().Breakpoint)
 | 
						|
		assertLineNumber(p, t, 24, "Continue 2") // Position 2 (watchpoint gone out of scope)
 | 
						|
 | 
						|
		if len(p.Breakpoints().M) != clearlen+1 {
 | 
						|
			// want 1 user breakpoint set at retaddr
 | 
						|
			t.Errorf("wrong number of breakpoints after watchpoint goes out of scope: %d", len(p.Breakpoints().M)-clearlen)
 | 
						|
		}
 | 
						|
 | 
						|
		if len(p.Breakpoints().WatchOutOfScope) != 1 {
 | 
						|
			t.Errorf("wrong number of out-of-scope watchpoints after watchpoint goes out of scope: %d", len(p.Breakpoints().WatchOutOfScope))
 | 
						|
		}
 | 
						|
 | 
						|
		err = p.ClearBreakpoint(retaddr)
 | 
						|
		assertNoError(err, t, "ClearBreakpoint")
 | 
						|
 | 
						|
		if len(p.Breakpoints().M) != clearlen {
 | 
						|
			// want 1 user breakpoint set at retaddr
 | 
						|
			t.Errorf("wrong number of breakpoints after removing user breakpoint: %d", len(p.Breakpoints().M)-clearlen)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestWatchpointStackBackwardsOutOfScope(t *testing.T) {
 | 
						|
	skipUnlessOn(t, "only for recorded targets", "rr")
 | 
						|
	protest.AllowRecording(t)
 | 
						|
 | 
						|
	withTestProcess("databpstack", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 11) // Position 0 breakpoint
 | 
						|
		clearlen := len(p.Breakpoints().M)
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue 0")
 | 
						|
		assertLineNumber(p, t, 11, "Continue 0") // Position 0
 | 
						|
 | 
						|
		scope, err := proc.GoroutineScope(p, p.CurrentThread())
 | 
						|
		assertNoError(err, t, "GoroutineScope")
 | 
						|
 | 
						|
		_, err = p.SetWatchpoint(0, scope, "w", proc.WatchWrite, nil)
 | 
						|
		assertNoError(err, t, "SetDataBreakpoint(write-only)")
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue 1")
 | 
						|
		assertLineNumber(p, t, 17, "Continue 1") // Position 1
 | 
						|
 | 
						|
		grp.ChangeDirection(proc.Backward)
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue 2")
 | 
						|
		t.Logf("%#v", p.CurrentThread().Breakpoint().Breakpoint)
 | 
						|
		assertLineNumber(p, t, 16, "Continue 2") // Position 1 again (because of inverted movement)
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue 3")
 | 
						|
		t.Logf("%#v", p.CurrentThread().Breakpoint().Breakpoint)
 | 
						|
		assertLineNumber(p, t, 11, "Continue 3") // Position 0 (breakpoint 1 hit)
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue 4")
 | 
						|
		t.Logf("%#v", p.CurrentThread().Breakpoint().Breakpoint)
 | 
						|
		assertLineNumber(p, t, 23, "Continue 4") // Position 2 (watchpoint gone out of scope)
 | 
						|
 | 
						|
		if len(p.Breakpoints().M) != clearlen {
 | 
						|
			t.Errorf("wrong number of breakpoints after watchpoint goes out of scope: %d", len(p.Breakpoints().M)-clearlen)
 | 
						|
		}
 | 
						|
 | 
						|
		if len(p.Breakpoints().WatchOutOfScope) != 1 {
 | 
						|
			t.Errorf("wrong number of out-of-scope watchpoints after watchpoint goes out of scope: %d", len(p.Breakpoints().WatchOutOfScope))
 | 
						|
		}
 | 
						|
 | 
						|
		if len(p.Breakpoints().M) != clearlen {
 | 
						|
			// want 1 user breakpoint set at retaddr
 | 
						|
			t.Errorf("wrong number of breakpoints after removing user breakpoint: %d", len(p.Breakpoints().M)-clearlen)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestSetOnFunctions(t *testing.T) {
 | 
						|
	// The set command between function variables should fail with an error
 | 
						|
	// Issue #2691
 | 
						|
	withTestProcess("goroutinestackprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFunctionBreakpoint(p, t, "main.main")
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		scope, err := proc.GoroutineScope(p, p.CurrentThread())
 | 
						|
		assertNoError(err, t, "GoroutineScope")
 | 
						|
		err = scope.SetVariable("main.func1", "main.func2")
 | 
						|
		if err == nil {
 | 
						|
			t.Fatal("expected error when assigning between function variables")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestNilPtrDerefInBreakInstr(t *testing.T) {
 | 
						|
	// Checks that having a breakpoint on the exact instruction that causes a
 | 
						|
	// nil pointer dereference does not cause problems.
 | 
						|
 | 
						|
	var asmfile string
 | 
						|
	switch runtime.GOARCH {
 | 
						|
	case "amd64":
 | 
						|
		asmfile = "main_amd64.s"
 | 
						|
	case "arm64":
 | 
						|
		asmfile = "main_arm64.s"
 | 
						|
	case "386":
 | 
						|
		asmfile = "main_386.s"
 | 
						|
	case "ppc64le":
 | 
						|
		asmfile = "main_ppc64le.s"
 | 
						|
	case "riscv64":
 | 
						|
		asmfile = "main_riscv64.s"
 | 
						|
	case "loong64":
 | 
						|
		asmfile = "main_loong64.s"
 | 
						|
	default:
 | 
						|
		t.Fatalf("assembly file for %s not provided", runtime.GOARCH)
 | 
						|
	}
 | 
						|
 | 
						|
	withTestProcess("asmnilptr/", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		f := filepath.Join(fixture.BuildDir, asmfile)
 | 
						|
		f = strings.Replace(f, "\\", "/", -1)
 | 
						|
		setFileBreakpoint(p, t, f, 5)
 | 
						|
		t.Logf("first continue")
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		t.Logf("second continue")
 | 
						|
		err := grp.Continue()
 | 
						|
		if runtime.GOOS == "darwin" && err != nil && err.Error() == "bad access" {
 | 
						|
			// this is also ok
 | 
						|
			return
 | 
						|
		}
 | 
						|
		t.Logf("third continue")
 | 
						|
		assertNoError(err, t, "Continue()")
 | 
						|
		bp := p.CurrentThread().Breakpoint()
 | 
						|
		if bp != nil {
 | 
						|
			t.Logf("%#v\n", bp.Breakpoint)
 | 
						|
		}
 | 
						|
		if bp == nil || (bp.Logical.Name != proc.UnrecoveredPanic) {
 | 
						|
			t.Fatalf("no breakpoint hit or wrong breakpoint hit: %#v", bp)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestStepIntoAutogeneratedSkip(t *testing.T) {
 | 
						|
	// Tests that autogenerated functions are skipped with the new naming
 | 
						|
	// scheme for autogenerated functions (issue #2948).
 | 
						|
	withTestProcess("stepintobug", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 9)
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		assertNoError(grp.Step(), t, "Step")
 | 
						|
		assertLineNumber(p, t, 12, "After step")
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestFollowExec(t *testing.T) {
 | 
						|
	skipOn(t, "follow exec not implemented on freebsd", "freebsd")
 | 
						|
	skipOn(t, "follow exec not implemented on macOS", "darwin")
 | 
						|
	withTestProcessArgs("spawn", t, ".", []string{"spawn", "3"}, 0, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		grp.LogicalBreakpoints[1] = &proc.LogicalBreakpoint{LogicalID: 1, Set: proc.SetBreakpoint{FunctionName: "main.traceme1"}, HitCount: make(map[int64]uint64)}
 | 
						|
		grp.LogicalBreakpoints[2] = &proc.LogicalBreakpoint{LogicalID: 2, Set: proc.SetBreakpoint{FunctionName: "main.traceme2"}, HitCount: make(map[int64]uint64)}
 | 
						|
		grp.LogicalBreakpoints[3] = &proc.LogicalBreakpoint{LogicalID: 3, Set: proc.SetBreakpoint{FunctionName: "main.traceme3"}, HitCount: make(map[int64]uint64)}
 | 
						|
 | 
						|
		assertNoError(grp.SetBreakpointEnabled(grp.LogicalBreakpoints[1], true), t, "EnableBreakpoint(main.traceme1)")
 | 
						|
		assertNoError(grp.SetBreakpointEnabled(grp.LogicalBreakpoints[2], true), t, "EnableBreakpoint(main.traceme2)")
 | 
						|
		assertNoError(grp.SetBreakpointEnabled(grp.LogicalBreakpoints[3], true), t, "EnableBreakpoint(main.traceme3)")
 | 
						|
 | 
						|
		assertNoError(grp.FollowExec(true, ""), t, "FollowExec")
 | 
						|
 | 
						|
		first := true
 | 
						|
		finished := false
 | 
						|
		pids := map[int]int{}
 | 
						|
		ns := map[string]int{}
 | 
						|
 | 
						|
		for {
 | 
						|
			t.Log("Continuing")
 | 
						|
			err := grp.Continue()
 | 
						|
			if errors.As(err, &proc.ErrProcessExited{}) {
 | 
						|
				break
 | 
						|
			}
 | 
						|
			assertNoError(err, t, "Continue")
 | 
						|
 | 
						|
			if first {
 | 
						|
				first = false
 | 
						|
				if grp.Selected != p {
 | 
						|
					t.Fatalf("first breakpoint hit was not on the parent process")
 | 
						|
				}
 | 
						|
				if grp.Selected.CurrentThread().Breakpoint().Breakpoint.LogicalID() != 1 {
 | 
						|
					t.Fatalf("wrong breakpoint %#v", grp.Selected.CurrentThread().Breakpoint().Breakpoint)
 | 
						|
				}
 | 
						|
				loc, err := proc.ThreadLocation(grp.Selected.CurrentThread())
 | 
						|
				assertNoError(err, t, "Location")
 | 
						|
				if loc.Fn.Name != "main.traceme1" {
 | 
						|
					t.Fatalf("wrong stop location %#v", loc)
 | 
						|
				}
 | 
						|
			} else if grp.Selected == p {
 | 
						|
				if finished {
 | 
						|
					t.Fatalf("breakpoint hit after the last one in the parent process")
 | 
						|
				}
 | 
						|
				if p.CurrentThread().Breakpoint().Breakpoint.LogicalID() != 3 {
 | 
						|
					t.Fatalf("wrong breakpoint %#v", p.CurrentThread().Breakpoint().Breakpoint)
 | 
						|
				}
 | 
						|
				loc, err := proc.ThreadLocation(p.CurrentThread())
 | 
						|
				assertNoError(err, t, "Location")
 | 
						|
				if loc.Fn.Name != "main.traceme3" {
 | 
						|
					t.Fatalf("wrong stop location %#v", loc)
 | 
						|
				}
 | 
						|
				finished = true
 | 
						|
			} else {
 | 
						|
				if finished {
 | 
						|
					t.Fatalf("breakpoint hit after the last one in a child process")
 | 
						|
				}
 | 
						|
				it := proc.ValidTargets{Group: grp}
 | 
						|
				for it.Next() {
 | 
						|
					tgt := it.Target
 | 
						|
					if !tgt.CurrentThread().Breakpoint().Active {
 | 
						|
						continue
 | 
						|
					}
 | 
						|
					if tgt.CurrentThread().Breakpoint().Breakpoint.LogicalID() != 2 {
 | 
						|
						t.Fatalf("wrong breakpoint %#v", grp.Selected.CurrentThread().Breakpoint().Breakpoint)
 | 
						|
					}
 | 
						|
					pids[tgt.Pid()]++
 | 
						|
					loc, err := proc.ThreadLocation(tgt.CurrentThread())
 | 
						|
					assertNoError(err, t, "Location")
 | 
						|
					if loc.Fn.Name != "main.traceme2" {
 | 
						|
						t.Fatalf("wrong stop location %#v", loc)
 | 
						|
					}
 | 
						|
					nvar := evalVariable(tgt, t, "n")
 | 
						|
					if nvar.Unreadable != nil {
 | 
						|
						t.Fatalf("unreadable variable 'n' on target %d: %v", tgt.Pid(), nvar.Unreadable)
 | 
						|
					}
 | 
						|
					t.Logf("variable 'n' on target %d: %#v (%v)", tgt.Pid(), nvar, nvar.Value)
 | 
						|
					ns[constant.StringVal(nvar.Value)]++
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if len(ns) != 3 {
 | 
						|
			t.Errorf("bad contents of ns: %#v", ns)
 | 
						|
		}
 | 
						|
		for _, v := range ns {
 | 
						|
			if v != 1 {
 | 
						|
				t.Errorf("bad contents of ns: %#v", ns)
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if ns["C0"] != 1 || ns["C1"] != 1 || ns["C2"] != 1 {
 | 
						|
			t.Errorf("bad contents of ns: %#v", ns)
 | 
						|
		}
 | 
						|
 | 
						|
		if len(pids) != 3 {
 | 
						|
			t.Errorf("bad contents of pids: %#v", pids)
 | 
						|
		}
 | 
						|
		for _, v := range pids {
 | 
						|
			if v != 1 {
 | 
						|
				t.Errorf("bad contents of pids: %#v", pids)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestEscapeCheckUnreadable(t *testing.T) {
 | 
						|
	// A failure in escapeCheck to dereference a field should not cause
 | 
						|
	// infinite recursion. See issue #3310.
 | 
						|
	withTestProcessArgs("reflecttypefncall", t, ".", []string{}, protest.AllNonOptimized, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 9)
 | 
						|
		assertNoError(grp.Continue(), t, "Continue")
 | 
						|
		proc.EvalExpressionWithCalls(grp, p.SelectedGoroutine(), "value.Type()", normalLoadConfig, true)
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestStepShadowConcurrentBreakpoint(t *testing.T) {
 | 
						|
	// Checks that a StepBreakpoint can not shadow a concurrently hit user breakpoint
 | 
						|
	withTestProcess("stepshadow", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		break2 := setFunctionBreakpoint(p, t, "main.stacktraceme2")
 | 
						|
		breakMain := setFileBreakpoint(p, t, fixture.Source, 15)
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
 | 
						|
		stacktraceme1calls, stacktraceme2calls := 0, 0
 | 
						|
 | 
						|
		for {
 | 
						|
			t.Logf("stop (%d %d):", stacktraceme1calls, stacktraceme2calls)
 | 
						|
			for _, th := range p.ThreadList() {
 | 
						|
				loc, _ := proc.ThreadLocation(th)
 | 
						|
				t.Logf("\t%s:%d\n", loc.File, loc.Line)
 | 
						|
				bp := th.Breakpoint().Breakpoint
 | 
						|
				if bp != nil && bp.Addr == break2.Addr {
 | 
						|
					stacktraceme2calls++
 | 
						|
				}
 | 
						|
				// make stop on the breakpoint in main.main the selected goroutine so we can use step later
 | 
						|
				if bp != nil && bp.Addr == breakMain.Addr {
 | 
						|
					g, _ := proc.GetG(th)
 | 
						|
					p.SwitchGoroutine(g)
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			file, lineno := currentLineNumber(p, t)
 | 
						|
 | 
						|
			var err error
 | 
						|
			var reason string
 | 
						|
			switch lineno {
 | 
						|
			default:
 | 
						|
				t.Fatalf("unexpected stop location %s:%d", file, lineno)
 | 
						|
			case 15: // loop in main.main
 | 
						|
				reason = "Step()"
 | 
						|
				err = grp.Step()
 | 
						|
			case 28: // main.stacktraceme1
 | 
						|
				stacktraceme1calls++
 | 
						|
				reason = "Continue()"
 | 
						|
				err = grp.Continue()
 | 
						|
			case 30, 31: // main.stacktraceme2
 | 
						|
				reason = "Continue()"
 | 
						|
				err = grp.Continue()
 | 
						|
			}
 | 
						|
			if _, isexited := err.(proc.ErrProcessExited); isexited {
 | 
						|
				break
 | 
						|
			}
 | 
						|
			assertNoError(err, t, reason)
 | 
						|
		}
 | 
						|
 | 
						|
		t.Logf("%d %d\n", stacktraceme1calls, stacktraceme2calls)
 | 
						|
 | 
						|
		if stacktraceme1calls != 100 {
 | 
						|
			t.Errorf("wrong number of calls to stacktraceme1 found: %d", stacktraceme1calls)
 | 
						|
		}
 | 
						|
		if stacktraceme2calls != 100 {
 | 
						|
			t.Errorf("wrong number of calls to stacktraceme2 found: %d", stacktraceme2calls)
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestFollowExecRegexFilter(t *testing.T) {
 | 
						|
	skipOn(t, "follow exec not implemented on freebsd", "freebsd")
 | 
						|
	skipOn(t, "follow exec not implemented on macOS", "darwin")
 | 
						|
	withTestProcessArgs("spawn", t, ".", []string{"spawn", "3"}, 0, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		grp.LogicalBreakpoints[1] = &proc.LogicalBreakpoint{LogicalID: 1, Set: proc.SetBreakpoint{FunctionName: "main.traceme1"}, HitCount: make(map[int64]uint64)}
 | 
						|
		grp.LogicalBreakpoints[2] = &proc.LogicalBreakpoint{LogicalID: 2, Set: proc.SetBreakpoint{FunctionName: "main.traceme2"}, HitCount: make(map[int64]uint64)}
 | 
						|
		grp.LogicalBreakpoints[3] = &proc.LogicalBreakpoint{LogicalID: 3, Set: proc.SetBreakpoint{FunctionName: "main.traceme3"}, HitCount: make(map[int64]uint64)}
 | 
						|
 | 
						|
		assertNoError(grp.SetBreakpointEnabled(grp.LogicalBreakpoints[1], true), t, "EnableBreakpoint(main.traceme1)")
 | 
						|
		assertNoError(grp.SetBreakpointEnabled(grp.LogicalBreakpoints[2], true), t, "EnableBreakpoint(main.traceme2)")
 | 
						|
		assertNoError(grp.SetBreakpointEnabled(grp.LogicalBreakpoints[3], true), t, "EnableBreakpoint(main.traceme3)")
 | 
						|
 | 
						|
		assertNoError(grp.FollowExec(true, "spawn.* child C1"), t, "FollowExec")
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue 1")
 | 
						|
		assertFunctionName(grp.Selected, t, "main.traceme1", "Program did not continue to the expected location (1)")
 | 
						|
		assertNoError(grp.Continue(), t, "Continue 2")
 | 
						|
		assertFunctionName(grp.Selected, t, "main.traceme2", "Program did not continue to the expected location (2)")
 | 
						|
		assertNoError(grp.Continue(), t, "Continue 3")
 | 
						|
		assertFunctionName(grp.Selected, t, "main.traceme3", "Program did not continue to the expected location (3)")
 | 
						|
		err := grp.Continue()
 | 
						|
		if errors.As(err, &proc.ErrProcessExited{}) {
 | 
						|
			return
 | 
						|
		}
 | 
						|
		assertNoError(err, t, "Continue 4")
 | 
						|
		if err == nil {
 | 
						|
			t.Fatal("process did not exit after 4 continues")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestReadTargetArguments(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcessArgs("restartargs", t, ".", []string{"one", "two", "three"}, 0, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		t.Logf("command line: %q\n", p.CmdLine)
 | 
						|
		if !strings.HasSuffix(p.CmdLine, " one two three") {
 | 
						|
			t.Fatalf("wrong command line")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func testWaitForSetup(t *testing.T, mu *sync.Mutex, started *bool) (*exec.Cmd, *proc.WaitFor) {
 | 
						|
	var buildFlags protest.BuildFlags
 | 
						|
	if buildMode == "pie" {
 | 
						|
		buildFlags |= protest.BuildModePIE
 | 
						|
	}
 | 
						|
	fixture := protest.BuildFixture(t, "loopprog", buildFlags)
 | 
						|
 | 
						|
	cmd := exec.Command(fixture.Path)
 | 
						|
 | 
						|
	go func() {
 | 
						|
		time.Sleep(2 * time.Second)
 | 
						|
		cmd.Stdout = os.Stdout
 | 
						|
		cmd.Stderr = os.Stderr
 | 
						|
		assertNoError(cmd.Start(), t, "starting fixture")
 | 
						|
		mu.Lock()
 | 
						|
		*started = true
 | 
						|
		mu.Unlock()
 | 
						|
	}()
 | 
						|
 | 
						|
	waitFor := &proc.WaitFor{Name: fixture.Path, Interval: 100 * time.Millisecond, Duration: 10 * time.Second}
 | 
						|
 | 
						|
	return cmd, waitFor
 | 
						|
}
 | 
						|
 | 
						|
func TestWaitFor(t *testing.T) {
 | 
						|
	skipOn(t, "waitfor implementation is delegated to debugserver", "darwin")
 | 
						|
	skipOn(t, "flaky", "freebsd")
 | 
						|
 | 
						|
	var mu sync.Mutex
 | 
						|
	started := false
 | 
						|
 | 
						|
	cmd, waitFor := testWaitForSetup(t, &mu, &started)
 | 
						|
 | 
						|
	pid, err := native.WaitFor(waitFor)
 | 
						|
	assertNoError(err, t, "waitFor.Wait()")
 | 
						|
	if pid != cmd.Process.Pid {
 | 
						|
		t.Errorf("pid mismatch, expected %d got %d", pid, cmd.Process.Pid)
 | 
						|
	}
 | 
						|
 | 
						|
	cmd.Process.Kill()
 | 
						|
	cmd.Wait()
 | 
						|
}
 | 
						|
 | 
						|
func TestWaitForAttach(t *testing.T) {
 | 
						|
	skipOn(t, "flaky", "freebsd")
 | 
						|
	if testBackend == "lldb" && runtime.GOOS == "linux" {
 | 
						|
		bs, _ := os.ReadFile("/proc/sys/kernel/yama/ptrace_scope")
 | 
						|
		if bs == nil || strings.TrimSpace(string(bs)) != "0" {
 | 
						|
			t.Logf("can not run TestAttachDetach: %v\n", bs)
 | 
						|
			return
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if testBackend == "rr" {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	var mu sync.Mutex
 | 
						|
	started := false
 | 
						|
 | 
						|
	cmd, waitFor := testWaitForSetup(t, &mu, &started)
 | 
						|
 | 
						|
	var p *proc.TargetGroup
 | 
						|
	var err error
 | 
						|
 | 
						|
	switch testBackend {
 | 
						|
	case "native":
 | 
						|
		p, err = native.Attach(0, waitFor, []string{})
 | 
						|
	case "lldb":
 | 
						|
		path := ""
 | 
						|
		if runtime.GOOS == "darwin" {
 | 
						|
			path = waitFor.Name
 | 
						|
		}
 | 
						|
		p, err = gdbserial.LLDBAttach(0, path, waitFor, []string{})
 | 
						|
	default:
 | 
						|
		err = fmt.Errorf("unknown backend %q", testBackend)
 | 
						|
	}
 | 
						|
 | 
						|
	assertNoError(err, t, "Attach")
 | 
						|
 | 
						|
	mu.Lock()
 | 
						|
	if !started {
 | 
						|
		t.Fatalf("attach succeeded but started is false")
 | 
						|
	}
 | 
						|
	mu.Unlock()
 | 
						|
 | 
						|
	p.Detach(true)
 | 
						|
 | 
						|
	cmd.Wait()
 | 
						|
}
 | 
						|
 | 
						|
func TestIssue3545(t *testing.T) {
 | 
						|
	protest.AllowRecording(t)
 | 
						|
	withTestProcessArgs("nilptr", t, "", []string{}, protest.EnableOptimization, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		err := grp.Continue()
 | 
						|
		if err != nil && err.Error() == "bad access" {
 | 
						|
			grp.Continue()
 | 
						|
		}
 | 
						|
		locations, err := proc.ThreadStacktrace(p, p.CurrentThread(), 40)
 | 
						|
		assertNoError(err, t, "Stacktrace()")
 | 
						|
		var foundMain bool
 | 
						|
		for _, loc := range locations {
 | 
						|
			if loc.Call.Fn != nil && loc.Call.Fn.Name == "main.main" {
 | 
						|
				if foundMain {
 | 
						|
					t.Fatal("main.main found more than once in the stacktrace")
 | 
						|
				}
 | 
						|
				foundMain = true
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if !foundMain {
 | 
						|
			t.Fatal("did not find main.main in stack trace")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestPanicLine(t *testing.T) {
 | 
						|
	withTestProcess("panicline", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		err := grp.Continue()
 | 
						|
		if runtime.GOOS == "darwin" && err != nil && err.Error() == "bad access" {
 | 
						|
			// not supported
 | 
						|
			return
 | 
						|
		}
 | 
						|
		assertNoError(err, t, "Continue()")
 | 
						|
		frames, err := proc.ThreadStacktrace(p, p.CurrentThread(), 20)
 | 
						|
		assertNoError(err, t, "ThreadStacktrace")
 | 
						|
		logStacktrace(t, p, frames)
 | 
						|
 | 
						|
		found := false
 | 
						|
		for _, frame := range frames {
 | 
						|
			if strings.HasSuffix(frame.Call.File, "panicline.go") && frame.Call.Line == 7 {
 | 
						|
				found = true
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if !found {
 | 
						|
			t.Fatalf("could not find panicline.go:6")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestReadClosure(t *testing.T) {
 | 
						|
	if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 23) {
 | 
						|
		t.Skip("not implemented")
 | 
						|
	}
 | 
						|
	withTestProcess("closurecontents", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		avalues := []int64{0, 3, 9, 27}
 | 
						|
		for i := range 4 {
 | 
						|
			assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
			accV := evalVariable(p, t, "acc")
 | 
						|
			t.Log(api.ConvertVar(accV).MultilineString("", ""))
 | 
						|
			if len(accV.Children) != 2 {
 | 
						|
				t.Fatal("wrong number of children")
 | 
						|
			}
 | 
						|
			found := 0
 | 
						|
			for j := range accV.Children {
 | 
						|
				v := &accV.Children[j]
 | 
						|
				switch v.Name {
 | 
						|
				case "scale":
 | 
						|
					found++
 | 
						|
					if val, _ := constant.Int64Val(v.Value); val != 3 {
 | 
						|
						t.Error("wrong value for scale")
 | 
						|
					}
 | 
						|
				case "a":
 | 
						|
					found++
 | 
						|
					if val, _ := constant.Int64Val(v.Value); val != avalues[i] {
 | 
						|
						t.Errorf("wrong value for a: %d", val)
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
			if found != 2 {
 | 
						|
				t.Error("wrong captured variables")
 | 
						|
			}
 | 
						|
 | 
						|
			assertVariable(t, evalVariable(p, t, "acc.scale"), varTest{name: "acc.scale", preserveName: true, value: "3", varType: "int"})
 | 
						|
			assertVariable(t, evalVariable(p, t, "acc.a"), varTest{name: "acc.a", preserveName: true, value: fmt.Sprintf("%d", avalues[i]), varType: "int"})
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestStepIntoGoroutine(t *testing.T) {
 | 
						|
	testseq2(t, "goroutinestackprog", "", []seqTest{
 | 
						|
		{contContinue, 23},
 | 
						|
		{contStep, 7},
 | 
						|
		{contNothing, func(p *proc.Target) {
 | 
						|
			vari := api.ConvertVar(evalVariable(p, t, "i"))
 | 
						|
			varis := vari.SinglelineString()
 | 
						|
			t.Logf("i = %s", varis)
 | 
						|
			if varis != "0" {
 | 
						|
				t.Fatalf("wrong value for variable i: %s", vari.SinglelineString())
 | 
						|
			}
 | 
						|
		}},
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestWatchpointInterface(t *testing.T) {
 | 
						|
	skipOn(t, "not implemented", "freebsd")
 | 
						|
	skipOn(t, "not implemented", "386")
 | 
						|
	skipOn(t, "not implemented", "ppc64le")
 | 
						|
	skipOn(t, "not implemented", "riscv64")
 | 
						|
	skipOn(t, "not implemented", "loong64")
 | 
						|
	skipOn(t, "see https://github.com/go-delve/delve/issues/2768", "windows")
 | 
						|
	protest.AllowRecording(t)
 | 
						|
 | 
						|
	withTestProcess("watchpointInterface", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		// Set breakpoint at line after error is created and printed
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 11)
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		assertLineNumber(p, t, 11, "continued to wrong line")
 | 
						|
 | 
						|
		// Get scope and set watchpoint on interface type
 | 
						|
		scope, err := proc.GoroutineScope(p, p.CurrentThread())
 | 
						|
		assertNoError(err, t, "GoroutineScope")
 | 
						|
 | 
						|
		// Set watchpoint on the error interface
 | 
						|
		_, err = p.SetWatchpoint(0, scope, "err", proc.WatchWrite, nil)
 | 
						|
		assertNoError(err, t, "SetWatchpoint on interface type")
 | 
						|
 | 
						|
		// Continue to hit the watchpoint when the error is modified
 | 
						|
		assertNoError(grp.Continue(), t, "Continue to watchpoint")
 | 
						|
 | 
						|
		// Verify we stopped at the correct line where err is modified
 | 
						|
		assertLineNumberIn(p, t, []int{12, 13}, "stopped at wrong line after interface watchpoint")
 | 
						|
 | 
						|
		// Continue to the end
 | 
						|
		assertNoError(grp.Continue(), t, "Final continue")
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestWatchpointInterfaceNil(t *testing.T) {
 | 
						|
	skipOn(t, "not implemented", "freebsd")
 | 
						|
	skipOn(t, "not implemented", "386")
 | 
						|
	skipOn(t, "not implemented", "ppc64le")
 | 
						|
	skipOn(t, "not implemented", "riscv64")
 | 
						|
	skipOn(t, "not implemented", "loong64")
 | 
						|
	skipOn(t, "see https://github.com/go-delve/delve/issues/2768", "windows")
 | 
						|
	protest.AllowRecording(t)
 | 
						|
 | 
						|
	withTestProcess("watchpointInterfaceNil", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		// Set breakpoint at line after error is created and printed
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 10)
 | 
						|
		assertNoError(grp.Continue(), t, "Continue()")
 | 
						|
		assertLineNumber(p, t, 10, "continued to wrong line")
 | 
						|
 | 
						|
		// Get scope and set watchpoint on interface type
 | 
						|
		scope, err := proc.GoroutineScope(p, p.CurrentThread())
 | 
						|
		assertNoError(err, t, "GoroutineScope")
 | 
						|
 | 
						|
		// Set watchpoint on the error interface
 | 
						|
		_, err = p.SetWatchpoint(0, scope, "err", proc.WatchWrite, nil)
 | 
						|
		assertNoError(err, t, "SetWatchpoint on interface type")
 | 
						|
 | 
						|
		// Continue to hit the watchpoint when the error is modified
 | 
						|
		assertNoError(grp.Continue(), t, "Continue to watchpoint")
 | 
						|
 | 
						|
		// Verify we stopped at the correct line where err is modified
 | 
						|
		assertLineNumberIn(p, t, []int{11, 12}, "stopped at wrong line after interface watchpoint")
 | 
						|
 | 
						|
		// Continue to hit the watchpoint when the error is modified again
 | 
						|
		assertNoError(grp.Continue(), t, "Continue to watchpoint (2)")
 | 
						|
 | 
						|
		// Verify we stopped at the correct line where err is modified
 | 
						|
		assertLineNumberIn(p, t, []int{13, 14}, "stopped at wrong line after interface watchpoint (2)")
 | 
						|
 | 
						|
		// Continue to the end
 | 
						|
		assertNoError(grp.Continue(), t, "Final continue")
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestStackwatchClearBug(t *testing.T) {
 | 
						|
	skipOn(t, "not implemented", "freebsd")
 | 
						|
	skipOn(t, "not implemented", "386")
 | 
						|
	skipOn(t, "not implemented", "ppc64le")
 | 
						|
	skipOn(t, "not implemented", "loong64")
 | 
						|
	skipOn(t, "see https://github.com/go-delve/delve/issues/2768", "windows")
 | 
						|
 | 
						|
	showbps := func(bps *proc.BreakpointMap) {
 | 
						|
		for _, bp := range bps.M {
 | 
						|
			t.Logf("\t%s\n", bp)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	withTestProcess("stackwatchbug", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		setFileBreakpoint(p, t, fixture.Source, 9)
 | 
						|
		bpsBefore := p.Breakpoints()
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue 0")
 | 
						|
 | 
						|
		scope, err := proc.GoroutineScope(p, p.CurrentThread())
 | 
						|
		assertNoError(err, t, "GoroutineScope")
 | 
						|
 | 
						|
		for _, s := range []string{"vars[0]", "vars[3]", "vars[2]", "vars[1]"} {
 | 
						|
			_, err := p.SetWatchpoint(0, scope, s, proc.WatchWrite, nil)
 | 
						|
			assertNoError(err, t, "SetWatchpoint(write-only)")
 | 
						|
		}
 | 
						|
 | 
						|
		t.Logf("After setting watchpoints:")
 | 
						|
		showbps(p.Breakpoints())
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue 1")
 | 
						|
		assertNoError(grp.Continue(), t, "Continue 2")
 | 
						|
		assertNoError(grp.Continue(), t, "Continue 3")
 | 
						|
		assertNoError(grp.Continue(), t, "Continue 4")
 | 
						|
		f, l := currentLineNumber(p, t)
 | 
						|
		t.Logf("at %s:%d", f, l)
 | 
						|
		if l != 19 {
 | 
						|
			t.Error("Wrong position after fourth continue")
 | 
						|
		}
 | 
						|
 | 
						|
		bpsAfter := p.Breakpoints()
 | 
						|
		t.Logf("After watchpoint goes out of scope:")
 | 
						|
		showbps(bpsAfter)
 | 
						|
		if len(bpsBefore.M) != len(bpsAfter.M) {
 | 
						|
			t.Errorf("wrong number of breakpoints")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestChainedBreakpoint(t *testing.T) {
 | 
						|
	assertCallerLine := func(t *testing.T, p *proc.Target, pos string, tgt int) {
 | 
						|
		t.Helper()
 | 
						|
		frames, err := proc.ThreadStacktrace(p, p.CurrentThread(), 5)
 | 
						|
		assertNoError(err, t, "ThreadStacktrace")
 | 
						|
		t.Logf("%s: %s:%d", pos, frames[1].Call.File, frames[1].Call.Line)
 | 
						|
		if frames[1].Call.Line != tgt {
 | 
						|
			t.Fatalf("wrong line number, expected %d", tgt)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	withTestProcess("bphitcountchain", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 | 
						|
		numphys := func(lbp *proc.LogicalBreakpoint) int {
 | 
						|
			count := 0
 | 
						|
			for _, bp := range p.Breakpoints().M {
 | 
						|
				if bp.LogicalID() == lbp.LogicalID {
 | 
						|
					count++
 | 
						|
				}
 | 
						|
			}
 | 
						|
			return count
 | 
						|
		}
 | 
						|
 | 
						|
		bp := setFunctionBreakpoint(p, t, "main.breakfunc3")
 | 
						|
		lbp3 := bp.Logical
 | 
						|
		bp = setFunctionBreakpoint(p, t, "main.breakfunc2")
 | 
						|
		lbp2 := bp.Logical
 | 
						|
		bp = setFunctionBreakpoint(p, t, "main.breakfunc1")
 | 
						|
		lbp1 := bp.Logical
 | 
						|
 | 
						|
		assertPhysCount := func(lbp1cnt, lbp2cnt, lbp3cnt int) {
 | 
						|
			t.Helper()
 | 
						|
			t.Logf("lbp1: %d lbp2: %d lbp3: %d", numphys(lbp1), numphys(lbp2), numphys(lbp3))
 | 
						|
			if numphys(lbp1) != lbp1cnt || numphys(lbp2) != lbp2cnt || numphys(lbp3) != lbp3cnt {
 | 
						|
				t.Fatal("wrong number of physical breakpoints")
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		assertNoError(grp.ChangeBreakpointCondition(lbp1, "", "== 1", false), t, "ChangeBreakpointCondition")
 | 
						|
		assertNoError(grp.ChangeBreakpointCondition(lbp2, fmt.Sprintf("delve.bphitcount[%d] > 0", lbp1.LogicalID), "== 1", false), t, "ChangeBreakpointCondition")
 | 
						|
		assertNoError(grp.ChangeBreakpointCondition(lbp3, fmt.Sprintf("delve.bphitcount[%d] > 0", lbp2.LogicalID), "== 1", false), t, "ChangeBreakpointCondition")
 | 
						|
 | 
						|
		assertPhysCount(1, 0, 0)
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue 1")
 | 
						|
		assertCallerLine(t, p, "continue 1", 21)
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue 2")
 | 
						|
		assertCallerLine(t, p, "continue 2", 25)
 | 
						|
 | 
						|
		assertPhysCount(0, 1, 0)
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue 3")
 | 
						|
		assertCallerLine(t, p, "continue 3", 28)
 | 
						|
 | 
						|
		assertPhysCount(0, 0, 1)
 | 
						|
 | 
						|
		err := grp.Continue()
 | 
						|
		if !errors.As(err, &proc.ErrProcessExited{}) {
 | 
						|
			assertNoError(err, t, "Continue 4")
 | 
						|
		}
 | 
						|
 | 
						|
		// === Restart ===
 | 
						|
 | 
						|
		t.Logf("=== Restart ===")
 | 
						|
 | 
						|
		grp2 := startTestProcessArgs(fixture, t, ".", []string{})
 | 
						|
		proc.Restart(grp2, grp, func(lbp *proc.LogicalBreakpoint, err error) {
 | 
						|
			t.Fatalf("discarded logical breakpoint %v: %v", lbp, err)
 | 
						|
		})
 | 
						|
 | 
						|
		grp = grp2
 | 
						|
		p = grp.Selected
 | 
						|
 | 
						|
		assertPhysCount(1, 0, 0)
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue 1")
 | 
						|
		assertCallerLine(t, p, "continue 1", 21)
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue 2")
 | 
						|
		assertCallerLine(t, p, "continue 2", 25)
 | 
						|
 | 
						|
		assertPhysCount(0, 1, 0)
 | 
						|
 | 
						|
		assertNoError(grp.Continue(), t, "Continue 3")
 | 
						|
		assertCallerLine(t, p, "continue 3", 28)
 | 
						|
 | 
						|
		assertPhysCount(0, 0, 1)
 | 
						|
 | 
						|
		err = grp.Continue()
 | 
						|
		if !errors.As(err, &proc.ErrProcessExited{}) {
 | 
						|
			assertNoError(err, t, "Continue 4")
 | 
						|
		}
 | 
						|
	})
 | 
						|
}
 |