diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go index a201e573..4fc4b4d9 100644 --- a/pkg/proc/gdbserial/gdbserver.go +++ b/pkg/proc/gdbserial/gdbserver.go @@ -135,6 +135,14 @@ type Thread struct { setbp bool // thread was stopped because of a breakpoint } +// ErrBackendUnavailable is returned when the stub program can not be found. +type ErrBackendUnavailable struct { +} + +func (err *ErrBackendUnavailable) Error() string { + return "backend unavailable" +} + // gdbRegisters represents the current value of the registers of a thread. // The storage space for all the registers is allocated as a single memory // block in buf, the value field inside an individual gdbRegister will be a @@ -392,6 +400,9 @@ func LLDBLaunch(cmd []string, wd string) (*Process, error) { proc = exec.Command(debugserverExecutable, args...) } else { + if _, err := exec.LookPath("lldb-server"); err != nil { + return nil, &ErrBackendUnavailable{} + } port = unusedPort() args := make([]string, 0, len(cmd)+3) args = append(args, "gdbserver") @@ -452,6 +463,9 @@ func LLDBAttach(pid int, path string) (*Process, error) { } proc = exec.Command(debugserverExecutable, "-R", fmt.Sprintf("127.0.0.1:%d", listener.Addr().(*net.TCPAddr).Port), "--attach="+strconv.Itoa(pid)) } else { + if _, err := exec.LookPath("lldb-server"); err != nil { + return nil, &ErrBackendUnavailable{} + } port = unusedPort() proc = exec.Command("lldb-server", "gdbserver", "--attach", strconv.Itoa(pid), port) } diff --git a/pkg/proc/gdbserial/rr.go b/pkg/proc/gdbserial/rr.go index 78c0ab43..6fb42cbb 100644 --- a/pkg/proc/gdbserial/rr.go +++ b/pkg/proc/gdbserial/rr.go @@ -15,6 +15,10 @@ import ( // Record uses rr to record the execution of the specified program and // returns the trace directory's path. func Record(cmd []string, wd string, quiet bool) (tracedir string, err error) { + if _, err := exec.LookPath("rr"); err != nil { + return "", &ErrBackendUnavailable{} + } + rfd, wfd, err := os.Pipe() if err != nil { return "", err @@ -49,6 +53,10 @@ func Record(cmd []string, wd string, quiet bool) (tracedir string, err error) { // Replay starts an instance of rr in replay mode, with the specified trace // directory, and connects to it. func Replay(tracedir string, quiet bool) (*Process, error) { + if _, err := exec.LookPath("rr"); err != nil { + return nil, &ErrBackendUnavailable{} + } + rrcmd := exec.Command("rr", "replay", "--dbgport=0", tracedir) rrcmd.Stdout = os.Stdout stderr, err := rrcmd.StderrPipe() diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index 099ebaf2..a1c8791c 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -113,13 +113,13 @@ func (d *Debugger) Launch(processArgs []string, wd string) (proc.Process, error) case "native": return native.Launch(processArgs, wd) case "lldb": - return gdbserial.LLDBLaunch(processArgs, wd) + return betterGdbserialLaunchError(gdbserial.LLDBLaunch(processArgs, wd)) case "rr": p, _, err := gdbserial.RecordAndReplay(processArgs, wd, false) return p, err case "default": if runtime.GOOS == "darwin" { - return gdbserial.LLDBLaunch(processArgs, wd) + return betterGdbserialLaunchError(gdbserial.LLDBLaunch(processArgs, wd)) } return native.Launch(processArgs, wd) default: @@ -137,10 +137,10 @@ func (d *Debugger) Attach(pid int, path string) (proc.Process, error) { case "native": return native.Attach(pid) case "lldb": - return gdbserial.LLDBAttach(pid, path) + return betterGdbserialLaunchError(gdbserial.LLDBAttach(pid, path)) case "default": if runtime.GOOS == "darwin" { - return gdbserial.LLDBAttach(pid, path) + return betterGdbserialLaunchError(gdbserial.LLDBAttach(pid, path)) } return native.Attach(pid) default: @@ -148,6 +148,19 @@ func (d *Debugger) Attach(pid int, path string) (proc.Process, error) { } } +var macOSBackendUnavailableErr = errors.New("debugserver or lldb-server not found: install XCode's command line tools or lldb-server") + +func betterGdbserialLaunchError(p proc.Process, err error) (proc.Process, error) { + if runtime.GOOS != "darwin" { + return p, err + } + if _, isUnavailable := err.(*gdbserial.ErrBackendUnavailable); !isUnavailable { + return p, err + } + + return p, macOSBackendUnavailableErr +} + // ProcessPid returns the PID of the process // the debugger is debugging. func (d *Debugger) ProcessPid() int {