From c8d9352522885a77bd53e1851350bf342b2747bd Mon Sep 17 00:00:00 2001 From: aarzilli Date: Fri, 10 Feb 2017 15:11:40 +0100 Subject: [PATCH] proc: Implement target.Interface for gdbserver backend --- CHANGELOG.md | 7 + Makefile | 17 +- cmd/dlv/cmds/commands.go | 13 +- cmd/dlv/dlv_test.go | 17 +- pkg/proc/bininfo.go | 5 + pkg/proc/gdbserver.go | 1416 +++++++++++++++++++++++++++ pkg/proc/gdbserver_conn.go | 1066 ++++++++++++++++++++ pkg/proc/gdbserver_unix.go | 9 + pkg/proc/gdbserver_windows.go | 7 + pkg/proc/proc_test.go | 617 +++++++----- pkg/proc/proc_unix_test.go | 22 +- pkg/proc/registers.go | 2 + pkg/proc/registers_darwin_amd64.go | 4 + pkg/proc/registers_linux_amd64.go | 4 + pkg/proc/registers_windows_amd64.go | 4 + pkg/proc/threads.go | 19 +- pkg/target/target.go | 1 + pkg/terminal/command_test.go | 16 + service/config.go | 5 +- service/debugger/debugger.go | 60 +- service/rpccommon/server.go | 1 + service/test/integration1_test.go | 3 + service/test/integration2_test.go | 13 + service/test/variables_test.go | 44 +- 24 files changed, 3078 insertions(+), 294 deletions(-) create mode 100644 pkg/proc/gdbserver.go create mode 100644 pkg/proc/gdbserver_conn.go create mode 100644 pkg/proc/gdbserver_unix.go create mode 100644 pkg/proc/gdbserver_windows.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d7c3be0..5051e7ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to Semantic Versioning. All changes mention the author, unless contributed by me (@derekparker). +## [NEWEST VERSION] RELEASE DATE + +### Added + +- Added support for core files (@heschik) +- Added support for lldb-server and debugserver as backend, using debugserver by default on macOS (@aarzilli) + ## [0.12.2] 2017-04-13 ### Fixed diff --git a/Makefile b/Makefile index e794dc01..fda10bac 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,7 @@ UNAME=$(shell uname) PREFIX=github.com/derekparker/delve GOVERSION=$(shell go version) BUILD_SHA=$(shell git rev-parse HEAD) +LLDB_SERVER=$(shell which lldb-server) ifeq "$(UNAME)" "Darwin" BUILD_FLAGS=-ldflags="-s -X main.Build=$(BUILD_SHA)" @@ -21,6 +22,7 @@ ALL_PACKAGES=$(shell go list ./... | grep -v /vendor/ | grep -v /scripts) # See https://github.com/golang/go/issues/11887#issuecomment-126117692. ifeq "$(UNAME)" "Darwin" TEST_FLAGS=-exec=$(shell pwd)/scripts/testsign + export PROCTEST=lldb DARWIN="true" endif @@ -63,9 +65,20 @@ endif else go test $(TEST_FLAGS) $(BUILD_FLAGS) $(ALL_PACKAGES) endif +ifneq "$(shell which lldb-server)" "" + @echo + @echo 'Testing LLDB backend (proc)' + go test $(TEST_FLAGS) $(BUILD_FLAGS) $(PREFIX)/pkg/proc -backend=lldb + @echo + @echo 'Testing LLDB backend (integration)' + go test $(TEST_FLAGS) $(BUILD_FLAGS) $(PREFIX)/service/test -backend=lldb + @echo + @echo 'Testing LLDB backend (terminal)' + go test $(TEST_FLAGS) $(BUILD_FLAGS) $(PREFIX)/pkg/terminal -backend=lldb +endif test-proc-run: - go test $(TEST_FLAGS) $(BUILD_FLAGS) -test.v -test.run="$(RUN)" $(PREFIX)/pkg/proc + go test $(TEST_FLAGS) $(BUILD_FLAGS) -test.v -test.run="$(RUN)" -backend=$(BACKEND) $(PREFIX)/pkg/proc test-integration-run: - go test $(TEST_FLAGS) $(BUILD_FLAGS) -test.run="$(RUN)" $(PREFIX)/service/test + go test $(TEST_FLAGS) $(BUILD_FLAGS) -test.run="$(RUN)" -backend=$(BACKEND) $(PREFIX)/service/test diff --git a/cmd/dlv/cmds/commands.go b/cmd/dlv/cmds/commands.go index 55dc202b..dae88b37 100644 --- a/cmd/dlv/cmds/commands.go +++ b/cmd/dlv/cmds/commands.go @@ -42,6 +42,9 @@ var ( // WorkingDir is the working directory for running the program. WorkingDir string + // Backend selection + Backend string + // RootCommand is the root of the command tree. RootCommand *cobra.Command @@ -92,10 +95,14 @@ func New() *cobra.Command { RootCommand.PersistentFlags().StringVar(&InitFile, "init", "", "Init file, executed by the terminal client.") RootCommand.PersistentFlags().StringVar(&BuildFlags, "build-flags", buildFlagsDefault, "Build flags, to be passed to the compiler.") RootCommand.PersistentFlags().StringVar(&WorkingDir, "wd", ".", "Working directory for running the program.") + RootCommand.PersistentFlags().StringVar(&Backend, "backend", "default", `Backend selection: + default Uses lldb on macOS, native everywhere else. + native Native backend. + lldb Uses lldb-server or debugserver.`) // 'attach' subcommand. attachCommand := &cobra.Command{ - Use: "attach pid", + Use: "attach pid [executable]", Short: "Attach to running process and begin debugging.", Long: `Attach to an already running process and begin debugging it. @@ -304,6 +311,7 @@ func traceCmd(cmd *cobra.Command, args []string) { AttachPid: traceAttachPid, APIVersion: 2, WorkingDir: WorkingDir, + Backend: Backend, }, Log) if err := server.Run(); err != nil { fmt.Fprintln(os.Stderr, err) @@ -361,7 +369,7 @@ func attachCmd(cmd *cobra.Command, args []string) { fmt.Fprintf(os.Stderr, "Invalid pid: %s\n", args[0]) os.Exit(1) } - os.Exit(execute(pid, nil, conf, "", executingOther)) + os.Exit(execute(pid, args[1:], conf, "", executingOther)) } func coreCmd(cmd *cobra.Command, args []string) { @@ -432,6 +440,7 @@ func execute(attachPid int, processArgs []string, conf *config.Config, coreFile AcceptMulti: AcceptMulti, APIVersion: APIVersion, WorkingDir: WorkingDir, + Backend: Backend, CoreFile: coreFile, }, Log) default: diff --git a/cmd/dlv/dlv_test.go b/cmd/dlv/dlv_test.go index 3fd2a224..963179e5 100644 --- a/cmd/dlv/dlv_test.go +++ b/cmd/dlv/dlv_test.go @@ -2,6 +2,7 @@ package main import ( "bufio" + "flag" "os" "os/exec" "path/filepath" @@ -13,6 +14,20 @@ import ( "github.com/derekparker/delve/service/rpc2" ) +var testBackend string + +func TestMain(m *testing.M) { + flag.StringVar(&testBackend, "backend", "", "selects backend") + flag.Parse() + if testBackend == "" { + testBackend = os.Getenv("PROCTEST") + if testBackend == "" { + testBackend = "native" + } + } + os.Exit(m.Run()) +} + func assertNoError(err error, t testing.TB, s string) { if err != nil { _, file, line, _ := runtime.Caller(1) @@ -52,7 +67,7 @@ func TestBuild(t *testing.T) { buildtestdir := filepath.Join(fixtures, "buildtest") - cmd := exec.Command(dlvbin, "debug", "--headless=true", "--listen="+listenAddr, "--api-version=2") + cmd := exec.Command(dlvbin, "debug", "--headless=true", "--listen="+listenAddr, "--api-version=2", "--backend="+testBackend) cmd.Dir = buildtestdir stdout, err := cmd.StdoutPipe() assertNoError(err, t, "stdout pipe") diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index 7a808092..6cd6669b 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -107,6 +107,11 @@ func (bi *BinaryInfo) PCToLine(pc uint64) (string, int, *gosym.Func) { return bi.goSymTable.PCToLine(pc) } +// LineToPC converts a file:line into a memory address. +func (bi *BinaryInfo) LineToPC(filename string, lineno int) (pc uint64, fn *gosym.Func, err error) { + return bi.goSymTable.LineToPC(filename, lineno) +} + func (bi *BinaryInfo) Close() error { return bi.closer.Close() } diff --git a/pkg/proc/gdbserver.go b/pkg/proc/gdbserver.go new file mode 100644 index 00000000..62912c80 --- /dev/null +++ b/pkg/proc/gdbserver.go @@ -0,0 +1,1416 @@ +// This file and its companion gdbserver_conn implement a target.Interface +// backed by a connection to a debugger speaking the "Gdb Remote Serial +// Protocol". +// +// The "Gdb Remote Serial Protocol" is a low level debugging protocol +// originally designed so that gdb could be used to debug programs running +// in embedded environments but it was later extended to support programs +// running on any environment and a variety of debuggers support it: +// gdbserver, lldb-server, macOS's debugserver and rr. +// +// The protocol is specified at: +// https://sourceware.org/gdb/onlinedocs/gdb/Remote-Protocol.html +// with additional documentation for lldb specific extensions described at: +// https://github.com/llvm-mirror/lldb/blob/master/docs/lldb-gdb-remote.txt +// +// Terminology: +// * inferior: the program we are trying to debug +// * stub: the debugger on the other side of the protocol's connection (for +// example lldb-server) +// * gdbserver: stub version of gdb +// * lldb-server: stub version of lldb +// * debugserver: a different stub version of lldb, installed with lldb on +// macOS. +// * mozilla rr: a stub that records the full execution of a program +// and can then play it back. +// +// Implementations of the protocol vary wildly between stubs, while there is +// a command to query the stub about supported features (qSupported) this +// only covers *some* of the more recent additions to the protocol and most +// of the older packets are optional and *not* implemented by all stubs. +// For example gdbserver implements 'g' (read all registers) but not 'p' +// (read single register) while lldb-server implements 'p' but not 'g'. +// +// The protocol is also underspecified with regards to how the stub should +// handle a multithreaded inferior. Its default mode of operation is +// "all-stop mode", when a thread hits a breakpoint all other threads are +// also stopped. But the protocol doesn't say what happens if a second +// thread hits a breakpoint while the stub is in the process of stopping all +// other threads. +// +// In practice the stub is allowed to swallow the second breakpoint hit or +// to return it at a later time. If the stub chooses the latter behavior +// (like gdbserver does) it is allowed to return delayed events on *any* +// vCont packet. This is incredibly inconvenient since if we get notified +// about a delayed breakpoint while we are trying to singlestep another +// thread it's impossible to know when the singlestep we requested ended. +// +// What this means is that gdbserver can only be supported for multithreaded +// inferiors by selecting non-stop mode, which behaves in a markedly +// different way from all-stop mode and isn't supported by anything except +// gdbserver. +// +// lldb-server/debugserver takes a different approach, only the first stop +// event is reported, if any other event happens "simultaneously" they are +// suppressed by the stub and the debugger can query for them using +// qThreadStopInfo. This is much easier for us to implement and the +// implementation gracefully degrates to the case where qThreadStopInfo is +// unavailable but the inferior is run in single threaded mode. +// +// Therefore the following code will assume lldb-server-like behavior. + +package proc + +import ( + "debug/gosym" + "encoding/binary" + "errors" + "fmt" + "go/ast" + "net" + "os" + "os/exec" + "runtime" + "strconv" + "strings" + "sync" + "time" + + "golang.org/x/arch/x86/x86asm" +) + +const ( + logGdbWire = false + logGdbWireFullStopPacket = false + showLldbServerOutput = false + logGdbWireMaxLen = 120 + + maxTransmitAttempts = 3 // number of retransmission attempts on failed checksum + initialInputBufferSize = 2048 // size of the input buffer for gdbConn +) + +const heartbeatInterval = 10 * time.Second + +// GdbserverProcess implements target.Interface using a connection to a +// debugger stub that understands Gdb Remote Serial Protocol. +type GdbserverProcess struct { + bi BinaryInfo + conn gdbConn + + threads map[int]*GdbserverThread + currentThread *GdbserverThread + selectedGoroutine *G + + exited bool + ctrlC bool // ctrl-c was sent to stop inferior + + breakpoints map[uint64]*Breakpoint + breakpointIDCounter int + internalBreakpointIDCounter int + + gcmdok bool // true if the stub supports g and G commands + threadStopInfo bool // true if the stub supports qThreadStopInfo + + loadGInstrAddr uint64 // address of the g loading instruction, zero if we couldn't allocate it + + process *exec.Cmd + + allGCache []*G +} + +// GdbserverThread is a thread of GdbserverProcess. +type GdbserverThread struct { + ID int + strID string + regs gdbRegisters + CurrentBreakpoint *Breakpoint + BreakpointConditionMet bool + BreakpointConditionError error + p *GdbserverProcess + setbp bool // thread was stopped because of a breakpoint +} + +// gdbRegisters represents the current value of the registers of a thread. +// The storage space for all the registers is allocated as a single memory +// block in buf, the value field inside an individual gdbRegister will be a +// slice of the global buf field. +type gdbRegisters struct { + regs map[string]gdbRegister + regsInfo []gdbRegisterInfo + tls uint64 + gaddr uint64 + hasgaddr bool + buf []byte +} + +type gdbRegister struct { + value []byte + regnum int +} + +// GdbserverConnect creates a GdbserverProcess connected to address addr. +// Path and pid are, respectively, the path to the executable of the target +// program and the PID of the target process, both are optional, however +// some stubs do not provide ways to determine path and pid automatically +// and GdbserverConnect will be unable to function without knowing them. +func GdbserverConnect(addr string, path string, pid int, attempts int) (*GdbserverProcess, error) { + var conn net.Conn + var err error + for i := 0; i < attempts; i++ { + conn, err = net.Dial("tcp", addr) + if err == nil { + break + } + time.Sleep(time.Second) + } + if err != nil { + return nil, err + } + + p := &GdbserverProcess{ + conn: gdbConn{ + conn: conn, + maxTransmitAttempts: maxTransmitAttempts, + inbuf: make([]byte, 0, initialInputBufferSize), + }, + + threads: make(map[int]*GdbserverThread), + bi: NewBinaryInfo(runtime.GOOS, runtime.GOARCH), + breakpoints: make(map[uint64]*Breakpoint), + gcmdok: true, + threadStopInfo: true, + } + + p.conn.pid = pid + err = p.conn.handshake() + if err != nil { + conn.Close() + return nil, err + } + + if path == "" { + // If we are attaching to a running process and the user didn't specify + // the executable file manually we must ask the stub for it. + // We support both qXfer:exec-file:read:: (the gdb way) and calling + // qProcessInfo (the lldb way). + // Unfortunately debugserver on macOS supports neither. + path, err = p.conn.readExecFile() + if err != nil { + if isProtocolErrorUnsupported(err) { + _, path, err = p.loadProcessInfo(pid) + if err != nil { + conn.Close() + return nil, err + } + } else { + conn.Close() + return nil, fmt.Errorf("could not determine executable path: %v", err) + } + } + } + + // None of the stubs we support returns the value of fs_base or gs_base + // along with the registers, therefore we have to resort to executing a MOV + // instruction on the inferior to find out where the G struct of a given + // thread is located. + // Here we try to allocate some memory on the inferior which we will use to + // store the MOV instruction. + // If the stub doesn't support memory allocation reloadRegisters will + // overwrite some existing memory to store the MOV. + if addr, err := p.conn.allocMemory(256); err == nil { + if _, err := p.conn.writeMemory(uintptr(addr), p.loadGInstr()); err == nil { + p.loadGInstrAddr = addr + } + } + + var wg sync.WaitGroup + err = p.bi.LoadBinaryInfo(path, &wg) + if err != nil { + conn.Close() + return nil, err + } + wg.Wait() + + err = p.updateThreadList(&threadUpdater{p: p}) + if err != nil { + conn.Close() + p.bi.Close() + return nil, err + } + + if p.conn.pid <= 0 { + p.conn.pid, _, err = p.loadProcessInfo(0) + if err != nil { + conn.Close() + p.bi.Close() + return nil, err + } + } + + scope := &EvalScope{0, 0, p.CurrentThread(), nil, &p.bi} + ver, isextld, err := scope.getGoInformation() + if err != nil { + conn.Close() + p.bi.Close() + return nil, err + } + + p.bi.arch.SetGStructOffset(ver, isextld) + p.selectedGoroutine, _ = GetG(p.CurrentThread()) + + panicpc, err := p.FindFunctionLocation("runtime.startpanic", true, 0) + if err == nil { + bp, err := p.SetBreakpoint(panicpc, UserBreakpoint, nil) + if err == nil { + bp.Name = "unrecovered-panic" + bp.ID = -1 + p.breakpointIDCounter-- + } + } + + return p, nil +} + +// unusedPort returns an unused tcp port +// This is a hack and subject to a race condition with other running +// programs, but most (all?) OS will cycle through all ephemeral ports +// before reassigning one port they just assigned, unless there's heavy +// churn in the ephemeral range this should work. +func unusedPort() string { + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + if err != nil { + return ":8081" + } + listener, err := net.ListenTCP("tcp", addr) + if err != nil { + return ":8081" + } + port := listener.Addr().(*net.TCPAddr).Port + listener.Close() + return fmt.Sprintf(":%d", port) +} + +const debugserverExecutable = "/Library/Developer/CommandLineTools/Library/PrivateFrameworks/LLDB.framework/Versions/A/Resources/debugserver" + +// LLDBLaunch starts an instance of lldb-server and connects to it, asking +// it to launch the specified target program with the specified arguments +// (cmd) on the specified directory wd. +func LLDBLaunch(cmd []string, wd string) (*GdbserverProcess, error) { + // check that the argument to Launch is an executable file + if fi, staterr := os.Stat(cmd[0]); staterr == nil && (fi.Mode()&0111) == 0 { + return nil, NotExecutableErr + } + + port := unusedPort() + isDebugserver := false + + var proc *exec.Cmd + if _, err := os.Stat(debugserverExecutable); err == nil { + args := make([]string, 0, len(cmd)+1) + args = append(args, "127.0.0.1"+port) + args = append(args, cmd...) + + isDebugserver = true + + proc = exec.Command(debugserverExecutable, args...) + } else { + args := make([]string, 0, len(cmd)+3) + args = append(args, "gdbserver") + args = append(args, port, "--") + args = append(args, cmd...) + + proc = exec.Command("lldb-server", args...) + } + + if showLldbServerOutput || logGdbWire { + proc.Stdout = os.Stdout + proc.Stderr = os.Stderr + } + if wd != "" { + proc.Dir = wd + } + + proc.SysProcAttr = backgroundSysProcAttr() + + err := proc.Start() + if err != nil { + return nil, err + } + + p, err := GdbserverConnect(port, cmd[0], 0, 10) + if err != nil { + return nil, err + } + + p.conn.isDebugserver = isDebugserver + p.process = proc + + return p, nil +} + +// LLDBAttach starts an instance of lldb-server and connects to it, asking +// it to attach to the specified pid. +// Path is path to the target's executable, path only needs to be specified +// for some stubs that do not provide an automated way of determining it +// (for example debugserver). +func LLDBAttach(pid int, path string) (*GdbserverProcess, error) { + port := unusedPort() + isDebugserver := false + var proc *exec.Cmd + if _, err := os.Stat(debugserverExecutable); err == nil { + isDebugserver = true + proc = exec.Command(debugserverExecutable, "127.0.0.1"+port, "--attach="+strconv.Itoa(pid)) + } else { + proc = exec.Command("lldb-server", "gdbserver", "--attach", strconv.Itoa(pid), port) + } + + proc.Stdout = os.Stdout + proc.Stderr = os.Stderr + + proc.SysProcAttr = backgroundSysProcAttr() + + err := proc.Start() + if err != nil { + return nil, err + } + + p, err := GdbserverConnect(port, path, pid, 10) + if err != nil { + return nil, err + } + + p.conn.isDebugserver = isDebugserver + p.process = proc + + return p, nil +} + +// loadProcessInfo uses qProcessInfo to load the inferior's PID and +// executable path. This command is not supported by all stubs and not all +// stubs will report both the PID and executable path. +func (p *GdbserverProcess) loadProcessInfo(pid int) (int, string, error) { + pi, err := p.conn.queryProcessInfo(pid) + if err != nil { + return 0, "", err + } + if pid == 0 { + n, _ := strconv.ParseUint(pi["pid"], 16, 64) + pid = int(n) + } + return pid, pi["name"], nil +} + +func (p *GdbserverProcess) BinInfo() *BinaryInfo { + return &p.bi +} + +func (p *GdbserverProcess) Pid() int { + return int(p.conn.pid) +} + +func (p *GdbserverProcess) Exited() bool { + return p.exited +} + +func (p *GdbserverProcess) Running() bool { + return p.conn.running +} + +func (p *GdbserverProcess) FindFileLocation(fileName string, lineNumber int) (uint64, error) { + return FindFileLocation(p.CurrentThread(), p.breakpoints, &p.bi, fileName, lineNumber) +} + +func (p *GdbserverProcess) FirstPCAfterPrologue(fn *gosym.Func, sameline bool) (uint64, error) { + return FirstPCAfterPrologue(p.CurrentThread(), p.breakpoints, &p.bi, fn, sameline) +} + +func (p *GdbserverProcess) FindFunctionLocation(funcName string, firstLine bool, lineOffset int) (uint64, error) { + return FindFunctionLocation(p.CurrentThread(), p.breakpoints, &p.bi, funcName, firstLine, lineOffset) +} + +func (p *GdbserverProcess) FindThread(threadID int) (IThread, bool) { + thread, ok := p.threads[threadID] + return thread, ok +} + +func (p *GdbserverProcess) ThreadList() []IThread { + r := make([]IThread, 0, len(p.threads)) + for _, thread := range p.threads { + r = append(r, thread) + } + return r +} + +func (p *GdbserverProcess) CurrentThread() IThread { + return p.currentThread +} + +func (p *GdbserverProcess) AllGCache() *[]*G { + return &p.allGCache +} + +func (p *GdbserverProcess) SelectedGoroutine() *G { + return p.selectedGoroutine +} + +const ( + interruptSignal = 0x2 + breakpointSignal = 0x5 + childSignal = 0x11 + stopSignal = 0x13 +) + +func (p *GdbserverProcess) ContinueOnce() (IThread, error) { + if p.exited { + return nil, &ProcessExitedError{Pid: p.conn.pid} + } + + // step threads stopped at any breakpoint over their breakpoint + for _, thread := range p.threads { + if thread.CurrentBreakpoint != nil { + if err := thread.stepInstruction(&threadUpdater{p: p}); err != nil { + return nil, err + } + } + } + + p.allGCache = nil + for _, th := range p.threads { + th.clearBreakpointState() + } + + p.ctrlC = false + + // resume all threads + var threadID string + var sig uint8 = 0 + var tu = threadUpdater{p: p} + var err error +continueLoop: + for { + tu.done = false + threadID, sig, err = p.conn.resume(sig, &tu) + if err != nil { + if _, exited := err.(ProcessExitedError); exited { + p.exited = true + } + return nil, err + } + + // 0x5 is always a breakpoint, a manual stop either manifests as 0x13 + // (lldb), 0x11 (debugserver) or 0x2 (gdbserver). + // Since 0x2 could also be produced by the user + // pressing ^C (in which case it should be passed to the inferior) we need + // the ctrlC flag to know that we are the originators. + switch sig { + case interruptSignal: // interrupt + if p.ctrlC { + break continueLoop + } + case breakpointSignal: // breakpoint + break continueLoop + case childSignal: // stop on debugserver but SIGCHLD on lldb-server/linux + if p.conn.isDebugserver { + break continueLoop + } + case stopSignal: // stop + break continueLoop + + // The following are fake BSD-style signals sent by debugserver + // Unfortunately debugserver can not convert them into signals for the + // process so we must stop here. + case 0x91, 0x92, 0x93, 0x94, 0x95, 0x96: /* TARGET_EXC_BAD_ACCESS */ + break continueLoop + default: + // any other signal is always propagated to inferior + } + } + + if err := p.updateThreadList(&tu); err != nil { + return nil, err + } + + if err := p.setCurrentBreakpoints(); err != nil { + return nil, err + } + + for _, thread := range p.threads { + if thread.strID == threadID { + return thread, nil + } + } + + return nil, fmt.Errorf("could not find thread %s", threadID) +} + +func (p *GdbserverProcess) StepInstruction() error { + if p.selectedGoroutine == nil { + return errors.New("cannot single step: no selected goroutine") + } + if p.selectedGoroutine.thread == nil { + if _, err := p.SetBreakpoint(p.selectedGoroutine.PC, NextBreakpoint, sameGoroutineCondition(p.selectedGoroutine)); err != nil { + return err + } + return Continue(p) + } + p.allGCache = nil + if p.exited { + return &ProcessExitedError{Pid: p.conn.pid} + } + p.selectedGoroutine.thread.(*GdbserverThread).clearBreakpointState() + err := p.selectedGoroutine.thread.(*GdbserverThread).StepInstruction() + if err != nil { + return err + } + return p.selectedGoroutine.thread.(*GdbserverThread).SetCurrentBreakpoint() +} + +func (p *GdbserverProcess) SwitchThread(tid int) error { + if th, ok := p.threads[tid]; ok { + p.currentThread = th + p.selectedGoroutine, _ = GetG(p.CurrentThread()) + return nil + } + return fmt.Errorf("thread %d does not exist", tid) +} + +func (p *GdbserverProcess) SwitchGoroutine(gid int) error { + g, err := FindGoroutine(p, gid) + if err != nil { + return err + } + if g == nil { + // user specified -1 and selectedGoroutine is nil + return nil + } + if g.thread != nil { + return p.SwitchThread(g.thread.ThreadID()) + } + p.selectedGoroutine = g + return nil +} + +func (p *GdbserverProcess) RequestManualStop() error { + p.ctrlC = true + return p.conn.sendCtrlC() +} + +func (p *GdbserverProcess) Halt() error { + p.ctrlC = true + return p.conn.sendCtrlC() +} + +func (p *GdbserverProcess) Kill() error { + if p.exited { + return nil + } + err := p.conn.kill() + if _, exited := err.(ProcessExitedError); exited { + p.exited = true + return nil + } + return err +} + +func (p *GdbserverProcess) Detach(kill bool) error { + if kill { + if err := p.Kill(); err != nil { + if _, exited := err.(ProcessExitedError); !exited { + return err + } + } + } + if !p.exited { + if err := p.conn.detach(); err != nil { + return err + } + } + if p.process != nil { + p.process.Process.Kill() + p.process.Wait() + } + return p.bi.Close() +} + +func (p *GdbserverProcess) Breakpoints() map[uint64]*Breakpoint { + return p.breakpoints +} + +func (p *GdbserverProcess) FindBreakpoint(pc uint64) (*Breakpoint, bool) { + // Check to see if address is past the breakpoint, (i.e. breakpoint was hit). + if bp, ok := p.breakpoints[pc-uint64(p.bi.arch.BreakpointSize())]; ok { + return bp, true + } + // Directly use addr to lookup breakpoint. + if bp, ok := p.breakpoints[pc]; ok { + return bp, true + } + return nil, false +} + +func (p *GdbserverProcess) SetBreakpoint(addr uint64, kind BreakpointKind, cond ast.Expr) (*Breakpoint, error) { + if bp, ok := p.breakpoints[addr]; ok { + return nil, BreakpointExistsError{bp.File, bp.Line, bp.Addr} + } + f, l, fn := p.bi.PCToLine(uint64(addr)) + if fn == nil { + return nil, InvalidAddressError{address: addr} + } + + newBreakpoint := &Breakpoint{ + FunctionName: fn.Name, + File: f, + Line: l, + Addr: addr, + Kind: kind, + Cond: cond, + HitCount: map[int]uint64{}, + } + + if kind != UserBreakpoint { + p.internalBreakpointIDCounter++ + newBreakpoint.ID = p.internalBreakpointIDCounter + } else { + p.breakpointIDCounter++ + newBreakpoint.ID = p.breakpointIDCounter + } + + if err := p.conn.setBreakpoint(addr); err != nil { + return nil, err + } + p.breakpoints[addr] = newBreakpoint + return newBreakpoint, nil +} + +func (p *GdbserverProcess) ClearBreakpoint(addr uint64) (*Breakpoint, error) { + if p.exited { + return nil, &ProcessExitedError{Pid: p.conn.pid} + } + bp := p.breakpoints[addr] + if bp == nil { + return nil, NoBreakpointError{addr: addr} + } + + if err := p.conn.clearBreakpoint(addr); err != nil { + return nil, err + } + + delete(p.breakpoints, addr) + + return bp, nil +} + +func (p *GdbserverProcess) ClearInternalBreakpoints() error { + for _, bp := range p.breakpoints { + if !bp.Internal() { + continue + } + if _, err := p.ClearBreakpoint(bp.Addr); err != nil { + return err + } + } + for i := range p.threads { + if p.threads[i].CurrentBreakpoint != nil && p.threads[i].CurrentBreakpoint.Internal() { + p.threads[i].CurrentBreakpoint = nil + } + } + return nil +} + +type threadUpdater struct { + p *GdbserverProcess + seen map[int]bool + done bool +} + +func (tu *threadUpdater) Add(threads []string) error { + if tu.done { + panic("threadUpdater: Add after Finish") + } + if tu.seen == nil { + tu.seen = map[int]bool{} + } + for _, threadID := range threads { + b := threadID + if period := strings.Index(b, "."); period >= 0 { + b = b[period+1:] + } + n, err := strconv.ParseUint(b, 16, 32) + if err != nil { + return &GdbMalformedThreadIDError{threadID} + } + tid := int(n) + tu.seen[tid] = true + if _, found := tu.p.threads[tid]; !found { + tu.p.threads[tid] = &GdbserverThread{ID: tid, strID: threadID, p: tu.p} + } + } + return nil +} + +func (tu *threadUpdater) Finish() { + tu.done = true + for threadID := range tu.p.threads { + _, threadSeen := tu.seen[threadID] + if threadSeen { + continue + } + delete(tu.p.threads, threadID) + if tu.p.currentThread.ID == threadID { + tu.p.currentThread = nil + } + } + if tu.p.currentThread == nil { + for _, thread := range tu.p.threads { + tu.p.currentThread = thread + break + } + } +} + +// updateThreadsList retrieves the list of inferior threads from the stub +// and passes it to the threadUpdater. +// Then it reloads the register information for all running threads. +// Some stubs will return the list of running threads in the stop packet, if +// this happens the threadUpdater will know that we have already updated the +// thread list and the first step of updateThreadList will be skipped. +// Registers are always reloaded. +func (p *GdbserverProcess) updateThreadList(tu *threadUpdater) error { + if !tu.done { + first := true + for { + threads, err := p.conn.queryThreads(first) + if err != nil { + return err + } + if len(threads) == 0 { + break + } + first = false + if err := tu.Add(threads); err != nil { + return err + } + } + + tu.Finish() + } + + if p.threadStopInfo { + for _, th := range p.threads { + sig, reason, err := p.conn.threadStopInfo(th.strID) + if err != nil { + if isProtocolErrorUnsupported(err) { + p.threadStopInfo = false + break + } + return err + } + th.setbp = (reason == "breakpoint" || (reason == "" && sig == breakpointSignal)) + } + } + + for _, thread := range p.threads { + if err := thread.reloadRegisters(); err != nil { + return err + } + } + return nil +} + +func (p *GdbserverProcess) setCurrentBreakpoints() error { + if p.threadStopInfo { + for _, th := range p.threads { + if th.setbp { + err := th.SetCurrentBreakpoint() + if err != nil { + return err + } + } + } + } + if !p.threadStopInfo { + for _, th := range p.threads { + if th.CurrentBreakpoint == nil { + err := th.SetCurrentBreakpoint() + if err != nil { + return err + } + } + } + } + return nil +} + +func (t *GdbserverThread) readMemory(addr uintptr, size int) (data []byte, err error) { + return t.p.conn.readMemory(addr, size) +} + +func (t *GdbserverThread) writeMemory(addr uintptr, data []byte) (written int, err error) { + return t.p.conn.writeMemory(addr, data) +} + +func (t *GdbserverThread) Location() (*Location, error) { + regs, err := t.Registers(false) + if err != nil { + return nil, err + } + pc := regs.PC() + f, l, fn := t.p.bi.PCToLine(pc) + return &Location{PC: pc, File: f, Line: l, Fn: fn}, nil +} + +func (t *GdbserverThread) Breakpoint() (breakpoint *Breakpoint, active bool, condErr error) { + return t.CurrentBreakpoint, (t.CurrentBreakpoint != nil && t.BreakpointConditionMet), t.BreakpointConditionError +} + +func (t *GdbserverThread) ThreadID() int { + return t.ID +} + +func (t *GdbserverThread) Registers(floatingPoint bool) (Registers, error) { + return &t.regs, nil +} + +func (t *GdbserverThread) Arch() Arch { + return t.p.bi.arch +} + +func (t *GdbserverThread) BinInfo() *BinaryInfo { + return &t.p.bi +} + +func (t *GdbserverThread) stepInstruction(tu *threadUpdater) error { + pc := t.regs.PC() + if _, atbp := t.p.breakpoints[pc]; atbp { + err := t.p.conn.clearBreakpoint(pc) + if err != nil { + return err + } + defer t.p.conn.setBreakpoint(pc) + } + _, _, err := t.p.conn.step(t.strID, tu) + return err +} + +func (t *GdbserverThread) StepInstruction() error { + if err := t.stepInstruction(&threadUpdater{p: t.p}); err != nil { + return err + } + return t.reloadRegisters() +} + +// loadGInstr returns the correct MOV instruction for the current +// OS/architecture that can be executed to load the address of G from an +// inferior's thread. +func (p *GdbserverProcess) loadGInstr() []byte { + switch p.bi.goos { + case "windows": + //TODO(aarzilli): implement + panic("not implemented") + case "linux": + switch p.bi.arch.GStructOffset() { + case 0xfffffffffffffff8, 0x0: + // mov rcx,QWORD PTR fs:0xfffffffffffffff8 + return []byte{0x64, 0x48, 0x8B, 0x0C, 0x25, 0xF8, 0xFF, 0xFF, 0xFF} + case 0xfffffffffffffff0: + // mov rcx,QWORD PTR fs:0xfffffffffffffff0 + return []byte{0x64, 0x48, 0x8B, 0x0C, 0x25, 0xF0, 0xFF, 0xFF, 0xFF} + default: + panic("not implemented") + } + case "darwin": + // mov rcx,QWORD PTR gs:0x8a0 + return []byte{0x65, 0x48, 0x8B, 0x0C, 0x25, 0xA0, 0x08, 0x00, 0x00} + default: + panic("unsupported operating system attempting to find Goroutine on Thread") + } +} + +// reloadRegisters loads the current value of the thread's registers. +// It will also load the address of the thread's G. +// Loading the address of G can be done in one of two ways reloadGAlloc, if +// the stub can allocate memory, or reloadGAtPC, if the stub can't. +func (t *GdbserverThread) reloadRegisters() error { + if t.regs.regs == nil { + t.regs.regs = make(map[string]gdbRegister) + t.regs.regsInfo = t.p.conn.regsInfo + + regsz := 0 + for _, reginfo := range t.p.conn.regsInfo { + if endoff := reginfo.Offset + (reginfo.Bitsize / 8); endoff > regsz { + regsz = endoff + } + } + t.regs.buf = make([]byte, regsz) + for _, reginfo := range t.p.conn.regsInfo { + t.regs.regs[reginfo.Name] = gdbRegister{regnum: reginfo.Regnum, value: t.regs.buf[reginfo.Offset : reginfo.Offset+reginfo.Bitsize/8]} + } + } + + if t.p.gcmdok { + if err := t.p.conn.readRegisters(t.strID, t.regs.buf); err != nil { + if isProtocolErrorUnsupported(err) { + t.p.gcmdok = false + } else { + return err + } + } + } + if !t.p.gcmdok { + for _, reginfo := range t.p.conn.regsInfo { + if err := t.p.conn.readRegister(t.strID, reginfo.Regnum, t.regs.regs[reginfo.Name].value); err != nil { + return err + } + } + } + + if t.p.loadGInstrAddr > 0 { + return t.reloadGAlloc() + } + return t.reloadGAtPC() +} + +func (t *GdbserverThread) writeSomeRegisters(regNames ...string) error { + if t.p.gcmdok { + return t.p.conn.writeRegisters(t.strID, t.regs.buf) + } + for _, regName := range regNames { + if err := t.p.conn.writeRegister(t.strID, t.regs.regs[regName].regnum, t.regs.regs[regName].value); err != nil { + return err + } + } + return nil +} + +func (t *GdbserverThread) readSomeRegisters(regNames ...string) error { + if t.p.gcmdok { + return t.p.conn.readRegisters(t.strID, t.regs.buf) + } + for _, regName := range regNames { + err := t.p.conn.readRegister(t.strID, t.regs.regs[regName].regnum, t.regs.regs[regName].value) + if err != nil { + return err + } + } + return nil +} + +// reloadGAtPC overwrites the instruction that the thread is stopped at with +// the MOV instruction used to load current G, executes this single +// instruction and then puts everything back the way it was. +func (t *GdbserverThread) reloadGAtPC() error { + movinstr := t.p.loadGInstr() + + if gdbserverThreadBlocked(t) { + t.regs.tls = 0 + t.regs.gaddr = 0 + t.regs.hasgaddr = true + return nil + } + + cx := t.regs.CX() + pc := t.regs.PC() + + // We are partially replicating the code of GdbserverThread.stepInstruction + // here. + // The reason is that lldb-server has a bug with writing to memory and + // setting/clearing breakpoints to that same memory which we must work + // around by clearing and re-setting the breakpoint in a specific sequence + // with the memory writes. + // Additionally all breakpoints in [pc, pc+len(movinstr)] need to be removed + for addr, _ := range t.p.breakpoints { + if addr >= pc && addr <= pc+uint64(len(movinstr)) { + err := t.p.conn.clearBreakpoint(addr) + if err != nil { + return err + } + defer t.p.conn.setBreakpoint(addr) + } + } + + savedcode, err := t.readMemory(uintptr(pc), len(movinstr)) + if err != nil { + return err + } + + _, err = t.writeMemory(uintptr(pc), movinstr) + if err != nil { + return err + } + + defer func() { + _, err0 := t.writeMemory(uintptr(pc), savedcode) + if err == nil { + err = err0 + } + t.regs.setPC(pc) + t.regs.setCX(cx) + err1 := t.writeSomeRegisters(regnamePC, regnameCX) + if err == nil { + err = err1 + } + }() + + _, _, err = t.p.conn.step(t.strID, nil) + if err != nil { + return err + } + + if err := t.readSomeRegisters(regnamePC, regnameCX); err != nil { + return err + } + + t.regs.gaddr = t.regs.CX() + t.regs.hasgaddr = true + + return err +} + +// reloadGAlloc makes the specified thread execute one instruction stored at +// t.p.loadGInstrAddr then restores the value of the thread's registers. +// t.p.loadGInstrAddr must point to valid memory on the inferior, containing +// a MOV instruction that loads the address of the current G in the RCX +// register. +func (t *GdbserverThread) reloadGAlloc() error { + if gdbserverThreadBlocked(t) { + t.regs.tls = 0 + t.regs.gaddr = 0 + t.regs.hasgaddr = true + return nil + } + + cx := t.regs.CX() + pc := t.regs.PC() + + t.regs.setPC(t.p.loadGInstrAddr) + if err := t.writeSomeRegisters(regnamePC); err != nil { + return err + } + + var err error + + defer func() { + t.regs.setPC(pc) + t.regs.setCX(cx) + err1 := t.writeSomeRegisters(regnamePC, regnameCX) + if err == nil { + err = err1 + } + }() + + _, _, err = t.p.conn.step(t.strID, nil) + if err != nil { + return err + } + + if err := t.readSomeRegisters(regnameCX); err != nil { + return err + } + + t.regs.gaddr = t.regs.CX() + t.regs.hasgaddr = true + + return err +} + +func gdbserverThreadBlocked(t *GdbserverThread) bool { + regs, err := t.Registers(false) + if err != nil { + return false + } + pc := regs.PC() + fn := t.BinInfo().goSymTable.PCToFunc(pc) + if fn == nil { + return false + } + switch fn.Name { + case "runtime.futex", "runtime.usleep", "runtime.clone": + return true + case "runtime.kevent": + return true + case "runtime.mach_semaphore_wait", "runtime.mach_semaphore_timedwait": + return true + } + return false +} + +func (t *GdbserverThread) clearBreakpointState() { + t.setbp = false + t.CurrentBreakpoint = nil + t.BreakpointConditionMet = false + t.BreakpointConditionError = nil +} + +func (thread *GdbserverThread) SetCurrentBreakpoint() error { + thread.CurrentBreakpoint = nil + regs, err := thread.Registers(false) + if err != nil { + return err + } + pc := regs.PC() + if bp, ok := thread.p.FindBreakpoint(pc); ok { + if thread.regs.PC() != bp.Addr { + if err := thread.regs.SetPC(thread, bp.Addr); err != nil { + return err + } + } + thread.CurrentBreakpoint = bp + thread.BreakpointConditionMet, thread.BreakpointConditionError = bp.checkCondition(thread) + if thread.CurrentBreakpoint != nil && thread.BreakpointConditionMet { + if g, err := GetG(thread); err == nil { + thread.CurrentBreakpoint.HitCount[g.ID]++ + } + thread.CurrentBreakpoint.TotalHitCount++ + } + } + return nil +} + +func (regs *gdbRegisters) PC() uint64 { + return binary.LittleEndian.Uint64(regs.regs[regnamePC].value) +} + +func (regs *gdbRegisters) setPC(value uint64) { + binary.LittleEndian.PutUint64(regs.regs[regnamePC].value, value) +} + +func (regs *gdbRegisters) SP() uint64 { + return binary.LittleEndian.Uint64(regs.regs[regnameSP].value) +} + +func (regs *gdbRegisters) BP() uint64 { + return binary.LittleEndian.Uint64(regs.regs[regnameBP].value) +} + +func (regs *gdbRegisters) CX() uint64 { + return binary.LittleEndian.Uint64(regs.regs[regnameCX].value) +} + +func (regs *gdbRegisters) setCX(value uint64) { + binary.LittleEndian.PutUint64(regs.regs[regnameCX].value, value) +} + +func (regs *gdbRegisters) TLS() uint64 { + return regs.tls +} + +func (regs *gdbRegisters) GAddr() (uint64, bool) { + return regs.gaddr, regs.hasgaddr +} + +func (regs *gdbRegisters) byName(name string) uint64 { + reg, ok := regs.regs[name] + if !ok { + return 0 + } + return binary.LittleEndian.Uint64(reg.value) +} + +func (regs *gdbRegisters) Get(n int) (uint64, error) { + reg := x86asm.Reg(n) + const ( + mask8 = 0x000f + mask16 = 0x00ff + mask32 = 0xffff + ) + + switch reg { + // 8-bit + case x86asm.AL: + return regs.byName("rax") & mask8, nil + case x86asm.CL: + return regs.byName("rcx") & mask8, nil + case x86asm.DL: + return regs.byName("rdx") & mask8, nil + case x86asm.BL: + return regs.byName("rbx") & mask8, nil + case x86asm.AH: + return (regs.byName("rax") >> 8) & mask8, nil + case x86asm.CH: + return (regs.byName("rcx") >> 8) & mask8, nil + case x86asm.DH: + return (regs.byName("rdx") >> 8) & mask8, nil + case x86asm.BH: + return (regs.byName("rbx") >> 8) & mask8, nil + case x86asm.SPB: + return regs.byName("rsp") & mask8, nil + case x86asm.BPB: + return regs.byName("rbp") & mask8, nil + case x86asm.SIB: + return regs.byName("rsi") & mask8, nil + case x86asm.DIB: + return regs.byName("rdi") & mask8, nil + case x86asm.R8B: + return regs.byName("r8") & mask8, nil + case x86asm.R9B: + return regs.byName("r9") & mask8, nil + case x86asm.R10B: + return regs.byName("r10") & mask8, nil + case x86asm.R11B: + return regs.byName("r11") & mask8, nil + case x86asm.R12B: + return regs.byName("r12") & mask8, nil + case x86asm.R13B: + return regs.byName("r13") & mask8, nil + case x86asm.R14B: + return regs.byName("r14") & mask8, nil + case x86asm.R15B: + return regs.byName("r15") & mask8, nil + + // 16-bit + case x86asm.AX: + return regs.byName("rax") & mask16, nil + case x86asm.CX: + return regs.byName("rcx") & mask16, nil + case x86asm.DX: + return regs.byName("rdx") & mask16, nil + case x86asm.BX: + return regs.byName("rbx") & mask16, nil + case x86asm.SP: + return regs.byName("rsp") & mask16, nil + case x86asm.BP: + return regs.byName("rbp") & mask16, nil + case x86asm.SI: + return regs.byName("rsi") & mask16, nil + case x86asm.DI: + return regs.byName("rdi") & mask16, nil + case x86asm.R8W: + return regs.byName("r8") & mask16, nil + case x86asm.R9W: + return regs.byName("r9") & mask16, nil + case x86asm.R10W: + return regs.byName("r10") & mask16, nil + case x86asm.R11W: + return regs.byName("r11") & mask16, nil + case x86asm.R12W: + return regs.byName("r12") & mask16, nil + case x86asm.R13W: + return regs.byName("r13") & mask16, nil + case x86asm.R14W: + return regs.byName("r14") & mask16, nil + case x86asm.R15W: + return regs.byName("r15") & mask16, nil + + // 32-bit + case x86asm.EAX: + return regs.byName("rax") & mask32, nil + case x86asm.ECX: + return regs.byName("rcx") & mask32, nil + case x86asm.EDX: + return regs.byName("rdx") & mask32, nil + case x86asm.EBX: + return regs.byName("rbx") & mask32, nil + case x86asm.ESP: + return regs.byName("rsp") & mask32, nil + case x86asm.EBP: + return regs.byName("rbp") & mask32, nil + case x86asm.ESI: + return regs.byName("rsi") & mask32, nil + case x86asm.EDI: + return regs.byName("rdi") & mask32, nil + case x86asm.R8L: + return regs.byName("r8") & mask32, nil + case x86asm.R9L: + return regs.byName("r9") & mask32, nil + case x86asm.R10L: + return regs.byName("r10") & mask32, nil + case x86asm.R11L: + return regs.byName("r11") & mask32, nil + case x86asm.R12L: + return regs.byName("r12") & mask32, nil + case x86asm.R13L: + return regs.byName("r13") & mask32, nil + case x86asm.R14L: + return regs.byName("r14") & mask32, nil + case x86asm.R15L: + return regs.byName("r15") & mask32, nil + + // 64-bit + case x86asm.RAX: + return regs.byName("rax"), nil + case x86asm.RCX: + return regs.byName("rcx"), nil + case x86asm.RDX: + return regs.byName("rdx"), nil + case x86asm.RBX: + return regs.byName("rbx"), nil + case x86asm.RSP: + return regs.byName("rsp"), nil + case x86asm.RBP: + return regs.byName("rbp"), nil + case x86asm.RSI: + return regs.byName("rsi"), nil + case x86asm.RDI: + return regs.byName("rdi"), nil + case x86asm.R8: + return regs.byName("r8"), nil + case x86asm.R9: + return regs.byName("r9"), nil + case x86asm.R10: + return regs.byName("r10"), nil + case x86asm.R11: + return regs.byName("r11"), nil + case x86asm.R12: + return regs.byName("r12"), nil + case x86asm.R13: + return regs.byName("r13"), nil + case x86asm.R14: + return regs.byName("r14"), nil + case x86asm.R15: + return regs.byName("r15"), nil + } + + return 0, UnknownRegisterError +} + +func (regs *gdbRegisters) SetPC(thread IThread, pc uint64) error { + regs.setPC(pc) + t := thread.(*GdbserverThread) + if t.p.gcmdok { + return t.p.conn.writeRegisters(t.strID, t.regs.buf) + } + reg := regs.regs[regnamePC] + return t.p.conn.writeRegister(t.strID, reg.regnum, reg.value) +} + +func (regs *gdbRegisters) Slice() []Register { + r := make([]Register, 0, len(regs.regsInfo)) + for _, reginfo := range regs.regsInfo { + switch { + case reginfo.Name == "eflags": + r = appendFlagReg(r, reginfo.Name, uint64(binary.LittleEndian.Uint32(regs.regs[reginfo.Name].value)), eflagsDescription, 32) + case reginfo.Name == "mxcsr": + r = appendFlagReg(r, reginfo.Name, uint64(binary.LittleEndian.Uint32(regs.regs[reginfo.Name].value)), mxcsrDescription, 32) + case reginfo.Bitsize == 16: + r = appendWordReg(r, reginfo.Name, binary.LittleEndian.Uint16(regs.regs[reginfo.Name].value)) + case reginfo.Bitsize == 32: + r = appendDwordReg(r, reginfo.Name, binary.LittleEndian.Uint32(regs.regs[reginfo.Name].value)) + case reginfo.Bitsize == 64: + r = appendQwordReg(r, reginfo.Name, binary.LittleEndian.Uint64(regs.regs[reginfo.Name].value)) + case reginfo.Bitsize == 80: + idx := 0 + for _, stprefix := range []string{"stmm", "st"} { + if strings.HasPrefix(reginfo.Name, stprefix) { + idx, _ = strconv.Atoi(reginfo.Name[len(stprefix):]) + break + } + } + value := regs.regs[reginfo.Name].value + r = appendX87Reg(r, idx, binary.LittleEndian.Uint16(value[8:]), binary.LittleEndian.Uint64(value[:8])) + + case reginfo.Bitsize == 128: + r = appendSSEReg(r, strings.ToUpper(reginfo.Name), regs.regs[reginfo.Name].value) + + case reginfo.Bitsize == 256: + if !strings.HasPrefix(strings.ToLower(reginfo.Name), "ymm") { + continue + } + + value := regs.regs[reginfo.Name].value + xmmName := "x" + reginfo.Name[1:] + r = appendSSEReg(r, strings.ToUpper(xmmName), value[:16]) + r = appendSSEReg(r, strings.ToUpper(reginfo.Name), value[16:]) + } + } + return r +} diff --git a/pkg/proc/gdbserver_conn.go b/pkg/proc/gdbserver_conn.go new file mode 100644 index 00000000..a5b0ed42 --- /dev/null +++ b/pkg/proc/gdbserver_conn.go @@ -0,0 +1,1066 @@ +package proc + +import ( + "bufio" + "bytes" + "encoding/xml" + "errors" + "fmt" + "io" + "net" + "os" + "strconv" + "strings" + "time" +) + +type gdbConn struct { + conn net.Conn + rdr *bufio.Reader + + inbuf []byte + outbuf bytes.Buffer + + running bool + + packetSize int // maximum packet size supported by stub + regsInfo []gdbRegisterInfo // list of registers + + pid int // cache process id + + ack bool // when ack is true acknowledgment packets are enabled + multiprocess bool // multiprocess extensions are active + maxTransmitAttempts int // maximum number of transmit or receive attempts when bad checksums are read + threadSuffixSupported bool // thread suffix supported by stub + isDebugserver bool // true if the stub is debugserver +} + +const ( + regnamePC = "rip" + regnameCX = "rcx" + regnameSP = "rsp" + regnameBP = "rbp" +) + +var ErrTooManyAttempts = errors.New("too many transmit attempts") +var ErrNoTargetDescrption = errors.New("target description not supported") + +// GdbProtocolError is an error response (Exx) of Gdb Remote Serial Protocol +// or an "unsupported command" response (empty packet). +type GdbProtocolError struct { + context string + cmd string + code string +} + +func (err *GdbProtocolError) Error() string { + cmd := err.cmd + if len(cmd) > 20 { + cmd = cmd[:20] + "..." + } + if err.code == "" { + return fmt.Sprintf("unsupported packet %s during %s", cmd, err.context) + } + return fmt.Sprintf("protocol error %s during %s for packet %s", err.code, err.context, cmd) +} + +func isProtocolErrorUnsupported(err error) bool { + gdberr, ok := err.(*GdbProtocolError) + if !ok { + return false + } + return gdberr.code == "" +} + +// GdbMalformedThreadIDError is returned when a the stub responds with a +// thread ID that does not conform with the Gdb Remote Serial Protocol +// specification. +type GdbMalformedThreadIDError struct { + tid string +} + +func (err *GdbMalformedThreadIDError) Error() string { + return fmt.Sprintf("malformed thread ID %q", err.tid) +} + +const ( + qSupportedSimple = "$qSupported:swbreak+;hwbreak+;no-resumed+;xmlRegisters=i386" + qSupportedMultiprocess = "$qSupported:multiprocess+;swbreak+;hwbreak+;no-resumed+;xmlRegisters=i386" +) + +func (conn *gdbConn) handshake() error { + conn.ack = true + conn.packetSize = 256 + conn.rdr = bufio.NewReader(conn.conn) + + // This first ack packet is needed to start up the connection + conn.sendack('+') + + conn.disableAck() + + // Try to enable thread suffixes for the command 'g' and 'p' + if _, err := conn.exec([]byte("$QThreadSuffixSupported"), "init"); err != nil { + if isProtocolErrorUnsupported(err) { + conn.threadSuffixSupported = false + } else { + return err + } + } else { + conn.threadSuffixSupported = true + } + + if !conn.threadSuffixSupported { + features, err := conn.qSupported(true) + if err != nil { + return err + } + conn.multiprocess = features["multiprocess"] + + // for some reason gdbserver won't let us read target.xml unless first we + // select a thread. + if conn.multiprocess { + conn.exec([]byte("$Hgp0.0"), "init") + } else { + conn.exec([]byte("$Hgp0"), "init") + } + } else { + // execute qSupported with the multiprocess feature disabled (the + // interaction of thread suffixes and multiprocess is not documented), we + // only need this call to configure conn.packetSize. + if _, err := conn.qSupported(false); err != nil { + return err + } + } + + // Attempt to figure out the name of the processor register. + // We either need qXfer:features:read (gdbserver/rr) or qRegisterInfo (lldb) + if err := conn.readRegisterInfo(); err != nil { + if isProtocolErrorUnsupported(err) { + if err := conn.readTargetXml(); err != nil { + return err + } + } else { + return err + } + } + + // We either need: + // * QListThreadsInStopReply + qThreadStopInfo (i.e. lldb-server/debugserver), + // * or a stub that runs the inferior in single threaded mode (i.e. rr). + // Otherwise we'll have problems handling breakpoints in multithreaded programs. + if _, err := conn.exec([]byte("$QListThreadsInStopReply"), "init"); err != nil { + gdberr, ok := err.(*GdbProtocolError) + if !ok { + return err + } + if gdberr.code != "" { + return err + } + } + + return nil +} + +// qSupported interprets qSupported responses. +func (conn *gdbConn) qSupported(multiprocess bool) (features map[string]bool, err error) { + q := qSupportedSimple + if multiprocess { + q = qSupportedMultiprocess + } + respBuf, err := conn.exec([]byte(q), "init/qSupported") + if err != nil { + return nil, err + } + resp := strings.Split(string(respBuf), ";") + features = make(map[string]bool) + for _, stubfeature := range resp { + if len(stubfeature) <= 0 { + continue + } else if equal := strings.Index(stubfeature, "="); equal >= 0 { + if stubfeature[:equal] == "PacketSize" { + if n, err := strconv.ParseInt(stubfeature[equal+1:], 16, 64); err == nil { + conn.packetSize = int(n) + } + } + } else if stubfeature[len(stubfeature)-1] == '+' { + features[stubfeature[:len(stubfeature)-1]] = true + } + } + return features, nil +} + +// disableAck disables protocol acks. +func (conn *gdbConn) disableAck() error { + _, err := conn.exec([]byte("$QStartNoAckMode"), "init/disableAck") + if err == nil { + conn.ack = false + } + return err +} + +// gdbTarget is a struct type used to parse target.xml +type gdbTarget struct { + Includes []gdbTargetInclude `xml:"xi include"` + Registers []gdbRegisterInfo `xml:"reg"` +} + +type gdbTargetInclude struct { + Href string `xml:"href,attr"` +} + +type gdbRegisterInfo struct { + Name string `xml:"name,attr"` + Bitsize int `xml:"bitsize,attr"` + Offset int + Regnum int `xml:"regnum,attr"` +} + +// readTargetXml reads target.xml file from stub using qXfer:features:read, +// then parses it requesting any additional files. +// The schema of target.xml is described by: +// https://github.com/bminor/binutils-gdb/blob/61baf725eca99af2569262d10aca03dcde2698f6/gdb/features/gdb-target.dtd +func (conn *gdbConn) readTargetXml() (err error) { + conn.regsInfo, err = conn.readAnnex("target.xml") + if err != nil { + return err + } + var offset int + var pcFound, cxFound, spFound bool + regnum := 0 + for i := range conn.regsInfo { + if conn.regsInfo[i].Regnum == 0 { + conn.regsInfo[i].Regnum = regnum + } else { + regnum = conn.regsInfo[i].Regnum + } + conn.regsInfo[i].Offset = offset + offset += conn.regsInfo[i].Bitsize / 8 + switch conn.regsInfo[i].Name { + case regnamePC: + pcFound = true + case regnameCX: + cxFound = true + case regnameSP: + spFound = true + } + regnum++ + } + if !pcFound { + return errors.New("could not find RIP register") + } + if !spFound { + return errors.New("could not find RSP register") + } + if !cxFound { + return errors.New("could not find RCX register") + } + return nil +} + +// readRegisterInfo uses qRegisterInfo to read register information (used +// when qXfer:feature:read is not supported). +func (conn *gdbConn) readRegisterInfo() (err error) { + regnum := 0 + var pcFound, cxFound, spFound bool + for { + conn.outbuf.Reset() + fmt.Fprintf(&conn.outbuf, "$qRegisterInfo%x", regnum) + respbytes, err := conn.exec(conn.outbuf.Bytes(), "register info") + if err != nil { + break + } + + var regname string + var offset int + var bitsize int + var contained bool + + resp := string(respbytes) + for { + semicolon := strings.Index(resp, ";") + keyval := resp + if semicolon >= 0 { + keyval = resp[:semicolon] + } + + colon := strings.Index(keyval, ":") + if colon >= 0 { + name := keyval[:colon] + value := keyval[colon+1:] + + switch name { + case "name": + regname = value + case "offset": + offset, _ = strconv.Atoi(value) + case "bitsize": + bitsize, _ = strconv.Atoi(value) + case "container-regs": + contained = true + } + } + + if semicolon < 0 { + break + } + resp = resp[semicolon+1:] + } + + if contained { + regnum++ + continue + } + + switch regname { + case regnamePC: + pcFound = true + case regnameCX: + cxFound = true + case regnameSP: + spFound = true + } + + conn.regsInfo = append(conn.regsInfo, gdbRegisterInfo{Regnum: regnum, Name: regname, Bitsize: bitsize, Offset: offset}) + + regnum++ + } + + if !pcFound { + return errors.New("could not find RIP register") + } + if !spFound { + return errors.New("could not find RSP register") + } + if !cxFound { + return errors.New("could not find RCX register") + } + + return nil +} + +func (conn *gdbConn) readAnnex(annex string) ([]gdbRegisterInfo, error) { + tgtbuf, err := conn.qXfer("features", annex) + if err != nil { + return nil, err + } + var tgt gdbTarget + if err := xml.Unmarshal(tgtbuf, &tgt); err != nil { + return nil, err + } + + for _, incl := range tgt.Includes { + regs, err := conn.readAnnex(incl.Href) + if err != nil { + return nil, err + } + tgt.Registers = append(tgt.Registers, regs...) + } + return tgt.Registers, nil +} + +func (conn *gdbConn) readExecFile() (string, error) { + outbuf, err := conn.qXfer("exec-file", "") + if err != nil { + return "", err + } + return string(outbuf), nil +} + +// qXfer executes a 'qXfer' read with the specified kind (i.e. feature, +// exec-file, etc...) and annex. +func (conn *gdbConn) qXfer(kind, annex string) ([]byte, error) { + out := []byte{} + for { + buf, err := conn.exec([]byte(fmt.Sprintf("$qXfer:%s:read:%s:%x,fff", kind, annex, len(out))), "target features transfer") + if err != nil { + return nil, err + } + + out = append(out, buf[1:]...) + if buf[0] == 'l' { + break + } + } + return out, nil +} + +// setBreakpoint executes a 'Z' (insert breakpoint) command of type '0' and kind '1' +func (conn *gdbConn) setBreakpoint(addr uint64) error { + conn.outbuf.Reset() + fmt.Fprintf(&conn.outbuf, "$Z0,%x,1", addr) + _, err := conn.exec(conn.outbuf.Bytes(), "set breakpoint") + return err +} + +// clearBreakpoint executes a 'z' (remove breakpoint) command of type '0' and kind '1' +func (conn *gdbConn) clearBreakpoint(addr uint64) error { + conn.outbuf.Reset() + fmt.Fprintf(&conn.outbuf, "$z0,%x,1", addr) + _, err := conn.exec(conn.outbuf.Bytes(), "clear breakpoint") + return err +} + +// kill executes a 'k' (kill) command. +func (conn *gdbConn) kill() error { + resp, err := conn.exec([]byte{'$', 'k'}, "kill") + if err == io.EOF { + // The stub is allowed to shut the connection on us immediately after a + // kill. This is not an error. + conn.conn.Close() + conn.conn = nil + return nil + } + if err != nil { + return err + } + _, _, err = conn.parseStopPacket(resp, "", nil) + return err +} + +// detach executes a 'D' (detach) command. +func (conn *gdbConn) detach() error { + if conn.conn == nil { + // Already detached + return nil + } + _, err := conn.exec([]byte{'$', 'D'}, "detach") + conn.conn.Close() + conn.conn = nil + return err +} + +// readRegisters executes a 'g' (read registers) command. +func (conn *gdbConn) readRegisters(threadID string, data []byte) error { + if !conn.threadSuffixSupported { + if err := conn.selectThread('g', threadID, "registers read"); err != nil { + return err + } + } + conn.outbuf.Reset() + conn.outbuf.WriteString("$g") + conn.appendThreadSelector(threadID) + resp, err := conn.exec(conn.outbuf.Bytes(), "registers read") + if err != nil { + return err + } + + for i := 0; i < len(resp); i += 2 { + n, _ := strconv.ParseUint(string(resp[i:i+2]), 16, 8) + data[i/2] = uint8(n) + } + + return nil +} + +// writeRegisters executes a 'G' (write registers) command. +func (conn *gdbConn) writeRegisters(threadID string, data []byte) error { + if !conn.threadSuffixSupported { + if err := conn.selectThread('g', threadID, "registers write"); err != nil { + return err + } + } + conn.outbuf.Reset() + conn.outbuf.WriteString("$G") + + for _, b := range data { + fmt.Fprintf(&conn.outbuf, "%02x", b) + } + conn.appendThreadSelector(threadID) + _, err := conn.exec(conn.outbuf.Bytes(), "registers write") + return err +} + +// readRegister executes 'p' (read register) command. +func (conn *gdbConn) readRegister(threadID string, regnum int, data []byte) error { + if !conn.threadSuffixSupported { + if err := conn.selectThread('g', threadID, "registers write"); err != nil { + return err + } + } + conn.outbuf.Reset() + fmt.Fprintf(&conn.outbuf, "$p%x", regnum) + conn.appendThreadSelector(threadID) + resp, err := conn.exec(conn.outbuf.Bytes(), "register read") + if err != nil { + return err + } + + for i := 0; i < len(resp); i += 2 { + n, _ := strconv.ParseUint(string(resp[i:i+2]), 16, 8) + data[i/2] = uint8(n) + } + + return nil +} + +// writeRegister executes 'P' (write register) command. +func (conn *gdbConn) writeRegister(threadID string, regnum int, data []byte) error { + if !conn.threadSuffixSupported { + if err := conn.selectThread('g', threadID, "registers write"); err != nil { + return err + } + } + conn.outbuf.Reset() + fmt.Fprintf(&conn.outbuf, "$P%x=", regnum) + for _, b := range data { + fmt.Fprintf(&conn.outbuf, "%02x", b) + } + conn.appendThreadSelector(threadID) + _, err := conn.exec(conn.outbuf.Bytes(), "register write") + return err +} + +// resume executes a 'vCont' command on all threads with action 'c' if sig +// is 0 or 'C' if it isn't. +func (conn *gdbConn) resume(sig uint8, tu *threadUpdater) (string, uint8, error) { + conn.outbuf.Reset() + if sig == 0 { + fmt.Fprintf(&conn.outbuf, "$vCont;c") + } else { + fmt.Fprintf(&conn.outbuf, "$vCont;C%02x", sig) + } + if err := conn.send(conn.outbuf.Bytes()); err != nil { + return "", 0, err + } + conn.running = true + defer func() { + conn.running = false + }() + return conn.waitForvContStop("resume", "-1", tu) +} + +// step executes a 'vCont' command on the specified thread with 's' action. +func (conn *gdbConn) step(threadID string, tu *threadUpdater) (string, uint8, error) { + conn.outbuf.Reset() + fmt.Fprintf(&conn.outbuf, "$vCont;s:%s", threadID) + if err := conn.send(conn.outbuf.Bytes()); err != nil { + return "", 0, err + } + return conn.waitForvContStop("singlestep", threadID, tu) +} + +func (conn *gdbConn) waitForvContStop(context string, threadID string, tu *threadUpdater) (string, uint8, error) { + for { + conn.conn.SetReadDeadline(time.Now().Add(heartbeatInterval)) + resp, err := conn.recv(nil, context) + conn.conn.SetReadDeadline(time.Time{}) + if neterr, isneterr := err.(net.Error); isneterr && neterr.Timeout() { + // Debugserver sometimes forgets to inform us that inferior stopped, + // sending this status request after a timeout helps us get unstuck. + // Debugserver will not respond to this request unless inferior is + // already stopped. + if conn.isDebugserver { + conn.send([]byte("$?")) + } + } else if err != nil { + return "", 0, err + } else { + repeat, sp, err := conn.parseStopPacket(resp, threadID, tu) + if !repeat { + return sp.threadID, sp.sig, err + } + } + } +} + +type stopPacket struct { + threadID string + sig uint8 + reason string +} + +// executes 'vCont' (continue/step) command +func (conn *gdbConn) parseStopPacket(resp []byte, threadID string, tu *threadUpdater) (repeat bool, sp stopPacket, err error) { + switch resp[0] { + case 'T': + if len(resp) < 3 { + return false, stopPacket{}, fmt.Errorf("malformed response for vCont %s", string(resp)) + } + + sig, err := strconv.ParseUint(string(resp[1:3]), 16, 8) + if err != nil { + return false, stopPacket{}, fmt.Errorf("malformed stop packet: %s", string(resp)) + } + sp.sig = uint8(sig) + + if logGdbWire && logGdbWireFullStopPacket { + fmt.Fprintf(os.Stderr, "full stop packet: %s\n", string(resp)) + } + + buf := resp[3:] + for buf != nil { + colon := bytes.Index(buf, []byte{':'}) + if colon < 0 { + break + } + key := buf[:colon] + buf = buf[colon+1:] + + semicolon := bytes.Index(buf, []byte{';'}) + var value []byte + if semicolon < 0 { + value = buf + buf = nil + } else { + value = buf[:semicolon] + buf = buf[semicolon+1:] + } + + switch string(key) { + case "thread": + sp.threadID = string(value) + case "threads": + if tu != nil { + tu.Add(strings.Split(string(value), ",")) + tu.Finish() + } + case "reason": + sp.reason = string(value) + } + } + + return false, sp, nil + + case 'W', 'X': + // process exited, next two character are exit code + + semicolon := bytes.Index(resp, []byte{';'}) + + if semicolon < 0 { + semicolon = len(resp) + } + status, _ := strconv.ParseUint(string(resp[1:semicolon]), 16, 8) + return false, stopPacket{}, ProcessExitedError{Pid: conn.pid, Status: int(status)} + + case 'N': + // we were singlestepping the thread and the thread exited + sp.threadID = threadID + return false, sp, nil + + case 'O': + data := make([]byte, 0, len(resp[1:])/2) + for i := 1; i < len(resp); i += 2 { + n, _ := strconv.ParseUint(string(resp[i:i+2]), 16, 8) + data = append(data, uint8(n)) + } + os.Stdout.Write(data) + return true, sp, nil + + default: + return false, sp, fmt.Errorf("unexpected response for vCont %c", resp[0]) + } +} + +const ctrlC = 0x03 // the ASCII character for ^C + +// executes a ctrl-C on the line +func (conn *gdbConn) sendCtrlC() error { + if logGdbWire { + fmt.Println("<- interrupt") + } + _, err := conn.conn.Write([]byte{ctrlC}) + return err +} + +// queryProcessInfo executes a qProcessInfoPID (if pid != 0) or a qProcessInfo (if pid == 0) +func (conn *gdbConn) queryProcessInfo(pid int) (map[string]string, error) { + conn.outbuf.Reset() + if pid != 0 { + fmt.Fprintf(&conn.outbuf, "$qProcessInfoPID:%d", pid) + } else { + fmt.Fprintf(&conn.outbuf, "$qProcessInfo") + } + resp, err := conn.exec(conn.outbuf.Bytes(), "process info for pid") + if err != nil { + return nil, err + } + + pi := make(map[string]string) + + for len(resp) > 0 { + semicolon := bytes.Index(resp, []byte{';'}) + keyval := resp + if semicolon >= 0 { + keyval = resp[:semicolon] + resp = resp[semicolon+1:] + } + + colon := bytes.Index(keyval, []byte{':'}) + if colon < 0 { + continue + } + + key := string(keyval[:colon]) + value := string(keyval[colon+1:]) + + switch key { + case "name": + name := make([]byte, len(value)/2) + for i := 0; i < len(value); i += 2 { + n, _ := strconv.ParseUint(string(value[i:i+2]), 16, 8) + name[i/2] = byte(n) + } + pi[key] = string(name) + + default: + pi[key] = value + } + } + return pi, nil +} + +// executes qfThreadInfo/qsThreadInfo commands +func (conn *gdbConn) queryThreads(first bool) (threads []string, err error) { + // https://sourceware.org/gdb/onlinedocs/gdb/General-Query-Packets.html + conn.outbuf.Reset() + if first { + conn.outbuf.WriteString("$qfThreadInfo") + } else { + conn.outbuf.WriteString("$qsThreadInfo") + } + + resp, err := conn.exec(conn.outbuf.Bytes(), "thread info") + if err != nil { + return nil, err + } + + switch resp[0] { + case 'l': + return nil, nil + case 'm': + // parse list... + default: + return nil, errors.New("malformed qfThreadInfo response") + } + + var pid int + resp = resp[1:] + for { + tidbuf := resp + comma := bytes.Index(tidbuf, []byte{','}) + if comma >= 0 { + tidbuf = tidbuf[:comma] + } + if conn.multiprocess && pid == 0 { + dot := bytes.Index(tidbuf, []byte{'.'}) + if dot >= 0 { + pid, _ = strconv.Atoi(string(tidbuf[1:dot])) + } + } + threads = append(threads, string(tidbuf)) + if comma < 0 { + break + } + resp = resp[comma+1:] + } + + if conn.multiprocess && pid > 0 { + conn.pid = pid + } + return threads, nil +} + +func (conn *gdbConn) selectThread(kind byte, threadID string, context string) error { + if conn.threadSuffixSupported { + panic("selectThread when thread suffix is supported") + } + conn.outbuf.Reset() + fmt.Fprintf(&conn.outbuf, "$H%c%s", kind, threadID) + _, err := conn.exec(conn.outbuf.Bytes(), context) + return err +} + +func (conn *gdbConn) appendThreadSelector(threadID string) { + if !conn.threadSuffixSupported { + return + } + fmt.Fprintf(&conn.outbuf, ";thread:%s;", threadID) +} + +// executes 'm' (read memory) command +func (conn *gdbConn) readMemory(addr uintptr, size int) (data []byte, err error) { + data = make([]byte, 0, size) + + for size > 0 { + conn.outbuf.Reset() + + // gdbserver will crash if we ask too many bytes... not return an error, actually crash + sz := size + if dataSize := (conn.packetSize - 4) / 2; sz > dataSize { + sz = dataSize + } + size = size - sz + + fmt.Fprintf(&conn.outbuf, "$m%x,%x", addr+uintptr(len(data)), sz) + resp, err := conn.exec(conn.outbuf.Bytes(), "memory read") + if err != nil { + return nil, err + } + + for i := 0; i < len(resp); i += 2 { + n, _ := strconv.ParseUint(string(resp[i:i+2]), 16, 8) + data = append(data, uint8(n)) + } + } + return data, nil +} + +// executes 'M' (write memory) command +func (conn *gdbConn) writeMemory(addr uintptr, data []byte) (written int, err error) { + conn.outbuf.Reset() + //TODO(aarzilli): do not send packets larger than conn.PacketSize + fmt.Fprintf(&conn.outbuf, "$M%x,%x:", addr, len(data)) + + for _, b := range data { + fmt.Fprintf(&conn.outbuf, "%02x", b) + } + + _, err = conn.exec(conn.outbuf.Bytes(), "memory write") + if err != nil { + return 0, err + } + return len(data), nil +} + +func (conn *gdbConn) allocMemory(sz uint64) (uint64, error) { + conn.outbuf.Reset() + fmt.Fprintf(&conn.outbuf, "$_M%x,rwx", sz) + resp, err := conn.exec(conn.outbuf.Bytes(), "memory allocation") + if err != nil { + return 0, err + } + return strconv.ParseUint(string(resp), 16, 64) +} + +// threadStopInfo executes a 'qThreadStopInfo' and returns the reason the +// thread stopped. +func (conn *gdbConn) threadStopInfo(threadID string) (sig uint8, reason string, err error) { + conn.outbuf.Reset() + fmt.Fprintf(&conn.outbuf, "$qThreadStopInfo%s", threadID) + resp, err := conn.exec(conn.outbuf.Bytes(), "thread stop info") + if err != nil { + return 0, "", err + } + _, sp, err := conn.parseStopPacket(resp, "", nil) + if err != nil { + return 0, "", err + } + return sp.sig, sp.reason, nil +} + +// exec executes a message to the stub and reads a response. +// The details of the wire protocol are described here: +// https://sourceware.org/gdb/onlinedocs/gdb/Overview.html#Overview +func (conn *gdbConn) exec(cmd []byte, context string) ([]byte, error) { + if err := conn.send(cmd); err != nil { + return nil, err + } + return conn.recv(cmd, context) + +} + +var hexdigit = []byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'} + +func (conn *gdbConn) send(cmd []byte) error { + if len(cmd) == 0 || cmd[0] != '$' { + panic("gdb protocol error: command doesn't start with '$'") + } + + // append checksum to packet + cmd = append(cmd, '#') + sum := checksum(cmd) + cmd = append(cmd, hexdigit[sum>>4]) + cmd = append(cmd, hexdigit[sum&0xf]) + + attempt := 0 + for { + if logGdbWire { + if len(cmd) > logGdbWireMaxLen { + fmt.Printf("<- %s...\n", string(cmd[:logGdbWireMaxLen])) + } else { + fmt.Printf("<- %s\n", string(cmd)) + } + } + _, err := conn.conn.Write(cmd) + if err != nil { + return err + } + + if !conn.ack { + break + } + + if conn.readack() { + break + } + if attempt > conn.maxTransmitAttempts { + return ErrTooManyAttempts + } + attempt++ + } + return nil +} + +func (conn *gdbConn) recv(cmd []byte, context string) (resp []byte, err error) { + attempt := 0 + for { + var err error + resp, err = conn.rdr.ReadBytes('#') + if err != nil { + return nil, err + } + + // read checksum + _, err = conn.rdr.Read(conn.inbuf[:2]) + if err != nil { + return nil, err + } + if logGdbWire { + out := resp + partial := false + if idx := bytes.Index(out, []byte{'\n'}); idx >= 0 { + out = resp[:idx] + partial = true + } + if len(out) > logGdbWireMaxLen { + out = out[:logGdbWireMaxLen] + partial = true + } + if !partial { + fmt.Printf("-> %s%s\n", string(resp), string(conn.inbuf[:2])) + } else { + fmt.Printf("-> %s...\n", string(out)) + } + } + + if !conn.ack { + break + } + + if resp[0] == '%' { + // If the first character is a % (instead of $) the stub sent us a + // notification packet, this is weird since we specifically claimed that + // we don't support notifications of any kind, but it should be safe to + // ignore regardless. + continue + } + + if checksumok(resp, conn.inbuf[:2]) { + conn.sendack('+') + break + } + if attempt > conn.maxTransmitAttempts { + conn.sendack('+') + return nil, ErrTooManyAttempts + } + attempt++ + conn.sendack('-') + } + + conn.inbuf, resp = wiredecode(resp, conn.inbuf) + + if len(resp) == 0 || resp[0] == 'E' { + cmdstr := "" + if cmd != nil { + cmdstr = string(cmd) + } + return nil, &GdbProtocolError{context, cmdstr, string(resp)} + } + + return resp, nil +} + +// Readack reads one byte from stub, returns true if the byte is '+' +func (conn *gdbConn) readack() bool { + b, err := conn.rdr.ReadByte() + if err != nil { + return false + } + if logGdbWire { + fmt.Printf("-> %s\n", string(b)) + } + return b == '+' +} + +// Sendack executes an ack character, c must be either '+' or '-' +func (conn *gdbConn) sendack(c byte) { + if c != '+' && c != '-' { + panic(fmt.Errorf("sendack(%c)", c)) + } + conn.conn.Write([]byte{c}) + if logGdbWire { + fmt.Printf("<- %s\n", string(c)) + } +} + +// wiredecode decodes the contents of in into buf. +// If buf is nil it will be allocated ex-novo, if the size of buf is not +// enough to hold the decoded contents it will be grown. +// Returns the newly allocated buffer as newbuf and the message contents as +// msg. +func wiredecode(in, buf []byte) (newbuf, msg []byte) { + if buf != nil { + buf = buf[:0] + } else { + buf = make([]byte, 0, 256) + } + + start := 1 + + for i := 0; i < len(in); i++ { + switch ch := in[i]; ch { + case '{': // escape + if i+1 >= len(in) { + buf = append(buf, ch) + } else { + buf = append(buf, in[i+1]^0x20) + } + case ':': + buf = append(buf, ch) + if i == 3 { + // we just read the sequence identifier + start = i + 1 + } + case '#': // end of packet + return buf, buf[start:] + case '*': // runlenght encoding marker + if i+1 >= len(in) || i == 0 { + buf = append(buf, ch) + } else { + n := in[i+1] - 29 + r := buf[len(buf)-1] + for j := uint8(0); j < n; j++ { + buf = append(buf, r) + } + i++ + } + default: + buf = append(buf, ch) + } + } + return buf, buf[start:] +} + +// Checksumok checks that checksum is a valid checksum for packet. +func checksumok(packet, checksumBuf []byte) bool { + if packet[0] != '$' { + return false + } + + sum := checksum(packet) + tgt, err := strconv.ParseUint(string(checksumBuf), 16, 8) + if err != nil { + return false + } + return sum == uint8(tgt) +} + +func checksum(packet []byte) (sum uint8) { + for i := 1; i < len(packet); i++ { + if packet[i] == '#' { + return sum + } + sum += packet[i] + } + return sum +} diff --git a/pkg/proc/gdbserver_unix.go b/pkg/proc/gdbserver_unix.go new file mode 100644 index 00000000..b77f7e03 --- /dev/null +++ b/pkg/proc/gdbserver_unix.go @@ -0,0 +1,9 @@ +// +build linux darwin + +package proc + +import "syscall" + +func backgroundSysProcAttr() *syscall.SysProcAttr { + return &syscall.SysProcAttr{Setpgid: true, Pgid: 0, Foreground: false} +} diff --git a/pkg/proc/gdbserver_windows.go b/pkg/proc/gdbserver_windows.go new file mode 100644 index 00000000..089f4db0 --- /dev/null +++ b/pkg/proc/gdbserver_windows.go @@ -0,0 +1,7 @@ +package proc + +import "syscall" + +func backgroundSysProcAttr() *syscall.SysProcAttr { + return nil +} diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index 1ffffa6f..07fe86ae 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -2,6 +2,8 @@ package proc import ( "bytes" + "debug/gosym" + "flag" "fmt" "go/ast" "go/constant" @@ -22,6 +24,7 @@ import ( ) var normalLoadConfig = LoadConfig{true, 1, 64, 64, -1} +var testBackend string func init() { runtime.GOMAXPROCS(4) @@ -29,12 +32,62 @@ func init() { } func TestMain(m *testing.M) { + flag.StringVar(&testBackend, "backend", "", "selects backend") + flag.Parse() + if testBackend == "" { + testBackend = os.Getenv("PROCTEST") + if testBackend == "" { + testBackend = "native" + } + } os.Exit(protest.RunTestsWithFixtures(m)) } -func withTestProcess(name string, t testing.TB, fn func(p *Process, fixture protest.Fixture)) { +// copy of target.Interface (plus LineToPC) +type IProcess interface { + Pid() int + Exited() bool + Running() bool + + BinInfo() *BinaryInfo + + FindThread(threadID int) (IThread, bool) + ThreadList() []IThread + CurrentThread() IThread + + SelectedGoroutine() *G + + ContinueOnce() (trapthread IThread, err error) + StepInstruction() error + SwitchThread(int) error + SwitchGoroutine(int) error + RequestManualStop() error + Halt() error + Kill() error + Detach(bool) error + + Breakpoints() map[uint64]*Breakpoint + SetBreakpoint(addr uint64, kind BreakpointKind, cond ast.Expr) (*Breakpoint, error) + ClearBreakpoint(addr uint64) (*Breakpoint, error) + ClearInternalBreakpoints() error + + FindFileLocation(fileName string, lineNumber int) (uint64, error) + FirstPCAfterPrologue(fn *gosym.Func, sameline bool) (uint64, error) + FindFunctionLocation(funcName string, firstLine bool, lineOffset int) (uint64, error) +} + +func withTestProcess(name string, t testing.TB, fn func(p IProcess, fixture protest.Fixture)) { fixture := protest.BuildFixture(name) - p, err := Launch([]string{fixture.Path}, ".") + var p IProcess + var err error + switch testBackend { + case "native": + p, err = Launch([]string{fixture.Path}, ".") + case "lldb": + p, err = LLDBLaunch([]string{fixture.Path}, ".") + default: + t.Fatalf("unknown backend %q", testBackend) + } if err != nil { t.Fatal("Launch():", err) } @@ -47,23 +100,33 @@ func withTestProcess(name string, t testing.TB, fn func(p *Process, fixture prot fn(p, fixture) } -func withTestProcessArgs(name string, t testing.TB, wd string, fn func(p *Process, fixture protest.Fixture), args []string) { +func withTestProcessArgs(name string, t testing.TB, wd string, fn func(p IProcess, fixture protest.Fixture), args []string) { fixture := protest.BuildFixture(name) - p, err := Launch(append([]string{fixture.Path}, args...), wd) + var p IProcess + var err error + + switch testBackend { + case "native": + p, err = Launch(append([]string{fixture.Path}, args...), wd) + case "lldb": + p, err = LLDBLaunch(append([]string{fixture.Path}, args...), wd) + default: + t.Fatal("unknown backend") + } if err != nil { t.Fatal("Launch():", err) } defer func() { p.Halt() - p.Kill() + p.Detach(true) }() fn(p, fixture) } -func getRegisters(p *Process, t *testing.T) Registers { - regs, err := p.Registers() +func getRegisters(p IProcess, t *testing.T) Registers { + regs, err := p.CurrentThread().Registers(false) if err != nil { t.Fatal("Registers():", err) } @@ -71,7 +134,7 @@ func getRegisters(p *Process, t *testing.T) Registers { return regs } -func dataAtAddr(thread *Thread, addr uint64) ([]byte, error) { +func dataAtAddr(thread memoryReadWriter, addr uint64) ([]byte, error) { return thread.readMemory(uintptr(addr), 1) } @@ -83,24 +146,23 @@ func assertNoError(err error, t testing.TB, s string) { } } -func currentPC(p *Process, t *testing.T) uint64 { - pc, err := p.PC() +func currentPC(p IProcess, t *testing.T) uint64 { + regs, err := p.CurrentThread().Registers(false) if err != nil { t.Fatal(err) } - return pc + return regs.PC() } -func currentLineNumber(p *Process, t *testing.T) (string, int) { +func currentLineNumber(p IProcess, t *testing.T) (string, int) { pc := currentPC(p, t) f, l, _ := p.BinInfo().goSymTable.PCToLine(pc) - return f, l } func TestExit(t *testing.T) { - withTestProcess("continuetestprog", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("continuetestprog", t, func(p IProcess, fixture protest.Fixture) { err := Continue(p) pe, ok := err.(ProcessExitedError) if !ok { @@ -109,14 +171,14 @@ func TestExit(t *testing.T) { if pe.Status != 0 { t.Errorf("Unexpected error status: %d", pe.Status) } - if pe.Pid != p.pid { + if pe.Pid != p.Pid() { t.Errorf("Unexpected process id: %d", pe.Pid) } }) } func TestExitAfterContinue(t *testing.T) { - withTestProcess("continuetestprog", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("continuetestprog", t, func(p IProcess, fixture protest.Fixture) { _, err := setFunctionBreakpoint(p, "main.sayhi") assertNoError(err, t, "setFunctionBreakpoint()") assertNoError(Continue(p), t, "First Continue()") @@ -128,13 +190,13 @@ func TestExitAfterContinue(t *testing.T) { if pe.Status != 0 { t.Errorf("Unexpected error status: %d", pe.Status) } - if pe.Pid != p.pid { + if pe.Pid != p.Pid() { t.Errorf("Unexpected process id: %d", pe.Pid) } }) } -func setFunctionBreakpoint(p *Process, fname string) (*Breakpoint, error) { +func setFunctionBreakpoint(p IProcess, fname string) (*Breakpoint, error) { addr, err := p.FindFunctionLocation(fname, true, 0) if err != nil { return nil, err @@ -142,7 +204,7 @@ func setFunctionBreakpoint(p *Process, fname string) (*Breakpoint, error) { return p.SetBreakpoint(addr, UserBreakpoint, nil) } -func setFileBreakpoint(p *Process, t *testing.T, fixture protest.Fixture, lineno int) *Breakpoint { +func setFileBreakpoint(p IProcess, t *testing.T, fixture protest.Fixture, lineno int) *Breakpoint { addr, err := p.FindFileLocation(fixture.Source, lineno) if err != nil { t.Fatalf("FindFileLocation: %v", err) @@ -156,19 +218,25 @@ func setFileBreakpoint(p *Process, t *testing.T, fixture protest.Fixture, lineno func TestHalt(t *testing.T) { stopChan := make(chan interface{}) - withTestProcess("loopprog", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("loopprog", t, func(p IProcess, fixture protest.Fixture) { _, err := setFunctionBreakpoint(p, "main.loop") assertNoError(err, t, "SetBreakpoint") assertNoError(Continue(p), t, "Continue") - for _, th := range p.threads { - if th.running != false { - t.Fatal("expected running = false for thread", th.ID) + if p.Running() { + t.Fatal("process still running") + } + if p, ok := p.(*Process); ok { + for _, th := range p.threads { + if th.running != false { + t.Fatal("expected running = false for thread", th.ID) + } + _, err := th.Registers(false) + assertNoError(err, t, "Registers") } - _, err := th.Registers(false) - assertNoError(err, t, "Registers") } go func() { for { + time.Sleep(100 * time.Millisecond) if p.Running() { if err := p.RequestManualStop(); err != nil { t.Fatal(err) @@ -183,32 +251,34 @@ func TestHalt(t *testing.T) { // Loop through threads and make sure they are all // actually stopped, err will not be nil if the process // is still running. - for _, th := range p.threads { - if !th.Stopped() { - t.Fatal("expected thread to be stopped, but was not") + if p, ok := p.(*Process); ok { + for _, th := range p.threads { + if !th.Stopped() { + t.Fatal("expected thread to be stopped, but was not") + } + if th.running != false { + t.Fatal("expected running = false for thread", th.ID) + } + _, err := th.Registers(false) + assertNoError(err, t, "Registers") } - if th.running != false { - t.Fatal("expected running = false for thread", th.ID) - } - _, err := th.Registers(false) - assertNoError(err, t, "Registers") } }) } func TestStep(t *testing.T) { - withTestProcess("testprog", t, func(p *Process, fixture protest.Fixture) { - helloworldfunc := p.BinInfo().goSymTable.LookupFunc("main.helloworld") - helloworldaddr := helloworldfunc.Entry + withTestProcess("testprog", t, func(p IProcess, fixture protest.Fixture) { + helloworldaddr, err := p.FindFunctionLocation("main.helloworld", false, 0) + assertNoError(err, t, "FindFunctionLocation") - _, err := p.SetBreakpoint(helloworldaddr, UserBreakpoint, nil) + _, err = p.SetBreakpoint(helloworldaddr, UserBreakpoint, nil) assertNoError(err, t, "SetBreakpoint()") assertNoError(Continue(p), t, "Continue()") regs := getRegisters(p, t) rip := regs.PC() - err = p.currentThread.StepInstruction() + err = p.CurrentThread().StepInstruction() assertNoError(err, t, "Step()") regs = getRegisters(p, t) @@ -219,18 +289,17 @@ func TestStep(t *testing.T) { } func TestBreakpoint(t *testing.T) { - withTestProcess("testprog", t, func(p *Process, fixture protest.Fixture) { - helloworldfunc := p.BinInfo().goSymTable.LookupFunc("main.helloworld") - helloworldaddr := helloworldfunc.Entry + withTestProcess("testprog", t, func(p IProcess, fixture protest.Fixture) { + helloworldaddr, err := p.FindFunctionLocation("main.helloworld", false, 0) + assertNoError(err, t, "FindFunctionLocation") bp, err := p.SetBreakpoint(helloworldaddr, UserBreakpoint, nil) assertNoError(err, t, "SetBreakpoint()") assertNoError(Continue(p), t, "Continue()") - pc, err := p.PC() - if err != nil { - t.Fatal(err) - } + regs, err := p.CurrentThread().Registers(false) + assertNoError(err, t, "Registers") + pc := regs.PC() if bp.TotalHitCount != 1 { t.Fatalf("Breakpoint should be hit once, got %d\n", bp.TotalHitCount) @@ -244,26 +313,18 @@ func TestBreakpoint(t *testing.T) { } func TestBreakpointInSeperateGoRoutine(t *testing.T) { - withTestProcess("testthreads", t, func(p *Process, fixture protest.Fixture) { - fn := p.BinInfo().goSymTable.LookupFunc("main.anotherthread") - if fn == nil { - t.Fatal("No fn exists") - } + withTestProcess("testthreads", t, func(p IProcess, fixture protest.Fixture) { + fnentry, err := p.FindFunctionLocation("main.anotherthread", false, 0) + assertNoError(err, t, "FindFunctionLocation") - _, err := p.SetBreakpoint(fn.Entry, UserBreakpoint, nil) - if err != nil { - t.Fatal(err) - } + _, err = p.SetBreakpoint(fnentry, UserBreakpoint, nil) + assertNoError(err, t, "SetBreakpoint") - err = Continue(p) - if err != nil { - t.Fatal(err) - } + assertNoError(Continue(p), t, "Continue") - pc, err := p.PC() - if err != nil { - t.Fatal(err) - } + regs, err := p.CurrentThread().Registers(false) + assertNoError(err, t, "Registers") + pc := regs.PC() f, l, _ := p.BinInfo().goSymTable.PCToLine(pc) if f != "testthreads.go" && l != 8 { @@ -273,7 +334,7 @@ func TestBreakpointInSeperateGoRoutine(t *testing.T) { } func TestBreakpointWithNonExistantFunction(t *testing.T) { - withTestProcess("testprog", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("testprog", t, func(p IProcess, fixture protest.Fixture) { _, err := p.SetBreakpoint(0, UserBreakpoint, nil) if err == nil { t.Fatal("Should not be able to break at non existant function") @@ -282,18 +343,17 @@ func TestBreakpointWithNonExistantFunction(t *testing.T) { } func TestClearBreakpointBreakpoint(t *testing.T) { - withTestProcess("testprog", t, func(p *Process, fixture protest.Fixture) { - fn := p.BinInfo().goSymTable.LookupFunc("main.sleepytime") - bp, err := p.SetBreakpoint(fn.Entry, UserBreakpoint, nil) + withTestProcess("testprog", t, func(p IProcess, fixture protest.Fixture) { + fnentry, err := p.FindFunctionLocation("main.sleepytime", false, 0) + assertNoError(err, t, "FindFunctionLocation") + bp, err := p.SetBreakpoint(fnentry, UserBreakpoint, nil) assertNoError(err, t, "SetBreakpoint()") - bp, err = p.ClearBreakpoint(fn.Entry) + bp, err = p.ClearBreakpoint(fnentry) assertNoError(err, t, "ClearBreakpoint()") - data, err := dataAtAddr(p.currentThread, bp.Addr) - if err != nil { - t.Fatal(err) - } + data, err := dataAtAddr(p.CurrentThread(), bp.Addr) + assertNoError(err, t, "dataAtAddr") int3 := []byte{0xcc} if bytes.Equal(data, int3) { @@ -310,9 +370,9 @@ type nextTest struct { begin, end int } -func countBreakpoints(p *Process) int { +func countBreakpoints(p IProcess) int { bpcount := 0 - for _, bp := range p.breakpoints { + for _, bp := range p.Breakpoints() { if bp.ID >= 0 { bpcount++ } @@ -328,7 +388,7 @@ const ( ) func testseq(program string, contFunc contFunc, testcases []nextTest, initialLocation string, t *testing.T) { - withTestProcess(program, t, func(p *Process, fixture protest.Fixture) { + withTestProcess(program, t, func(p IProcess, fixture protest.Fixture) { var bp *Breakpoint var err error if initialLocation != "" { @@ -342,11 +402,14 @@ func testseq(program string, contFunc contFunc, testcases []nextTest, initialLoc assertNoError(err, t, "SetBreakpoint()") assertNoError(Continue(p), t, "Continue()") p.ClearBreakpoint(bp.Addr) - p.currentThread.SetPC(bp.Addr) + regs, err := p.CurrentThread().Registers(false) + assertNoError(err, t, "Registers") + assertNoError(regs.SetPC(p.CurrentThread(), bp.Addr), t, "SetPC") f, ln := currentLineNumber(p, t) for _, tc := range testcases { - pc, _ := p.currentThread.PC() + regs, _ := p.CurrentThread().Registers(false) + pc := regs.PC() if ln != tc.begin { t.Fatalf("Program not stopped at correct spot expected %d was %s:%d", tc.begin, filepath.Base(f), ln) } @@ -359,14 +422,15 @@ func testseq(program string, contFunc contFunc, testcases []nextTest, initialLoc } f, ln = currentLineNumber(p, t) - pc, _ = p.currentThread.PC() + regs, _ = p.CurrentThread().Registers(false) + pc = regs.PC() if ln != tc.end { t.Fatalf("Program did not continue to correct next location expected %d was %s:%d (%#x)", tc.end, filepath.Base(f), ln, pc) } } if countBreakpoints(p) != 0 { - t.Fatal("Not all breakpoints were cleaned up", len(p.breakpoints)) + t.Fatal("Not all breakpoints were cleaned up", len(p.Breakpoints())) } }) } @@ -424,7 +488,7 @@ func TestNextConcurrent(t *testing.T) { {9, 10}, {10, 11}, } - withTestProcess("parallel_next", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("parallel_next", t, func(p IProcess, fixture protest.Fixture) { bp, err := setFunctionBreakpoint(p, "main.sayhi") assertNoError(err, t, "SetBreakpoint") assertNoError(Continue(p), t, "Continue") @@ -435,10 +499,10 @@ func TestNextConcurrent(t *testing.T) { _, err = p.ClearBreakpoint(bp.Addr) assertNoError(err, t, "ClearBreakpoint()") for _, tc := range testcases { - g, err := GetG(p.currentThread) + g, err := 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 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) @@ -465,7 +529,7 @@ func TestNextConcurrentVariant2(t *testing.T) { {9, 10}, {10, 11}, } - withTestProcess("parallel_next", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("parallel_next", t, func(p IProcess, fixture protest.Fixture) { _, err := setFunctionBreakpoint(p, "main.sayhi") assertNoError(err, t, "SetBreakpoint") assertNoError(Continue(p), t, "Continue") @@ -474,10 +538,10 @@ func TestNextConcurrentVariant2(t *testing.T) { initVval, _ := constant.Int64Val(initV.Value) assertNoError(err, t, "EvalVariable") for _, tc := range testcases { - g, err := GetG(p.currentThread) + g, err := 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 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) @@ -488,7 +552,7 @@ func TestNextConcurrentVariant2(t *testing.T) { v, err := evalVariable(p, "n") assertNoError(err, t, "EvalVariable") vval, _ = constant.Int64Val(v.Value) - if p.currentThread.CurrentBreakpoint == nil { + if bp, _, _ := p.CurrentThread().Breakpoint(); bp == nil { if vval != initVval { t.Fatal("Did not end up on same goroutine") } @@ -534,7 +598,7 @@ func TestNextNetHTTP(t *testing.T) { {11, 12}, {12, 13}, } - withTestProcess("testnextnethttp", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("testnextnethttp", t, func(p IProcess, fixture protest.Fixture) { go func() { for !p.Running() { time.Sleep(50 * time.Millisecond) @@ -570,15 +634,14 @@ func TestNextNetHTTP(t *testing.T) { } func TestRuntimeBreakpoint(t *testing.T) { - withTestProcess("testruntimebreakpoint", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("testruntimebreakpoint", t, func(p IProcess, fixture protest.Fixture) { err := Continue(p) if err != nil { t.Fatal(err) } - pc, err := p.PC() - if err != nil { - t.Fatal(err) - } + regs, err := p.CurrentThread().Registers(false) + assertNoError(err, t, "Registers") + pc := regs.PC() _, l, _ := p.BinInfo().PCToLine(pc) if l != 10 { t.Fatal("did not respect breakpoint") @@ -598,8 +661,8 @@ func returnAddress(thread IThread) (uint64, error) { } func TestFindReturnAddress(t *testing.T) { - withTestProcess("testnextprog", t, func(p *Process, fixture protest.Fixture) { - start, _, err := p.BinInfo().goSymTable.LineToPC(fixture.Source, 24) + withTestProcess("testnextprog", t, func(p IProcess, fixture protest.Fixture) { + start, _, err := p.BinInfo().LineToPC(fixture.Source, 24) if err != nil { t.Fatal(err) } @@ -611,11 +674,11 @@ func TestFindReturnAddress(t *testing.T) { if err != nil { t.Fatal(err) } - addr, err := returnAddress(p.currentThread) + addr, err := returnAddress(p.CurrentThread()) if err != nil { t.Fatal(err) } - _, l, _ := p.BinInfo().goSymTable.PCToLine(addr) + _, l, _ := p.BinInfo().PCToLine(addr) if l != 40 { t.Fatalf("return address not found correctly, expected line 40") } @@ -623,26 +686,24 @@ func TestFindReturnAddress(t *testing.T) { } func TestFindReturnAddressTopOfStackFn(t *testing.T) { - withTestProcess("testreturnaddress", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("testreturnaddress", t, func(p IProcess, fixture protest.Fixture) { fnName := "runtime.rt0_go" - fn := p.BinInfo().goSymTable.LookupFunc(fnName) - if fn == nil { - t.Fatalf("could not find function %s", fnName) - } - if _, err := p.SetBreakpoint(fn.Entry, UserBreakpoint, nil); err != nil { + fnentry, err := p.FindFunctionLocation(fnName, false, 0) + assertNoError(err, t, "FindFunctionLocation") + if _, err := p.SetBreakpoint(fnentry, UserBreakpoint, nil); err != nil { t.Fatal(err) } if err := Continue(p); err != nil { t.Fatal(err) } - if _, err := returnAddress(p.currentThread); err == nil { + if _, err := returnAddress(p.CurrentThread()); err == nil { t.Fatal("expected error to be returned") } }) } func TestSwitchThread(t *testing.T) { - withTestProcess("testnextprog", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("testnextprog", t, func(p IProcess, fixture protest.Fixture) { // With invalid thread id err := p.SwitchThread(-1) if err == nil { @@ -661,10 +722,10 @@ func TestSwitchThread(t *testing.T) { t.Fatal(err) } var nt int - ct := p.currentThread.ID - for tid := range p.threads { - if tid != ct { - nt = tid + ct := p.CurrentThread().ThreadID() + for _, thread := range p.ThreadList() { + if thread.ThreadID() != ct { + nt = thread.ThreadID() break } } @@ -676,7 +737,7 @@ func TestSwitchThread(t *testing.T) { if err != nil { t.Fatal(err) } - if p.currentThread.ID != nt { + if p.CurrentThread().ThreadID() != nt { t.Fatal("Did not switch threads") } }) @@ -692,7 +753,7 @@ func TestCGONext(t *testing.T) { return } - withTestProcess("cgotest", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("cgotest", t, func(p IProcess, fixture protest.Fixture) { pc, err := p.FindFunctionLocation("main.main", true, 0) if err != nil { t.Fatal(err) @@ -731,13 +792,13 @@ func TestStacktrace(t *testing.T) { {{4, "main.stacktraceme"}, {8, "main.func1"}, {16, "main.main"}}, {{4, "main.stacktraceme"}, {8, "main.func1"}, {12, "main.func2"}, {17, "main.main"}}, } - withTestProcess("stacktraceprog", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("stacktraceprog", t, func(p IProcess, fixture protest.Fixture) { bp, err := setFunctionBreakpoint(p, "main.stacktraceme") assertNoError(err, t, "BreakByLocation()") for i := range stacks { assertNoError(Continue(p), t, "Continue()") - locations, err := ThreadStacktrace(p.currentThread, 40) + locations, err := ThreadStacktrace(p.CurrentThread(), 40) assertNoError(err, t, "Stacktrace()") if len(locations) != len(stacks[i])+2 { @@ -762,10 +823,10 @@ func TestStacktrace(t *testing.T) { } func TestStacktrace2(t *testing.T) { - withTestProcess("retstack", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("retstack", t, func(p IProcess, fixture protest.Fixture) { assertNoError(Continue(p), t, "Continue()") - locations, err := ThreadStacktrace(p.currentThread, 40) + locations, err := ThreadStacktrace(p.CurrentThread(), 40) assertNoError(err, t, "Stacktrace()") if !stackMatch([]loc{{-1, "main.f"}, {16, "main.main"}}, locations, false) { for i := range locations { @@ -775,7 +836,7 @@ func TestStacktrace2(t *testing.T) { } assertNoError(Continue(p), t, "Continue()") - locations, err = ThreadStacktrace(p.currentThread, 40) + locations, err = ThreadStacktrace(p.CurrentThread(), 40) assertNoError(err, t, "Stacktrace()") if !stackMatch([]loc{{-1, "main.g"}, {17, "main.main"}}, locations, false) { for i := range locations { @@ -813,13 +874,13 @@ func TestStacktraceGoroutine(t *testing.T) { mainStack := []loc{{13, "main.stacktraceme"}, {26, "main.main"}} agoroutineStacks := [][]loc{[]loc{{8, "main.agoroutine"}}, []loc{{9, "main.agoroutine"}}, []loc{{10, "main.agoroutine"}}} - withTestProcess("goroutinestackprog", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("goroutinestackprog", t, func(p IProcess, fixture protest.Fixture) { bp, err := setFunctionBreakpoint(p, "main.stacktraceme") assertNoError(err, t, "BreakByLocation()") assertNoError(Continue(p), t, "Continue()") - gs, err := p.GoroutinesInfo() + gs, err := GoroutinesInfo(p) assertNoError(err, t, "GoroutinesInfo") agoroutineCount := 0 @@ -872,7 +933,11 @@ func TestStacktraceGoroutine(t *testing.T) { } func TestKill(t *testing.T) { - withTestProcess("testprog", t, func(p *Process, fixture protest.Fixture) { + if testBackend == "lldb" { + // k command presumably works but leaves the process around? + return + } + withTestProcess("testprog", t, func(p IProcess, fixture protest.Fixture) { if err := p.Kill(); err != nil { t.Fatal(err) } @@ -880,21 +945,21 @@ func TestKill(t *testing.T) { t.Fatal("expected process to have exited") } if runtime.GOOS == "linux" { - _, err := os.Open(fmt.Sprintf("/proc/%d/", p.pid)) + _, err := os.Open(fmt.Sprintf("/proc/%d/", p.Pid())) if err == nil { - t.Fatal("process has not exited", p.pid) + t.Fatal("process has not exited", p.Pid()) } } }) } -func testGSupportFunc(name string, t *testing.T, p *Process, fixture protest.Fixture) { +func testGSupportFunc(name string, t *testing.T, p IProcess, fixture protest.Fixture) { bp, err := setFunctionBreakpoint(p, "main.main") assertNoError(err, t, name+": BreakByLocation()") assertNoError(Continue(p), t, name+": Continue()") - g, err := GetG(p.currentThread) + g, err := GetG(p.CurrentThread()) assertNoError(err, t, name+": GetG()") if g == nil { @@ -907,7 +972,7 @@ func testGSupportFunc(name string, t *testing.T, p *Process, fixture protest.Fix } func TestGetG(t *testing.T) { - withTestProcess("testprog", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("testprog", t, func(p IProcess, fixture protest.Fixture) { testGSupportFunc("nocgo", t, p, fixture) }) @@ -919,13 +984,13 @@ func TestGetG(t *testing.T) { return } - withTestProcess("cgotest", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("cgotest", t, func(p IProcess, fixture protest.Fixture) { testGSupportFunc("cgo", t, p, fixture) }) } func TestContinueMulti(t *testing.T) { - withTestProcess("integrationprog", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("integrationprog", t, func(p IProcess, fixture protest.Fixture) { bp1, err := setFunctionBreakpoint(p, "main.main") assertNoError(err, t, "BreakByLocation()") @@ -941,11 +1006,11 @@ func TestContinueMulti(t *testing.T) { } assertNoError(err, t, "Continue()") - if p.CurrentBreakpoint().ID == bp1.ID { + if bp, _, _ := p.CurrentThread().Breakpoint(); bp.ID == bp1.ID { mainCount++ } - if p.CurrentBreakpoint().ID == bp2.ID { + if bp, _, _ := p.CurrentThread().Breakpoint(); bp.ID == bp2.ID { sayhiCount++ } } @@ -988,7 +1053,7 @@ func TestParseVersionString(t *testing.T) { } func TestBreakpointOnFunctionEntry(t *testing.T) { - withTestProcess("testprog", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("testprog", t, func(p IProcess, fixture protest.Fixture) { addr, err := p.FindFunctionLocation("main.main", false, 0) assertNoError(err, t, "FindFunctionLocation()") _, err = p.SetBreakpoint(addr, UserBreakpoint, nil) @@ -1002,17 +1067,17 @@ func TestBreakpointOnFunctionEntry(t *testing.T) { } func TestProcessReceivesSIGCHLD(t *testing.T) { - withTestProcess("sigchldprog", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("sigchldprog", t, func(p IProcess, fixture protest.Fixture) { err := Continue(p) _, ok := err.(ProcessExitedError) if !ok { - t.Fatalf("Continue() returned unexpected error type %s", err) + t.Fatalf("Continue() returned unexpected error type %v", err) } }) } func TestIssue239(t *testing.T) { - withTestProcess("is sue239", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("is sue239", t, func(p IProcess, fixture protest.Fixture) { pos, _, err := p.BinInfo().goSymTable.LineToPC(fixture.Source, 17) assertNoError(err, t, "LineToPC()") _, err = p.SetBreakpoint(pos, UserBreakpoint, nil) @@ -1021,16 +1086,17 @@ func TestIssue239(t *testing.T) { }) } -func evalVariable(p *Process, symbol string) (*Variable, error) { - scope, err := GoroutineScope(p.currentThread) +func evalVariable(p IProcess, symbol string) (*Variable, error) { + scope, err := GoroutineScope(p.CurrentThread()) + if err != nil { return nil, err } return scope.EvalVariable(symbol, normalLoadConfig) } -func setVariable(p *Process, symbol, value string) error { - scope, err := GoroutineScope(p.currentThread) +func setVariable(p IProcess, symbol, value string) error { + scope, err := GoroutineScope(p.CurrentThread()) if err != nil { return err } @@ -1073,7 +1139,7 @@ func TestVariableEvaluation(t *testing.T) { {"ba", reflect.Slice, nil, 200, 200, 64}, } - withTestProcess("testvariables", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("testvariables", t, func(p IProcess, fixture protest.Fixture) { assertNoError(Continue(p), t, "Continue() returned an error") for _, tc := range testcases { @@ -1123,13 +1189,13 @@ func TestVariableEvaluation(t *testing.T) { } func TestFrameEvaluation(t *testing.T) { - withTestProcess("goroutinestackprog", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("goroutinestackprog", t, func(p IProcess, fixture protest.Fixture) { _, err := setFunctionBreakpoint(p, "main.stacktraceme") assertNoError(err, t, "setFunctionBreakpoint") assertNoError(Continue(p), t, "Continue()") // Testing evaluation on goroutines - gs, err := p.GoroutinesInfo() + gs, err := GoroutinesInfo(p) assertNoError(err, t, "GoroutinesInfo") found := make([]bool, 10) for _, g := range gs { @@ -1172,7 +1238,7 @@ func TestFrameEvaluation(t *testing.T) { // Testing evaluation on frames assertNoError(Continue(p), t, "Continue() 2") - g, err := GetG(p.currentThread) + g, err := GetG(p.CurrentThread()) assertNoError(err, t, "GetG()") for i := 0; i <= 3; i++ { @@ -1190,7 +1256,7 @@ func TestFrameEvaluation(t *testing.T) { } func TestPointerSetting(t *testing.T) { - withTestProcess("testvariables2", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("testvariables2", t, func(p IProcess, fixture protest.Fixture) { assertNoError(Continue(p), t, "Continue() returned an error") pval := func(n int64) { @@ -1205,7 +1271,7 @@ func TestPointerSetting(t *testing.T) { pval(1) // change p1 to point to i2 - scope, err := GoroutineScope(p.currentThread) + scope, err := GoroutineScope(p.CurrentThread()) assertNoError(err, t, "Scope()") i2addr, err := scope.EvalExpression("i2", normalLoadConfig) assertNoError(err, t, "EvalExpression()") @@ -1219,7 +1285,7 @@ func TestPointerSetting(t *testing.T) { } func TestVariableFunctionScoping(t *testing.T) { - withTestProcess("testvariables", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("testvariables", t, func(p IProcess, fixture protest.Fixture) { err := Continue(p) assertNoError(err, t, "Continue() returned an error") @@ -1244,7 +1310,7 @@ func TestVariableFunctionScoping(t *testing.T) { } func TestRecursiveStructure(t *testing.T) { - withTestProcess("testvariables2", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("testvariables2", t, func(p IProcess, fixture protest.Fixture) { assertNoError(Continue(p), t, "Continue()") v, err := evalVariable(p, "aas") assertNoError(err, t, "EvalVariable()") @@ -1254,7 +1320,7 @@ func TestRecursiveStructure(t *testing.T) { func TestIssue316(t *testing.T) { // A pointer loop that includes one interface should not send dlv into an infinite loop - withTestProcess("testvariables2", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("testvariables2", t, func(p IProcess, fixture protest.Fixture) { assertNoError(Continue(p), t, "Continue()") _, err := evalVariable(p, "iface5") assertNoError(err, t, "EvalVariable()") @@ -1263,7 +1329,7 @@ func TestIssue316(t *testing.T) { func TestIssue325(t *testing.T) { // nil pointer dereference when evaluating interfaces to function pointers - withTestProcess("testvariables2", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("testvariables2", t, func(p IProcess, fixture protest.Fixture) { assertNoError(Continue(p), t, "Continue()") iface2fn1v, err := evalVariable(p, "iface2fn1") assertNoError(err, t, "EvalVariable()") @@ -1276,8 +1342,8 @@ func TestIssue325(t *testing.T) { } func TestBreakpointCounts(t *testing.T) { - withTestProcess("bpcountstest", t, func(p *Process, fixture protest.Fixture) { - addr, _, err := p.BinInfo().goSymTable.LineToPC(fixture.Source, 12) + withTestProcess("bpcountstest", t, func(p IProcess, fixture protest.Fixture) { + addr, _, err := p.BinInfo().LineToPC(fixture.Source, 12) assertNoError(err, t, "LineToPC") bp, err := p.SetBreakpoint(addr, UserBreakpoint, nil) assertNoError(err, t, "SetBreakpoint()") @@ -1311,7 +1377,7 @@ func TestBreakpointCounts(t *testing.T) { 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 *Process, fixture protest.Fixture) { + withTestProcess("testvariables2", b, func(p IProcess, fixture protest.Fixture) { assertNoError(Continue(p), b, "Continue()") for i := 0; i < b.N; i++ { _, err := evalVariable(p, "bencharr") @@ -1327,8 +1393,8 @@ func TestBreakpointCountsWithDetection(t *testing.T) { return } m := map[int64]int64{} - withTestProcess("bpcountstest", t, func(p *Process, fixture protest.Fixture) { - addr, _, err := p.BinInfo().goSymTable.LineToPC(fixture.Source, 12) + withTestProcess("bpcountstest", t, func(p IProcess, fixture protest.Fixture) { + addr, _, err := p.BinInfo().LineToPC(fixture.Source, 12) assertNoError(err, t, "LineToPC") bp, err := p.SetBreakpoint(addr, UserBreakpoint, nil) assertNoError(err, t, "SetBreakpoint()") @@ -1340,8 +1406,8 @@ func TestBreakpointCountsWithDetection(t *testing.T) { } assertNoError(err, t, "Continue()") } - for _, th := range p.threads { - if th.CurrentBreakpoint == nil { + for _, th := range p.ThreadList() { + if bp, _, _ := th.Breakpoint(); bp == nil { continue } scope, err := GoroutineScope(th) @@ -1386,7 +1452,7 @@ 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 *Process, fixture protest.Fixture) { + withTestProcess("testvariables2", b, func(p IProcess, fixture protest.Fixture) { assertNoError(Continue(p), b, "Continue()") for i := 0; i < b.N; i++ { _, err := evalVariable(p, "bencharr") @@ -1400,7 +1466,7 @@ func BenchmarkMap(b *testing.B) { // 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 *Process, fixture protest.Fixture) { + withTestProcess("testvariables2", b, func(p IProcess, fixture protest.Fixture) { assertNoError(Continue(p), b, "Continue()") for i := 0; i < b.N; i++ { _, err := evalVariable(p, "m1") @@ -1410,11 +1476,18 @@ func BenchmarkMap(b *testing.B) { } func BenchmarkGoroutinesInfo(b *testing.B) { - withTestProcess("testvariables2", b, func(p *Process, fixture protest.Fixture) { + withTestProcess("testvariables2", b, func(p IProcess, fixture protest.Fixture) { assertNoError(Continue(p), b, "Continue()") for i := 0; i < b.N; i++ { - p.allGCache = nil - _, err := p.GoroutinesInfo() + switch p := p.(type) { + case *GdbserverProcess: + p.allGCache = nil + case *Process: + p.allGCache = nil + case *CoreProcess: + p.allGCache = nil + } + _, err := GoroutinesInfo(p) assertNoError(err, b, "GoroutinesInfo") } }) @@ -1422,8 +1495,8 @@ func BenchmarkGoroutinesInfo(b *testing.B) { func TestIssue262(t *testing.T) { // Continue does not work when the current breakpoint is set on a NOP instruction - withTestProcess("issue262", t, func(p *Process, fixture protest.Fixture) { - addr, _, err := p.BinInfo().goSymTable.LineToPC(fixture.Source, 11) + withTestProcess("issue262", t, func(p IProcess, fixture protest.Fixture) { + addr, _, err := p.BinInfo().LineToPC(fixture.Source, 11) assertNoError(err, t, "LineToPC") _, err = p.SetBreakpoint(addr, UserBreakpoint, nil) assertNoError(err, t, "SetBreakpoint()") @@ -1444,8 +1517,8 @@ 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 - withTestProcess("issue305", t, func(p *Process, fixture protest.Fixture) { - addr, _, err := p.BinInfo().goSymTable.LineToPC(fixture.Source, 5) + withTestProcess("issue305", t, func(p IProcess, fixture protest.Fixture) { + addr, _, err := p.BinInfo().LineToPC(fixture.Source, 5) assertNoError(err, t, "LineToPC()") _, err = p.SetBreakpoint(addr, UserBreakpoint, nil) assertNoError(err, t, "SetBreakpoint()") @@ -1463,7 +1536,7 @@ func TestIssue305(t *testing.T) { func TestPointerLoops(t *testing.T) { // Pointer loops through map entries, pointers and slices // Regression test for issue #341 - withTestProcess("testvariables2", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("testvariables2", t, func(p IProcess, fixture protest.Fixture) { assertNoError(Continue(p), t, "Continue()") for _, expr := range []string{"mapinf", "ptrinf", "sliceinf"} { t.Logf("requesting %s", expr) @@ -1475,9 +1548,9 @@ func TestPointerLoops(t *testing.T) { } func BenchmarkLocalVariables(b *testing.B) { - withTestProcess("testvariables", b, func(p *Process, fixture protest.Fixture) { + withTestProcess("testvariables", b, func(p IProcess, fixture protest.Fixture) { assertNoError(Continue(p), b, "Continue() returned an error") - scope, err := GoroutineScope(p.currentThread) + scope, err := GoroutineScope(p.CurrentThread()) assertNoError(err, b, "Scope()") for i := 0; i < b.N; i++ { _, err := scope.LocalVariables(normalLoadConfig) @@ -1487,8 +1560,8 @@ func BenchmarkLocalVariables(b *testing.B) { } func TestCondBreakpoint(t *testing.T) { - withTestProcess("parallel_next", t, func(p *Process, fixture protest.Fixture) { - addr, _, err := p.BinInfo().goSymTable.LineToPC(fixture.Source, 9) + withTestProcess("parallel_next", t, func(p IProcess, fixture protest.Fixture) { + addr, _, err := p.BinInfo().LineToPC(fixture.Source, 9) assertNoError(err, t, "LineToPC") bp, err := p.SetBreakpoint(addr, UserBreakpoint, nil) assertNoError(err, t, "SetBreakpoint()") @@ -1511,8 +1584,8 @@ func TestCondBreakpoint(t *testing.T) { } func TestCondBreakpointError(t *testing.T) { - withTestProcess("parallel_next", t, func(p *Process, fixture protest.Fixture) { - addr, _, err := p.BinInfo().goSymTable.LineToPC(fixture.Source, 9) + withTestProcess("parallel_next", t, func(p IProcess, fixture protest.Fixture) { + addr, _, err := p.BinInfo().LineToPC(fixture.Source, 9) assertNoError(err, t, "LineToPC") bp, err := p.SetBreakpoint(addr, UserBreakpoint, nil) assertNoError(err, t, "SetBreakpoint()") @@ -1556,7 +1629,7 @@ func TestCondBreakpointError(t *testing.T) { func TestIssue356(t *testing.T) { // slice with a typedef does not get printed correctly - withTestProcess("testvariables2", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("testvariables2", t, func(p IProcess, fixture protest.Fixture) { assertNoError(Continue(p), t, "Continue() returned an error") mmvar, err := evalVariable(p, "mainMenu") assertNoError(err, t, "EvalVariable()") @@ -1567,13 +1640,13 @@ func TestIssue356(t *testing.T) { } func TestStepIntoFunction(t *testing.T) { - withTestProcess("teststep", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("teststep", t, func(p IProcess, fixture protest.Fixture) { // Continue until breakpoint assertNoError(Continue(p), t, "Continue() returned an error") // Step into function assertNoError(Step(p), t, "Step() returned an error") // We should now be inside the function. - loc, err := p.CurrentLocation() + loc, err := p.CurrentThread().Location() if err != nil { t.Fatal(err) } @@ -1591,8 +1664,8 @@ func TestStepIntoFunction(t *testing.T) { func TestIssue384(t *testing.T) { // Crash related to reading uninitialized memory, introduced by the memory prefetching optimization - withTestProcess("issue384", t, func(p *Process, fixture protest.Fixture) { - start, _, err := p.BinInfo().goSymTable.LineToPC(fixture.Source, 13) + withTestProcess("issue384", t, func(p IProcess, fixture protest.Fixture) { + start, _, err := p.BinInfo().LineToPC(fixture.Source, 13) assertNoError(err, t, "LineToPC()") _, err = p.SetBreakpoint(start, UserBreakpoint, nil) assertNoError(err, t, "SetBreakpoint()") @@ -1604,14 +1677,14 @@ func TestIssue384(t *testing.T) { func TestIssue332_Part1(t *testing.T) { // Next shouldn't step inside a function call - withTestProcess("issue332", t, func(p *Process, fixture protest.Fixture) { - start, _, err := p.BinInfo().goSymTable.LineToPC(fixture.Source, 8) + withTestProcess("issue332", t, func(p IProcess, fixture protest.Fixture) { + start, _, err := p.BinInfo().LineToPC(fixture.Source, 8) assertNoError(err, t, "LineToPC()") _, err = p.SetBreakpoint(start, UserBreakpoint, nil) assertNoError(err, t, "SetBreakpoint()") assertNoError(Continue(p), t, "Continue()") assertNoError(Next(p), t, "first Next()") - locations, err := ThreadStacktrace(p.currentThread, 2) + locations, err := ThreadStacktrace(p.CurrentThread(), 2) assertNoError(err, t, "Stacktrace()") if locations[0].Call.Fn == nil { t.Fatalf("Not on a function") @@ -1630,8 +1703,8 @@ func TestIssue332_Part2(t *testing.T) { // 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: " // because the incorrect FDE data leads to reading the wrong stack address as the return address - withTestProcess("issue332", t, func(p *Process, fixture protest.Fixture) { - start, _, err := p.BinInfo().goSymTable.LineToPC(fixture.Source, 8) + withTestProcess("issue332", t, func(p IProcess, fixture protest.Fixture) { + start, _, err := p.BinInfo().LineToPC(fixture.Source, 8) assertNoError(err, t, "LineToPC()") _, err = p.SetBreakpoint(start, UserBreakpoint, nil) assertNoError(err, t, "SetBreakpoint()") @@ -1640,7 +1713,7 @@ func TestIssue332_Part2(t *testing.T) { // step until we enter changeMe for { assertNoError(Step(p), t, "Step()") - locations, err := ThreadStacktrace(p.currentThread, 2) + locations, err := ThreadStacktrace(p.CurrentThread(), 2) assertNoError(err, t, "Stacktrace()") if locations[0].Call.Fn == nil { t.Fatalf("Not on a function") @@ -1650,8 +1723,9 @@ func TestIssue332_Part2(t *testing.T) { } } - pc, err := p.currentThread.PC() - assertNoError(err, t, "PC()") + regs, err := p.CurrentThread().Registers(false) + assertNoError(err, t, "Registers()") + pc := regs.PC() pcAfterPrologue, err := p.FindFunctionLocation("main.changeMe", true, -1) assertNoError(err, t, "FindFunctionLocation()") pcEntry, err := p.FindFunctionLocation("main.changeMe", false, 0) @@ -1673,7 +1747,7 @@ func TestIssue332_Part2(t *testing.T) { } func TestIssue396(t *testing.T) { - withTestProcess("callme", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("callme", t, func(p IProcess, fixture protest.Fixture) { _, err := p.FindFunctionLocation("main.init", true, -1) assertNoError(err, t, "FindFunctionLocation()") }) @@ -1681,8 +1755,8 @@ func TestIssue396(t *testing.T) { func TestIssue414(t *testing.T) { // Stepping until the program exits - withTestProcess("math", t, func(p *Process, fixture protest.Fixture) { - start, _, err := p.BinInfo().goSymTable.LineToPC(fixture.Source, 9) + withTestProcess("math", t, func(p IProcess, fixture protest.Fixture) { + start, _, err := p.BinInfo().LineToPC(fixture.Source, 9) assertNoError(err, t, "LineToPC()") _, err = p.SetBreakpoint(start, UserBreakpoint, nil) assertNoError(err, t, "SetBreakpoint()") @@ -1700,10 +1774,10 @@ func TestIssue414(t *testing.T) { } func TestPackageVariables(t *testing.T) { - withTestProcess("testvariables", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("testvariables", t, func(p IProcess, fixture protest.Fixture) { err := Continue(p) assertNoError(err, t, "Continue()") - scope, err := GoroutineScope(p.currentThread) + scope, err := GoroutineScope(p.CurrentThread()) assertNoError(err, t, "Scope()") vars, err := scope.PackageVariables(normalLoadConfig) assertNoError(err, t, "PackageVariables()") @@ -1726,28 +1800,28 @@ func TestIssue149(t *testing.T) { return } // setting breakpoint on break statement - withTestProcess("break", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("break", t, func(p IProcess, fixture protest.Fixture) { _, err := p.FindFileLocation(fixture.Source, 8) assertNoError(err, t, "FindFileLocation()") }) } func TestPanicBreakpoint(t *testing.T) { - withTestProcess("panic", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("panic", t, func(p IProcess, fixture protest.Fixture) { assertNoError(Continue(p), t, "Continue()") - bp := p.CurrentBreakpoint() + bp, _, _ := p.CurrentThread().Breakpoint() if bp == nil || bp.Name != "unrecovered-panic" { - t.Fatalf("not on unrecovered-panic breakpoint: %v", p.CurrentBreakpoint()) + t.Fatalf("not on unrecovered-panic breakpoint: %v", bp) } }) } func TestCmdLineArgs(t *testing.T) { - expectSuccess := func(p *Process, fixture protest.Fixture) { + expectSuccess := func(p IProcess, fixture protest.Fixture) { err := Continue(p) - bp := p.CurrentBreakpoint() + bp, _, _ := p.CurrentThread().Breakpoint() if bp != nil && bp.Name == "unrecovered-panic" { - t.Fatalf("testing args failed on unrecovered-panic breakpoint: %v", p.CurrentBreakpoint()) + t.Fatalf("testing args failed on unrecovered-panic breakpoint: %v", bp) } exit, exited := err.(ProcessExitedError) if !exited { @@ -1759,11 +1833,11 @@ func TestCmdLineArgs(t *testing.T) { } } - expectPanic := func(p *Process, fixture protest.Fixture) { + expectPanic := func(p IProcess, fixture protest.Fixture) { Continue(p) - bp := p.CurrentBreakpoint() + bp, _, _ := p.CurrentThread().Breakpoint() if bp == nil || bp.Name != "unrecovered-panic" { - t.Fatalf("not on unrecovered-panic breakpoint: %v", p.CurrentBreakpoint()) + t.Fatalf("not on unrecovered-panic breakpoint: %v", bp) } } @@ -1786,7 +1860,7 @@ func TestIssue462(t *testing.T) { if runtime.GOOS == "windows" { return } - withTestProcess("testnextnethttp", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("testnextnethttp", t, func(p IProcess, fixture protest.Fixture) { go func() { for !p.Running() { time.Sleep(50 * time.Millisecond) @@ -1806,7 +1880,7 @@ func TestIssue462(t *testing.T) { }() assertNoError(Continue(p), t, "Continue()") - _, err := ThreadStacktrace(p.currentThread, 40) + _, err := ThreadStacktrace(p.CurrentThread(), 40) assertNoError(err, t, "Stacktrace()") }) } @@ -1821,7 +1895,7 @@ func TestIssue554(t *testing.T) { } func TestNextParked(t *testing.T) { - withTestProcess("parallel_next", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("parallel_next", t, func(p IProcess, fixture protest.Fixture) { bp, err := setFunctionBreakpoint(p, "main.sayhi") assertNoError(err, t, "SetBreakpoint()") @@ -1836,7 +1910,7 @@ func TestNextParked(t *testing.T) { } assertNoError(err, t, "Continue()") - gs, err := p.GoroutinesInfo() + gs, err := GoroutinesInfo(p) assertNoError(err, t, "GoroutinesInfo()") for _, g := range gs { @@ -1851,14 +1925,14 @@ func TestNextParked(t *testing.T) { p.ClearBreakpoint(bp.Addr) assertNoError(Next(p), 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) + 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) { - withTestProcess("parallel_next", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("parallel_next", t, func(p IProcess, fixture protest.Fixture) { bp, err := setFunctionBreakpoint(p, "main.sayhi") assertNoError(err, t, "SetBreakpoint()") @@ -1873,7 +1947,7 @@ func TestStepParked(t *testing.T) { } assertNoError(err, t, "Continue()") - gs, err := p.GoroutinesInfo() + gs, err := GoroutinesInfo(p) assertNoError(err, t, "GoroutinesInfo()") for _, g := range gs { @@ -1898,8 +1972,8 @@ func TestStepParked(t *testing.T) { p.ClearBreakpoint(bp.Addr) assertNoError(Step(p), 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) + 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) } }) } @@ -1961,9 +2035,9 @@ func TestUnsupportedArch(t *testing.T) { 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. - withTestProcess("issue573", t, func(p *Process, fixture protest.Fixture) { - f := p.BinInfo().goSymTable.LookupFunc("main.foo") - _, err := p.SetBreakpoint(f.Entry, UserBreakpoint, nil) + withTestProcess("issue573", t, func(p IProcess, fixture protest.Fixture) { + fentry, _ := p.FindFunctionLocation("main.foo", false, 0) + _, err := p.SetBreakpoint(fentry, UserBreakpoint, nil) assertNoError(err, t, "SetBreakpoint()") assertNoError(Continue(p), t, "Continue()") assertNoError(Step(p), t, "Step() #1") @@ -1973,7 +2047,7 @@ func TestIssue573(t *testing.T) { } func TestTestvariables2Prologue(t *testing.T) { - withTestProcess("testvariables2", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("testvariables2", t, func(p IProcess, fixture protest.Fixture) { addrEntry, err := p.FindFunctionLocation("main.main", false, 0) assertNoError(err, t, "FindFunctionLocation - entrypoint") addrPrologue, err := p.FindFunctionLocation("main.main", true, 0) @@ -2083,7 +2157,7 @@ func TestStepIgnorePrivateRuntime(t *testing.T) { func TestIssue561(t *testing.T) { // Step fails to make progress when PC is at a CALL instruction // where a breakpoint is also set. - withTestProcess("issue561", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("issue561", t, func(p IProcess, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture, 10) assertNoError(Continue(p), t, "Continue()") assertNoError(Step(p), t, "Step()") @@ -2095,7 +2169,7 @@ func TestIssue561(t *testing.T) { } func TestStepOut(t *testing.T) { - withTestProcess("testnextprog", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("testnextprog", t, func(p IProcess, fixture protest.Fixture) { bp, err := setFunctionBreakpoint(p, "main.helloworld") assertNoError(err, t, "SetBreakpoint()") assertNoError(Continue(p), t, "Continue()") @@ -2116,7 +2190,7 @@ func TestStepOut(t *testing.T) { } func TestStepConcurrentDirect(t *testing.T) { - withTestProcess("teststepconcurrent", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("teststepconcurrent", t, func(p IProcess, fixture protest.Fixture) { pc, err := p.FindFileLocation(fixture.Source, 37) assertNoError(err, t, "FindFileLocation()") bp, err := p.SetBreakpoint(pc, UserBreakpoint, nil) @@ -2134,7 +2208,7 @@ func TestStepConcurrentDirect(t *testing.T) { } } - gid := p.selectedGoroutine.ID + gid := p.SelectedGoroutine().ID seq := []int{37, 38, 13, 15, 16, 38} @@ -2142,8 +2216,8 @@ func TestStepConcurrentDirect(t *testing.T) { count := 0 for { anyerr := false - if p.selectedGoroutine.ID != gid { - t.Errorf("Step switched to different goroutine %d %d\n", gid, p.selectedGoroutine.ID) + 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) @@ -2152,11 +2226,11 @@ func TestStepConcurrentDirect(t *testing.T) { // loop exited break } - frames, err := ThreadStacktrace(p.currentThread, 20) + frames, err := ThreadStacktrace(p.CurrentThread(), 20) if err != nil { - t.Errorf("Could not get stacktrace of goroutine %d\n", p.selectedGoroutine.ID) + 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.ID) + 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) } @@ -2180,8 +2254,8 @@ func TestStepConcurrentDirect(t *testing.T) { }) } -func nextInProgress(p *Process) bool { - for _, bp := range p.breakpoints { +func nextInProgress(p IProcess) bool { + for _, bp := range p.Breakpoints() { if bp.Internal() { return true } @@ -2190,7 +2264,7 @@ func nextInProgress(p *Process) bool { } func TestStepConcurrentPtr(t *testing.T) { - withTestProcess("teststepconcurrent", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("teststepconcurrent", t, func(p IProcess, fixture protest.Fixture) { pc, err := p.FindFileLocation(fixture.Source, 24) assertNoError(err, t, "FindFileLocation()") _, err = p.SetBreakpoint(pc, UserBreakpoint, nil) @@ -2216,13 +2290,15 @@ func TestStepConcurrentPtr(t *testing.T) { f, ln := currentLineNumber(p, t) if ln != 24 { - for _, th := range p.threads { - t.Logf("thread %d stopped on breakpoint %v", th.ID, th.CurrentBreakpoint) + for _, th := range p.ThreadList() { + bp, bpactive, bperr := th.Breakpoint() + t.Logf("thread %d stopped on breakpoint %v %v %v", th.ThreadID(), bp, bpactive, bperr) } - t.Fatalf("Program did not continue at expected location (24): %s:%d %#x [%v] (gid %d count %d)", f, ln, currentPC(p, t), p.currentThread.CurrentBreakpoint, p.selectedGoroutine.ID, count) + 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 + gid := p.SelectedGoroutine().ID kvar, err := evalVariable(p, "k") assertNoError(err, t, "EvalVariable()") @@ -2237,14 +2313,14 @@ func TestStepConcurrentPtr(t *testing.T) { assertNoError(Step(p), t, "Step()") for nextInProgress(p) { - 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) + 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(Continue(p), t, "Continue()") } - if p.selectedGoroutine.ID != gid { - t.Fatalf("Step switched goroutines (wanted: %d got: %d)", gid, p.selectedGoroutine.ID) + if p.SelectedGoroutine().ID != gid { + t.Fatalf("Step switched goroutines (wanted: %d got: %d)", gid, p.SelectedGoroutine().ID) } f, ln = currentLineNumber(p, t) @@ -2267,7 +2343,7 @@ func TestStepConcurrentPtr(t *testing.T) { } func TestStepOutDefer(t *testing.T) { - withTestProcess("testnextdefer", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("testnextdefer", t, func(p IProcess, fixture protest.Fixture) { pc, err := p.FindFileLocation(fixture.Source, 9) assertNoError(err, t, "FindFileLocation()") bp, err := p.SetBreakpoint(pc, UserBreakpoint, nil) @@ -2282,7 +2358,7 @@ func TestStepOutDefer(t *testing.T) { assertNoError(StepOut(p), t, "StepOut()") - f, l, _ := p.BinInfo().goSymTable.PCToLine(currentPC(p, t)) + 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) } @@ -2293,7 +2369,7 @@ 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 - withTestProcess("defercall", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("defercall", t, func(p IProcess, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture, 11) assertNoError(Continue(p), t, "Continue()") p.ClearBreakpoint(bp.Addr) @@ -2308,7 +2384,7 @@ func TestStepOutDeferReturnAndDirectCall(t *testing.T) { } func TestStepOnCallPtrInstr(t *testing.T) { - withTestProcess("teststepprog", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("teststepprog", t, func(p IProcess, fixture protest.Fixture) { pc, err := p.FindFileLocation(fixture.Source, 10) assertNoError(err, t, "FindFileLocation()") _, err = p.SetBreakpoint(pc, UserBreakpoint, nil) @@ -2323,11 +2399,10 @@ func TestStepOnCallPtrInstr(t *testing.T) { if ln != 10 { break } - pc, err := p.currentThread.PC() - assertNoError(err, t, "PC()") - regs, err := p.currentThread.Registers(false) + regs, err := p.CurrentThread().Registers(false) assertNoError(err, t, "Registers()") - text, err := disassemble(p.currentThread, regs, p.breakpoints, p.BinInfo(), pc, pc+maxInstructionLength) + pc := regs.PC() + text, err := disassemble(p.CurrentThread(), regs, p.Breakpoints(), p.BinInfo(), pc, pc+maxInstructionLength) assertNoError(err, t, "Disassemble()") if text[0].IsCall() { found = true @@ -2350,11 +2425,19 @@ func TestStepOnCallPtrInstr(t *testing.T) { } func TestIssue594(t *testing.T) { + if runtime.GOOS == "darwin" && testBackend == "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 + return + } // 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. - withTestProcess("issue594", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("issue594", t, func(p IProcess, fixture protest.Fixture) { assertNoError(Continue(p), t, "Continue()") f, ln := currentLineNumber(p, t) if ln != 21 { @@ -2367,7 +2450,7 @@ 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 - withTestProcess("defercall", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("defercall", t, func(p IProcess, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture, 17) assertNoError(Continue(p), t, "Continue()") p.ClearBreakpoint(bp.Addr) @@ -2387,8 +2470,8 @@ func TestWorkDir(t *testing.T) { if runtime.GOOS == "darwin" { wd = "/private/tmp" } - withTestProcessArgs("workdir", t, wd, func(p *Process, fixture protest.Fixture) { - addr, _, err := p.BinInfo().goSymTable.LineToPC(fixture.Source, 14) + withTestProcessArgs("workdir", t, wd, func(p IProcess, fixture protest.Fixture) { + addr, _, err := p.BinInfo().LineToPC(fixture.Source, 14) assertNoError(err, t, "LineToPC") p.SetBreakpoint(addr, UserBreakpoint, nil) Continue(p) @@ -2411,7 +2494,7 @@ func TestNegativeIntEvaluation(t *testing.T) { {"ni16", "int16", int64(-5)}, {"ni32", "int32", int64(-5)}, } - withTestProcess("testvariables2", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("testvariables2", t, func(p IProcess, fixture protest.Fixture) { assertNoError(Continue(p), t, "Continue()") for _, tc := range testcases { v, err := evalVariable(p, tc.name) @@ -2428,7 +2511,7 @@ func TestNegativeIntEvaluation(t *testing.T) { func TestIssue683(t *testing.T) { // Step panics when source file can not be found - withTestProcess("issue683", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("issue683", t, func(p IProcess, fixture protest.Fixture) { _, err := setFunctionBreakpoint(p, "main.main") assertNoError(err, t, "setFunctionBreakpoint()") assertNoError(Continue(p), t, "First Continue()") @@ -2444,7 +2527,7 @@ func TestIssue683(t *testing.T) { } func TestIssue664(t *testing.T) { - withTestProcess("issue664", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("issue664", t, func(p IProcess, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture, 4) assertNoError(Continue(p), t, "Continue()") assertNoError(Next(p), t, "Next()") @@ -2457,13 +2540,13 @@ func TestIssue664(t *testing.T) { // Benchmarks (*Processs).Continue + (*Scope).FunctionArguments func BenchmarkTrace(b *testing.B) { - withTestProcess("traceperf", b, func(p *Process, fixture protest.Fixture) { + withTestProcess("traceperf", b, func(p IProcess, fixture protest.Fixture) { _, err := setFunctionBreakpoint(p, "main.PerfCheck") assertNoError(err, b, "setFunctionBreakpoint()") b.ResetTimer() for i := 0; i < b.N; i++ { assertNoError(Continue(p), b, "Continue()") - s, err := GoroutineScope(p.currentThread) + s, err := GoroutineScope(p.CurrentThread()) assertNoError(err, b, "Scope()") _, err = s.FunctionArguments(LoadConfig{false, 0, 64, 0, 3}) assertNoError(err, b, "FunctionArguments()") @@ -2477,7 +2560,7 @@ func TestNextInDeferReturn(t *testing.T) { // instruction leaves the curg._defer field non-nil but with curg._defer.fn // field being nil. // We need to deal with this without panicing. - withTestProcess("defercall", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("defercall", t, func(p IProcess, fixture protest.Fixture) { _, err := setFunctionBreakpoint(p, "runtime.deferreturn") assertNoError(err, t, "setFunctionBreakpoint()") assertNoError(Continue(p), t, "First Continue()") @@ -2513,7 +2596,7 @@ func TestStacktraceWithBarriers(t *testing.T) { defer os.Setenv("GODEBUG", godebugOld) os.Setenv("GODEBUG", "gcrescanstacks=1") - withTestProcess("binarytrees", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("binarytrees", t, func(p IProcess, fixture protest.Fixture) { // We want to get a user goroutine with a stack barrier, to get that we execute the program until runtime.gcInstallStackBarrier is executed AND the goroutine it was executed onto contains a call to main.bottomUpTree _, err := setFunctionBreakpoint(p, "runtime.gcInstallStackBarrier") assertNoError(err, t, "setFunctionBreakpoint()") @@ -2525,10 +2608,10 @@ func TestStacktraceWithBarriers(t *testing.T) { return } assertNoError(err, t, "Continue()") - gs, err := p.GoroutinesInfo() + gs, err := GoroutinesInfo(p) assertNoError(err, t, "GoroutinesInfo()") - for _, th := range p.threads { - if th.CurrentBreakpoint == nil { + for _, th := range p.ThreadList() { + if bp, _, _ := th.Breakpoint(); bp == nil { continue } @@ -2557,7 +2640,7 @@ func TestStacktraceWithBarriers(t *testing.T) { assertNoError(StepOut(p), t, "StepOut()") - gs, err := p.GoroutinesInfo() + gs, err := GoroutinesInfo(p) assertNoError(err, t, "GoroutinesInfo()") for _, goid := range stackBarrierGoids { @@ -2597,9 +2680,12 @@ func TestStacktraceWithBarriers(t *testing.T) { } func TestAttachDetach(t *testing.T) { - if runtime.GOOS == "darwin" { - // does not work on darwin - return + if testBackend == "lldb" && runtime.GOOS == "linux" { + bs, _ := ioutil.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 + } } fixture := protest.BuildFixture("testnextnethttp") cmd := exec.Command(fixture.Path) @@ -2621,7 +2707,22 @@ func TestAttachDetach(t *testing.T) { } } - p, err := Attach(cmd.Process.Pid) + var p IProcess + var err error + + switch testBackend { + case "native": + p, err = Attach(cmd.Process.Pid) + case "lldb": + path := "" + if runtime.GOOS == "darwin" { + path = fixture.Path + } + p, err = LLDBAttach(cmd.Process.Pid, path) + default: + err = fmt.Errorf("unknown backend %q", testBackend) + } + assertNoError(err, t, "Attach") go func() { time.Sleep(1 * time.Second) diff --git a/pkg/proc/proc_unix_test.go b/pkg/proc/proc_unix_test.go index fd76c92c..5e4bcffc 100644 --- a/pkg/proc/proc_unix_test.go +++ b/pkg/proc/proc_unix_test.go @@ -6,24 +6,40 @@ import ( "syscall" "testing" "time" + "runtime" protest "github.com/derekparker/delve/pkg/proc/test" ) func TestIssue419(t *testing.T) { + if testBackend == "lldb" && runtime.GOOS == "darwin" { + // debugserver bug? + return + } // SIGINT directed at the inferior should be passed along not swallowed by delve - withTestProcess("issue419", t, func(p *Process, fixture protest.Fixture) { + withTestProcess("issue419", t, func(p IProcess, fixture protest.Fixture) { + _, err := setFunctionBreakpoint(p, "main.main") + assertNoError(err, t, "SetBreakpoint()") + assertNoError(Continue(p), t, "Continue()") go func() { for { + time.Sleep(500 * time.Millisecond) if p.Running() { time.Sleep(2 * time.Second) - err := syscall.Kill(p.pid, syscall.SIGINT) + if p.Pid() <= 0 { + // if we don't stop the inferior the test will never finish + p.RequestManualStop() + p.Kill() + t.Fatalf("Pid is zero or negative: %d", p.Pid()) + return + } + err := syscall.Kill(p.Pid(), syscall.SIGINT) assertNoError(err, t, "syscall.Kill") return } } }() - err := Continue(p) + err = Continue(p) if _, exited := err.(ProcessExitedError); !exited { t.Fatalf("Unexpected error after Continue(): %v\n", err) } diff --git a/pkg/proc/registers.go b/pkg/proc/registers.go index 843397e9..b1e14c66 100644 --- a/pkg/proc/registers.go +++ b/pkg/proc/registers.go @@ -20,6 +20,8 @@ type Registers interface { BP() uint64 CX() uint64 TLS() uint64 + // GAddr returns the address of the G variable if it is known, 0 and false otherwise + GAddr() (uint64, bool) Get(int) (uint64, error) SetPC(IThread, uint64) error Slice() []Register diff --git a/pkg/proc/registers_darwin_amd64.go b/pkg/proc/registers_darwin_amd64.go index f9878afb..91610606 100644 --- a/pkg/proc/registers_darwin_amd64.go +++ b/pkg/proc/registers_darwin_amd64.go @@ -105,6 +105,10 @@ func (r *Regs) TLS() uint64 { return r.gsBase } +func (r *Regs) GAddr() (uint64, bool) { + return 0, false +} + // SetPC sets the RIP register to the value specified by `pc`. func (r *Regs) SetPC(t IThread, pc uint64) error { thread := t.(*Thread) diff --git a/pkg/proc/registers_linux_amd64.go b/pkg/proc/registers_linux_amd64.go index aec6c043..b1081d17 100644 --- a/pkg/proc/registers_linux_amd64.go +++ b/pkg/proc/registers_linux_amd64.go @@ -83,6 +83,10 @@ func (r *Regs) TLS() uint64 { return r.regs.Fs_base } +func (r *Regs) GAddr() (uint64, bool) { + return 0, false +} + // SetPC sets RIP to the value specified by 'pc'. func (r *Regs) SetPC(t IThread, pc uint64) (err error) { thread := t.(*Thread) diff --git a/pkg/proc/registers_windows_amd64.go b/pkg/proc/registers_windows_amd64.go index 6993c615..644ad921 100644 --- a/pkg/proc/registers_windows_amd64.go +++ b/pkg/proc/registers_windows_amd64.go @@ -124,6 +124,10 @@ func (r *Regs) TLS() uint64 { return r.tls } +func (r *Regs) GAddr() (uint64, bool) { + return 0, false +} + // SetPC sets the RIP register to the value specified by `pc`. func (r *Regs) SetPC(t IThread, pc uint64) error { thread := t.(*Thread) diff --git a/pkg/proc/threads.go b/pkg/proc/threads.go index 558803cf..a2182a98 100644 --- a/pkg/proc/threads.go +++ b/pkg/proc/threads.go @@ -209,6 +209,12 @@ func next(dbp Continuable, stepInto bool) error { return err } + for i := range text { + if text[i].Inst == nil { + fmt.Printf("error at instruction %d\n", i) + } + } + cond := sameGoroutineCondition(selg) if stepInto { @@ -377,13 +383,16 @@ func getGVariable(thread IThread) (*Variable, error) { return nil, fmt.Errorf("g struct offset not initialized") } - gaddrbs, err := thread.readMemory(uintptr(regs.TLS()+arch.GStructOffset()), arch.PtrSize()) - if err != nil { - return nil, err + gaddr, hasgaddr := regs.GAddr() + if !hasgaddr { + gaddrbs, err := thread.readMemory(uintptr(regs.TLS()+arch.GStructOffset()), arch.PtrSize()) + if err != nil { + return nil, err + } + gaddr = binary.LittleEndian.Uint64(gaddrbs) } - gaddr := uintptr(binary.LittleEndian.Uint64(gaddrbs)) - return newGVariable(thread, gaddr, arch.DerefTLS()) + return newGVariable(thread, uintptr(gaddr), arch.DerefTLS()) } func newGVariable(thread IThread, gaddr uintptr, deref bool) (*Variable, error) { diff --git a/pkg/target/target.go b/pkg/target/target.go index bc581c6a..f768a621 100644 --- a/pkg/target/target.go +++ b/pkg/target/target.go @@ -91,3 +91,4 @@ type VariableEval interface { var _ Interface = &proc.Process{} var _ Interface = &proc.CoreProcess{} +var _ Interface = &proc.GdbserverProcess{} diff --git a/pkg/terminal/command_test.go b/pkg/terminal/command_test.go index 3c49d702..b3bac727 100644 --- a/pkg/terminal/command_test.go +++ b/pkg/terminal/command_test.go @@ -1,6 +1,7 @@ package terminal import ( + "flag" "fmt" "io/ioutil" "net" @@ -17,6 +18,20 @@ import ( "github.com/derekparker/delve/service/rpccommon" ) +var testBackend string + +func TestMain(m *testing.M) { + flag.StringVar(&testBackend, "backend", "", "selects backend") + flag.Parse() + if testBackend == "" { + testBackend = os.Getenv("PROCTEST") + if testBackend == "" { + testBackend = "native" + } + } + os.Exit(m.Run()) +} + type FakeTerminal struct { *Term t testing.TB @@ -81,6 +96,7 @@ func withTestTerminal(name string, t testing.TB, fn func(*FakeTerminal)) { server := rpccommon.NewServer(&service.Config{ Listener: listener, ProcessArgs: []string{test.BuildFixture(name).Path}, + Backend: testBackend, }, false) if err := server.Run(); err != nil { t.Fatal(err) diff --git a/service/config.go b/service/config.go index 30b398e6..42741b80 100644 --- a/service/config.go +++ b/service/config.go @@ -26,6 +26,9 @@ type Config struct { // APIVersion selects which version of the API to serve (default: 1). APIVersion int - // CoreFile specifies the path to the core dump to open + // CoreFile specifies the path to the core dump to open. CoreFile string + + // Selects server backend. + Backend string } diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index 4bf4b2a6..48f32582 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -49,8 +49,10 @@ type Config struct { // attach. AttachPid int - // CoreFile specifies the path to the core dump to open + // CoreFile specifies the path to the core dump to open. CoreFile string + // Backend specifies the debugger backend. + Backend string } // New creates a new Debugger. @@ -63,7 +65,11 @@ func New(config *Config) (*Debugger, error) { switch { case d.config.AttachPid > 0: log.Printf("attaching to pid %d", d.config.AttachPid) - p, err := proc.Attach(d.config.AttachPid) + path := "" + if len(d.config.ProcessArgs) > 0 { + path = d.config.ProcessArgs[0] + } + p, err := d.Attach(d.config.AttachPid, path) if err != nil { return nil, attachErrorMessage(d.config.AttachPid, err) } @@ -79,7 +85,7 @@ func New(config *Config) (*Debugger, error) { default: log.Printf("launching process with args: %v", d.config.ProcessArgs) - p, err := proc.Launch(d.config.ProcessArgs, d.config.WorkingDir) + p, err := d.Launch(d.config.ProcessArgs, d.config.WorkingDir) if err != nil { if err != proc.NotExecutableErr && err != proc.UnsupportedLinuxArchErr && err != proc.UnsupportedWindowsArchErr && err != proc.UnsupportedDarwinArchErr { err = fmt.Errorf("could not launch process: %s", err) @@ -91,6 +97,49 @@ func New(config *Config) (*Debugger, error) { return d, nil } +func (d *Debugger) Launch(processArgs []string, wd string) (target.Interface, error) { + switch d.config.Backend { + case "native": + return proc.Launch(processArgs, wd) + case "lldb": + return proc.LLDBLaunch(processArgs, wd) + case "default": + if runtime.GOOS == "darwin" { + return proc.LLDBLaunch(processArgs, wd) + } + return proc.Launch(processArgs, wd) + default: + return nil, fmt.Errorf("unknown backend %q", d.config.Backend) + } +} + +// ErrNoAttachPath is the error returned when the client tries to attach to +// a process on macOS using the lldb backend without specifying the path to +// the target's executable. +var ErrNoAttachPath = errors.New("must specify executable path on macOS") + +func (d *Debugger) Attach(pid int, path string) (target.Interface, error) { + switch d.config.Backend { + case "native": + return proc.Attach(pid) + case "lldb": + if runtime.GOOS == "darwin" && path == "" { + return nil, ErrNoAttachPath + } + return proc.LLDBAttach(pid, path) + case "default": + if runtime.GOOS == "darwin" { + if path == "" { + return nil, ErrNoAttachPath + } + return proc.LLDBAttach(pid, path) + } + return proc.Attach(pid) + default: + return nil, fmt.Errorf("unknown backend %q", d.config.Backend) + } +} + // ProcessPid returns the PID of the process // the debugger is debugging. func (d *Debugger) ProcessPid() int { @@ -142,7 +191,7 @@ func (d *Debugger) Restart() ([]api.DiscardedBreakpoint, error) { return nil, err } } - p, err := proc.Launch(d.config.ProcessArgs, d.config.WorkingDir) + p, err := d.Launch(d.config.ProcessArgs, d.config.WorkingDir) if err != nil { return nil, fmt.Errorf("could not launch process: %s", err) } @@ -166,8 +215,9 @@ func (d *Debugger) Restart() ([]api.DiscardedBreakpoint, error) { return nil, err } } + err = d.target.Detach(true) d.target = p - return discarded, nil + return discarded, err } // State returns the current state of the debugger. diff --git a/service/rpccommon/server.go b/service/rpccommon/server.go index a137c22a..7c9e9235 100644 --- a/service/rpccommon/server.go +++ b/service/rpccommon/server.go @@ -115,6 +115,7 @@ func (s *ServerImpl) Run() error { AttachPid: s.config.AttachPid, WorkingDir: s.config.WorkingDir, CoreFile: s.config.CoreFile, + Backend: s.config.Backend, }); err != nil { return err } diff --git a/service/test/integration1_test.go b/service/test/integration1_test.go index b63bc79d..498f4ef2 100644 --- a/service/test/integration1_test.go +++ b/service/test/integration1_test.go @@ -29,6 +29,7 @@ func withTestClient1(name string, t *testing.T, fn func(c *rpc1.RPCClient)) { server := rpccommon.NewServer(&service.Config{ Listener: listener, ProcessArgs: []string{protest.BuildFixture(name).Path}, + Backend: testBackend, }, false) if err := server.Run(); err != nil { t.Fatal(err) @@ -50,6 +51,7 @@ func Test1RunWithInvalidPath(t *testing.T) { server := rpccommon.NewServer(&service.Config{ Listener: listener, ProcessArgs: []string{"invalid_path"}, + Backend: testBackend, }, false) if err := server.Run(); err == nil { t.Fatal("Expected Run to return error for invalid program path") @@ -138,6 +140,7 @@ func Test1Restart_attachPid(t *testing.T) { server := rpccommon.NewServer(&service.Config{ Listener: nil, AttachPid: 999, + Backend: testBackend, }, false) if err := server.Restart(); err == nil { t.Fatal("expected error on restart after attaching to pid but got none") diff --git a/service/test/integration2_test.go b/service/test/integration2_test.go index 6c0c931a..3f2ecdc7 100644 --- a/service/test/integration2_test.go +++ b/service/test/integration2_test.go @@ -1,6 +1,7 @@ package servicetest import ( + "flag" "fmt" "math/rand" "net" @@ -22,8 +23,17 @@ import ( ) var normalLoadConfig = api.LoadConfig{true, 1, 64, 64, -1} +var testBackend string func TestMain(m *testing.M) { + flag.StringVar(&testBackend, "backend", "", "selects backend") + flag.Parse() + if testBackend == "" { + testBackend = os.Getenv("PROCTEST") + if testBackend == "" { + testBackend = "native" + } + } os.Exit(protest.RunTestsWithFixtures(m)) } @@ -36,6 +46,7 @@ func withTestClient2(name string, t *testing.T, fn func(c service.Client)) { server := rpccommon.NewServer(&service.Config{ Listener: listener, ProcessArgs: []string{protest.BuildFixture(name).Path}, + Backend: testBackend, }, false) if err := server.Run(); err != nil { t.Fatal(err) @@ -58,6 +69,7 @@ func TestRunWithInvalidPath(t *testing.T) { Listener: listener, ProcessArgs: []string{"invalid_path"}, APIVersion: 2, + Backend: testBackend, }, false) if err := server.Run(); err == nil { t.Fatal("Expected Run to return error for invalid program path") @@ -147,6 +159,7 @@ func TestRestart_attachPid(t *testing.T) { Listener: nil, AttachPid: 999, APIVersion: 2, + Backend: testBackend, }, false) if err := server.Restart(); err == nil { t.Fatal("expected error on restart after attaching to pid but got none") diff --git a/service/test/variables_test.go b/service/test/variables_test.go index 109b84da..95028c7e 100644 --- a/service/test/variables_test.go +++ b/service/test/variables_test.go @@ -9,6 +9,7 @@ import ( "github.com/derekparker/delve/pkg/proc" "github.com/derekparker/delve/service/api" + "github.com/derekparker/delve/pkg/target" protest "github.com/derekparker/delve/pkg/proc/test" ) @@ -53,7 +54,7 @@ func assertVariable(t *testing.T, variable *proc.Variable, expected varTest) { } } -func evalVariable(p *proc.Process, symbol string, cfg proc.LoadConfig) (*proc.Variable, error) { +func evalVariable(p target.Interface, symbol string, cfg proc.LoadConfig) (*proc.Variable, error) { scope, err := proc.GoroutineScope(p.CurrentThread()) if err != nil { return nil, err @@ -67,7 +68,7 @@ func (tc *varTest) alternateVarTest() varTest { return r } -func setVariable(p *proc.Process, symbol, value string) error { +func setVariable(p target.Interface, symbol, value string) error { scope, err := proc.GoroutineScope(p.CurrentThread()) if err != nil { return err @@ -77,16 +78,25 @@ func setVariable(p *proc.Process, symbol, value string) error { const varTestBreakpointLineNumber = 59 -func withTestProcess(name string, t *testing.T, fn func(p *proc.Process, fixture protest.Fixture)) { +func withTestProcess(name string, t *testing.T, fn func(p target.Interface, fixture protest.Fixture)) { fixture := protest.BuildFixture(name) - p, err := proc.Launch([]string{fixture.Path}, ".") + var p target.Interface + var err error + switch testBackend { + case "native": + p, err = proc.Launch([]string{fixture.Path}, ".") + case "lldb": + p, err = proc.LLDBLaunch([]string{fixture.Path}, ".") + default: + t.Fatalf("unknown backend %q", testBackend) + } if err != nil { t.Fatal("Launch():", err) } defer func() { p.Halt() - p.Kill() + p.Detach(true) }() fn(p, fixture) @@ -137,7 +147,7 @@ func TestVariableEvaluation(t *testing.T) { {"NonExistent", true, "", "", "", fmt.Errorf("could not find symbol value for NonExistent")}, } - withTestProcess("testvariables", t, func(p *proc.Process, fixture protest.Fixture) { + withTestProcess("testvariables", t, func(p target.Interface, fixture protest.Fixture) { err := proc.Continue(p) assertNoError(err, t, "Continue() returned an error") @@ -215,7 +225,7 @@ func TestVariableEvaluationShort(t *testing.T) { {"NonExistent", true, "", "", "", fmt.Errorf("could not find symbol value for NonExistent")}, } - withTestProcess("testvariables", t, func(p *proc.Process, fixture protest.Fixture) { + withTestProcess("testvariables", t, func(p target.Interface, fixture protest.Fixture) { err := proc.Continue(p) assertNoError(err, t, "Continue() returned an error") @@ -270,7 +280,7 @@ func TestMultilineVariableEvaluation(t *testing.T) { Nest: *(*main.Nest)(…`, "", "main.Nest", nil}, } - withTestProcess("testvariables", t, func(p *proc.Process, fixture protest.Fixture) { + withTestProcess("testvariables", t, func(p target.Interface, fixture protest.Fixture) { err := proc.Continue(p) assertNoError(err, t, "Continue() returned an error") @@ -343,7 +353,7 @@ func TestLocalVariables(t *testing.T) { {"baz", true, "\"bazburzum\"", "", "string", nil}}}, } - withTestProcess("testvariables", t, func(p *proc.Process, fixture protest.Fixture) { + withTestProcess("testvariables", t, func(p target.Interface, fixture protest.Fixture) { err := proc.Continue(p) assertNoError(err, t, "Continue() returned an error") @@ -367,7 +377,7 @@ func TestLocalVariables(t *testing.T) { } func TestEmbeddedStruct(t *testing.T) { - withTestProcess("testvariables2", t, func(p *proc.Process, fixture protest.Fixture) { + withTestProcess("testvariables2", t, func(p target.Interface, fixture protest.Fixture) { testcases := []varTest{ {"b.val", true, "-314", "-314", "int", nil}, {"b.A.val", true, "-314", "-314", "int", nil}, @@ -397,7 +407,7 @@ func TestEmbeddedStruct(t *testing.T) { } func TestComplexSetting(t *testing.T) { - withTestProcess("testvariables", t, func(p *proc.Process, fixture protest.Fixture) { + withTestProcess("testvariables", t, func(p target.Interface, fixture protest.Fixture) { err := proc.Continue(p) assertNoError(err, t, "Continue() returned an error") @@ -643,7 +653,7 @@ func TestEvalExpression(t *testing.T) { } } - withTestProcess("testvariables2", t, func(p *proc.Process, fixture protest.Fixture) { + withTestProcess("testvariables2", t, func(p target.Interface, fixture protest.Fixture) { assertNoError(proc.Continue(p), t, "Continue() returned an error") for _, tc := range testcases { variable, err := evalVariable(p, tc.name, pnormalLoadConfig) @@ -667,7 +677,7 @@ func TestEvalExpression(t *testing.T) { } func TestEvalAddrAndCast(t *testing.T) { - withTestProcess("testvariables2", t, func(p *proc.Process, fixture protest.Fixture) { + withTestProcess("testvariables2", t, func(p target.Interface, fixture protest.Fixture) { assertNoError(proc.Continue(p), t, "Continue() returned an error") c1addr, err := evalVariable(p, "&c1", pnormalLoadConfig) assertNoError(err, t, "EvalExpression(&c1)") @@ -693,7 +703,7 @@ func TestEvalAddrAndCast(t *testing.T) { } func TestMapEvaluation(t *testing.T) { - withTestProcess("testvariables2", t, func(p *proc.Process, fixture protest.Fixture) { + withTestProcess("testvariables2", t, func(p target.Interface, fixture protest.Fixture) { assertNoError(proc.Continue(p), t, "Continue() returned an error") m1v, err := evalVariable(p, "m1", pnormalLoadConfig) assertNoError(err, t, "EvalVariable()") @@ -727,7 +737,7 @@ func TestMapEvaluation(t *testing.T) { } func TestUnsafePointer(t *testing.T) { - withTestProcess("testvariables2", t, func(p *proc.Process, fixture protest.Fixture) { + withTestProcess("testvariables2", t, func(p target.Interface, fixture protest.Fixture) { assertNoError(proc.Continue(p), t, "Continue() returned an error") up1v, err := evalVariable(p, "up1", pnormalLoadConfig) assertNoError(err, t, "EvalVariable(up1)") @@ -764,7 +774,7 @@ func TestIssue426(t *testing.T) { // Serialization of type expressions (go/ast.Expr) containing anonymous structs or interfaces // differs from the serialization used by the linker to produce DWARF type information - withTestProcess("testvariables2", t, func(p *proc.Process, fixture protest.Fixture) { + withTestProcess("testvariables2", t, func(p target.Interface, fixture protest.Fixture) { assertNoError(proc.Continue(p), t, "Continue() returned an error") for _, testcase := range testcases { v, err := evalVariable(p, testcase.name, pnormalLoadConfig) @@ -815,7 +825,7 @@ func TestPackageRenames(t *testing.T) { return } - withTestProcess("pkgrenames", t, func(p *proc.Process, fixture protest.Fixture) { + withTestProcess("pkgrenames", t, func(p target.Interface, fixture protest.Fixture) { assertNoError(proc.Continue(p), t, "Continue() returned an error") for _, tc := range testcases { variable, err := evalVariable(p, tc.name, pnormalLoadConfig)