mirror of
https://github.com/go-delve/delve.git
synced 2025-11-02 21:40:22 +08:00
Refactor: Use thread-locked goroutine for ptrace ops
Previously either the terminal client or the debugger service would either lock main goroutine to a thread or provide a locked goroutine to run _all_ DebuggedProcess functions in. This is unnecessary because only ptrace functions need to be run from the same thread that originated the PT_ATTACH request. Here we use a specific thread-locked goroutine to service any ptrace request. That goroutine is also responsible for the initial spawning / attaching of the process, since it must be responsible for the PT_ATTACH request.
This commit is contained in:
@ -4,20 +4,16 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"regexp"
|
||||
"runtime"
|
||||
|
||||
"github.com/derekparker/delve/proc"
|
||||
"github.com/derekparker/delve/service/api"
|
||||
)
|
||||
|
||||
// Debugger provides a thread-safe DebuggedProcess service. Instances of
|
||||
// Debugger can be exposed by other services.
|
||||
// Debugger provides a conveniant way to convert from internal
|
||||
// structure representations to API representations.
|
||||
type Debugger struct {
|
||||
config *Config
|
||||
process *proc.DebuggedProcess
|
||||
processOps chan func(*proc.DebuggedProcess)
|
||||
stop chan stopSignal
|
||||
running bool
|
||||
config *Config
|
||||
process *proc.DebuggedProcess
|
||||
}
|
||||
|
||||
// Config provides the configuration to start a Debugger.
|
||||
@ -33,222 +29,107 @@ type Config struct {
|
||||
AttachPid int
|
||||
}
|
||||
|
||||
// stopSignal is used to stop the debugger.
|
||||
type stopSignal struct {
|
||||
// KillProcess indicates whether to kill the debugee following detachment.
|
||||
KillProcess bool
|
||||
}
|
||||
|
||||
// New creates a new Debugger.
|
||||
func New(config *Config) *Debugger {
|
||||
debugger := &Debugger{
|
||||
processOps: make(chan func(*proc.DebuggedProcess)),
|
||||
config: config,
|
||||
stop: make(chan stopSignal),
|
||||
func New(config *Config) (*Debugger, error) {
|
||||
d := &Debugger{
|
||||
config: config,
|
||||
}
|
||||
return debugger
|
||||
}
|
||||
|
||||
// withProcess facilitates thread-safe access to the DebuggedProcess. Most
|
||||
// interaction with DebuggedProcess should occur via calls to withProcess[1],
|
||||
// and the functions placed on the processOps channel should be consumed and
|
||||
// executed from the same thread as the DebuggedProcess.
|
||||
//
|
||||
// This is convenient because it allows things like HTTP handlers in
|
||||
// goroutines to work with the DebuggedProcess with synchronous semantics.
|
||||
//
|
||||
// [1] There are some exceptional cases where direct access is okay; for
|
||||
// instance, when performing an operation like halt which merely sends a
|
||||
// signal to the process rather than performing something like a ptrace
|
||||
// operation.
|
||||
func (d *Debugger) withProcess(f func(*proc.DebuggedProcess) error) error {
|
||||
if !d.running {
|
||||
return fmt.Errorf("debugger isn't running")
|
||||
}
|
||||
|
||||
result := make(chan error)
|
||||
d.processOps <- func(proc *proc.DebuggedProcess) {
|
||||
result <- f(proc)
|
||||
}
|
||||
return <-result
|
||||
}
|
||||
|
||||
// Run starts debugging a process until Detach is called.
|
||||
func (d *Debugger) Run() error {
|
||||
// We must ensure here that we are running on the same thread during
|
||||
// the execution of dbg. This is due to the fact that ptrace(2) expects
|
||||
// all commands after PTRACE_ATTACH to come from the same thread.
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
d.running = true
|
||||
defer func() { d.running = false }()
|
||||
|
||||
// Create the process by either attaching or launching.
|
||||
if d.config.AttachPid > 0 {
|
||||
log.Printf("attaching to pid %d", d.config.AttachPid)
|
||||
p, err := proc.Attach(d.config.AttachPid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't attach to pid %d: %s", d.config.AttachPid, err)
|
||||
return nil, fmt.Errorf("couldn't attach to pid %d: %s", d.config.AttachPid, err)
|
||||
}
|
||||
d.process = p
|
||||
} else {
|
||||
log.Printf("launching process with args: %v", d.config.ProcessArgs)
|
||||
p, err := proc.Launch(d.config.ProcessArgs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't launch process: %s", err)
|
||||
return nil, fmt.Errorf("couldn't launch process: %s", err)
|
||||
}
|
||||
d.process = p
|
||||
}
|
||||
|
||||
// Handle access to the process from the current thread.
|
||||
log.Print("debugger started")
|
||||
for {
|
||||
select {
|
||||
case f := <-d.processOps:
|
||||
// Execute the function
|
||||
f(d.process)
|
||||
case s := <-d.stop:
|
||||
// Handle shutdown
|
||||
log.Print("debugger is stopping")
|
||||
|
||||
// Clear breakpoints
|
||||
bps := []*proc.Breakpoint{}
|
||||
for _, bp := range d.process.Breakpoints {
|
||||
if bp != nil {
|
||||
bps = append(bps, bp)
|
||||
}
|
||||
}
|
||||
for _, bp := range d.process.HardwareBreakpoints() {
|
||||
if bp != nil {
|
||||
bps = append(bps, bp)
|
||||
}
|
||||
}
|
||||
for _, bp := range bps {
|
||||
_, err := d.process.Clear(bp.Addr)
|
||||
if err != nil {
|
||||
log.Printf("warning: couldn't clear breakpoint @ %#v: %s", bp.Addr, err)
|
||||
} else {
|
||||
log.Printf("cleared breakpoint @ %#v", bp.Addr)
|
||||
}
|
||||
}
|
||||
|
||||
// Kill the process if requested
|
||||
if s.KillProcess {
|
||||
if err := d.process.Detach(); err == nil {
|
||||
log.Print("killed process")
|
||||
} else {
|
||||
log.Printf("couldn't kill process: %s", err)
|
||||
}
|
||||
} else {
|
||||
// Detach
|
||||
if !d.process.Exited() {
|
||||
if err := proc.PtraceDetach(d.process.Pid, 0); err == nil {
|
||||
log.Print("detached from process")
|
||||
} else {
|
||||
log.Printf("couldn't detach from process: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// Detach stops the debugger.
|
||||
func (d *Debugger) Detach(kill bool) error {
|
||||
if !d.running {
|
||||
return fmt.Errorf("debugger isn't running")
|
||||
}
|
||||
|
||||
d.stop <- stopSignal{KillProcess: kill}
|
||||
return nil
|
||||
return d.process.Detach(kill)
|
||||
}
|
||||
|
||||
func (d *Debugger) State() (*api.DebuggerState, error) {
|
||||
var state *api.DebuggerState
|
||||
var (
|
||||
state *api.DebuggerState
|
||||
thread *api.Thread
|
||||
)
|
||||
th := d.process.CurrentThread
|
||||
if th != nil {
|
||||
thread = api.ConvertThread(th)
|
||||
}
|
||||
|
||||
err := d.withProcess(func(p *proc.DebuggedProcess) error {
|
||||
var thread *api.Thread
|
||||
th := p.CurrentThread
|
||||
if th != nil {
|
||||
thread = convertThread(th)
|
||||
}
|
||||
var breakpoint *api.Breakpoint
|
||||
bp := d.process.CurrentBreakpoint()
|
||||
if bp != nil {
|
||||
breakpoint = api.ConvertBreakpoint(bp)
|
||||
}
|
||||
|
||||
var breakpoint *api.Breakpoint
|
||||
bp := p.CurrentBreakpoint()
|
||||
if bp != nil {
|
||||
breakpoint = convertBreakpoint(bp)
|
||||
}
|
||||
state = &api.DebuggerState{
|
||||
Breakpoint: breakpoint,
|
||||
CurrentThread: thread,
|
||||
Exited: d.process.Exited(),
|
||||
}
|
||||
|
||||
state = &api.DebuggerState{
|
||||
Breakpoint: breakpoint,
|
||||
CurrentThread: thread,
|
||||
Exited: p.Exited(),
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return state, err
|
||||
return state, nil
|
||||
}
|
||||
|
||||
func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint, error) {
|
||||
var createdBp *api.Breakpoint
|
||||
err := d.withProcess(func(p *proc.DebuggedProcess) error {
|
||||
var loc string
|
||||
switch {
|
||||
case len(requestedBp.File) > 0:
|
||||
loc = fmt.Sprintf("%s:%d", requestedBp.File, requestedBp.Line)
|
||||
case len(requestedBp.FunctionName) > 0:
|
||||
loc = requestedBp.FunctionName
|
||||
default:
|
||||
return fmt.Errorf("no file or function name specified")
|
||||
}
|
||||
var loc string
|
||||
switch {
|
||||
case len(requestedBp.File) > 0:
|
||||
loc = fmt.Sprintf("%s:%d", requestedBp.File, requestedBp.Line)
|
||||
case len(requestedBp.FunctionName) > 0:
|
||||
loc = requestedBp.FunctionName
|
||||
default:
|
||||
return nil, fmt.Errorf("no file or function name specified")
|
||||
}
|
||||
|
||||
bp, breakError := p.BreakByLocation(loc)
|
||||
if breakError != nil {
|
||||
return breakError
|
||||
}
|
||||
createdBp = convertBreakpoint(bp)
|
||||
log.Printf("created breakpoint: %#v", createdBp)
|
||||
return nil
|
||||
})
|
||||
return createdBp, err
|
||||
bp, err := d.process.BreakByLocation(loc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
createdBp = api.ConvertBreakpoint(bp)
|
||||
log.Printf("created breakpoint: %#v", createdBp)
|
||||
return createdBp, nil
|
||||
}
|
||||
|
||||
func (d *Debugger) ClearBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint, error) {
|
||||
var clearedBp *api.Breakpoint
|
||||
err := d.withProcess(func(p *proc.DebuggedProcess) error {
|
||||
bp, err := p.Clear(requestedBp.Addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can't clear breakpoint @%x: %s", requestedBp.Addr, err)
|
||||
}
|
||||
clearedBp = convertBreakpoint(bp)
|
||||
log.Printf("cleared breakpoint: %#v", clearedBp)
|
||||
return nil
|
||||
})
|
||||
bp, err := d.process.Clear(requestedBp.Addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Can't clear breakpoint @%x: %s", requestedBp.Addr, err)
|
||||
}
|
||||
clearedBp = api.ConvertBreakpoint(bp)
|
||||
log.Printf("cleared breakpoint: %#v", clearedBp)
|
||||
return clearedBp, err
|
||||
}
|
||||
|
||||
func (d *Debugger) Breakpoints() []*api.Breakpoint {
|
||||
bps := []*api.Breakpoint{}
|
||||
d.withProcess(func(p *proc.DebuggedProcess) error {
|
||||
for _, bp := range p.HardwareBreakpoints() {
|
||||
if bp == nil {
|
||||
continue
|
||||
}
|
||||
bps = append(bps, convertBreakpoint(bp))
|
||||
for _, bp := range d.process.HardwareBreakpoints() {
|
||||
if bp == nil {
|
||||
continue
|
||||
}
|
||||
bps = append(bps, api.ConvertBreakpoint(bp))
|
||||
}
|
||||
|
||||
for _, bp := range p.Breakpoints {
|
||||
if bp.Temp {
|
||||
continue
|
||||
}
|
||||
bps = append(bps, convertBreakpoint(bp))
|
||||
for _, bp := range d.process.Breakpoints {
|
||||
if bp.Temp {
|
||||
continue
|
||||
}
|
||||
return nil
|
||||
})
|
||||
bps = append(bps, api.ConvertBreakpoint(bp))
|
||||
}
|
||||
return bps
|
||||
}
|
||||
|
||||
@ -263,12 +144,9 @@ func (d *Debugger) FindBreakpoint(id int) *api.Breakpoint {
|
||||
|
||||
func (d *Debugger) Threads() []*api.Thread {
|
||||
threads := []*api.Thread{}
|
||||
d.withProcess(func(p *proc.DebuggedProcess) error {
|
||||
for _, th := range p.Threads {
|
||||
threads = append(threads, convertThread(th))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
for _, th := range d.process.Threads {
|
||||
threads = append(threads, api.ConvertThread(th))
|
||||
}
|
||||
return threads
|
||||
}
|
||||
|
||||
@ -291,26 +169,17 @@ func (d *Debugger) Command(command *api.DebuggerCommand) (*api.DebuggerState, er
|
||||
var err error
|
||||
switch command.Name {
|
||||
case api.Continue:
|
||||
err = d.withProcess(func(p *proc.DebuggedProcess) error {
|
||||
log.Print("continuing")
|
||||
e := p.Continue()
|
||||
return e
|
||||
})
|
||||
log.Print("continuing")
|
||||
err = d.process.Continue()
|
||||
case api.Next:
|
||||
err = d.withProcess(func(p *proc.DebuggedProcess) error {
|
||||
log.Print("nexting")
|
||||
return p.Next()
|
||||
})
|
||||
log.Print("nexting")
|
||||
err = d.process.Next()
|
||||
case api.Step:
|
||||
err = d.withProcess(func(p *proc.DebuggedProcess) error {
|
||||
log.Print("stepping")
|
||||
return p.Step()
|
||||
})
|
||||
log.Print("stepping")
|
||||
err = d.process.Step()
|
||||
case api.SwitchThread:
|
||||
err = d.withProcess(func(p *proc.DebuggedProcess) error {
|
||||
log.Printf("switching to thread %d", command.ThreadID)
|
||||
return p.SwitchThread(command.ThreadID)
|
||||
})
|
||||
log.Printf("switching to thread %d", command.ThreadID)
|
||||
err = d.process.SwitchThread(command.ThreadID)
|
||||
case api.Halt:
|
||||
// RequestManualStop does not invoke any ptrace syscalls, so it's safe to
|
||||
// access the process directly.
|
||||
@ -333,14 +202,11 @@ func (d *Debugger) Sources(filter string) ([]string, error) {
|
||||
}
|
||||
|
||||
files := []string{}
|
||||
d.withProcess(func(p *proc.DebuggedProcess) error {
|
||||
for f := range p.Sources() {
|
||||
if regex.Match([]byte(f)) {
|
||||
files = append(files, f)
|
||||
}
|
||||
for f := range d.process.Sources() {
|
||||
if regex.Match([]byte(f)) {
|
||||
files = append(files, f)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
@ -351,14 +217,11 @@ func (d *Debugger) Functions(filter string) ([]string, error) {
|
||||
}
|
||||
|
||||
funcs := []string{}
|
||||
d.withProcess(func(p *proc.DebuggedProcess) error {
|
||||
for _, f := range p.Funcs() {
|
||||
if f.Sym != nil && regex.Match([]byte(f.Name)) {
|
||||
funcs = append(funcs, f.Name)
|
||||
}
|
||||
for _, f := range d.process.Funcs() {
|
||||
if f.Sym != nil && regex.Match([]byte(f.Name)) {
|
||||
funcs = append(funcs, f.Name)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return funcs, nil
|
||||
}
|
||||
|
||||
@ -369,166 +232,75 @@ func (d *Debugger) PackageVariables(threadID int, filter string) ([]api.Variable
|
||||
}
|
||||
|
||||
vars := []api.Variable{}
|
||||
err = d.withProcess(func(p *proc.DebuggedProcess) error {
|
||||
thread, found := p.Threads[threadID]
|
||||
if !found {
|
||||
return fmt.Errorf("couldn't find thread %d", threadID)
|
||||
thread, found := d.process.Threads[threadID]
|
||||
if !found {
|
||||
return nil, fmt.Errorf("couldn't find thread %d", threadID)
|
||||
}
|
||||
pv, err := thread.PackageVariables()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, v := range pv {
|
||||
if regex.Match([]byte(v.Name)) {
|
||||
vars = append(vars, api.ConvertVar(v))
|
||||
}
|
||||
pv, err := thread.PackageVariables()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range pv {
|
||||
if regex.Match([]byte(v.Name)) {
|
||||
vars = append(vars, convertVar(v))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return vars, err
|
||||
}
|
||||
|
||||
func (d *Debugger) LocalVariables(threadID int) ([]api.Variable, error) {
|
||||
vars := []api.Variable{}
|
||||
err := d.withProcess(func(p *proc.DebuggedProcess) error {
|
||||
thread, found := p.Threads[threadID]
|
||||
if !found {
|
||||
return fmt.Errorf("couldn't find thread %d", threadID)
|
||||
}
|
||||
pv, err := thread.LocalVariables()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range pv {
|
||||
vars = append(vars, convertVar(v))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
thread, found := d.process.Threads[threadID]
|
||||
if !found {
|
||||
return nil, fmt.Errorf("couldn't find thread %d", threadID)
|
||||
}
|
||||
pv, err := thread.LocalVariables()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, v := range pv {
|
||||
vars = append(vars, api.ConvertVar(v))
|
||||
}
|
||||
return vars, err
|
||||
}
|
||||
|
||||
func (d *Debugger) FunctionArguments(threadID int) ([]api.Variable, error) {
|
||||
vars := []api.Variable{}
|
||||
err := d.withProcess(func(p *proc.DebuggedProcess) error {
|
||||
thread, found := p.Threads[threadID]
|
||||
if !found {
|
||||
return fmt.Errorf("couldn't find thread %d", threadID)
|
||||
}
|
||||
pv, err := thread.FunctionArguments()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range pv {
|
||||
vars = append(vars, convertVar(v))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
thread, found := d.process.Threads[threadID]
|
||||
if !found {
|
||||
return nil, fmt.Errorf("couldn't find thread %d", threadID)
|
||||
}
|
||||
pv, err := thread.FunctionArguments()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, v := range pv {
|
||||
vars = append(vars, api.ConvertVar(v))
|
||||
}
|
||||
return vars, err
|
||||
}
|
||||
|
||||
func (d *Debugger) EvalVariableInThread(threadID int, symbol string) (*api.Variable, error) {
|
||||
var variable *api.Variable
|
||||
err := d.withProcess(func(p *proc.DebuggedProcess) error {
|
||||
thread, found := p.Threads[threadID]
|
||||
if !found {
|
||||
return fmt.Errorf("couldn't find thread %d", threadID)
|
||||
}
|
||||
v, err := thread.EvalVariable(symbol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
converted := convertVar(v)
|
||||
variable = &converted
|
||||
return nil
|
||||
})
|
||||
return variable, err
|
||||
thread, found := d.process.Threads[threadID]
|
||||
if !found {
|
||||
return nil, fmt.Errorf("couldn't find thread %d", threadID)
|
||||
}
|
||||
v, err := thread.EvalVariable(symbol)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
converted := api.ConvertVar(v)
|
||||
return &converted, err
|
||||
}
|
||||
|
||||
func (d *Debugger) Goroutines() ([]*api.Goroutine, error) {
|
||||
goroutines := []*api.Goroutine{}
|
||||
err := d.withProcess(func(p *proc.DebuggedProcess) error {
|
||||
gs, err := p.GoroutinesInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, g := range gs {
|
||||
goroutines = append(goroutines, convertGoroutine(g))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
gs, err := d.process.GoroutinesInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, g := range gs {
|
||||
goroutines = append(goroutines, api.ConvertGoroutine(g))
|
||||
}
|
||||
return goroutines, err
|
||||
}
|
||||
|
||||
// convertBreakpoint converts an internal breakpoint to an API Breakpoint.
|
||||
func convertBreakpoint(bp *proc.Breakpoint) *api.Breakpoint {
|
||||
return &api.Breakpoint{
|
||||
ID: bp.ID,
|
||||
FunctionName: bp.FunctionName,
|
||||
File: bp.File,
|
||||
Line: bp.Line,
|
||||
Addr: bp.Addr,
|
||||
}
|
||||
}
|
||||
|
||||
// convertThread converts an internal thread to an API Thread.
|
||||
func convertThread(th *proc.Thread) *api.Thread {
|
||||
var (
|
||||
function *api.Function
|
||||
file string
|
||||
line int
|
||||
pc uint64
|
||||
)
|
||||
|
||||
loc, err := th.Location()
|
||||
if err == nil {
|
||||
pc = loc.PC
|
||||
file = loc.File
|
||||
line = loc.Line
|
||||
if loc.Fn != nil {
|
||||
function = &api.Function{
|
||||
Name: loc.Fn.Name,
|
||||
Type: loc.Fn.Type,
|
||||
Value: loc.Fn.Value,
|
||||
GoType: loc.Fn.GoType,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &api.Thread{
|
||||
ID: th.Id,
|
||||
PC: pc,
|
||||
File: file,
|
||||
Line: line,
|
||||
Function: function,
|
||||
}
|
||||
}
|
||||
|
||||
// convertVar converts an internal variable to an API Variable.
|
||||
func convertVar(v *proc.Variable) api.Variable {
|
||||
return api.Variable{
|
||||
Name: v.Name,
|
||||
Value: v.Value,
|
||||
Type: v.Type,
|
||||
}
|
||||
}
|
||||
|
||||
// convertGoroutine converts an internal Goroutine to an API Goroutine.
|
||||
func convertGoroutine(g *proc.G) *api.Goroutine {
|
||||
var function *api.Function
|
||||
if g.Func != nil {
|
||||
function = &api.Function{
|
||||
Name: g.Func.Name,
|
||||
Type: g.Func.Type,
|
||||
Value: g.Func.Value,
|
||||
GoType: g.Func.GoType,
|
||||
}
|
||||
}
|
||||
|
||||
return &api.Goroutine{
|
||||
ID: g.Id,
|
||||
PC: g.PC,
|
||||
File: g.File,
|
||||
Line: g.Line,
|
||||
Function: function,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user