proc,terminal: read command line of new processes (#3346)
Read the command line of the main target process as well as any other process Delve attaches to in follow exec mode. The command line can be viewed using the 'target list' command. In follow exec mode this command line is used to match the follow exec regex to decide whether or not to attach to a child process. On macOS or when using rr the list of arguments is not available for attached processes since there is no way to use the gdb serial protocol to read it. Fixes #2242
This commit is contained in:
parent
801a9109c7
commit
e95ae9c21b
@ -227,7 +227,7 @@ func OpenCore(corePath, exePath string, debugInfoDirs []string) (*proc.TargetGro
|
||||
DisableAsyncPreempt: false,
|
||||
CanDump: false,
|
||||
})
|
||||
_, err = addTarget(p, p.pid, currentThread, exePath, proc.StopAttached)
|
||||
_, err = addTarget(p, p.pid, currentThread, exePath, proc.StopAttached, "")
|
||||
return grp, err
|
||||
}
|
||||
|
||||
|
@ -278,7 +278,7 @@ func newProcess(process *os.Process) *gdbProcess {
|
||||
}
|
||||
|
||||
// Listen waits for a connection from the stub.
|
||||
func (p *gdbProcess) Listen(listener net.Listener, path string, pid int, debugInfoDirs []string, stopReason proc.StopReason) (*proc.TargetGroup, error) {
|
||||
func (p *gdbProcess) Listen(listener net.Listener, path, cmdline string, pid int, debugInfoDirs []string, stopReason proc.StopReason) (*proc.TargetGroup, error) {
|
||||
acceptChan := make(chan net.Conn)
|
||||
|
||||
go func() {
|
||||
@ -292,7 +292,7 @@ func (p *gdbProcess) Listen(listener net.Listener, path string, pid int, debugIn
|
||||
if conn == nil {
|
||||
return nil, errors.New("could not connect")
|
||||
}
|
||||
return p.Connect(conn, path, pid, debugInfoDirs, stopReason)
|
||||
return p.Connect(conn, path, cmdline, pid, debugInfoDirs, stopReason)
|
||||
case status := <-p.waitChan:
|
||||
listener.Close()
|
||||
return nil, fmt.Errorf("stub exited while waiting for connection: %v", status)
|
||||
@ -300,11 +300,11 @@ func (p *gdbProcess) Listen(listener net.Listener, path string, pid int, debugIn
|
||||
}
|
||||
|
||||
// Dial attempts to connect to the stub.
|
||||
func (p *gdbProcess) Dial(addr string, path string, pid int, debugInfoDirs []string, stopReason proc.StopReason) (*proc.TargetGroup, error) {
|
||||
func (p *gdbProcess) Dial(addr string, path, cmdline string, pid int, debugInfoDirs []string, stopReason proc.StopReason) (*proc.TargetGroup, error) {
|
||||
for {
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err == nil {
|
||||
return p.Connect(conn, path, pid, debugInfoDirs, stopReason)
|
||||
return p.Connect(conn, path, cmdline, pid, debugInfoDirs, stopReason)
|
||||
}
|
||||
select {
|
||||
case status := <-p.waitChan:
|
||||
@ -321,7 +321,7 @@ func (p *gdbProcess) Dial(addr string, path string, pid int, debugInfoDirs []str
|
||||
// program and the PID of the target process, both are optional, however
|
||||
// some stubs do not provide ways to determine path and pid automatically
|
||||
// and Connect will be unable to function without knowing them.
|
||||
func (p *gdbProcess) Connect(conn net.Conn, path string, pid int, debugInfoDirs []string, stopReason proc.StopReason) (*proc.TargetGroup, error) {
|
||||
func (p *gdbProcess) Connect(conn net.Conn, path, cmdline string, pid int, debugInfoDirs []string, stopReason proc.StopReason) (*proc.TargetGroup, error) {
|
||||
p.conn.conn = conn
|
||||
p.conn.pid = pid
|
||||
err := p.conn.handshake(p.regnames)
|
||||
@ -345,7 +345,7 @@ func (p *gdbProcess) Connect(conn net.Conn, path string, pid int, debugInfoDirs
|
||||
p.gcmdok = false
|
||||
}
|
||||
|
||||
tgt, err := p.initialize(path, debugInfoDirs, stopReason)
|
||||
tgt, err := p.initialize(path, cmdline, debugInfoDirs, stopReason)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -569,9 +569,9 @@ func LLDBLaunch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs [
|
||||
|
||||
var grp *proc.TargetGroup
|
||||
if listener != nil {
|
||||
grp, err = p.Listen(listener, cmd[0], 0, debugInfoDirs, proc.StopLaunched)
|
||||
grp, err = p.Listen(listener, cmd[0], strings.Join(cmd, " "), 0, debugInfoDirs, proc.StopLaunched)
|
||||
} else {
|
||||
grp, err = p.Dial(port, cmd[0], 0, debugInfoDirs, proc.StopLaunched)
|
||||
grp, err = p.Dial(port, cmd[0], strings.Join(cmd, " "), 0, debugInfoDirs, proc.StopLaunched)
|
||||
}
|
||||
if p.conn.pid != 0 && foreground && isatty.IsTerminal(os.Stdin.Fd()) {
|
||||
// Make the target process the controlling process of the tty if it is a foreground process.
|
||||
@ -635,9 +635,9 @@ func LLDBAttach(pid int, path string, debugInfoDirs []string) (*proc.TargetGroup
|
||||
|
||||
var grp *proc.TargetGroup
|
||||
if listener != nil {
|
||||
grp, err = p.Listen(listener, path, pid, debugInfoDirs, proc.StopAttached)
|
||||
grp, err = p.Listen(listener, path, "", pid, debugInfoDirs, proc.StopAttached)
|
||||
} else {
|
||||
grp, err = p.Dial(port, path, pid, debugInfoDirs, proc.StopAttached)
|
||||
grp, err = p.Dial(port, path, "", pid, debugInfoDirs, proc.StopAttached)
|
||||
}
|
||||
return grp, err
|
||||
}
|
||||
@ -673,7 +673,7 @@ func (p *gdbProcess) EntryPoint() (uint64, error) {
|
||||
// initialize uses qProcessInfo to load the inferior's PID and
|
||||
// executable path. This command is not supported by all stubs and not all
|
||||
// stubs will report both the PID and executable path.
|
||||
func (p *gdbProcess) initialize(path string, debugInfoDirs []string, stopReason proc.StopReason) (*proc.TargetGroup, error) {
|
||||
func (p *gdbProcess) initialize(path, cmdline string, debugInfoDirs []string, stopReason proc.StopReason) (*proc.TargetGroup, error) {
|
||||
var err error
|
||||
if path == "" {
|
||||
// If we are attaching to a running process and the user didn't specify
|
||||
@ -730,7 +730,7 @@ func (p *gdbProcess) initialize(path string, debugInfoDirs []string, stopReason
|
||||
StopReason: stopReason,
|
||||
CanDump: runtime.GOOS == "darwin",
|
||||
})
|
||||
_, err = addTarget(p, p.conn.pid, p.currentThread, path, stopReason)
|
||||
_, err = addTarget(p, p.conn.pid, p.currentThread, path, stopReason, cmdline)
|
||||
if err != nil {
|
||||
p.Detach(true)
|
||||
return nil, err
|
||||
|
@ -124,7 +124,7 @@ func Record(cmd []string, wd string, quiet bool, redirects [3]string) (tracedir
|
||||
|
||||
// Replay starts an instance of rr in replay mode, with the specified trace
|
||||
// directory, and connects to it.
|
||||
func Replay(tracedir string, quiet, deleteOnDetach bool, debugInfoDirs []string, rrOnProcessPid int) (*proc.TargetGroup, error) {
|
||||
func Replay(tracedir string, quiet, deleteOnDetach bool, debugInfoDirs []string, rrOnProcessPid int, cmdline string) (*proc.TargetGroup, error) {
|
||||
if err := checkRRAvailable(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -168,7 +168,7 @@ func Replay(tracedir string, quiet, deleteOnDetach bool, debugInfoDirs []string,
|
||||
safeRemoveAll(p.tracedir)
|
||||
}
|
||||
}
|
||||
tgt, err := p.Dial(init.port, init.exe, 0, debugInfoDirs, proc.StopLaunched)
|
||||
tgt, err := p.Dial(init.port, init.exe, cmdline, 0, debugInfoDirs, proc.StopLaunched)
|
||||
if err != nil {
|
||||
rrcmd.Process.Kill()
|
||||
return nil, err
|
||||
@ -293,7 +293,7 @@ func RecordAndReplay(cmd []string, wd string, quiet bool, debugInfoDirs []string
|
||||
if tracedir == "" {
|
||||
return nil, "", err
|
||||
}
|
||||
t, err := Replay(tracedir, quiet, true, debugInfoDirs, 0)
|
||||
t, err := Replay(tracedir, quiet, true, debugInfoDirs, 0, strings.Join(cmd, " "))
|
||||
return t, tracedir, err
|
||||
}
|
||||
|
||||
|
@ -142,4 +142,4 @@ func (t *nativeThread) SoftExc() bool {
|
||||
panic(ErrNativeBackendDisabled)
|
||||
}
|
||||
|
||||
func initialize(dbp *nativeProcess) error { return nil }
|
||||
func initialize(dbp *nativeProcess) (string, error) { return "", nil }
|
||||
|
@ -207,12 +207,14 @@ func (procgrp *processGroup) procForThread(tid int) *nativeProcess {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (procgrp *processGroup) add(p *nativeProcess, pid int, currentThread proc.Thread, path string, stopReason proc.StopReason) (*proc.Target, error) {
|
||||
tgt, err := procgrp.addTarget(p, pid, currentThread, path, stopReason)
|
||||
func (procgrp *processGroup) add(p *nativeProcess, pid int, currentThread proc.Thread, path string, stopReason proc.StopReason, cmdline string) (*proc.Target, error) {
|
||||
tgt, err := procgrp.addTarget(p, pid, currentThread, path, stopReason, cmdline)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
procgrp.procs = append(procgrp.procs, p)
|
||||
if tgt != nil {
|
||||
procgrp.procs = append(procgrp.procs, p)
|
||||
}
|
||||
return tgt, nil
|
||||
}
|
||||
|
||||
@ -285,20 +287,24 @@ func (dbp *nativeProcess) FindBreakpoint(pc uint64, adjustPC bool) (*proc.Breakp
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (dbp *nativeProcess) initializeBasic() error {
|
||||
if err := initialize(dbp); err != nil {
|
||||
return err
|
||||
func (dbp *nativeProcess) initializeBasic() (string, error) {
|
||||
cmdline, err := initialize(dbp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := dbp.updateThreadList(); err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
return nil
|
||||
return cmdline, nil
|
||||
}
|
||||
|
||||
// initialize will ensure that all relevant information is loaded
|
||||
// so the process is ready to be debugged.
|
||||
func (dbp *nativeProcess) initialize(path string, debugInfoDirs []string) (*proc.TargetGroup, error) {
|
||||
dbp.initializeBasic()
|
||||
cmdline, err := dbp.initializeBasic()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stopReason := proc.StopLaunched
|
||||
if !dbp.childProcess {
|
||||
stopReason = proc.StopAttached
|
||||
@ -321,7 +327,7 @@ func (dbp *nativeProcess) initialize(path string, debugInfoDirs []string) (*proc
|
||||
CanDump: runtime.GOOS == "linux" || runtime.GOOS == "freebsd" || (runtime.GOOS == "windows" && runtime.GOARCH == "amd64"),
|
||||
})
|
||||
procgrp.addTarget = addTarget
|
||||
tgt, err := procgrp.add(dbp, dbp.pid, dbp.memthread, path, stopReason)
|
||||
tgt, err := procgrp.add(dbp, dbp.pid, dbp.memthread, path, stopReason, cmdline)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -489,4 +489,4 @@ func (dbp *nativeProcess) GetBufferedTracepoints() []ebpf.RawUProbeParams {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func initialize(dbp *nativeProcess) error { return nil }
|
||||
func initialize(dbp *nativeProcess) (string, error) { return "", nil }
|
||||
|
@ -2,11 +2,7 @@
|
||||
#include <sys/mount.h>
|
||||
#include <sys/queue.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/user.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <libprocstat.h>
|
||||
#include <libutil.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
@ -142,12 +142,12 @@ func Attach(pid int, debugInfoDirs []string) (*proc.TargetGroup, error) {
|
||||
return tgt, nil
|
||||
}
|
||||
|
||||
func initialize(dbp *nativeProcess) error {
|
||||
func initialize(dbp *nativeProcess) (string, 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.ReplaceAll(string(comm_str), "%", "%%")
|
||||
return nil
|
||||
return getCmdLine(dbp.pid), nil
|
||||
}
|
||||
|
||||
// kill kills the target process.
|
||||
@ -227,6 +227,24 @@ func findExecutable(path string, pid int) string {
|
||||
return path
|
||||
}
|
||||
|
||||
func getCmdLine(pid int) string {
|
||||
ps := C.procstat_open_sysctl()
|
||||
kp := C.kinfo_getproc(C.int(pid))
|
||||
argv := C.procstat_getargv(ps, kp, 0)
|
||||
goargv := []string{}
|
||||
for {
|
||||
arg := *argv
|
||||
if arg == nil {
|
||||
break
|
||||
}
|
||||
argv = (**C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(argv)) + unsafe.Sizeof(*argv)))
|
||||
goargv = append(goargv, C.GoString(arg))
|
||||
}
|
||||
C.free(unsafe.Pointer(kp))
|
||||
C.procstat_close(ps)
|
||||
return strings.Join(goargv, " ")
|
||||
}
|
||||
|
||||
func trapWait(procgrp *processGroup, pid int) (*nativeThread, error) {
|
||||
return procgrp.procs[0].trapWaitInternal(pid, trapWaitNormal)
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
#include <sys/types.h>
|
||||
#include <sys/user.h>
|
||||
#include <libutil.h>
|
||||
#include <libprocstat.h>
|
||||
|
||||
char * find_command_name(int pid);
|
||||
char * find_executable(int pid);
|
||||
|
@ -169,7 +169,7 @@ func Attach(pid int, debugInfoDirs []string) (*proc.TargetGroup, error) {
|
||||
return tgt, nil
|
||||
}
|
||||
|
||||
func initialize(dbp *nativeProcess) error {
|
||||
func initialize(dbp *nativeProcess) (string, error) {
|
||||
comm, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/comm", dbp.pid))
|
||||
if err == nil {
|
||||
// removes newline character
|
||||
@ -179,22 +179,22 @@ func initialize(dbp *nativeProcess) error {
|
||||
if comm == nil || len(comm) <= 0 {
|
||||
stat, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/stat", dbp.pid))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read proc stat: %v", err)
|
||||
return "", fmt.Errorf("could not read proc stat: %v", err)
|
||||
}
|
||||
expr := fmt.Sprintf("%d\\s*\\((.*)\\)", dbp.pid)
|
||||
rexp, err := regexp.Compile(expr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("regexp compile error: %v", err)
|
||||
return "", fmt.Errorf("regexp compile error: %v", err)
|
||||
}
|
||||
match := rexp.FindSubmatch(stat)
|
||||
if match == nil {
|
||||
return fmt.Errorf("no match found using regexp '%s' in /proc/%d/stat", expr, dbp.pid)
|
||||
return "", fmt.Errorf("no match found using regexp '%s' in /proc/%d/stat", expr, dbp.pid)
|
||||
}
|
||||
comm = match[1]
|
||||
}
|
||||
dbp.os.comm = strings.ReplaceAll(string(comm), "%", "%%")
|
||||
|
||||
return nil
|
||||
return getCmdLine(dbp.pid), nil
|
||||
}
|
||||
|
||||
func (dbp *nativeProcess) GetBufferedTracepoints() []ebpf.RawUProbeParams {
|
||||
@ -460,8 +460,8 @@ func trapWaitInternal(procgrp *processGroup, pid int, options trapWaitOptions) (
|
||||
}
|
||||
dbp = newChildProcess(procgrp.procs[0], wpid)
|
||||
dbp.followExec = true
|
||||
dbp.initializeBasic()
|
||||
_, err := procgrp.add(dbp, dbp.pid, dbp.memthread, findExecutable("", dbp.pid), proc.StopLaunched)
|
||||
cmdline, _ := dbp.initializeBasic()
|
||||
tgt, err := procgrp.add(dbp, dbp.pid, dbp.memthread, findExecutable("", dbp.pid), proc.StopLaunched, cmdline)
|
||||
if err != nil {
|
||||
_ = dbp.Detach(false)
|
||||
return nil, err
|
||||
@ -469,12 +469,16 @@ func trapWaitInternal(procgrp *processGroup, pid int, options trapWaitOptions) (
|
||||
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 tgt != nil {
|
||||
// If tgt is nil we decided we are not interested in debugging this
|
||||
// process, and we have already detached from it.
|
||||
err = dbp.threads[dbp.pid].Continue()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
//TODO(aarzilli): if we want to give users the ability to stop the target
|
||||
//group on exec here is where we should return
|
||||
continue
|
||||
}
|
||||
if th == nil {
|
||||
@ -917,3 +921,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, " ")
|
||||
}
|
||||
|
@ -4,10 +4,12 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
|
||||
sys "golang.org/x/sys/windows"
|
||||
|
||||
"github.com/go-delve/delve/pkg/logflags"
|
||||
"github.com/go-delve/delve/pkg/proc"
|
||||
"github.com/go-delve/delve/pkg/proc/internal/ebpf"
|
||||
)
|
||||
@ -68,7 +70,7 @@ func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ strin
|
||||
return tgt, nil
|
||||
}
|
||||
|
||||
func initialize(dbp *nativeProcess) error {
|
||||
func initialize(dbp *nativeProcess) (string, error) {
|
||||
// It should not actually be possible for the
|
||||
// call to waitForDebugEvent to fail, since Windows
|
||||
// will always fire a CREATE_PROCESS_DEBUG_EVENT event
|
||||
@ -80,19 +82,22 @@ func initialize(dbp *nativeProcess) error {
|
||||
tid, exitCode, err = dbp.waitForDebugEvent(waitBlocking)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
if tid == 0 {
|
||||
dbp.postExit()
|
||||
return proc.ErrProcessExited{Pid: dbp.pid, Status: exitCode}
|
||||
return "", proc.ErrProcessExited{Pid: dbp.pid, Status: exitCode}
|
||||
}
|
||||
|
||||
cmdline := dbp.getCmdLine()
|
||||
|
||||
// Suspend all threads so that the call to _ContinueDebugEvent will
|
||||
// not resume the target.
|
||||
for _, thread := range dbp.threads {
|
||||
if !thread.os.dbgUiRemoteBreakIn {
|
||||
_, err := _SuspendThread(thread.os.hThread)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -100,10 +105,10 @@ func initialize(dbp *nativeProcess) error {
|
||||
dbp.execPtraceFunc(func() {
|
||||
err = _ContinueDebugEvent(uint32(dbp.pid), uint32(dbp.os.breakThread), _DBG_CONTINUE)
|
||||
})
|
||||
return err
|
||||
return cmdline, err
|
||||
}
|
||||
|
||||
// findExePath searches for process pid, and returns its executable path.
|
||||
// findExePath searches for process pid, and returns its executable path
|
||||
func findExePath(pid int) (string, error) {
|
||||
// Original code suggested different approach (see below).
|
||||
// Maybe it could be useful in the future.
|
||||
@ -604,6 +609,129 @@ func (dbp *nativeProcess) GetBufferedTracepoints() []ebpf.RawUProbeParams {
|
||||
return nil
|
||||
}
|
||||
|
||||
type _PROCESS_BASIC_INFORMATION struct {
|
||||
ExitStatus sys.NTStatus
|
||||
PebBaseAddress uintptr
|
||||
AffinityMask uintptr
|
||||
BasePriority int32
|
||||
UniqueProcessId uintptr
|
||||
InheritedFromUniqueProcessId uintptr
|
||||
}
|
||||
|
||||
type _PEB struct {
|
||||
reserved1 [2]byte
|
||||
BeingDebugged byte
|
||||
BitField byte
|
||||
reserved3 uintptr
|
||||
ImageBaseAddress uintptr
|
||||
Ldr uintptr
|
||||
ProcessParameters uintptr
|
||||
reserved4 [3]uintptr
|
||||
AtlThunkSListPtr uintptr
|
||||
reserved5 uintptr
|
||||
reserved6 uint32
|
||||
reserved7 uintptr
|
||||
reserved8 uint32
|
||||
AtlThunkSListPtr32 uint32
|
||||
reserved9 [45]uintptr
|
||||
reserved10 [96]byte
|
||||
PostProcessInitRoutine uintptr
|
||||
reserved11 [128]byte
|
||||
reserved12 [1]uintptr
|
||||
SessionId uint32
|
||||
}
|
||||
|
||||
type _RTL_USER_PROCESS_PARAMETERS struct {
|
||||
MaximumLength, Length uint32
|
||||
|
||||
Flags, DebugFlags uint32
|
||||
|
||||
ConsoleHandle sys.Handle
|
||||
ConsoleFlags uint32
|
||||
StandardInput, StandardOutput, StandardError sys.Handle
|
||||
|
||||
CurrentDirectory struct {
|
||||
DosPath _NTUnicodeString
|
||||
Handle sys.Handle
|
||||
}
|
||||
|
||||
DllPath _NTUnicodeString
|
||||
ImagePathName _NTUnicodeString
|
||||
CommandLine _NTUnicodeString
|
||||
Environment unsafe.Pointer
|
||||
|
||||
StartingX, StartingY, CountX, CountY, CountCharsX, CountCharsY, FillAttribute uint32
|
||||
|
||||
WindowFlags, ShowWindowFlags uint32
|
||||
WindowTitle, DesktopInfo, ShellInfo, RuntimeData _NTUnicodeString
|
||||
CurrentDirectories [32]struct {
|
||||
Flags uint16
|
||||
Length uint16
|
||||
TimeStamp uint32
|
||||
DosPath _NTString
|
||||
}
|
||||
|
||||
EnvironmentSize, EnvironmentVersion uintptr
|
||||
|
||||
PackageDependencyData uintptr
|
||||
ProcessGroupId uint32
|
||||
LoaderThreads uint32
|
||||
|
||||
RedirectionDllName _NTUnicodeString
|
||||
HeapPartitionName _NTUnicodeString
|
||||
DefaultThreadpoolCpuSetMasks uintptr
|
||||
DefaultThreadpoolCpuSetMaskCount uint32
|
||||
}
|
||||
|
||||
type _NTString struct {
|
||||
Length uint16
|
||||
MaximumLength uint16
|
||||
Buffer uintptr
|
||||
}
|
||||
|
||||
type _NTUnicodeString struct {
|
||||
Length uint16
|
||||
MaximumLength uint16
|
||||
Buffer uintptr
|
||||
}
|
||||
|
||||
func (dbp *nativeProcess) getCmdLine() string {
|
||||
logger := logflags.DebuggerLogger()
|
||||
var info _PROCESS_BASIC_INFORMATION
|
||||
err := sys.NtQueryInformationProcess(sys.Handle(dbp.os.hProcess), sys.ProcessBasicInformation, unsafe.Pointer(&info), uint32(unsafe.Sizeof(info)), nil)
|
||||
if err != nil {
|
||||
logger.Errorf("NtQueryInformationProcess: %v", err)
|
||||
return ""
|
||||
}
|
||||
var peb _PEB
|
||||
err = _ReadProcessMemory(dbp.os.hProcess, info.PebBaseAddress, (*byte)(unsafe.Pointer(&peb)), unsafe.Sizeof(peb), nil)
|
||||
if err != nil {
|
||||
logger.Errorf("Reading PEB: %v", err)
|
||||
return ""
|
||||
}
|
||||
var upp _RTL_USER_PROCESS_PARAMETERS
|
||||
err = _ReadProcessMemory(dbp.os.hProcess, peb.ProcessParameters, (*byte)(unsafe.Pointer(&upp)), unsafe.Sizeof(upp), nil)
|
||||
if err != nil {
|
||||
logger.Errorf("Reading ProcessParameters: %v", err)
|
||||
return ""
|
||||
}
|
||||
if upp.CommandLine.Length%2 != 0 {
|
||||
logger.Errorf("CommandLine length not a multiple of 2")
|
||||
return ""
|
||||
}
|
||||
buf := make([]byte, upp.CommandLine.Length)
|
||||
err = _ReadProcessMemory(dbp.os.hProcess, upp.CommandLine.Buffer, &buf[0], uintptr(len(buf)), nil)
|
||||
if err != nil {
|
||||
logger.Errorf("Reading CommandLine: %v", err)
|
||||
return ""
|
||||
}
|
||||
utf16buf := make([]uint16, len(buf)/2)
|
||||
for i := 0; i < len(buf); i += 2 {
|
||||
utf16buf[i/2] = uint16(buf[i+1])<<8 + uint16(buf[i])
|
||||
}
|
||||
return string(utf16.Decode(utf16buf))
|
||||
}
|
||||
|
||||
func killProcess(pid int) error {
|
||||
p, err := os.FindProcess(pid)
|
||||
if err != nil {
|
||||
|
@ -172,6 +172,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, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||
@ -6118,3 +6129,43 @@ func TestStepShadowConcurrentBreakpoint(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, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||
grp.LogicalBreakpoints[1] = &proc.LogicalBreakpoint{LogicalID: 1, Set: proc.SetBreakpoint{FunctionName: "main.traceme1"}, HitCount: make(map[int64]uint64)}
|
||||
grp.LogicalBreakpoints[2] = &proc.LogicalBreakpoint{LogicalID: 2, Set: proc.SetBreakpoint{FunctionName: "main.traceme2"}, HitCount: make(map[int64]uint64)}
|
||||
grp.LogicalBreakpoints[3] = &proc.LogicalBreakpoint{LogicalID: 3, Set: proc.SetBreakpoint{FunctionName: "main.traceme3"}, HitCount: make(map[int64]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")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestReadTargetArguments(t *testing.T) {
|
||||
protest.AllowRecording(t)
|
||||
withTestProcessArgs("restartargs", t, ".", []string{"one", "two", "three"}, 0, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||
t.Logf("command line: %q\n", p.CmdLine)
|
||||
if !strings.HasSuffix(p.CmdLine, " one two three") {
|
||||
t.Fatalf("wrong command line")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -41,7 +41,8 @@ type Target struct {
|
||||
proc ProcessInternal
|
||||
recman RecordingManipulationInternal
|
||||
|
||||
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
|
||||
@ -160,7 +161,7 @@ func DisableAsyncPreemptEnv() []string {
|
||||
|
||||
// newTarget returns an initialized Target object.
|
||||
// The p argument can optionally implement the RecordingManipulation interface.
|
||||
func (grp *TargetGroup) newTarget(p ProcessInternal, pid int, currentThread Thread, path string) (*Target, error) {
|
||||
func (grp *TargetGroup) newTarget(p ProcessInternal, pid int, currentThread Thread, path, cmdline string) (*Target, error) {
|
||||
entryPoint, err := p.EntryPoint()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -182,6 +183,7 @@ func (grp *TargetGroup) newTarget(p ProcessInternal, pid int, currentThread Thre
|
||||
fncallForG: make(map[int64]*callInjection),
|
||||
currentThread: currentThread,
|
||||
pid: pid,
|
||||
CmdLine: cmdline,
|
||||
}
|
||||
|
||||
if recman, ok := p.(RecordingManipulationInternal); ok {
|
||||
|
@ -2,8 +2,8 @@ package proc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/go-delve/delve/pkg/logflags"
|
||||
@ -20,6 +20,7 @@ type TargetGroup struct {
|
||||
targets []*Target
|
||||
Selected *Target
|
||||
followExecEnabled bool
|
||||
followExecRegex *regexp.Regexp
|
||||
|
||||
RecordingManipulation
|
||||
recman RecordingManipulationInternal
|
||||
@ -48,7 +49,7 @@ type NewTargetGroupConfig struct {
|
||||
CanDump bool // Can create core dumps (must implement ProcessInternal.MemoryMap)
|
||||
}
|
||||
|
||||
type AddTargetFunc func(ProcessInternal, int, Thread, string, StopReason) (*Target, error)
|
||||
type AddTargetFunc func(ProcessInternal, int, Thread, string, StopReason, string) (*Target, error)
|
||||
|
||||
// NewGroup creates a TargetGroup containing the specified Target.
|
||||
func NewGroup(procgrp ProcessGroup, cfg NewTargetGroupConfig) (*TargetGroup, AddTargetFunc) {
|
||||
@ -86,17 +87,29 @@ func Restart(grp, oldgrp *TargetGroup, discard func(*LogicalBreakpoint, error))
|
||||
}
|
||||
}
|
||||
if oldgrp.followExecEnabled {
|
||||
grp.FollowExec(true, "")
|
||||
rgx := ""
|
||||
if oldgrp.followExecRegex != nil {
|
||||
rgx = oldgrp.followExecRegex.String()
|
||||
}
|
||||
grp.FollowExec(true, rgx)
|
||||
}
|
||||
}
|
||||
|
||||
func (grp *TargetGroup) addTarget(p ProcessInternal, pid int, currentThread Thread, path string, stopReason StopReason) (*Target, error) {
|
||||
t, err := grp.newTarget(p, pid, currentThread, path)
|
||||
func (grp *TargetGroup) addTarget(p ProcessInternal, pid int, currentThread Thread, path string, stopReason StopReason, cmdline string) (*Target, error) {
|
||||
logger := logflags.DebuggerLogger()
|
||||
t, err := grp.newTarget(p, pid, currentThread, path, cmdline)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.StopReason = stopReason
|
||||
//TODO(aarzilli): check if the target's command line matches the regex
|
||||
if grp.followExecRegex != nil && len(grp.targets) > 0 {
|
||||
if !grp.followExecRegex.MatchString(cmdline) {
|
||||
logger.Debugf("Detaching from child target %d %q", t.Pid(), t.CmdLine)
|
||||
t.detach(false)
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
logger.Debugf("Adding target %d %q", t.Pid(), t.CmdLine)
|
||||
if t.partOfGroup {
|
||||
panic("internal error: target is already part of group")
|
||||
}
|
||||
@ -108,7 +121,7 @@ func (grp *TargetGroup) addTarget(p ProcessInternal, pid int, currentThread Thre
|
||||
if grp.Selected == nil {
|
||||
grp.Selected = t
|
||||
}
|
||||
logger := logflags.DebuggerLogger()
|
||||
t.Breakpoints().Logical = grp.LogicalBreakpoints
|
||||
for _, lbp := range grp.LogicalBreakpoints {
|
||||
if lbp.LogicalID < 0 {
|
||||
continue
|
||||
@ -340,8 +353,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() {
|
||||
|
@ -2564,7 +2564,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
|
||||
}
|
||||
|
@ -454,9 +454,9 @@ func ConvertDumpState(dumpState *proc.DumpState) *DumpState {
|
||||
|
||||
// ConvertTarget converts a proc.Target into a api.Target.
|
||||
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())),
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -176,7 +176,7 @@ func New(config *Config, processArgs []string) (*Debugger, error) {
|
||||
switch d.config.Backend {
|
||||
case "rr":
|
||||
d.log.Infof("opening trace %s", d.config.CoreFile)
|
||||
d.target, err = gdbserial.Replay(d.config.CoreFile, false, false, d.config.DebugInfoDirectories, d.config.RrOnProcessPid)
|
||||
d.target, err = gdbserial.Replay(d.config.CoreFile, false, false, d.config.DebugInfoDirectories, d.config.RrOnProcessPid, "")
|
||||
default:
|
||||
d.log.Infof("opening core file %s (executable %s)", d.config.CoreFile, d.processArgs[0])
|
||||
d.target, err = core.OpenCore(d.config.CoreFile, d.processArgs[0], d.config.DebugInfoDirectories)
|
||||
@ -335,7 +335,7 @@ func (d *Debugger) recordingRun(run func() (string, error)) (*proc.TargetGroup,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return gdbserial.Replay(tracedir, false, true, d.config.DebugInfoDirectories, 0)
|
||||
return gdbserial.Replay(tracedir, false, true, d.config.DebugInfoDirectories, 0, strings.Join(d.processArgs, " "))
|
||||
}
|
||||
|
||||
// Attach will attach to the process specified by 'pid'.
|
||||
@ -568,6 +568,7 @@ func (d *Debugger) state(retLoadCfg *proc.LoadConfig, withBreakpointInfo bool) (
|
||||
|
||||
state = &api.DebuggerState{
|
||||
Pid: tgt.Pid(),
|
||||
TargetCommandLine: tgt.CmdLine,
|
||||
SelectedGoroutine: goroutine,
|
||||
Exited: exited,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user