Skip to content

Commit

Permalink
proc,terminal: read command line of new processes
Browse files Browse the repository at this point in the history
When attaching to child processes in follow exec mode, read their
command line.
Use it to match the follow exec regexp and also display it in the
traget list.

Updates go-delve#2242
  • Loading branch information
aarzilli committed Aug 12, 2022
1 parent b5f5408 commit e4de278
Show file tree
Hide file tree
Showing 9 changed files with 102 additions and 18 deletions.
4 changes: 2 additions & 2 deletions pkg/proc/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,6 @@ func (cctx *ContinueOnceContext) Proc(i int) ProcessInternal {
}

// AddTarget adds a target to the group being resumed.
func (cctx *ContinueOnceContext) AddTarget(tgt *Target) {
cctx.grp.addTarget(tgt)
func (cctx *ContinueOnceContext) AddTarget(tgt *Target) bool {
return cctx.grp.addTarget(tgt)
}
31 changes: 25 additions & 6 deletions pkg/proc/native/proc_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ func Launch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []str
if err != nil {
return nil, err
}
tgt.CmdLine = getCmdLine(dbp.pid)
return tgt, nil
}

Expand All @@ -159,6 +160,7 @@ func Attach(pid int, debugInfoDirs []string) (*proc.Target, error) {
_ = dbp.Detach(false)
return nil, err
}
tgt.CmdLine = getCmdLine(dbp.pid)

// ElfUpdateSharedObjects can only be done after we initialize because it
// needs an initialized BinaryInfo object to work.
Expand Down Expand Up @@ -463,15 +465,18 @@ func trapWaitInternal(cctx *proc.ContinueOnceContext, pid int, options trapWaitO
_ = dbp.Detach(false)
return nil, err
}
cctx.AddTarget(tgt)
tgt.CmdLine = getCmdLine(dbp.pid)
attached := cctx.AddTarget(tgt)
if halt {
return nil, nil
}
//TODO(aarzilli): if we want to give users the ability to stop the target
//group on exec here is where we should return
err = dbp.threads[dbp.pid].Continue()
if err != nil {
return nil, err
if attached {
//TODO(aarzilli): if we want to give users the ability to stop the target
//group on exec here is where we should return
err = dbp.threads[dbp.pid].Continue()
if err != nil {
return nil, err
}
}
continue
}
Expand Down Expand Up @@ -910,3 +915,17 @@ func (dbp *nativeProcess) FollowExec(v bool) error {
func killProcess(pid int) error {
return sys.Kill(pid, sys.SIGINT)
}

func getCmdLine(pid int) string {
buf, _ := ioutil.ReadFile(fmt.Sprintf("/proc/%d/cmdline", pid))
args := strings.SplitN(string(buf), "\x00", -1)
for i := range args {
if strings.Contains(args[i], " ") {
args[i] = strconv.Quote(args[i])
}
}
if len(args) > 0 && args[len(args)-1] == "" {
args = args[:len(args)-1]
}
return strings.Join(args, " ")
}
43 changes: 43 additions & 0 deletions pkg/proc/proc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,17 @@ func assertLineNumber(p *proc.Target, t *testing.T, lineno int, descr string) (s
return f, l
}

func assertFunctionName(p *proc.Target, t *testing.T, fnname string, descr string) {
pc := currentPC(p, t)
f, l, fn := p.BinInfo().PCToLine(pc)
if fn == nil {
t.Fatalf("%s expected function %s got %s:%d", descr, fnname, f, l)
}
if fn.Name != fnname {
t.Fatalf("%s expected function %s got %s %s:%d", descr, fnname, fn.Name, f, l)
}
}

func TestExit(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("continuetestprog", t, func(p *proc.Target, fixture protest.Fixture) {
Expand Down Expand Up @@ -6106,3 +6117,35 @@ func TestFollowExec(t *testing.T) {
}
})
}

func TestFollowExecRegexFilter(t *testing.T) {
skipUnlessOn(t, "follow exec only supported on linux", "linux")
withTestProcessArgs("spawn", t, ".", []string{"spawn", "3"}, 0, func(p *proc.Target, fixture protest.Fixture) {
grp := proc.NewGroup(p)

grp.LogicalBreakpoints[1] = &proc.LogicalBreakpoint{LogicalID: 1, Set: proc.SetBreakpoint{FunctionName: "main.traceme1"}, HitCount: make(map[int]uint64)}
grp.LogicalBreakpoints[2] = &proc.LogicalBreakpoint{LogicalID: 2, Set: proc.SetBreakpoint{FunctionName: "main.traceme2"}, HitCount: make(map[int]uint64)}
grp.LogicalBreakpoints[3] = &proc.LogicalBreakpoint{LogicalID: 3, Set: proc.SetBreakpoint{FunctionName: "main.traceme3"}, HitCount: make(map[int]uint64)}

assertNoError(grp.EnableBreakpoint(grp.LogicalBreakpoints[1]), t, "EnableBreakpoint(main.traceme1)")
assertNoError(grp.EnableBreakpoint(grp.LogicalBreakpoints[3]), t, "EnableBreakpoint(main.traceme3)")

assertNoError(grp.FollowExec(true, "spawn.* child C1"), t, "FollowExec")

assertNoError(grp.Continue(), t, "Continue 1")
assertFunctionName(grp.Selected, t, "main.traceme1", "Program did not continue to the expected location (1)")
assertNoError(grp.Continue(), t, "Continue 2")
assertFunctionName(grp.Selected, t, "main.traceme2", "Program did not continue to the expected location (2)")
assertNoError(grp.Continue(), t, "Continue 3")
assertFunctionName(grp.Selected, t, "main.traceme3", "Program did not continue to the expected location (3)")
err := grp.Continue()
if err != nil {
_, isexited := err.(proc.ErrProcessExited)
if !isexited {
assertNoError(err, t, "Continue 4")
}
} else {
t.Fatal("process did not exit after 4 continues")
}
})
}
3 changes: 2 additions & 1 deletion pkg/proc/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ type Target struct {
recman RecordingManipulationInternal
continueOnce ContinueOnceFunc

pid int
pid int
CmdLine string

// StopReason describes the reason why the target process is stopped.
// A process could be stopped for multiple simultaneous reasons, in which
Expand Down
32 changes: 25 additions & 7 deletions pkg/proc/target_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package proc

import (
"bytes"
"errors"
"fmt"
"regexp"
"strings"

"github.com/go-delve/delve/pkg/logflags"
Expand All @@ -18,6 +18,7 @@ type TargetGroup struct {
targets []*Target
Selected *Target
followExecEnabled bool
followExecRegex *regexp.Regexp

RecordingManipulation
recman RecordingManipulationInternal
Expand Down Expand Up @@ -81,19 +82,30 @@ func NewGroupRestart(t *Target, oldgrp *TargetGroup, discard func(*LogicalBreakp
}
}
if oldgrp.followExecEnabled {
grp.FollowExec(true, "")
rgx := ""
if oldgrp.followExecRegex != nil {
rgx = oldgrp.followExecRegex.String()
}
grp.FollowExec(true, rgx)
}
return grp
}

func (grp *TargetGroup) addTarget(t *Target) {
//TODO(aarzilli): check if the target's command line matches the regex
func (grp *TargetGroup) addTarget(t *Target) bool {
logger := logflags.DebuggerLogger()
if grp.followExecRegex != nil {
if !grp.followExecRegex.MatchString(t.CmdLine) {
logger.Debugf("Detaching from child target %d %q", t.Pid(), t.CmdLine)
t.detach(false)
return false
}
}
logger.Debugf("Adding target %d %q", t.Pid(), t.CmdLine)
if t.partOfGroup {
panic("internal error: target is already part of group")
}
t.partOfGroup = true
t.Breakpoints().Logical = grp.LogicalBreakpoints
logger := logflags.DebuggerLogger()
for _, lbp := range grp.LogicalBreakpoints {
if lbp.LogicalID < 0 {
continue
Expand All @@ -106,6 +118,7 @@ func (grp *TargetGroup) addTarget(t *Target) {
}
}
grp.targets = append(grp.targets, t)
return true
}

// Targets returns a slice of all targets in the group, including the
Expand Down Expand Up @@ -324,8 +337,13 @@ func (grp *TargetGroup) DisableBreakpoint(lbp *LogicalBreakpoint) error {
// If regex is not the empty string only processes whose command line
// matches regex will be added to the target group.
func (grp *TargetGroup) FollowExec(v bool, regex string) error {
if regex != "" {
return errors.New("regex not implemented")
grp.followExecRegex = nil
if regex != "" && v {
var err error
grp.followExecRegex, err = regexp.Compile(regex)
if err != nil {
return err
}
}
it := ValidTargets{Group: grp}
for it.Next() {
Expand Down
2 changes: 1 addition & 1 deletion pkg/terminal/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -2516,7 +2516,7 @@ func printcontext(t *Term, state *api.DebuggerState) {

if state.Pid != t.oldPid {
if t.oldPid != 0 {
fmt.Fprintf(t.stdout, "Switch target process from %d to %d\n", t.oldPid, state.Pid)
fmt.Fprintf(t.stdout, "Switch target process from %d to %d (%s)\n", t.oldPid, state.Pid, state.TargetCommandLine)
}
t.oldPid = state.Pid
}
Expand Down
2 changes: 1 addition & 1 deletion service/api/conversions.go
Original file line number Diff line number Diff line change
Expand Up @@ -451,9 +451,9 @@ func ConvertDumpState(dumpState *proc.DumpState) *DumpState {
}

func ConvertTarget(tgt *proc.Target, convertThreadBreakpoint func(proc.Thread) *Breakpoint) *Target {
//TODO(aarzilli): copy command line here
return &Target{
Pid: tgt.Pid(),
CmdLine: tgt.CmdLine,
CurrentThread: ConvertThread(tgt.CurrentThread(), convertThreadBreakpoint(tgt.CurrentThread())),
}
}
2 changes: 2 additions & 0 deletions service/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ var ErrNotExecutable = errors.New("not an executable file")
type DebuggerState struct {
// PID of the process we are debugging.
Pid int
// Command line of the process we are debugging.
TargetCommandLine string
// Running is true if the process is running and no other information can be collected.
Running bool
// Recording is true if the process is currently being recorded and no other
Expand Down
1 change: 1 addition & 0 deletions service/debugger/debugger.go
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,7 @@ func (d *Debugger) state(retLoadCfg *proc.LoadConfig, withBreakpointInfo bool) (

state = &api.DebuggerState{
Pid: tgt.Pid(),
TargetCommandLine: tgt.CmdLine,
SelectedGoroutine: goroutine,
Exited: exited,
}
Expand Down

0 comments on commit e4de278

Please sign in to comment.