proc/native,proc/gdbserial: let target access terminal
Change the linux verison of proc/native and proc/gdbserial (with debugserver) so that they let the target process use the terminal when delve is launched in headless mode. Windows already worked, proc/gdbserial (with rr) already worked. I couldn't find a way to make proc/gdbserial (with lldb-server) work. No tests are added because I can't think of a way to test for foregroundness of a process. Fixes #65
This commit is contained in:
parent
c7cde8b151
commit
cc86bde549
@ -505,6 +505,7 @@ func execute(attachPid int, processArgs []string, conf *config.Config, coreFile
|
|||||||
WorkingDir: WorkingDir,
|
WorkingDir: WorkingDir,
|
||||||
Backend: Backend,
|
Backend: Backend,
|
||||||
CoreFile: coreFile,
|
CoreFile: coreFile,
|
||||||
|
Foreground: Headless,
|
||||||
|
|
||||||
DisconnectChan: disconnectChan,
|
DisconnectChan: disconnectChan,
|
||||||
}, logflags.Debugger())
|
}, logflags.Debugger())
|
||||||
|
|||||||
@ -191,7 +191,7 @@ func New(process *os.Process) *Process {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Listen waits for a connection from the stub.
|
// Listen waits for a connection from the stub.
|
||||||
func (p *Process) Listen(listener net.Listener, path string, pid int) error {
|
func (p *Process) Listen(listener net.Listener, path string, pid int, foreground bool) error {
|
||||||
acceptChan := make(chan net.Conn)
|
acceptChan := make(chan net.Conn)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@ -205,7 +205,7 @@ func (p *Process) Listen(listener net.Listener, path string, pid int) error {
|
|||||||
if conn == nil {
|
if conn == nil {
|
||||||
return errors.New("could not connect")
|
return errors.New("could not connect")
|
||||||
}
|
}
|
||||||
return p.Connect(conn, path, pid)
|
return p.Connect(conn, path, pid, foreground)
|
||||||
case status := <-p.waitChan:
|
case status := <-p.waitChan:
|
||||||
listener.Close()
|
listener.Close()
|
||||||
return fmt.Errorf("stub exited while waiting for connection: %v", status)
|
return fmt.Errorf("stub exited while waiting for connection: %v", status)
|
||||||
@ -217,7 +217,7 @@ func (p *Process) Dial(addr string, path string, pid int) error {
|
|||||||
for {
|
for {
|
||||||
conn, err := net.Dial("tcp", addr)
|
conn, err := net.Dial("tcp", addr)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return p.Connect(conn, path, pid)
|
return p.Connect(conn, path, pid, false)
|
||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
case status := <-p.waitChan:
|
case status := <-p.waitChan:
|
||||||
@ -234,7 +234,7 @@ func (p *Process) Dial(addr string, path string, pid int) error {
|
|||||||
// program and the PID of the target process, both are optional, however
|
// program and the PID of the target process, both are optional, however
|
||||||
// some stubs do not provide ways to determine path and pid automatically
|
// some stubs do not provide ways to determine path and pid automatically
|
||||||
// and Connect will be unable to function without knowing them.
|
// and Connect will be unable to function without knowing them.
|
||||||
func (p *Process) Connect(conn net.Conn, path string, pid int) error {
|
func (p *Process) Connect(conn net.Conn, path string, pid int, foreground bool) error {
|
||||||
p.conn.conn = conn
|
p.conn.conn = conn
|
||||||
|
|
||||||
p.conn.pid = pid
|
p.conn.pid = pid
|
||||||
@ -341,6 +341,14 @@ func (p *Process) Connect(conn net.Conn, path string, pid int) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if foreground {
|
||||||
|
// Moves process group of the target to foreground. Here we use the PID of
|
||||||
|
// the debugserver instance because we already asked Go to put it into a
|
||||||
|
// new process group (Setpgid) and debugserver does not spawn the target
|
||||||
|
// into a different process group.
|
||||||
|
moveToForeground(p.process.Pid)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -380,7 +388,7 @@ func getLdEnvVars() []string {
|
|||||||
// LLDBLaunch starts an instance of lldb-server and connects to it, asking
|
// LLDBLaunch starts an instance of lldb-server and connects to it, asking
|
||||||
// it to launch the specified target program with the specified arguments
|
// it to launch the specified target program with the specified arguments
|
||||||
// (cmd) on the specified directory wd.
|
// (cmd) on the specified directory wd.
|
||||||
func LLDBLaunch(cmd []string, wd string) (*Process, error) {
|
func LLDBLaunch(cmd []string, wd string, foreground bool) (*Process, error) {
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "windows":
|
case "windows":
|
||||||
return nil, ErrUnsupportedOS
|
return nil, ErrUnsupportedOS
|
||||||
@ -404,6 +412,9 @@ func LLDBLaunch(cmd []string, wd string) (*Process, error) {
|
|||||||
ldEnvVars := getLdEnvVars()
|
ldEnvVars := getLdEnvVars()
|
||||||
args := make([]string, 0, len(cmd)+4+len(ldEnvVars))
|
args := make([]string, 0, len(cmd)+4+len(ldEnvVars))
|
||||||
args = append(args, ldEnvVars...)
|
args = append(args, ldEnvVars...)
|
||||||
|
if foreground {
|
||||||
|
args = append(args, "--stdio-path", "/dev/tty")
|
||||||
|
}
|
||||||
args = append(args, "-F", "-R", fmt.Sprintf("127.0.0.1:%d", listener.Addr().(*net.TCPAddr).Port), "--")
|
args = append(args, "-F", "-R", fmt.Sprintf("127.0.0.1:%d", listener.Addr().(*net.TCPAddr).Port), "--")
|
||||||
args = append(args, cmd...)
|
args = append(args, cmd...)
|
||||||
|
|
||||||
@ -423,10 +434,13 @@ func LLDBLaunch(cmd []string, wd string) (*Process, error) {
|
|||||||
proc = exec.Command("lldb-server", args...)
|
proc = exec.Command("lldb-server", args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if logflags.LLDBServerOutput() || logflags.GdbWire() {
|
if logflags.LLDBServerOutput() || logflags.GdbWire() || foreground {
|
||||||
proc.Stdout = os.Stdout
|
proc.Stdout = os.Stdout
|
||||||
proc.Stderr = os.Stderr
|
proc.Stderr = os.Stderr
|
||||||
}
|
}
|
||||||
|
if foreground {
|
||||||
|
proc.Stdin = os.Stdin
|
||||||
|
}
|
||||||
if wd != "" {
|
if wd != "" {
|
||||||
proc.Dir = wd
|
proc.Dir = wd
|
||||||
}
|
}
|
||||||
@ -442,7 +456,7 @@ func LLDBLaunch(cmd []string, wd string) (*Process, error) {
|
|||||||
p.conn.isDebugserver = isDebugserver
|
p.conn.isDebugserver = isDebugserver
|
||||||
|
|
||||||
if listener != nil {
|
if listener != nil {
|
||||||
err = p.Listen(listener, cmd[0], 0)
|
err = p.Listen(listener, cmd[0], 0, p.conn.isDebugserver && foreground)
|
||||||
} else {
|
} else {
|
||||||
err = p.Dial(port, cmd[0], 0)
|
err = p.Dial(port, cmd[0], 0)
|
||||||
}
|
}
|
||||||
@ -495,7 +509,7 @@ func LLDBAttach(pid int, path string) (*Process, error) {
|
|||||||
p.conn.isDebugserver = isDebugserver
|
p.conn.isDebugserver = isDebugserver
|
||||||
|
|
||||||
if listener != nil {
|
if listener != nil {
|
||||||
err = p.Listen(listener, path, pid)
|
err = p.Listen(listener, path, pid, false)
|
||||||
} else {
|
} else {
|
||||||
err = p.Dial(port, path, pid)
|
err = p.Dial(port, path, pid)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,8 +2,15 @@
|
|||||||
|
|
||||||
package gdbserial
|
package gdbserial
|
||||||
|
|
||||||
import "syscall"
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
func backgroundSysProcAttr() *syscall.SysProcAttr {
|
func backgroundSysProcAttr() *syscall.SysProcAttr {
|
||||||
return &syscall.SysProcAttr{Setpgid: true, Pgid: 0, Foreground: false}
|
return &syscall.SysProcAttr{Setpgid: true, Pgid: 0, Foreground: false}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func moveToForeground(pid int) {
|
||||||
|
syscall.Syscall(syscall.SYS_IOCTL, uintptr(0), uintptr(syscall.TIOCSPGRP), uintptr(unsafe.Pointer(&pid)))
|
||||||
|
}
|
||||||
|
|||||||
@ -5,3 +5,7 @@ import "syscall"
|
|||||||
func backgroundSysProcAttr() *syscall.SysProcAttr {
|
func backgroundSysProcAttr() *syscall.SysProcAttr {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func moveToForeground(pid int) {
|
||||||
|
panic("lldb backend not supported on windows")
|
||||||
|
}
|
||||||
|
|||||||
@ -36,7 +36,7 @@ type OSProcessDetails struct {
|
|||||||
// custom fork/exec process in order to take advantage of
|
// custom fork/exec process in order to take advantage of
|
||||||
// PT_SIGEXC on Darwin which will turn Unix signals into
|
// PT_SIGEXC on Darwin which will turn Unix signals into
|
||||||
// Mach exceptions.
|
// Mach exceptions.
|
||||||
func Launch(cmd []string, wd string) (*Process, error) {
|
func Launch(cmd []string, wd string, foreground bool) (*Process, error) {
|
||||||
// check that the argument to Launch is an executable file
|
// check that the argument to Launch is an executable file
|
||||||
if fi, staterr := os.Stat(cmd[0]); staterr == nil && (fi.Mode()&0111) == 0 {
|
if fi, staterr := os.Stat(cmd[0]); staterr == nil && (fi.Mode()&0111) == 0 {
|
||||||
return nil, proc.NotExecutableErr
|
return nil, proc.NotExecutableErr
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
sys "golang.org/x/sys/unix"
|
sys "golang.org/x/sys/unix"
|
||||||
|
|
||||||
@ -43,7 +44,7 @@ type OSProcessDetails struct {
|
|||||||
// Launch creates and begins debugging a new process. First entry in
|
// Launch creates and begins debugging a new process. First entry in
|
||||||
// `cmd` is the program to run, and then rest are the arguments
|
// `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.
|
// to be supplied to that process. `wd` is working directory of the program.
|
||||||
func Launch(cmd []string, wd string) (*Process, error) {
|
func Launch(cmd []string, wd string, foreground bool) (*Process, error) {
|
||||||
var (
|
var (
|
||||||
process *exec.Cmd
|
process *exec.Cmd
|
||||||
err error
|
err error
|
||||||
@ -59,6 +60,9 @@ func Launch(cmd []string, wd string) (*Process, error) {
|
|||||||
process.Stdout = os.Stdout
|
process.Stdout = os.Stdout
|
||||||
process.Stderr = os.Stderr
|
process.Stderr = os.Stderr
|
||||||
process.SysProcAttr = &syscall.SysProcAttr{Ptrace: true, Setpgid: true}
|
process.SysProcAttr = &syscall.SysProcAttr{Ptrace: true, Setpgid: true}
|
||||||
|
if foreground {
|
||||||
|
process.Stdin = os.Stdin
|
||||||
|
}
|
||||||
if wd != "" {
|
if wd != "" {
|
||||||
process.Dir = wd
|
process.Dir = wd
|
||||||
}
|
}
|
||||||
@ -73,6 +77,10 @@ func Launch(cmd []string, wd string) (*Process, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("waiting for target execve failed: %s", err)
|
return nil, fmt.Errorf("waiting for target execve failed: %s", err)
|
||||||
}
|
}
|
||||||
|
if foreground {
|
||||||
|
// Sets target process as the controlling process for our tty, equivalent to tcsetpgrp
|
||||||
|
syscall.Syscall(syscall.SYS_IOCTL, uintptr(0), uintptr(syscall.TIOCSPGRP), uintptr(unsafe.Pointer(&dbp.pid)))
|
||||||
|
}
|
||||||
return initializeDebugProcess(dbp, process.Path)
|
return initializeDebugProcess(dbp, process.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -36,7 +36,7 @@ func openExecutablePathPE(path string) (*pe.File, io.Closer, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Launch creates and begins debugging a new process.
|
// Launch creates and begins debugging a new process.
|
||||||
func Launch(cmd []string, wd string) (*Process, error) {
|
func Launch(cmd []string, wd string, foreground bool) (*Process, error) {
|
||||||
argv0Go, err := filepath.Abs(cmd[0])
|
argv0Go, err := filepath.Abs(cmd[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@ -59,9 +59,9 @@ func withTestProcessArgs(name string, t testing.TB, wd string, args []string, bu
|
|||||||
|
|
||||||
switch testBackend {
|
switch testBackend {
|
||||||
case "native":
|
case "native":
|
||||||
p, err = native.Launch(append([]string{fixture.Path}, args...), wd)
|
p, err = native.Launch(append([]string{fixture.Path}, args...), wd, false)
|
||||||
case "lldb":
|
case "lldb":
|
||||||
p, err = gdbserial.LLDBLaunch(append([]string{fixture.Path}, args...), wd)
|
p, err = gdbserial.LLDBLaunch(append([]string{fixture.Path}, args...), wd, false)
|
||||||
case "rr":
|
case "rr":
|
||||||
protest.MustHaveRecordingAllowed(t)
|
protest.MustHaveRecordingAllowed(t)
|
||||||
t.Log("recording")
|
t.Log("recording")
|
||||||
@ -2036,7 +2036,7 @@ func TestIssue509(t *testing.T) {
|
|||||||
cmd.Dir = nomaindir
|
cmd.Dir = nomaindir
|
||||||
assertNoError(cmd.Run(), t, "go build")
|
assertNoError(cmd.Run(), t, "go build")
|
||||||
exepath := filepath.Join(nomaindir, "debug")
|
exepath := filepath.Join(nomaindir, "debug")
|
||||||
_, err := native.Launch([]string{exepath}, ".")
|
_, err := native.Launch([]string{exepath}, ".", false)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("expected error but none was generated")
|
t.Fatalf("expected error but none was generated")
|
||||||
}
|
}
|
||||||
@ -2070,7 +2070,7 @@ func TestUnsupportedArch(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer os.Remove(outfile)
|
defer os.Remove(outfile)
|
||||||
|
|
||||||
p, err := native.Launch([]string{outfile}, ".")
|
p, err := native.Launch([]string{outfile}, ".", false)
|
||||||
switch err {
|
switch err {
|
||||||
case proc.UnsupportedLinuxArchErr, proc.UnsupportedWindowsArchErr, proc.UnsupportedDarwinArchErr:
|
case proc.UnsupportedLinuxArchErr, proc.UnsupportedWindowsArchErr, proc.UnsupportedDarwinArchErr:
|
||||||
// all good
|
// all good
|
||||||
|
|||||||
@ -32,6 +32,9 @@ type Config struct {
|
|||||||
// Selects server backend.
|
// Selects server backend.
|
||||||
Backend string
|
Backend string
|
||||||
|
|
||||||
|
// Foreground lets target process access stdin.
|
||||||
|
Foreground bool
|
||||||
|
|
||||||
// DisconnectChan will be closed by the server when the client disconnects
|
// DisconnectChan will be closed by the server when the client disconnects
|
||||||
DisconnectChan chan<- struct{}
|
DisconnectChan chan<- struct{}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -54,6 +54,9 @@ type Config struct {
|
|||||||
CoreFile string
|
CoreFile string
|
||||||
// Backend specifies the debugger backend.
|
// Backend specifies the debugger backend.
|
||||||
Backend string
|
Backend string
|
||||||
|
|
||||||
|
// Foreground lets target process access stdin.
|
||||||
|
Foreground bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new Debugger. ProcessArgs specify the commandline arguments for the
|
// New creates a new Debugger. ProcessArgs specify the commandline arguments for the
|
||||||
@ -111,17 +114,17 @@ func New(config *Config, processArgs []string) (*Debugger, error) {
|
|||||||
func (d *Debugger) Launch(processArgs []string, wd string) (proc.Process, error) {
|
func (d *Debugger) Launch(processArgs []string, wd string) (proc.Process, error) {
|
||||||
switch d.config.Backend {
|
switch d.config.Backend {
|
||||||
case "native":
|
case "native":
|
||||||
return native.Launch(processArgs, wd)
|
return native.Launch(processArgs, wd, d.config.Foreground)
|
||||||
case "lldb":
|
case "lldb":
|
||||||
return betterGdbserialLaunchError(gdbserial.LLDBLaunch(processArgs, wd))
|
return betterGdbserialLaunchError(gdbserial.LLDBLaunch(processArgs, wd, d.config.Foreground))
|
||||||
case "rr":
|
case "rr":
|
||||||
p, _, err := gdbserial.RecordAndReplay(processArgs, wd, false)
|
p, _, err := gdbserial.RecordAndReplay(processArgs, wd, false)
|
||||||
return p, err
|
return p, err
|
||||||
case "default":
|
case "default":
|
||||||
if runtime.GOOS == "darwin" {
|
if runtime.GOOS == "darwin" {
|
||||||
return betterGdbserialLaunchError(gdbserial.LLDBLaunch(processArgs, wd))
|
return betterGdbserialLaunchError(gdbserial.LLDBLaunch(processArgs, wd, d.config.Foreground))
|
||||||
}
|
}
|
||||||
return native.Launch(processArgs, wd)
|
return native.Launch(processArgs, wd, d.config.Foreground)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unknown backend %q", d.config.Backend)
|
return nil, fmt.Errorf("unknown backend %q", d.config.Backend)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -115,6 +115,7 @@ func (s *ServerImpl) Run() error {
|
|||||||
WorkingDir: s.config.WorkingDir,
|
WorkingDir: s.config.WorkingDir,
|
||||||
CoreFile: s.config.CoreFile,
|
CoreFile: s.config.CoreFile,
|
||||||
Backend: s.config.Backend,
|
Backend: s.config.Backend,
|
||||||
|
Foreground: s.config.Foreground,
|
||||||
},
|
},
|
||||||
s.config.ProcessArgs); err != nil {
|
s.config.ProcessArgs); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@ -112,9 +112,9 @@ func withTestProcess(name string, t *testing.T, fn func(p proc.Process, fixture
|
|||||||
var tracedir string
|
var tracedir string
|
||||||
switch testBackend {
|
switch testBackend {
|
||||||
case "native":
|
case "native":
|
||||||
p, err = native.Launch([]string{fixture.Path}, ".")
|
p, err = native.Launch([]string{fixture.Path}, ".", false)
|
||||||
case "lldb":
|
case "lldb":
|
||||||
p, err = gdbserial.LLDBLaunch([]string{fixture.Path}, ".")
|
p, err = gdbserial.LLDBLaunch([]string{fixture.Path}, ".", false)
|
||||||
case "rr":
|
case "rr":
|
||||||
protest.MustHaveRecordingAllowed(t)
|
protest.MustHaveRecordingAllowed(t)
|
||||||
t.Log("recording")
|
t.Log("recording")
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user