*: 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:
parent
114b76aefc
commit
df65be43ae
@ -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)
|
19
Documentation/installation/freebsd/install.md
Normal file
19
Documentation/installation/freebsd/install.md
Normal file
@ -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
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))
|
||||
}
|
60
pkg/proc/fbsdutil/regs_test.go
Normal file
60
pkg/proc/fbsdutil/regs_test.go
Normal file
@ -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
|
||||
|
||||
|
93
pkg/proc/native/proc_freebsd.c
Normal file
93
pkg/proc/native/proc_freebsd.c
Normal file
@ -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;
|
||||
}
|
366
pkg/proc/native/proc_freebsd.go
Normal file
366
pkg/proc/native/proc_freebsd.go
Normal file
@ -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)
|
||||
}
|
4
pkg/proc/native/proc_freebsd.h
Normal file
4
pkg/proc/native/proc_freebsd.h
Normal file
@ -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()}
|
||||
|
79
pkg/proc/native/ptrace_freebsd.go
Normal file
79
pkg/proc/native/ptrace_freebsd.go
Normal file
@ -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(®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))
|
||||
}
|
73
pkg/proc/native/ptrace_freebsd_amd64.c
Normal file
73
pkg/proc/native/ptrace_freebsd_amd64.c
Normal file
@ -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);
|
||||
}
|
||||
}
|
7
pkg/proc/native/ptrace_freebsd_amd64.h
Normal file
7
pkg/proc/native/ptrace_freebsd_amd64.h
Normal file
@ -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, ®set)
|
||||
return regset, err
|
||||
return
|
||||
}
|
||||
|
92
pkg/proc/native/registers_freebsd_amd64.go
Normal file
92
pkg/proc/native/registers_freebsd_amd64.go
Normal file
@ -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
|
||||
}
|
133
pkg/proc/native/threads_freebsd.go
Normal file
133
pkg/proc/native/threads_freebsd.go
Normal file
@ -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
|
||||
}
|
||||
|
||||
|
14
service/debugger/debugger_freebsd.go
Normal file
14
service/debugger/debugger_freebsd.go
Normal file
@ -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
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.
|
Loading…
Reference in New Issue
Block a user