Example #1
0
        public void Launch(string workingDirectory, Logger logger)
        {
            _terminalProcess = new Process
            {
                StartInfo =
                {
                    CreateNoWindow   = false,
                    UseShellExecute  = false,
                    WorkingDirectory = workingDirectory,
                    FileName         = GetProcessExecutable(),
                    Arguments        = GetProcessArgs(),
                    // Redirect stdout and stderr to logging
                    // or else it will send to the default stdout and stderr
                    RedirectStandardOutput = true,
                    RedirectStandardError  = true
                }
            };

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

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

            SetNewProcessEnvironment(_terminalProcess.StartInfo);
            _terminalProcess.Start();
            _terminalProcess.BeginOutputReadLine();
            _terminalProcess.BeginErrorReadLine();
        }
Example #2
0
 /// <summary>
 /// Kills the pipe process and its child processes.
 /// It maybe the debugger itself it is local.
 /// </summary>
 /// <param name="p">Process to kill.</param>
 private void KillPipeProcessAndChildren(Process p)
 {
     UnixUtilities.KillProcessTree(p);
     if (!p.HasExited)
     {
         p.Kill();
     }
 }
Example #3
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);
            }
        }
        public override void InitStreams(LaunchOptions options, out StreamReader reader, out StreamWriter writer)
        {
            LocalLaunchOptions localOptions = (LocalLaunchOptions)options;

            // 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 = "/";
                }
            }

            _dbgStdInName  = UnixUtilities.MakeFifo(Logger);
            _dbgStdOutName = UnixUtilities.MakeFifo(Logger);
            string pidFifo = UnixUtilities.MakeFifo(Logger);

            // Used for testing
            Logger?.WriteLine(string.Concat("TempFile=", _dbgStdInName));
            Logger?.WriteLine(string.Concat("TempFile=", _dbgStdOutName));
            Logger?.WriteLine(string.Concat("TempFile=", pidFifo));

            // Setup the streams on the fifos as soon as possible.
            FileStream dbgStdInStream  = new FileStream(_dbgStdInName, FileMode.Open);
            FileStream dbgStdOutStream = new FileStream(_dbgStdOutName, FileMode.Open);
            FileStream pidStream       = new FileStream(pidFifo, FileMode.Open);

            string debuggerCmd           = UnixUtilities.GetDebuggerCommand(localOptions);
            string launchDebuggerCommand = UnixUtilities.LaunchLocalDebuggerCommand(
                debuggeeDir,
                _dbgStdInName,
                _dbgStdOutName,
                pidFifo,
                debuggerCmd);

            // Only pass the environment to launch clrdbg. For other modes, there are commands that set the environment variables
            // directly for the debuggee.
            ReadOnlyCollection <EnvironmentEntry> environmentForDebugger = localOptions.DebuggerMIMode == MIMode.Clrdbg ?
                                                                           localOptions.Environment :
                                                                           new ReadOnlyCollection <EnvironmentEntry>(new EnvironmentEntry[] { });;

            TerminalLauncher terminal = TerminalLauncher.MakeTerminal("DebuggerTerminal", launchDebuggerCommand, localOptions.Environment);

            terminal.Launch(debuggeeDir);

            int shellPid = -1;

            using (StreamReader pidReader = new StreamReader(pidStream, Encoding.UTF8, true, UnixUtilities.StreamBufferSize))
            {
                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(MICoreResources.Error_LocalUnixTerminalDebuggerInitializationFailed);
                }

                _shellProcessMonitor = new ProcessMonitor(shellPid);
                _shellProcessMonitor.ProcessExited += ShellExited;
                _shellProcessMonitor.Start();

                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(MICoreResources.Error_LocalUnixTerminalDebuggerInitializationFailed);
                }
            }

            // The in/out names are confusing in this case as they are relative to gdb.
            // What that means is the names are backwards wrt miengine hence the reader
            // being the writer and vice-versa
            // Mono seems to hang when the debugger sends a large response unless we specify a larger buffer here
            writer = new StreamWriter(dbgStdInStream, new UTF8Encoding(false, true), UnixUtilities.StreamBufferSize);
            reader = new StreamReader(dbgStdOutStream, Encoding.UTF8, true, UnixUtilities.StreamBufferSize);
        }
Example #5
0
 private bool HasExited()
 {
     return(!UnixUtilities.IsProcessRunning(_processId));
 }
Example #6
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);
        }