예제 #1
0
        private static RuntimePlatform CalculateRuntimePlatform()
        {
#if !XPLAT
            return(RuntimePlatform.Windows);
#else
            const PlatformID MonoOldUnix = (PlatformID)128;

            switch (Environment.OSVersion.Platform)
            {
            case PlatformID.Win32NT:
                return(RuntimePlatform.Windows);

            case PlatformID.Unix:
            case MonoOldUnix:
                // Mono returns PlatformID.Unix on OSX for compatibility
                return(PlatformUtilities.GetUnixVariant());

            case PlatformID.MacOSX:
                return(RuntimePlatform.MacOSX);

            default:
                return(RuntimePlatform.Unknown);
            }
#endif
        }
예제 #2
0
        public override void InitStreams(LaunchOptions options, out StreamReader reader, out StreamWriter writer)
        {
            LocalLaunchOptions localOptions  = (LocalLaunchOptions)options;
            string             miDebuggerDir = System.IO.Path.GetDirectoryName(localOptions.MIDebuggerPath);

            Process proc = new Process();

            proc.StartInfo.FileName         = localOptions.MIDebuggerPath;
            proc.StartInfo.Arguments        = "--interpreter=mi";
            proc.StartInfo.WorkingDirectory = miDebuggerDir;

            // On Windows, GDB locally requires that the directory be on the PATH, being the working directory isn't good enough
            if (PlatformUtilities.IsWindows() &&
                options.DebuggerMIMode == MIMode.Gdb)
            {
                string path = proc.StartInfo.GetEnvironmentVariable("PATH");
                path = (string.IsNullOrEmpty(path) ? miDebuggerDir : path + ";" + miDebuggerDir);
                proc.StartInfo.SetEnvironmentVariable("PATH", path);
            }

            foreach (EnvironmentEntry entry in localOptions.Environment)
            {
                proc.StartInfo.SetEnvironmentVariable(entry.Name, entry.Value);
            }

            InitProcess(proc, out reader, out writer);
        }
예제 #3
0
        /// <summary>
        /// Launch a new terminal, spin up a new bash shell, cd to the working dir, execute a tty command to get the shell tty and store it.
        /// Start the debugger in mi mode setting the tty to the terminal defined earlier and redirect stdin/stdout
        /// to the correct pipes. After the debugger exits, cleanup the FIFOs. This is done using the trap command to add a
        /// signal handler for SIGHUP on the console (executing the two rm commands)
        /// </summary>
        /// <param name="debuggeeDir">Path to the debuggee directory</param>
        /// <param name="dbgStdInName">File where the stdin for the debugger process is redirected to</param>
        /// <param name="dbgStdOutName">File where the stdout for the debugger process is redirected to</param>
        /// <param name="pidFifo">File where the debugger pid is written to</param>
        /// <param name="dbgCmdScript">Script file to pass the debugger launch command</param>
        /// <param name="debuggerCmd">Command to the debugger</param>
        /// <param name="debuggerArgs">MIDebugger arguments</param>
        /// <returns></returns>
        internal static string LaunchLocalDebuggerCommand(
            string debuggeeDir,
            string dbgStdInName,
            string dbgStdOutName,
            string pidFifo,
            string dbgCmdScript,
            string debuggerCmd,
            string debuggerArgs)
        {
            // On OSX, 'wait' will return once there is a status change from the launched process rather than for it to exit, so
            // we need to use 'fg' there. This works as our bash prompt is launched through apple script rather than 'bash -c'.
            // On Linux, fg will fail with 'no job control' because our commands are being executed through 'bash -c', and
            // bash doesn't support fg in this mode, so we need to use 'wait' there.
            string waitForCompletionCommand = PlatformUtilities.IsOSX() ? "fg > /dev/null; " : "wait $pid; ";

            // echo the shell pid so that we can monitor it
            // Change to the current working directory
            // find the tty
            // enable monitor on the session
            // set the trap command to remove the fifos
            // execute the debugger command in the background
            // Clear the output of executing a process in the background: [job number] pid
            // echo and wait the debugger pid to know whether we need to fake an exit by the debugger
            return(FormattableString.Invariant($"echo $$ > {pidFifo} ; cd \"{debuggeeDir}\" ; DbgTerm=`tty` ; set -o monitor ; trap 'rm \"{dbgStdInName}\" \"{dbgStdOutName}\" \"{pidFifo}\" \"{dbgCmdScript}\"' EXIT ; {debuggerCmd} {debuggerArgs} --tty=$DbgTerm < \"{dbgStdInName}\" > \"{dbgStdOutName}\" & clear; pid=$! ; echo $pid > \"{pidFifo}\" ; {waitForCompletionCommand}"));
        }
예제 #4
0
        /// <summary>
        /// Launch a new terminal, spin up a new bash shell, cd to the working dir, execute a tty command to get the shell tty and store it.
        /// Start the debugger in mi mode setting the tty to the terminal defined earlier and redirect stdin/stdout
        /// to the correct pipes. After the debugger exits, cleanup the FIFOs. This is done using the trap command to add a
        /// signal handler for SIGHUP on the console (executing the two rm commands)
        /// </summary>
        /// <param name="debuggeeDir">Path to the debuggee directory</param>
        /// <param name="dbgStdInName">File where the stdin for the debugger process is redirected to</param>
        /// <param name="dbgStdOutName">File where the stdout for the debugger process is redirected to</param>
        /// <param name="pidFifo">File where the debugger pid is written to</param>
        /// <param name="debuggerCmd">Command to the debugger</param>
        /// <returns></returns>
        internal static string LaunchLocalDebuggerCommand(
            string debuggeeDir,
            string dbgStdInName,
            string dbgStdOutName,
            string pidFifo,
            string debuggerCmd)
        {
            // On OSX, 'wait' will return once there is a status change from the launched process rather than for it to exit, so
            // we need to use 'fg' there. This works as our bash prompt is launched through apple script rather than 'bash -c'.
            // On Linux, fg will fail with 'no job control' because our commands are being executed through 'bash -c', and
            // bash doesn't support fg in this mode, so we need to use 'wait' there.
            string waitForCompletionCommand = PlatformUtilities.IsOSX() ? "fg > /dev/null; " : "wait $pid; ";

            return(string.Format(CultureInfo.InvariantCulture,
                                 // echo the shell pid so that we can monitor it
                                 "echo $$ > {3}; " +
                                 "cd {0}; " +
                                 "DbgTerm=`tty`; " +
                                 "trap 'rm {1} {2} {3}' EXIT; " +
                                 "{4} --interpreter=mi --tty=$DbgTerm < {1} > {2} & " +
                                 // Clear the output of executing a process in the background: [job number] pid
                                 "clear; " +
                                 // echo and wait the debugger pid to know whether
                                 // we need to fake an exit by the debugger
                                 "pid=$! ; " +
                                 "echo $pid > {3}; " +
                                 "{5}",
                                 debuggeeDir,             /* 0 */
                                 dbgStdInName,            /* 1 */
                                 dbgStdOutName,           /* 2 */
                                 pidFifo,                 /* 3 */
                                 debuggerCmd,             /* 4 */
                                 waitForCompletionCommand /* 5 */
                                 ));
        }
예제 #5
0
        internal static void KillProcessTree(Process p)
        {
            bool isLinux = PlatformUtilities.IsLinux();
            bool isOSX   = PlatformUtilities.IsOSX();

            if (isLinux || isOSX)
            {
                // On linux run 'ps -x -o "%p %P"' (similarly on Mac), which generates a list of the process ids (%p) and parent process ids (%P).
                // Using this list, issue a 'kill' command for each child process. Kill the children (recursively) to eliminate
                // the entire process tree rooted at p.
                Process ps = new Process();
                ps.StartInfo.FileName  = "/bin/ps";
                ps.StartInfo.Arguments = isLinux ? "-x -o \"%p %P\"" : "-x -o \"pid ppid\"";
                ps.StartInfo.RedirectStandardOutput = true;
                ps.StartInfo.UseShellExecute        = false;
                ps.Start();
                string line;
                List <Tuple <int, int> > processAndParent = new List <Tuple <int, int> >();
                char[] whitespace = new char[] { ' ', '\t' };
                while ((line = ps.StandardOutput.ReadLine()) != null)
                {
                    line = line.Trim();
                    int id, pid;
                    if (Int32.TryParse(line.Substring(0, line.IndexOfAny(whitespace)), NumberStyles.Integer, CultureInfo.InvariantCulture, out id) &&
                        Int32.TryParse(line.Substring(line.IndexOfAny(whitespace)).Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out pid))
                    {
                        processAndParent.Add(new Tuple <int, int>(id, pid));
                    }
                }
                KillChildren(processAndParent, p.Id);
            }
        }
예제 #6
0
        public override void InitStreams(LaunchOptions options, out StreamReader reader, out StreamWriter writer)
        {
            LocalLaunchOptions localOptions  = (LocalLaunchOptions)options;
            string             miDebuggerDir = System.IO.Path.GetDirectoryName(localOptions.MIDebuggerPath);

            Process proc = new Process();

            proc.StartInfo.FileName  = localOptions.MIDebuggerPath;
            proc.StartInfo.Arguments = localOptions.GetMiDebuggerArgs();

            // LLDB has the -environment-cd mi command that is used to set the working dir for gdb/clrdbg, but it doesn't work.
            // So, set lldb's working dir to the user's requested folder before launch.
            proc.StartInfo.WorkingDirectory = options.DebuggerMIMode == MIMode.Lldb ? options.WorkingDirectory : miDebuggerDir;

            // On Windows, GDB locally requires that the directory be on the PATH, being the working directory isn't good enough
            if (PlatformUtilities.IsWindows() &&
                options.DebuggerMIMode == MIMode.Gdb)
            {
                string path = proc.StartInfo.GetEnvironmentVariable("PATH");
                path = (string.IsNullOrEmpty(path) ? miDebuggerDir : path + ";" + miDebuggerDir);
                proc.StartInfo.SetEnvironmentVariable("PATH", path);
            }

            // Only pass the environment to launch clrdbg. For other modes, there are commands that set the environment variables
            // directly for the debuggee.
            if (options.DebuggerMIMode == MIMode.Clrdbg)
            {
                foreach (EnvironmentEntry entry in localOptions.Environment)
                {
                    proc.StartInfo.SetEnvironmentVariable(entry.Name, entry.Value);
                }
            }

            InitProcess(proc, out reader, out writer);
        }
예제 #7
0
        public ProcessMonitor(int processId)
        {
            if (!PlatformUtilities.IsLinux() && !PlatformUtilities.IsOSX())
            {
                throw new NotImplementedException();
            }

            _processId = processId;
        }
예제 #8
0
        private void LaunchSuccess(int?pid)
        {
            if (_pidReader != null)
            {
                int           shellPid;
                Task <string> readShellPidTask = _pidReader.ReadLineAsync();
                if (readShellPidTask.Wait(TimeSpan.FromSeconds(10)))
                {
                    shellPid = int.Parse(readShellPidTask.Result, CultureInfo.InvariantCulture);
                    // Used for testing
                    Logger?.WriteLine(string.Concat("ShellPid=", shellPid));
                }
                else
                {
                    // Something is wrong because we didn't get the pid of shell
                    ForceDisposeStreamReader(_pidReader);
                    Close();
                    throw new TimeoutException(string.Format(CultureInfo.CurrentCulture, MICoreResources.Error_RunInTerminalFailure, MICoreResources.Error_TimeoutWaitingForConnection));
                }

                if (!PlatformUtilities.IsWindows())
                {
                    _shellProcessMonitor = new ProcessMonitor(shellPid);
                    _shellProcessMonitor.ProcessExited += ShellExited;
                    _shellProcessMonitor.Start();
                }
                else
                {
                    Process shellProcess = Process.GetProcessById(shellPid);
                    shellProcess.EnableRaisingEvents = true;
                    shellProcess.Exited += ShellExited;
                }

                Task <string> readDebuggerPidTask = _pidReader.ReadLineAsync();
                try
                {
                    readDebuggerPidTask.Wait(_streamReadPidCancellationTokenSource.Token);
                    _debuggerPid = int.Parse(readDebuggerPidTask.Result, CultureInfo.InvariantCulture);
                }
                catch (OperationCanceledException)
                {
                    // Something is wrong because we didn't get the pid of the debugger
                    ForceDisposeStreamReader(_pidReader);
                    Close();
                    throw new OperationCanceledException(string.Format(CultureInfo.CurrentCulture, MICoreResources.Error_RunInTerminalFailure, MICoreResources.Error_UnableToEstablishConnectionToLauncher));
                }
            }

            if (debuggerPidCallback != null)
            {
                debuggerPidCallback(_debuggerPid);
            }
        }
예제 #9
0
파일: gdb.cs 프로젝트: chuckries/MIEngine
 public override bool UseExternalConsoleForLocalLaunch(LocalLaunchOptions localLaunchOptions)
 {
     // NOTE: On Linux, there are issues if we try to have GDB launch the process as a child of VS
     // code -- it will cause a deadlock during debuggee launch. So we always use the external console
     // unless we are in a scenario where the debuggee will not be a child process.
     if (PlatformUtilities.IsLinux())
     {
         return(String.IsNullOrEmpty(localLaunchOptions.MIDebuggerServerAddress) && !localLaunchOptions.IsCoreDump);
     }
     else
     {
         return(base.UseExternalConsoleForLocalLaunch(localLaunchOptions));
     }
 }
예제 #10
0
        internal bool PreparePath(string path, bool useUnixFormat, out string pathMI)
        {
            bool requiresQuotes = false;

            path = path.Trim();
            if (useUnixFormat)  // convert directory separators
            {
                path = PlatformUtilities.WindowsPathToUnixPath(path);
            }
            if (path.IndexOf(' ') != -1)                    // path contains spaces. Convert to c-string format
            {
                path           = path.Replace(@"\", @"\\"); // escape any backslashes in the path
                requiresQuotes = true;                      // parameter containing the name will need to be quoted
            }
            pathMI = path;
            return(requiresQuotes);
        }
예제 #11
0
        public static TerminalLauncher MakeTerminal(string title, string initScript, ReadOnlyCollection <EnvironmentEntry> environment)
        {
            TerminalLauncher terminal = null;

            if (PlatformUtilities.IsLinux())
            {
                terminal = new LinuxTerminalLauncher(title, initScript, environment);
            }
            else if (PlatformUtilities.IsOSX())
            {
                terminal = new MacTerminalLauncher(title, initScript, environment);
            }
            else
            {
                Debug.Fail("Cannot make a terminal for non Linux or OS X.");
                throw new InvalidOperationException();
            }

            return(terminal);
        }
예제 #12
0
        internal static string GetDebuggerCommand(LocalLaunchOptions localOptions)
        {
            string quotedDebuggerPath = String.Format(CultureInfo.InvariantCulture, "\"{0}\"", localOptions.MIDebuggerPath);

            if (PlatformUtilities.IsLinux())
            {
                string debuggerPathCorrectElevation = quotedDebuggerPath;
                string prompt = string.Empty;

                // If running as root, make sure the new console is also root.
                bool isRoot = UnixNativeMethods.GetEUid() == 0;

                // If the system doesn't allow a non-root process to attach to another process, try to run GDB as root
                if (localOptions.ProcessId.HasValue && !isRoot && UnixUtilities.GetRequiresRootAttach(localOptions.DebuggerMIMode))
                {
                    prompt = String.Format(CultureInfo.CurrentCulture, "echo -n '{0}'; read yn; if [ \"$yn\" != 'y' ] && [ \"$yn\" != 'Y' ] ; then exit 1; fi; ", MICoreResources.Warn_AttachAsRootProcess);

                    // Prefer pkexec for a nice graphical prompt, but fall back to sudo if it's not available
                    if (File.Exists(UnixUtilities.PKExecPath))
                    {
                        debuggerPathCorrectElevation = String.Concat(UnixUtilities.PKExecPath, " ", debuggerPathCorrectElevation);
                    }
                    else if (File.Exists(UnixUtilities.SudoPath))
                    {
                        debuggerPathCorrectElevation = String.Concat(UnixUtilities.SudoPath, " ", debuggerPathCorrectElevation);
                    }
                    else
                    {
                        Debug.Fail("Root required to attach, but no means of elevating available!");
                    }
                }

                return(String.Concat(prompt, debuggerPathCorrectElevation));
            }
            else
            {
                return(quotedDebuggerPath);
            }
        }
예제 #13
0
        public static bool IsBinarySigned(string filePath)
        {
            if (!PlatformUtilities.IsOSX())
            {
                throw new NotImplementedException();
            }

            Process p = new Process
            {
                StartInfo =
                {
                    CreateNoWindow  = true,
                    UseShellExecute = true,
                    FileName        = CodeSignPath,
                    Arguments       = "--display " + filePath
                }
            };

            p.Start();
            p.WaitForExit();
            return(p.ExitCode == 0);
        }
예제 #14
0
        public static bool IsBinarySigned(string filePath, Logger logger)
        {
            if (!PlatformUtilities.IsOSX())
            {
                throw new NotImplementedException();
            }

            Process p = new Process
            {
                StartInfo =
                {
                    CreateNoWindow         = true,
                    UseShellExecute        = false,
                    FileName               = CodeSignPath,
                    Arguments              = "--display " + filePath,
                    RedirectStandardOutput = true,
                    RedirectStandardError  = true
                }
            };

            p.OutputDataReceived += (sender, e) =>
            {
                OutputNonEmptyString(e.Data, "codeSign-stdout: ", logger);
            };

            p.ErrorDataReceived += (sender, e) =>
            {
                OutputNonEmptyString(e.Data, "codeSign-stderr: ", logger);
            };

            p.Start();
            p.BeginOutputReadLine();
            p.BeginErrorReadLine();
            p.WaitForExit();
            return(p.ExitCode == 0);
        }
예제 #15
0
        public override void InitStreams(LaunchOptions options, out StreamReader reader, out StreamWriter writer)
        {
            LocalLaunchOptions localOptions  = (LocalLaunchOptions)options;
            string             miDebuggerDir = System.IO.Path.GetDirectoryName(localOptions.MIDebuggerPath);

            Process proc = new Process();

            proc.StartInfo.FileName  = localOptions.MIDebuggerPath;
            proc.StartInfo.Arguments = localOptions.GetMiDebuggerArgs();

            // LLDB has the -environment-cd mi command that is used to set the working dir for gdb, but it doesn't work.
            // So, set lldb's working dir to the user's requested folder before launch.
            proc.StartInfo.WorkingDirectory = options.DebuggerMIMode == MIMode.Lldb ? options.WorkingDirectory : miDebuggerDir;

            // On Windows, GDB locally requires that the directory be on the PATH, being the working directory isn't good enough
            if (PlatformUtilities.IsWindows() &&
                options.DebuggerMIMode == MIMode.Gdb)
            {
                string path = proc.StartInfo.GetEnvironmentVariable("PATH");
                path = (string.IsNullOrEmpty(path) ? miDebuggerDir : path + ";" + miDebuggerDir);
                proc.StartInfo.SetEnvironmentVariable("PATH", path);
            }

            // Allow to execute custom commands before launching debugger.
            // For ex., instructing GDB not to break for certain signals
            if (options.DebuggerMIMode == MIMode.Gdb && !string.IsNullOrWhiteSpace(options.WorkingDirectory))
            {
                var gdbInitFile = Path.Combine(options.WorkingDirectory, ".gdbinit");
                if (File.Exists(gdbInitFile))
                {
                    proc.StartInfo.Arguments += " -x \"" + gdbInitFile + "\"";
                }
            }

            InitProcess(proc, out reader, out writer);
        }
예제 #16
0
        public override async void Init(ITransportCallback transportCallback, LaunchOptions options, Logger logger, HostWaitLoop waitLoop = null)
        {
            LocalLaunchOptions localOptions = options as LocalLaunchOptions;

            Encoding encNoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);

            string        commandPipeName;
            string        outputPipeName;
            string        pidPipeName;
            List <string> cmdArgs = new List <string>();

            string windowtitle = FormattableString.Invariant($"cppdbg: {Path.GetFileName(options.ExePath)}");

            if (PlatformUtilities.IsWindows())
            {
                // Create Windows Named pipes
                commandPipeName = Utilities.GetMIEngineTemporaryFilename("In");
                outputPipeName  = Utilities.GetMIEngineTemporaryFilename("Out");
                pidPipeName     = Utilities.GetMIEngineTemporaryFilename("Pid");
                string errorPipeName = Utilities.GetMIEngineTemporaryFilename("Error");

                NamedPipeServerStream inputToDebugger    = new NamedPipeServerStream(commandPipeName, PipeDirection.Out, 1, PipeTransmissionMode.Byte);
                NamedPipeServerStream outputFromDebugger = new NamedPipeServerStream(outputPipeName, PipeDirection.In, 1, PipeTransmissionMode.Byte);
                NamedPipeServerStream errorFromDebugger  = new NamedPipeServerStream(errorPipeName, PipeDirection.In, 1, PipeTransmissionMode.Byte);
                NamedPipeServerStream pidPipe            = new NamedPipeServerStream(pidPipeName, PipeDirection.In, 1, PipeTransmissionMode.Byte);

                _pidReader = new StreamReader(pidPipe, encNoBom, false, UnixUtilities.StreamBufferSize);

                string thisModulePath = typeof(RunInTerminalTransport).GetTypeInfo().Assembly.ManifestModule.FullyQualifiedName;
                string launchCommand  = Path.Combine(Path.GetDirectoryName(thisModulePath), "WindowsDebugLauncher.exe");

                if (!File.Exists(launchCommand))
                {
                    string errorMessage = string.Format(CultureInfo.CurrentCulture, MICoreResources.Error_InternalFileMissing, launchCommand);
                    transportCallback.OnStdErrorLine(errorMessage);
                    transportCallback.OnDebuggerProcessExit(null);
                    return;
                }

                cmdArgs.Add(launchCommand);
                cmdArgs.Add("--stdin=" + commandPipeName);
                cmdArgs.Add("--stdout=" + outputPipeName);
                cmdArgs.Add("--stderr=" + errorPipeName);
                cmdArgs.Add("--pid=" + pidPipeName);
                cmdArgs.Add("--dbgExe=" + localOptions.MIDebuggerPath);
                cmdArgs.Add(localOptions.GetMiDebuggerArgs());

                _waitForConnection = Task.WhenAll(
                    inputToDebugger.WaitForConnectionAsync(),
                    outputFromDebugger.WaitForConnectionAsync(),
                    errorFromDebugger.WaitForConnectionAsync(),
                    pidPipe.WaitForConnectionAsync());

                _commandStream = new StreamWriter(inputToDebugger, encNoBom);
                _outputStream  = new StreamReader(outputFromDebugger, encNoBom, false, UnixUtilities.StreamBufferSize);
                _errorStream   = new StreamReader(errorFromDebugger, encNoBom, false, UnixUtilities.StreamBufferSize);
            }
            else
            {
                // Do Linux style pipes
                commandPipeName = UnixUtilities.MakeFifo(identifier: "In", logger: logger);
                outputPipeName  = UnixUtilities.MakeFifo(identifier: "Out", logger: logger);
                pidPipeName     = UnixUtilities.MakeFifo(identifier: "Pid", logger: logger);

                // Create filestreams
                FileStream stdInStream  = new FileStream(commandPipeName, FileMode.Open);
                FileStream stdOutStream = new FileStream(outputPipeName, FileMode.Open);
                _pidReader = new StreamReader(new FileStream(pidPipeName, FileMode.Open), encNoBom, false, UnixUtilities.StreamBufferSize);

                string debuggerCmd = UnixUtilities.GetDebuggerCommand(localOptions);

                // Default working directory is next to the app
                string debuggeeDir;
                if (Path.IsPathRooted(options.ExePath) && File.Exists(options.ExePath))
                {
                    debuggeeDir = Path.GetDirectoryName(options.ExePath);
                }
                else
                {
                    // If we don't know where the app is, default to HOME, and if we somehow can't get that, go with the root directory.
                    debuggeeDir = Environment.GetEnvironmentVariable("HOME");
                    if (string.IsNullOrEmpty(debuggeeDir))
                    {
                        debuggeeDir = "/";
                    }
                }

                string dbgCmdScript          = Path.Combine(Path.GetTempPath(), Utilities.GetMIEngineTemporaryFilename(identifier: "Cmd"));
                string launchDebuggerCommand = UnixUtilities.LaunchLocalDebuggerCommand(
                    debuggeeDir,
                    commandPipeName,
                    outputPipeName,
                    pidPipeName,
                    dbgCmdScript,
                    debuggerCmd,
                    localOptions.GetMiDebuggerArgs());

                logger?.WriteTextBlock("DbgCmd:", launchDebuggerCommand);

                using (FileStream dbgCmdStream = new FileStream(dbgCmdScript, FileMode.CreateNew))
                    using (StreamWriter dbgCmdWriter = new StreamWriter(dbgCmdStream, encNoBom)
                    {
                        AutoFlush = true
                    })
                    {
                        dbgCmdWriter.WriteLine("#!/bin/sh");
                        dbgCmdWriter.Write(launchDebuggerCommand);
                        dbgCmdWriter.Flush();
                    }

                if (PlatformUtilities.IsOSX())
                {
                    string osxLaunchScript = GetOSXLaunchScript();

                    // Call osascript with a path to the AppleScript. The apple script takes 2 parameters: a title for the terminal and the launch script.
                    cmdArgs.Add("/usr/bin/osascript");
                    cmdArgs.Add(osxLaunchScript);
                    cmdArgs.Add(FormattableString.Invariant($"\"{windowtitle}\""));
                    cmdArgs.Add(FormattableString.Invariant($"sh {dbgCmdScript} ;")); // needs a semicolon because this command is running through the launchscript.
                }
                else
                {
                    cmdArgs.Add("sh");
                    cmdArgs.Add(dbgCmdScript);
                }

                _outputStream  = new StreamReader(stdOutStream, encNoBom, false, UnixUtilities.StreamBufferSize);
                _commandStream = new StreamWriter(stdInStream, encNoBom);
            }

            RunInTerminalLauncher launcher = new RunInTerminalLauncher(windowtitle, localOptions.Environment);

            launcher.Launch(
                cmdArgs,
                localOptions.UseExternalConsole,
                LaunchSuccess,
                (error) =>
            {
                transportCallback.OnStdErrorLine(error);
                throw new InvalidOperationException(error);
            },
                logger);
            logger?.WriteLine("Wait for connection completion.");

            if (_waitForConnection != null)
            {
                // Add a timeout for waiting for connection - 20 seconds
                Task  waitOrTimeout = Task.WhenAny(_waitForConnection, Task.Delay(20000));
                await waitOrTimeout;
                if (waitOrTimeout.Status != TaskStatus.RanToCompletion)
                {
                    string errorMessage = String.Format(CultureInfo.CurrentCulture, MICoreResources.Error_DebuggerInitializeFailed_NoStdErr, "WindowsDebugLauncher.exe");
                    transportCallback.OnStdErrorLine(errorMessage);
                    transportCallback.OnDebuggerProcessExit(null);
                    return;
                }
            }

            base.Init(transportCallback, options, logger, waitLoop);
        }