From df65be43aeb1b0f64c042827e6ccd6c91f91eed7 Mon Sep 17 00:00:00 2001 From: Robert Ayrapetyan Date: Fri, 12 Jul 2019 18:28:04 -0700 Subject: [PATCH] *: FreeBSD initial support (#1480) * FreeBSD initial support * first code review fixes * regs slice upd * execPtraceFunc wrap * disabled concurrency tests fixed kill() issue * disabled concurrency tests fixed kill() issue * cleanup vendor related code * cleanup ptrace calls * vendoring latest changes * Revert "vendoring latest changes" This reverts commit 833cb87b * vendoring latest changes * requested changes --- Documentation/installation/README.md | 1 + Documentation/installation/freebsd/install.md | 19 + pkg/proc/bininfo.go | 2 +- pkg/proc/fbsdutil/regs.go | 333 ++++++++++++++++ pkg/proc/fbsdutil/regs_test.go | 60 +++ pkg/proc/gdbserial/gdbserver.go | 5 +- pkg/proc/gdbserial/gdbserver_unix.go | 2 +- pkg/proc/native/proc_freebsd.c | 93 +++++ pkg/proc/native/proc_freebsd.go | 366 ++++++++++++++++++ pkg/proc/native/proc_freebsd.h | 4 + pkg/proc/native/proc_linux.go | 18 +- pkg/proc/native/ptrace_freebsd.go | 79 ++++ pkg/proc/native/ptrace_freebsd_amd64.c | 73 ++++ pkg/proc/native/ptrace_freebsd_amd64.h | 7 + pkg/proc/native/ptrace_linux.go | 2 +- pkg/proc/native/registers_freebsd_amd64.go | 92 +++++ pkg/proc/native/threads_freebsd.go | 133 +++++++ pkg/proc/proc_test.go | 30 ++ pkg/terminal/command_test.go | 13 +- pkg/terminal/terminal_test.go | 7 + service/debugger/debugger_freebsd.go | 14 + service/test/integration1_test.go | 3 + service/test/integration2_test.go | 3 + vendor/gopkg.in/yaml.v2/NOTICE | 13 + 24 files changed, 1352 insertions(+), 20 deletions(-) create mode 100644 Documentation/installation/freebsd/install.md create mode 100644 pkg/proc/fbsdutil/regs.go create mode 100644 pkg/proc/fbsdutil/regs_test.go create mode 100644 pkg/proc/native/proc_freebsd.c create mode 100644 pkg/proc/native/proc_freebsd.go create mode 100644 pkg/proc/native/proc_freebsd.h create mode 100644 pkg/proc/native/ptrace_freebsd.go create mode 100644 pkg/proc/native/ptrace_freebsd_amd64.c create mode 100644 pkg/proc/native/ptrace_freebsd_amd64.h create mode 100644 pkg/proc/native/registers_freebsd_amd64.go create mode 100644 pkg/proc/native/threads_freebsd.go create mode 100644 service/debugger/debugger_freebsd.go create mode 100644 vendor/gopkg.in/yaml.v2/NOTICE diff --git a/Documentation/installation/README.md b/Documentation/installation/README.md index 6a99e988..aeb66c5d 100644 --- a/Documentation/installation/README.md +++ b/Documentation/installation/README.md @@ -7,3 +7,4 @@ Please note you *must* have **Go 1.8** or higher installed in order to compile D - [OSX](osx/install.md) - [Linux](linux/install.md) - [Windows](windows/install.md) +- [FreeBSD](freebsd/install.md) \ No newline at end of file diff --git a/Documentation/installation/freebsd/install.md b/Documentation/installation/freebsd/install.md new file mode 100644 index 00000000..d96eb8d1 --- /dev/null +++ b/Documentation/installation/freebsd/install.md @@ -0,0 +1,19 @@ +# Installation on FreeBSD + +Please use the following steps to build and install Delve on FreeBSD. + +There are two ways to install on FreeBSD. First is the standard `go get` method: + +``` +go get -u github.com/go-delve/delve/cmd/dlv +``` + +Alternatively make sure $GOPATH is set (e.g. as `~/.go`) and: + +``` +$ git clone https://github.com/go-delve/delve.git $GOPATH/src/github.com/go-delve/delve +$ cd $GOPATH/src/github.com/go-delve/delve +$ gmake install +``` + +Note: If you are using Go 1.5 you must set `GO15VENDOREXPERIMENT=1` before continuing. The `GO15VENDOREXPERIMENT` env var simply opts into the [Go 1.5 Vendor Experiment](https://docs.google.com/document/d/1Bz5-UB7g2uPBdOx-rw5t9MxJwkfpx90cqG9AFL0JAYo/). diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index 07b1ddf9..cf64ebcc 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -322,7 +322,7 @@ func loadBinaryInfo(bi *BinaryInfo, image *Image, path string, entryPoint uint64 defer wg.Wait() switch bi.GOOS { - case "linux": + case "linux", "freebsd": return loadBinaryInfoElf(bi, image, path, entryPoint, &wg) case "windows": return loadBinaryInfoPE(bi, image, path, entryPoint, &wg) diff --git a/pkg/proc/fbsdutil/regs.go b/pkg/proc/fbsdutil/regs.go new file mode 100644 index 00000000..b8710870 --- /dev/null +++ b/pkg/proc/fbsdutil/regs.go @@ -0,0 +1,333 @@ +package fbsdutil + +import ( + "golang.org/x/arch/x86/x86asm" + + "github.com/go-delve/delve/pkg/proc" + "github.com/go-delve/delve/pkg/proc/linutil" +) + +// AMD64Registers implements the proc.Registers interface for the native/freebsd +// backend and core/freebsd backends, on AMD64. +type AMD64Registers struct { + Regs *AMD64PtraceRegs + Fpregs []proc.Register + Fpregset *AMD64Xstate + Fsbase uint64 +} + +// AMD64PtraceRegs is the struct used by the freebsd kernel to return the +// general purpose registers for AMD64 CPUs. +// source: sys/x86/include/reg.h +type AMD64PtraceRegs struct { + R15 int64 + R14 int64 + R13 int64 + R12 int64 + R11 int64 + R10 int64 + R9 int64 + R8 int64 + Rdi int64 + Rsi int64 + Rbp int64 + Rbx int64 + Rdx int64 + Rcx int64 + Rax int64 + Trapno uint32 + Fs uint16 + Gs uint16 + Err uint32 + Es uint16 + Ds uint16 + Rip int64 + Cs int64 + Rflags int64 + Rsp int64 + Ss int64 +} + +// Slice returns the registers as a list of (name, value) pairs. +func (r *AMD64Registers) Slice(floatingPoint bool) []proc.Register { + var regs64 = []struct { + k string + v int64 + }{ + {"R15", r.Regs.R15}, + {"R14", r.Regs.R14}, + {"R13", r.Regs.R13}, + {"R12", r.Regs.R12}, + {"R11", r.Regs.R11}, + {"R10", r.Regs.R10}, + {"R9", r.Regs.R9}, + {"R8", r.Regs.R8}, + {"Rdi", r.Regs.Rdi}, + {"Rsi", r.Regs.Rsi}, + {"Rbp", r.Regs.Rbp}, + {"Rbx", r.Regs.Rbx}, + {"Rdx", r.Regs.Rdx}, + {"Rcx", r.Regs.Rcx}, + {"Rax", r.Regs.Rax}, + {"Rip", r.Regs.Rip}, + {"Cs", r.Regs.Cs}, + {"Rflags", r.Regs.Rflags}, + {"Rsp", r.Regs.Rsp}, + {"Ss", r.Regs.Ss}, + } + var regs32 = []struct { + k string + v uint32 + }{ + {"Trapno", r.Regs.Trapno}, + {"Err", r.Regs.Err}, + } + var regs16 = []struct { + k string + v uint16 + }{ + {"Fs", r.Regs.Fs}, + {"Gs", r.Regs.Gs}, + {"Es", r.Regs.Es}, + {"Ds", r.Regs.Ds}, + } + out := make([]proc.Register, 0, + len(regs64)+ + len(regs32)+ + len(regs16)+ + 1+ // for Rflags + len(r.Fpregs)) + for _, reg := range regs64 { + // FreeBSD defines the registers as signed, but Linux defines + // them as unsigned. Of course, a register doesn't really have + // a concept of signedness. Cast to what Delve expects. + out = proc.AppendQwordReg(out, reg.k, uint64(reg.v)) + } + for _, reg := range regs32 { + out = proc.AppendDwordReg(out, reg.k, reg.v) + } + for _, reg := range regs16 { + out = proc.AppendWordReg(out, reg.k, reg.v) + } + // x86 called this register "Eflags". amd64 extended it and renamed it + // "Rflags", but Linux still uses the old name. + out = proc.AppendEflagReg(out, "Rflags", uint64(r.Regs.Rflags)) + if floatingPoint { + out = append(out, r.Fpregs...) + } + return out +} + +// PC returns the value of RIP register. +func (r *AMD64Registers) PC() uint64 { + return uint64(r.Regs.Rip) +} + +// SP returns the value of RSP register. +func (r *AMD64Registers) SP() uint64 { + return uint64(r.Regs.Rsp) +} + +func (r *AMD64Registers) BP() uint64 { + return uint64(r.Regs.Rbp) +} + +// CX returns the value of RCX register. +func (r *AMD64Registers) CX() uint64 { + return uint64(r.Regs.Rcx) +} + +// TLS returns the address of the thread local storage memory segment. +func (r *AMD64Registers) TLS() uint64 { + return r.Fsbase +} + +// GAddr returns the address of the G variable if it is known, 0 and false +// otherwise. +func (r *AMD64Registers) GAddr() (uint64, bool) { + return 0, false +} + +// Get returns the value of the n-th register (in x86asm order). +func (r *AMD64Registers) Get(n int) (uint64, error) { + reg := x86asm.Reg(n) + const ( + mask8 = 0x000000ff + mask16 = 0x0000ffff + mask32 = 0xffffffff + ) + + switch reg { + // 8-bit + case x86asm.AL: + return uint64(r.Regs.Rax) & mask8, nil + case x86asm.CL: + return uint64(r.Regs.Rcx) & mask8, nil + case x86asm.DL: + return uint64(r.Regs.Rdx) & mask8, nil + case x86asm.BL: + return uint64(r.Regs.Rbx) & mask8, nil + case x86asm.AH: + return (uint64(r.Regs.Rax) >> 8) & mask8, nil + case x86asm.CH: + return (uint64(r.Regs.Rcx) >> 8) & mask8, nil + case x86asm.DH: + return (uint64(r.Regs.Rdx) >> 8) & mask8, nil + case x86asm.BH: + return (uint64(r.Regs.Rbx) >> 8) & mask8, nil + case x86asm.SPB: + return uint64(r.Regs.Rsp) & mask8, nil + case x86asm.BPB: + return uint64(r.Regs.Rbp) & mask8, nil + case x86asm.SIB: + return uint64(r.Regs.Rsi) & mask8, nil + case x86asm.DIB: + return uint64(r.Regs.Rdi) & mask8, nil + case x86asm.R8B: + return uint64(r.Regs.R8) & mask8, nil + case x86asm.R9B: + return uint64(r.Regs.R9) & mask8, nil + case x86asm.R10B: + return uint64(r.Regs.R10) & mask8, nil + case x86asm.R11B: + return uint64(r.Regs.R11) & mask8, nil + case x86asm.R12B: + return uint64(r.Regs.R12) & mask8, nil + case x86asm.R13B: + return uint64(r.Regs.R13) & mask8, nil + case x86asm.R14B: + return uint64(r.Regs.R14) & mask8, nil + case x86asm.R15B: + return uint64(r.Regs.R15) & mask8, nil + + // 16-bit + case x86asm.AX: + return uint64(r.Regs.Rax) & mask16, nil + case x86asm.CX: + return uint64(r.Regs.Rcx) & mask16, nil + case x86asm.DX: + return uint64(r.Regs.Rdx) & mask16, nil + case x86asm.BX: + return uint64(r.Regs.Rbx) & mask16, nil + case x86asm.SP: + return uint64(r.Regs.Rsp) & mask16, nil + case x86asm.BP: + return uint64(r.Regs.Rbp) & mask16, nil + case x86asm.SI: + return uint64(r.Regs.Rsi) & mask16, nil + case x86asm.DI: + return uint64(r.Regs.Rdi) & mask16, nil + case x86asm.R8W: + return uint64(r.Regs.R8) & mask16, nil + case x86asm.R9W: + return uint64(r.Regs.R9) & mask16, nil + case x86asm.R10W: + return uint64(r.Regs.R10) & mask16, nil + case x86asm.R11W: + return uint64(r.Regs.R11) & mask16, nil + case x86asm.R12W: + return uint64(r.Regs.R12) & mask16, nil + case x86asm.R13W: + return uint64(r.Regs.R13) & mask16, nil + case x86asm.R14W: + return uint64(r.Regs.R14) & mask16, nil + case x86asm.R15W: + return uint64(r.Regs.R15) & mask16, nil + + // 32-bit + case x86asm.EAX: + return uint64(r.Regs.Rax) & mask32, nil + case x86asm.ECX: + return uint64(r.Regs.Rcx) & mask32, nil + case x86asm.EDX: + return uint64(r.Regs.Rdx) & mask32, nil + case x86asm.EBX: + return uint64(r.Regs.Rbx) & mask32, nil + case x86asm.ESP: + return uint64(r.Regs.Rsp) & mask32, nil + case x86asm.EBP: + return uint64(r.Regs.Rbp) & mask32, nil + case x86asm.ESI: + return uint64(r.Regs.Rsi) & mask32, nil + case x86asm.EDI: + return uint64(r.Regs.Rdi) & mask32, nil + case x86asm.R8L: + return uint64(r.Regs.R8) & mask32, nil + case x86asm.R9L: + return uint64(r.Regs.R9) & mask32, nil + case x86asm.R10L: + return uint64(r.Regs.R10) & mask32, nil + case x86asm.R11L: + return uint64(r.Regs.R11) & mask32, nil + case x86asm.R12L: + return uint64(r.Regs.R12) & mask32, nil + case x86asm.R13L: + return uint64(r.Regs.R13) & mask32, nil + case x86asm.R14L: + return uint64(r.Regs.R14) & mask32, nil + case x86asm.R15L: + return uint64(r.Regs.R15) & mask32, nil + + // 64-bit + case x86asm.RAX: + return uint64(r.Regs.Rax), nil + case x86asm.RCX: + return uint64(r.Regs.Rcx), nil + case x86asm.RDX: + return uint64(r.Regs.Rdx), nil + case x86asm.RBX: + return uint64(r.Regs.Rbx), nil + case x86asm.RSP: + return uint64(r.Regs.Rsp), nil + case x86asm.RBP: + return uint64(r.Regs.Rbp), nil + case x86asm.RSI: + return uint64(r.Regs.Rsi), nil + case x86asm.RDI: + return uint64(r.Regs.Rdi), nil + case x86asm.R8: + return uint64(r.Regs.R8), nil + case x86asm.R9: + return uint64(r.Regs.R9), nil + case x86asm.R10: + return uint64(r.Regs.R10), nil + case x86asm.R11: + return uint64(r.Regs.R11), nil + case x86asm.R12: + return uint64(r.Regs.R12), nil + case x86asm.R13: + return uint64(r.Regs.R13), nil + case x86asm.R14: + return uint64(r.Regs.R14), nil + case x86asm.R15: + return uint64(r.Regs.R15), nil + } + + return 0, proc.ErrUnknownRegister +} + +// Copy returns a copy of these registers that is guarenteed not to change. +func (r *AMD64Registers) Copy() proc.Registers { + var rr AMD64Registers + rr.Regs = &AMD64PtraceRegs{} + rr.Fpregset = &AMD64Xstate{} + *(rr.Regs) = *(r.Regs) + if r.Fpregset != nil { + *(rr.Fpregset) = *(r.Fpregset) + } + if r.Fpregs != nil { + rr.Fpregs = make([]proc.Register, len(r.Fpregs)) + copy(rr.Fpregs, r.Fpregs) + } + return &rr +} + +type AMD64Xstate linutil.AMD64Xstate + +func AMD64XstateRead(xstateargs []byte, readLegacy bool, regset *AMD64Xstate) error { + return linutil.AMD64XstateRead(xstateargs, readLegacy, (*linutil.AMD64Xstate)(regset)) +} + +func (xsave *AMD64Xstate) Decode() (regs []proc.Register) { + return (*linutil.AMD64Xstate).Decode((*linutil.AMD64Xstate)(xsave)) +} diff --git a/pkg/proc/fbsdutil/regs_test.go b/pkg/proc/fbsdutil/regs_test.go new file mode 100644 index 00000000..30773ffa --- /dev/null +++ b/pkg/proc/fbsdutil/regs_test.go @@ -0,0 +1,60 @@ +package fbsdutil + +import ( + "testing" + + "golang.org/x/arch/x86/x86asm" +) + +func TestAMD64Get(t *testing.T) { + val := int64(0x7fffffffdeadbeef) + regs := AMD64Registers{ + Regs: &AMD64PtraceRegs{ + Rax: val, + }, + } + // Test AL, low 8 bits of RAX + al, err := regs.Get(int(x86asm.AL)) + if err != nil { + t.Fatal(err) + } + if al != 0xef { + t.Fatalf("expected %#v, got %#v\n", 0xef, al) + } + + // Test AH, high 8 bits of RAX + ah, err := regs.Get(int(x86asm.AH)) + if err != nil { + t.Fatal(err) + } + if ah != 0xBE { + t.Fatalf("expected %#v, got %#v\n", 0xbe, ah) + } + + // Test AX, lower 16 bits of RAX + ax, err := regs.Get(int(x86asm.AX)) + if err != nil { + t.Fatal(err) + } + if ax != 0xBEEF { + t.Fatalf("expected %#v, got %#v\n", 0xbeef, ax) + } + + // Test EAX, lower 32 bits of RAX + eax, err := regs.Get(int(x86asm.EAX)) + if err != nil { + t.Fatal(err) + } + if eax != 0xDEADBEEF { + t.Fatalf("expected %#v, got %#v\n", 0xdeadbeef, eax) + } + + // Test RAX, full 64 bits of register + rax, err := regs.Get(int(x86asm.RAX)) + if err != nil { + t.Fatal(err) + } + if rax != uint64(val) { + t.Fatalf("expected %#v, got %#v\n", val, rax) + } +} diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go index 78179f70..6df19900 100644 --- a/pkg/proc/gdbserial/gdbserver.go +++ b/pkg/proc/gdbserial/gdbserver.go @@ -1318,15 +1318,12 @@ func (t *Thread) Blocked() bool { func (p *Process) loadGInstr() []byte { var op []byte switch p.bi.GOOS { - case "windows": + case "windows", "darwin", "freebsd": // mov rcx, QWORD PTR gs:{uint32(off)} op = []byte{0x65, 0x48, 0x8b, 0x0c, 0x25} case "linux": // mov rcx,QWORD PTR fs:{uint32(off)} op = []byte{0x64, 0x48, 0x8B, 0x0C, 0x25} - case "darwin": - // mov rcx,QWORD PTR gs:{uint32(off)} - op = []byte{0x65, 0x48, 0x8B, 0x0C, 0x25} default: panic("unsupported operating system attempting to find Goroutine on Thread") } diff --git a/pkg/proc/gdbserial/gdbserver_unix.go b/pkg/proc/gdbserial/gdbserver_unix.go index 14ca46a6..5943718a 100644 --- a/pkg/proc/gdbserial/gdbserver_unix.go +++ b/pkg/proc/gdbserial/gdbserver_unix.go @@ -1,4 +1,4 @@ -// +build linux darwin +// +build linux darwin freebsd package gdbserial diff --git a/pkg/proc/native/proc_freebsd.c b/pkg/proc/native/proc_freebsd.c new file mode 100644 index 00000000..09890d7c --- /dev/null +++ b/pkg/proc/native/proc_freebsd.c @@ -0,0 +1,93 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "proc_freebsd.h" + +/* + * Returns the absolute pathname of the process's executable, if one was found. + * Must be freed by the caller. Sets errno on failure. + */ +char * find_executable(int pid) { + struct procstat *ps; + struct kinfo_proc *kp; + char *pathname; + + /* + * procstat_open_sysctl is for running processes. For core files, use + * procstat_open_core + */ + ps = procstat_open_sysctl(); + kp = kinfo_getproc(pid); + pathname = malloc(MNAMELEN); + if (ps && kp && pathname) + procstat_getpathname(ps, kp, pathname, MNAMELEN); + free(kp); + procstat_close(ps); + return (pathname); +} + +/* + * Returns the comm value of the process, which is usually the basename of its + * executable. Must be freed by the caller. Sets errno on failure. + */ +char * find_command_name(int pid) { + char *command_name = NULL; + struct kinfo_proc *kinfo; + + kinfo = kinfo_getproc(pid); + if (kinfo != NULL) { + command_name = malloc(COMMLEN + 1); + if (command_name != NULL) + strlcpy(command_name, kinfo->ki_comm, COMMLEN + 1); + free(kinfo); + } + + return (command_name); +} + +int find_status(int pid){ + char status; + struct kinfo_proc *kinfo; + kinfo = kinfo_getproc(pid); + if (kinfo != NULL) + status = kinfo->ki_stat; + else + status = '?'; + free(kinfo); + + return (status); +} + +int get_entry_point(int pid) { + void *ep = NULL; + + struct procstat *ps = procstat_open_sysctl(); + if (ps == NULL) + return -1; + + uint cnt = 0; + struct kinfo_proc *kipp = procstat_getprocs(ps, KERN_PROC_PID, pid, &cnt); + if (cnt == 0) + return -1; + + Elf_Auxinfo *auxv = procstat_getauxv(ps, kipp, &cnt); + if (auxv == NULL) + return -1; + + for (int i = 0; i < cnt; i++) { + if (auxv[i].a_type == AT_ENTRY) { + ep = auxv[i].a_un.a_ptr; + break; + } + } + procstat_freeauxv(ps, auxv); + return (int)ep; +} diff --git a/pkg/proc/native/proc_freebsd.go b/pkg/proc/native/proc_freebsd.go new file mode 100644 index 00000000..7489f91a --- /dev/null +++ b/pkg/proc/native/proc_freebsd.go @@ -0,0 +1,366 @@ +package native + +// #cgo LDFLAGS: -lprocstat +// #include +// #include "proc_freebsd.h" +import "C" +import ( + "fmt" + "os" + "os/exec" + "os/signal" + "strings" + "syscall" + "unsafe" + + sys "golang.org/x/sys/unix" + + "github.com/go-delve/delve/pkg/proc" + + isatty "github.com/mattn/go-isatty" +) + +// Process statuses +const ( + StatusIdle = 1 + StatusRunning = 2 + StatusSleeping = 3 + StatusStopped = 4 + StatusZombie = 5 + StatusWaiting = 6 + StatusLocked = 7 +) + +// OSProcessDetails contains FreeBSD specific +// process details. +type OSProcessDetails struct { + comm string + tid int +} + +// Launch creates and begins debugging a new process. First entry in +// `cmd` is the program to run, and then rest are the arguments +// to be supplied to that process. `wd` is working directory of the program. +// If the DWARF information cannot be found in the binary, Delve will look +// for external debug files in the directories passed in. +func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (*Process, error) { + var ( + process *exec.Cmd + err 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, proc.ErrNotExecutable + } + + if !isatty.IsTerminal(os.Stdin.Fd()) { + // exec.(*Process).Start will fail if we try to send a process to + // foreground but we are not attached to a terminal. + foreground = false + } + + dbp := New(0) + dbp.common = proc.NewCommonProcess(true) + dbp.execPtraceFunc(func() { + process = exec.Command(cmd[0]) + process.Args = cmd + process.Stdout = os.Stdout + process.Stderr = os.Stderr + process.SysProcAttr = &syscall.SysProcAttr{Ptrace: true, Setpgid: true, Foreground: foreground} + if foreground { + signal.Ignore(syscall.SIGTTOU, syscall.SIGTTIN) + process.Stdin = os.Stdin + } + if wd != "" { + process.Dir = wd + } + err = process.Start() + }) + if err != nil { + return nil, err + } + dbp.pid = process.Process.Pid + dbp.childProcess = true + _, _, err = dbp.wait(process.Process.Pid, 0) + if err != nil { + return nil, fmt.Errorf("waiting for target execve failed: %s", err) + } + if err = dbp.initialize(cmd[0], debugInfoDirs); err != nil { + return nil, err + } + return dbp, nil +} + +// Attach to an existing process with the given PID. Once attached, if +// the DWARF information cannot be found in the binary, Delve will look +// for external debug files in the directories passed in. +func Attach(pid int, debugInfoDirs []string) (*Process, error) { + dbp := New(pid) + dbp.common = proc.NewCommonProcess(true) + + var err error + dbp.execPtraceFunc(func() { err = PtraceAttach(dbp.pid) }) + if err != nil { + return nil, err + } + _, _, err = dbp.wait(dbp.pid, 0) + if err != nil { + return nil, err + } + + err = dbp.initialize(findExecutable("", dbp.pid), debugInfoDirs) + if err != nil { + dbp.Detach(false) + return nil, err + } + return dbp, nil +} + +func initialize(dbp *Process) error { + comm, _ := C.find_command_name(C.int(dbp.pid)) + defer C.free(unsafe.Pointer(comm)) + comm_str := C.GoString(comm) + dbp.os.comm = strings.Replace(string(comm_str), "%", "%%", -1) + return nil +} + +// kill kills the target process. +func (dbp *Process) kill() (err error) { + if dbp.exited { + return nil + } + dbp.execPtraceFunc(func() { err = PtraceCont(dbp.pid, int(sys.SIGKILL)) }) + if err != nil { + return err + } + if _, _, err = dbp.wait(dbp.pid, 0); err != nil { + return err + } + dbp.postExit() + return nil +} + +// Used by RequestManualStop +func (dbp *Process) requestManualStop() (err error) { + return sys.Kill(dbp.pid, sys.SIGTRAP) +} + +// Attach to a newly created thread, and store that thread in our list of +// known threads. +func (dbp *Process) addThread(tid int, attach bool) (*Thread, error) { + if thread, ok := dbp.threads[tid]; ok { + return thread, nil + } + + var err error + dbp.execPtraceFunc(func() { err = sys.PtraceLwpEvents(dbp.pid, 1) }) + if err == syscall.ESRCH { + if _, _, err = dbp.waitFast(dbp.pid); err != nil { + return nil, fmt.Errorf("error while waiting after adding process: %d %s", dbp.pid, err) + } + } + + dbp.threads[tid] = &Thread{ + ID: tid, + dbp: dbp, + os: new(OSSpecificDetails), + } + + if dbp.currentThread == nil { + dbp.SwitchThread(tid) + } + + return dbp.threads[tid], nil +} + +// Used by initialize +func (dbp *Process) updateThreadList() error { + var tids []int32 + dbp.execPtraceFunc(func() { tids = PtraceGetLwpList(dbp.pid) }) + for _, tid := range tids { + if _, err := dbp.addThread(int(tid), false); err != nil { + return err + } + } + dbp.os.tid = int(tids[0]) + return nil +} + +// Used by Attach +func findExecutable(path string, pid int) string { + if path == "" { + cstr := C.find_executable(C.int(pid)) + defer C.free(unsafe.Pointer(cstr)) + path = C.GoString(cstr) + } + return path +} + +func (dbp *Process) trapWait(pid int) (*Thread, error) { + return dbp.trapWaitInternal(pid, false) +} + +// Used by stop and trapWait +func (dbp *Process) trapWaitInternal(pid int, halt bool) (*Thread, error) { + for { + wpid, status, err := dbp.wait(pid, 0) + if err != nil { + return nil, fmt.Errorf("wait err %s %d", err, pid) + } + if status.Killed() { + // "Killed" status may arrive as a result of a Process.Kill() of some other process in + // the system performed by the same tracer (e.g. in the previous test) + continue + } + if status.Exited() { + dbp.postExit() + return nil, proc.ErrProcessExited{Pid: wpid, Status: status.ExitStatus()} + } + + var info sys.PtraceLwpInfoStruct + dbp.execPtraceFunc(func() { info, err = ptraceGetLwpInfo(wpid) }) + if err != nil { + return nil, fmt.Errorf("ptraceGetLwpInfo err %s %d", err, pid) + } + tid := int(info.Lwpid) + pl_flags := int(info.Flags) + th, ok := dbp.threads[tid] + if ok { + th.Status = (*WaitStatus)(status) + } + + if status.StopSignal() == sys.SIGTRAP { + if pl_flags&sys.PL_FLAG_EXITED != 0 { + delete(dbp.threads, tid) + dbp.execPtraceFunc(func() { err = PtraceCont(tid, 0) }) + if err != nil { + return nil, err + } + continue + } else if pl_flags&sys.PL_FLAG_BORN != 0 { + th, err = dbp.addThread(int(tid), false) + if err != nil { + if err == sys.ESRCH { + // process died while we were adding it + continue + } + return nil, err + } + if halt { + return nil, nil + } + if err = th.Continue(); err != nil { + if err == sys.ESRCH { + // thread died while we were adding it + delete(dbp.threads, int(tid)) + continue + } + return nil, fmt.Errorf("could not continue new thread %d %s", tid, err) + } + continue + } + } + + if th == nil { + continue + } + + if (halt && status.StopSignal() == sys.SIGSTOP) || (status.StopSignal() == sys.SIGTRAP) { + return th, nil + } + + // TODO(dp) alert user about unexpected signals here. + if err := th.resumeWithSig(int(status.StopSignal())); err != nil { + if err == sys.ESRCH { + return nil, proc.ErrProcessExited{Pid: dbp.pid} + } + return nil, err + } + } +} + +// Helper function used here and in threads_freebsd.go +// Return the status code +func status(pid int) rune { + status := rune(C.find_status(C.int(pid))) + return status +} + +// Used by stop and singleStep +// waitFast is like wait but does not handle process-exit correctly +func (dbp *Process) waitFast(pid int) (int, *sys.WaitStatus, error) { + var s sys.WaitStatus + wpid, err := sys.Wait4(pid, &s, 0, nil) + return wpid, &s, err +} + +// Only used in this file +func (dbp *Process) wait(pid, options int) (int, *sys.WaitStatus, error) { + var s sys.WaitStatus + wpid, err := sys.Wait4(pid, &s, options, nil) + return wpid, &s, err +} + +// Only used in this file +func (dbp *Process) exitGuard(err error) error { + if err != sys.ESRCH { + return err + } + if status(dbp.pid) == StatusZombie { + _, err := dbp.trapWaitInternal(-1, false) + return err + } + + return err +} + +// Used by ContinueOnce +func (dbp *Process) resume() error { + // all threads stopped over a breakpoint are made to step over it + for _, thread := range dbp.threads { + if thread.CurrentBreakpoint.Breakpoint != nil { + if err := thread.StepInstruction(); err != nil { + return err + } + thread.CurrentBreakpoint.Clear() + } + } + // all threads are resumed + var err error + dbp.execPtraceFunc(func() { err = PtraceCont(dbp.pid, 0) }) + return err +} + +// Used by ContinueOnce +// stop stops all running threads and sets breakpoints +func (dbp *Process) stop(trapthread *Thread) (err error) { + if dbp.exited { + return &proc.ErrProcessExited{Pid: dbp.Pid()} + } + // set breakpoints on all threads + for _, th := range dbp.threads { + if th.CurrentBreakpoint.Breakpoint == nil { + if err := th.SetCurrentBreakpoint(); err != nil { + return err + } + } + } + return nil +} + +// Used by Detach +func (dbp *Process) detach(kill bool) error { + return PtraceDetach(dbp.pid) +} + +// Used by PostInitializationSetup +// EntryPoint will return the process entry point address, useful for debugging PIEs. +func (dbp *Process) EntryPoint() (uint64, error) { + ep, err := C.get_entry_point(C.int(dbp.pid)) + return uint64(ep), err +} + +// Usedy by Detach +func killProcess(pid int) error { + return sys.Kill(pid, sys.SIGINT) +} diff --git a/pkg/proc/native/proc_freebsd.h b/pkg/proc/native/proc_freebsd.h new file mode 100644 index 00000000..a202c2b1 --- /dev/null +++ b/pkg/proc/native/proc_freebsd.h @@ -0,0 +1,4 @@ +char * find_command_name(int pid); +char * find_executable(int pid); +int find_status(int pid); +int get_entry_point(int pid); diff --git a/pkg/proc/native/proc_linux.go b/pkg/proc/native/proc_linux.go index 81be437b..fc4a5898 100644 --- a/pkg/proc/native/proc_linux.go +++ b/pkg/proc/native/proc_linux.go @@ -317,21 +317,17 @@ func (dbp *Process) trapWaitInternal(pid int, halt bool) (*Thread, error) { th.os.running = false return th, nil } - if th != nil { - // TODO(dp) alert user about unexpected signals here. - if err := th.resumeWithSig(int(status.StopSignal())); err != nil { - if err == sys.ESRCH { - return nil, proc.ErrProcessExited{Pid: dbp.pid} - } - return nil, err + + // TODO(dp) alert user about unexpected signals here. + if err := th.resumeWithSig(int(status.StopSignal())); err != nil { + if err == sys.ESRCH { + return nil, proc.ErrProcessExited{Pid: dbp.pid} } + return nil, err } } } -func (dbp *Process) loadProcessInformation() { -} - func status(pid int, comm string) rune { f, err := os.Open(fmt.Sprintf("/proc/%d/stat", pid)) if err != nil { @@ -422,7 +418,7 @@ func (dbp *Process) resume() error { return nil } -// stop stops all running threads threads and sets breakpoints +// stop stops all running threads and sets breakpoints func (dbp *Process) stop(trapthread *Thread) (err error) { if dbp.exited { return &proc.ErrProcessExited{Pid: dbp.Pid()} diff --git a/pkg/proc/native/ptrace_freebsd.go b/pkg/proc/native/ptrace_freebsd.go new file mode 100644 index 00000000..494ae4c8 --- /dev/null +++ b/pkg/proc/native/ptrace_freebsd.go @@ -0,0 +1,79 @@ +package native + +// #cgo LDFLAGS: -lutil +//#include +//#include +// +// #include +// #include "ptrace_freebsd_amd64.h" +import "C" + +import ( + "syscall" + "unsafe" + + sys "golang.org/x/sys/unix" + + "github.com/go-delve/delve/pkg/proc/fbsdutil" +) + +// PtraceAttach executes the sys.PtraceAttach call. +// pid must be a PID, not a LWPID +func PtraceAttach(pid int) error { + return sys.PtraceAttach(pid) +} + +// PtraceDetach calls ptrace(PTRACE_DETACH). +func PtraceDetach(pid int) error { + return sys.PtraceDetach(pid) +} + +// PtraceCont executes ptrace PTRACE_CONT +// id may be a PID or an LWPID +func PtraceCont(id, sig int) error { + return sys.PtraceCont(id, sig) +} + +// PtraceSingleStep executes ptrace PTRACE_SINGLE_STEP. +// id may be a PID or an LWPID +func PtraceSingleStep(id int) error { + return sys.PtraceSingleStep(id) +} + +// Get a list of the thread ids of a process +func PtraceGetLwpList(pid int) (tids []int32) { + num_lwps, _ := C.ptrace_get_num_lwps(C.int(pid)) + tids = make([]int32, num_lwps) + n, _ := C.ptrace_get_lwp_list(C.int(pid), (*C.int)(unsafe.Pointer(&tids[0])), C.size_t(num_lwps)) + return tids[0:n] +} + +// Get info of the thread that caused wpid's process to stop. +func ptraceGetLwpInfo(wpid int) (info sys.PtraceLwpInfoStruct, err error) { + err = sys.PtraceLwpInfo(wpid, uintptr(unsafe.Pointer(&info))) + return info, err +} + +func PtraceGetRegset(id int) (regset fbsdutil.AMD64Xstate, err error) { + _, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETFPREGS, uintptr(id), uintptr(unsafe.Pointer(®set.AMD64PtraceFpRegs)), 0, 0, 0) + if err == syscall.Errno(0) || err == syscall.ENODEV { + var xsave_len C.size_t + xsave, _ := C.ptrace_get_xsave(C.int(id), &xsave_len) + defer C.free(unsafe.Pointer(xsave)) + if xsave != nil { + xsave_sl := C.GoBytes(unsafe.Pointer(xsave), C.int(xsave_len)) + err = fbsdutil.AMD64XstateRead(xsave_sl, false, ®set) + } + } + return +} + +// id may be a PID or an LWPID +func ptraceReadData(id int, addr uintptr, data []byte) (n int, err error) { + return sys.PtraceIO(sys.PIOD_READ_D, id, addr, data, len(data)) +} + +// id may be a PID or an LWPID +func ptraceWriteData(id int, addr uintptr, data []byte) (n int, err error) { + return sys.PtraceIO(sys.PIOD_WRITE_D, id, addr, data, len(data)) +} diff --git a/pkg/proc/native/ptrace_freebsd_amd64.c b/pkg/proc/native/ptrace_freebsd_amd64.c new file mode 100644 index 00000000..40494711 --- /dev/null +++ b/pkg/proc/native/ptrace_freebsd_amd64.c @@ -0,0 +1,73 @@ +#include +#include + +#include +#include +#include +#include + +#include "ptrace_freebsd_amd64.h" + +/* Returns the number of kernel threads associated with the traced process. */ +int ptrace_get_num_lwps(int pid) { + int ret; + errno = 0; + ret = ptrace(PT_GETNUMLWPS, (pid_t)pid, 0, 0); + return (ret); +} + +/* + * Fetches the list of LWPs for a given process into tids. Returns the number + * of LWP entries filled in. Sets errno on return. + */ +int ptrace_get_lwp_list(int pid, int *tids, size_t len) { + int ret; + errno = 0; + ret = ptrace(PT_GETLWPLIST, (pid_t)pid, (caddr_t)tids, len); + return (ret); +} + +/* + * Returns a pointer to the X86 XSAVE data, or NULL on failure. Returns the + * length of the buffer in the len argument. Must be freed when no longer in + * use. Modifies errno. + */ +unsigned char* ptrace_get_xsave(int tid, size_t *len) { + static ssize_t xsave_len = 0; + static int getxstate_info_errno = 0; + unsigned char *buf; + int err; + + if (xsave_len == 0) { + /* Haven't tried to set the size yet */ + struct ptrace_xstate_info info; + err = ptrace(PT_GETXSTATE_INFO, (pid_t)tid, + (caddr_t)&info, sizeof(info)); + if (err == 0) + xsave_len = info.xsave_len; + else { + xsave_len = -1; + getxstate_info_errno = errno; + } + } + if (xsave_len < 0) { + /* Not supported on this system */ + errno = getxstate_info_errno; + return (NULL); + } + + buf = malloc(xsave_len); + if (buf == NULL) { + errno; + return (NULL); + } + err = ptrace(PT_GETXSTATE, (pid_t)tid, (caddr_t)buf, xsave_len); + if (err == 0) { + errno = 0; + *len = xsave_len; + return (buf); + } else { + free(buf); + return (NULL); + } +} diff --git a/pkg/proc/native/ptrace_freebsd_amd64.h b/pkg/proc/native/ptrace_freebsd_amd64.h new file mode 100644 index 00000000..8d06d2d0 --- /dev/null +++ b/pkg/proc/native/ptrace_freebsd_amd64.h @@ -0,0 +1,7 @@ +#include + +struct ptrace_lwpinfo; + +unsigned char* ptrace_get_xsave(int tid, size_t *len); +int ptrace_get_lwp_list(int pid, int *tids, size_t len); +int ptrace_get_num_lwps(int pid); diff --git a/pkg/proc/native/ptrace_linux.go b/pkg/proc/native/ptrace_linux.go index 8c33dd31..936615ac 100644 --- a/pkg/proc/native/ptrace_linux.go +++ b/pkg/proc/native/ptrace_linux.go @@ -80,5 +80,5 @@ func PtraceGetRegset(tid int) (regset linutil.AMD64Xstate, err error) { regset.Xsave = xstateargs[:iov.Len] err = linutil.AMD64XstateRead(regset.Xsave, false, ®set) - return regset, err + return } diff --git a/pkg/proc/native/registers_freebsd_amd64.go b/pkg/proc/native/registers_freebsd_amd64.go new file mode 100644 index 00000000..84c009df --- /dev/null +++ b/pkg/proc/native/registers_freebsd_amd64.go @@ -0,0 +1,92 @@ +package native + +import ( + "fmt" + + sys "golang.org/x/sys/unix" + + "github.com/go-delve/delve/pkg/proc" + "github.com/go-delve/delve/pkg/proc/fbsdutil" +) + +// SetPC sets RIP to the value specified by 'pc'. +func (thread *Thread) SetPC(pc uint64) error { + ir, err := registers(thread, false) + if err != nil { + return err + } + r := ir.(*fbsdutil.AMD64Registers) + r.Regs.Rip = int64(pc) + thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, (*sys.Reg)(r.Regs)) }) + return err +} + +// SetSP sets RSP to the value specified by 'sp' +func (thread *Thread) SetSP(sp uint64) (err error) { + var ir proc.Registers + ir, err = registers(thread, false) + if err != nil { + return err + } + r := ir.(*fbsdutil.AMD64Registers) + r.Regs.Rsp = int64(sp) + thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, (*sys.Reg)(r.Regs)) }) + return +} + +func (thread *Thread) SetDX(dx uint64) (err error) { + var ir proc.Registers + ir, err = registers(thread, false) + if err != nil { + return err + } + r := ir.(*fbsdutil.AMD64Registers) + r.Regs.Rdx = int64(dx) + thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, (*sys.Reg)(r.Regs)) }) + return +} + +func registers(thread *Thread, floatingPoint bool) (proc.Registers, error) { + var ( + regs fbsdutil.AMD64PtraceRegs + err error + ) + thread.dbp.execPtraceFunc(func() { err = sys.PtraceGetRegs(thread.ID, (*sys.Reg)(®s)) }) + if err != nil { + return nil, err + } + var fsbase int64 + thread.dbp.execPtraceFunc(func() { err = sys.PtraceGetFsBase(thread.ID, &fsbase) }) + if err != nil { + return nil, err + } + r := &fbsdutil.AMD64Registers{®s, nil, nil, uint64(fsbase)} + if floatingPoint { + var fpregset fbsdutil.AMD64Xstate + r.Fpregs, fpregset, err = thread.fpRegisters() + r.Fpregset = &fpregset + if err != nil { + return nil, err + } + } + return r, nil +} + +const ( + _X86_XSTATE_MAX_SIZE = 2688 + _NT_X86_XSTATE = 0x202 + + _XSAVE_HEADER_START = 512 + _XSAVE_HEADER_LEN = 64 + _XSAVE_EXTENDED_REGION_START = 576 + _XSAVE_SSE_REGION_LEN = 416 +) + +func (thread *Thread) fpRegisters() (regs []proc.Register, fpregs fbsdutil.AMD64Xstate, err error) { + thread.dbp.execPtraceFunc(func() { fpregs, err = PtraceGetRegset(thread.ID) }) + if err != nil { + err = fmt.Errorf("could not get floating point registers: %v", err.Error()) + } + regs = fpregs.Decode() + return +} diff --git a/pkg/proc/native/threads_freebsd.go b/pkg/proc/native/threads_freebsd.go new file mode 100644 index 00000000..22880d12 --- /dev/null +++ b/pkg/proc/native/threads_freebsd.go @@ -0,0 +1,133 @@ +package native + +// #include +import "C" +import ( + "fmt" + "github.com/go-delve/delve/pkg/proc/fbsdutil" + "syscall" + "unsafe" + + sys "golang.org/x/sys/unix" + + "github.com/go-delve/delve/pkg/proc" +) + +type WaitStatus sys.WaitStatus + +// OSSpecificDetails hold FreeBSD specific process details. +type OSSpecificDetails struct { + registers sys.Reg +} + +func (t *Thread) stop() (err error) { + _, err = C.thr_kill2(C.pid_t(t.dbp.pid), C.long(t.ID), C.int(sys.SIGSTOP)) + if err != nil { + err = fmt.Errorf("stop err %s on thread %d", err, t.ID) + return + } + // If the process is stopped, we must continue it so it can receive the + // signal + t.dbp.execPtraceFunc(func() { err = PtraceCont(t.dbp.pid, 0) }) + if err != nil { + return err + } + _, _, err = t.dbp.waitFast(t.dbp.pid) + if err != nil { + err = fmt.Errorf("wait err %s on thread %d", err, t.ID) + return + } + return +} + +func (t *Thread) Stopped() bool { + state := status(t.dbp.pid) + return state == StatusStopped +} + +func (t *Thread) resume() error { + return t.resumeWithSig(0) +} + +func (t *Thread) resumeWithSig(sig int) (err error) { + t.dbp.execPtraceFunc(func() { err = PtraceCont(t.ID, sig) }) + return +} + +func (t *Thread) singleStep() (err error) { + t.dbp.execPtraceFunc(func() { err = PtraceSingleStep(t.ID) }) + if err != nil { + return err + } + for { + th, err := t.dbp.trapWait(t.dbp.pid) + if err != nil { + return err + } + if th.ID == t.ID { + break + } + t.dbp.execPtraceFunc(func() { err = PtraceCont(th.ID, 0) }) + if err != nil { + return err + } + } + return nil +} + +func (t *Thread) Blocked() bool { + loc, err := t.Location() + if err != nil { + return false + } + if loc.Fn != nil && ((loc.Fn.Name == "runtime.futex") || (loc.Fn.Name == "runtime.usleep") || (loc.Fn.Name == "runtime.clone")) { + return true + } + return false +} + +func (t *Thread) restoreRegisters(savedRegs proc.Registers) error { + sr := savedRegs.(*fbsdutil.AMD64Registers) + + var restoreRegistersErr error + t.dbp.execPtraceFunc(func() { + restoreRegistersErr = sys.PtraceSetRegs(t.ID, (*sys.Reg)(sr.Regs)) + if restoreRegistersErr != nil { + return + } + if sr.Fpregset.Xsave != nil { + iov := sys.Iovec{Base: &sr.Fpregset.Xsave[0], Len: uint64(len(sr.Fpregset.Xsave))} + _, _, restoreRegistersErr = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_SETREGS, uintptr(t.ID), uintptr(unsafe.Pointer(&iov)), 0, 0, 0) + return + } + + _, _, restoreRegistersErr = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_SETFPREGS, uintptr(t.ID), uintptr(unsafe.Pointer(&sr.Fpregset.AMD64PtraceFpRegs)), 0, 0, 0) + return + }) + if restoreRegistersErr == syscall.Errno(0) { + restoreRegistersErr = nil + } + return restoreRegistersErr +} + +func (t *Thread) WriteMemory(addr uintptr, data []byte) (written int, err error) { + if t.dbp.exited { + return 0, proc.ErrProcessExited{Pid: t.dbp.pid} + } + if len(data) == 0 { + return 0, nil + } + t.dbp.execPtraceFunc(func() { written, err = ptraceWriteData(t.ID, addr, data) }) + return written, err +} + +func (t *Thread) ReadMemory(data []byte, addr uintptr) (n int, err error) { + if t.dbp.exited { + return 0, proc.ErrProcessExited{Pid: t.dbp.pid} + } + if len(data) == 0 { + return 0, nil + } + t.dbp.execPtraceFunc(func() { n, err = ptraceReadData(t.ID, addr, data) }) + return n, err +} diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index 22fc4c64..ff48b8b7 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -524,6 +524,9 @@ func TestNextGeneral(t *testing.T) { } func TestNextConcurrent(t *testing.T) { + if runtime.GOOS == "freebsd" { + t.Skip("test is not valid on FreeBSD") + } testcases := []nextTest{ {8, 9}, {9, 10}, @@ -560,6 +563,9 @@ func TestNextConcurrent(t *testing.T) { } func TestNextConcurrentVariant2(t *testing.T) { + if runtime.GOOS == "freebsd" { + t.Skip("test is not valid on FreeBSD") + } // Just like TestNextConcurrent but instead of removing the initial breakpoint we check that when it happens is for other goroutines testcases := []nextTest{ {8, 9}, @@ -1397,6 +1403,9 @@ func TestIssue325(t *testing.T) { } func TestBreakpointCounts(t *testing.T) { + if runtime.GOOS == "freebsd" { + t.Skip("test is not valid on FreeBSD") + } protest.AllowRecording(t) withTestProcess("bpcountstest", t, func(p proc.Process, fixture protest.Fixture) { addr, _, err := p.BinInfo().LineToPC(fixture.Source, 12) @@ -1614,6 +1623,9 @@ func BenchmarkLocalVariables(b *testing.B) { } func TestCondBreakpoint(t *testing.T) { + if runtime.GOOS == "freebsd" { + t.Skip("test is not valid on FreeBSD") + } protest.AllowRecording(t) withTestProcess("parallel_next", t, func(p proc.Process, fixture protest.Fixture) { addr, _, err := p.BinInfo().LineToPC(fixture.Source, 9) @@ -1638,6 +1650,9 @@ func TestCondBreakpoint(t *testing.T) { } func TestCondBreakpointError(t *testing.T) { + if runtime.GOOS == "freebsd" { + t.Skip("test is not valid on FreeBSD") + } protest.AllowRecording(t) withTestProcess("parallel_next", t, func(p proc.Process, fixture protest.Fixture) { addr, _, err := p.BinInfo().LineToPC(fixture.Source, 9) @@ -1954,6 +1969,9 @@ func TestIssue462(t *testing.T) { } func TestNextParked(t *testing.T) { + if runtime.GOOS == "freebsd" { + t.Skip("test is not valid on FreeBSD") + } protest.AllowRecording(t) withTestProcess("parallel_next", t, func(p proc.Process, fixture protest.Fixture) { bp, err := setFunctionBreakpoint(p, "main.sayhi") @@ -2005,6 +2023,9 @@ func TestNextParked(t *testing.T) { } func TestStepParked(t *testing.T) { + if runtime.GOOS == "freebsd" { + t.Skip("test is not valid on FreeBSD") + } protest.AllowRecording(t) withTestProcess("parallel_next", t, func(p proc.Process, fixture protest.Fixture) { bp, err := setFunctionBreakpoint(p, "main.sayhi") @@ -2326,6 +2347,9 @@ func TestStepOut(t *testing.T) { } func TestStepConcurrentDirect(t *testing.T) { + if runtime.GOOS == "freebsd" { + t.Skip("test is not valid on FreeBSD") + } protest.AllowRecording(t) withTestProcess("teststepconcurrent", t, func(p proc.Process, fixture protest.Fixture) { pc, err := proc.FindFileLocation(p, fixture.Source, 37) @@ -2392,6 +2416,9 @@ func TestStepConcurrentDirect(t *testing.T) { } func TestStepConcurrentPtr(t *testing.T) { + if runtime.GOOS == "freebsd" { + t.Skip("test is not valid on FreeBSD") + } protest.AllowRecording(t) withTestProcess("teststepconcurrent", t, func(p proc.Process, fixture protest.Fixture) { pc, err := proc.FindFileLocation(p, fixture.Source, 24) @@ -4355,6 +4382,9 @@ func testCallConcurrentCheckReturns(p proc.Process, t *testing.T, gid1, gid2 int } func TestCallConcurrent(t *testing.T) { + if runtime.GOOS == "freebsd" { + t.Skip("test is not valid on FreeBSD") + } protest.MustSupportFunctionCalls(t, testBackend) withTestProcess("teststepconcurrent", t, func(p proc.Process, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture, 24) diff --git a/pkg/terminal/command_test.go b/pkg/terminal/command_test.go index f6c24654..bb633891 100644 --- a/pkg/terminal/command_test.go +++ b/pkg/terminal/command_test.go @@ -424,6 +424,9 @@ func TestScopePrefix(t *testing.T) { } func TestOnPrefix(t *testing.T) { + if runtime.GOOS == "freebsd" { + t.Skip("test is not valid on FreeBSD") + } const prefix = "\ti: " test.AllowRecording(t) withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) { @@ -443,7 +446,7 @@ func TestOnPrefix(t *testing.T) { out := strings.Split(outstr, "\n") for i := range out { - if !strings.HasPrefix(out[i], "\ti: ") { + if !strings.HasPrefix(out[i], prefix) { continue } id, err := strconv.Atoi(out[i][len(prefix):]) @@ -477,6 +480,9 @@ func TestNoVars(t *testing.T) { } func TestOnPrefixLocals(t *testing.T) { + if runtime.GOOS == "freebsd" { + t.Skip("test is not valid on FreeBSD") + } const prefix = "\ti: " test.AllowRecording(t) withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) { @@ -496,7 +502,7 @@ func TestOnPrefixLocals(t *testing.T) { out := strings.Split(outstr, "\n") for i := range out { - if !strings.HasPrefix(out[i], "\ti: ") { + if !strings.HasPrefix(out[i], prefix) { continue } id, err := strconv.Atoi(out[i][len(prefix):]) @@ -532,6 +538,9 @@ func countOccurrences(s string, needle string) int { } func TestIssue387(t *testing.T) { + if runtime.GOOS == "freebsd" { + t.Skip("test is not valid on FreeBSD") + } // a breakpoint triggering during a 'next' operation will interrupt it test.AllowRecording(t) withTestTerminal("issue387", t, func(term *FakeTerminal) { diff --git a/pkg/terminal/terminal_test.go b/pkg/terminal/terminal_test.go index ddf337f0..05c78c7b 100644 --- a/pkg/terminal/terminal_test.go +++ b/pkg/terminal/terminal_test.go @@ -37,6 +37,10 @@ func platformCases() []tCase { // Should be case-sensitive {[]tRule{{"/tmp/path", "/new/path2"}}, "/TmP/path/file.go", "/TmP/path/file.go"}, } + casesFreebsd := []tCase{ + // Should be case-sensitive + {[]tRule{{"/tmp/path", "/new/path2"}}, "/TmP/path/file.go", "/TmP/path/file.go"}, + } casesDarwin := []tCase{ // Can be either case-sensitive or case-insensitive depending on // filesystem settings, we always treat it as case-sensitive. @@ -63,6 +67,9 @@ func platformCases() []tCase { if runtime.GOOS == "linux" { return append(casesUnix, casesLinux...) } + if runtime.GOOS == "freebsd" { + return append(casesUnix, casesFreebsd...) + } return casesUnix } diff --git a/service/debugger/debugger_freebsd.go b/service/debugger/debugger_freebsd.go new file mode 100644 index 00000000..b6f36676 --- /dev/null +++ b/service/debugger/debugger_freebsd.go @@ -0,0 +1,14 @@ +package debugger + +import ( + "fmt" + sys "golang.org/x/sys/unix" +) + +func attachErrorMessage(pid int, err error) error { + return fmt.Errorf("could not attach to pid %d: %s", pid, err) +} + +func stopProcess(pid int) error { + return sys.Kill(pid, sys.SIGSTOP) +} diff --git a/service/test/integration1_test.go b/service/test/integration1_test.go index bc7f880e..34682343 100644 --- a/service/test/integration1_test.go +++ b/service/test/integration1_test.go @@ -972,6 +972,9 @@ func Test1NegativeStackDepthBug(t *testing.T) { } func Test1ClientServer_CondBreakpoint(t *testing.T) { + if runtime.GOOS == "freebsd" { + t.Skip("test is not valid on FreeBSD") + } withTestClient1("parallel_next", t, func(c *rpc1.RPCClient) { bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: 1}) assertNoError(err, t, "CreateBreakpoint()") diff --git a/service/test/integration2_test.go b/service/test/integration2_test.go index e9cf28cc..18a62e40 100644 --- a/service/test/integration2_test.go +++ b/service/test/integration2_test.go @@ -1055,6 +1055,9 @@ func TestNegativeStackDepthBug(t *testing.T) { } func TestClientServer_CondBreakpoint(t *testing.T) { + if runtime.GOOS == "freebsd" { + t.Skip("test is not valid on FreeBSD") + } protest.AllowRecording(t) withTestClient2("parallel_next", t, func(c service.Client) { bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: 1}) diff --git a/vendor/gopkg.in/yaml.v2/NOTICE b/vendor/gopkg.in/yaml.v2/NOTICE new file mode 100644 index 00000000..866d74a7 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/NOTICE @@ -0,0 +1,13 @@ +Copyright 2011-2016 Canonical Ltd. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License.