*: 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
This commit is contained in:
Robert Ayrapetyan 2019-07-12 18:28:04 -07:00 committed by Derek Parker
parent 114b76aefc
commit df65be43ae
24 changed files with 1352 additions and 20 deletions

@ -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)

@ -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/).

@ -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)

333
pkg/proc/fbsdutil/regs.go Normal file

@ -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))
}

@ -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)
}
}

@ -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")
}

@ -1,4 +1,4 @@
// +build linux darwin
// +build linux darwin freebsd
package gdbserial

@ -0,0 +1,93 @@
#include <sys/param.h>
#include <sys/mount.h>
#include <sys/queue.h>
#include <sys/sysctl.h>
#include <sys/user.h>
#include <libprocstat.h>
#include <libutil.h>
#include <stdlib.h>
#include <string.h>
#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;
}

@ -0,0 +1,366 @@
package native
// #cgo LDFLAGS: -lprocstat
// #include <stdlib.h>
// #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)
}

@ -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);

@ -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()}

@ -0,0 +1,79 @@
package native
// #cgo LDFLAGS: -lutil
//#include <sys/types.h>
//#include <sys/ptrace.h>
//
// #include <stdlib.h>
// #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(&regset.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, &regset)
}
}
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))
}

@ -0,0 +1,73 @@
#include <sys/types.h>
#include <sys/ptrace.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#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);
}
}

@ -0,0 +1,7 @@
#include <stddef.h>
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);

@ -80,5 +80,5 @@ func PtraceGetRegset(tid int) (regset linutil.AMD64Xstate, err error) {
regset.Xsave = xstateargs[:iov.Len]
err = linutil.AMD64XstateRead(regset.Xsave, false, &regset)
return regset, err
return
}

@ -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)(&regs)) })
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{&regs, 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
}

@ -0,0 +1,133 @@
package native
// #include <sys/thr.h>
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
}

@ -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)

@ -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) {

@ -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
}

@ -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)
}

@ -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()")

@ -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})

13
vendor/gopkg.in/yaml.v2/NOTICE generated vendored Normal file

@ -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.