Beispiel #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) =>
            {
                logger?.WriteLine("term-stdout: " + e.Data);
            };

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

            SetNewProcessEnvironment(_terminalProcess.StartInfo);
            _terminalProcess.Start();
            _terminalProcess.BeginOutputReadLine();
            _terminalProcess.BeginErrorReadLine();
        }
Beispiel #2
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);
            }
        }
        private void MakeGdbFifo(string path)
        {
            // Mod is normally in octal, but C# has no octal values. This is 384 (rw owner, no rights anyone else)
            const int rw_owner = 384;
            int       result   = LinuxNativeMethods.MkFifo(path, rw_owner);

            if (result != 0)
            {
                // Failed to create the fifo. Bail.
                Logger?.WriteLine("Failed to create gdb fifo");
                throw new ArgumentException("MakeGdbFifo failed to create fifo at path {0}", path);
            }
        }
 private void FifoWatcher_Deleted(object sender, FileSystemEventArgs e)
 {
     try
     {
         if (e.FullPath == _gdbStdInName || e.FullPath == _gdbStdOutName)
         {
             Logger?.WriteLine("Gdb fifo deleted, stop debugging");
             _fifoWatcher.Deleted -= FifoWatcher_Deleted;
             this.Callback.OnDebuggerProcessExit(null);
         }
     }
     catch
     {
         // Don't take down OpenDebugAD7 if the file watcher handler failed
     }
 }
Beispiel #5
0
        private void ShellExited(object sender, EventArgs e)
        {
            if (sender is ProcessMonitor)
            {
                ((ProcessMonitor)sender).ProcessExited -= ShellExited;
            }

            if (sender is Process)
            {
                ((Process)sender).Exited -= ShellExited;
            }

            Logger?.WriteLine("Shell exited, stop debugging");
            this.Callback.OnDebuggerProcessExit(null);

            Close();
        }
Beispiel #6
0
        internal static string MakeFifo(string identifier = null, Logger logger = null)
        {
            string path = Path.Combine(Path.GetTempPath(), Utilities.GetMIEngineTemporaryFilename(identifier));

            // Mod is normally in octal, but C# has no octal values. This is 384 (rw owner, no rights anyone else)
            const int rw_owner = 384;

            byte[] pathAsBytes = new byte[Encoding.UTF8.GetByteCount(path) + 1];
            Encoding.UTF8.GetBytes(path, 0, path.Length, pathAsBytes, 0);
            int result = UnixNativeMethods.MkFifo(pathAsBytes, rw_owner);

            if (result != 0)
            {
                // Failed to create the fifo. Bail.
                logger?.WriteLine("Failed to create fifo");
                throw new ArgumentException("MakeFifo failed to create fifo at path {0}", path);
            }

            return(path);
        }
Beispiel #7
0
        /// <summary>
        /// Exception filter function used to report exceptions to telemetry. This **ALWAYS** returns 'true'.
        /// </summary>
        /// <param name="currentException">The current exception which is about to be caught.</param>
        /// <param name="logger">For logging messages</param>
        /// <param name="reportOnlyCorrupting">If true, only corrupting exceptions are reported</param>
        /// <returns>true</returns>
        public static bool BeforeCatch(Exception currentException, Logger logger, bool reportOnlyCorrupting)
        {
            if (reportOnlyCorrupting && !IsCorruptingException(currentException))
            {
                return(true); // ignore non-corrupting exceptions
            }

            try
            {
                HostTelemetry.ReportCurrentException(currentException, "Microsoft.MIDebugEngine");

                logger?.WriteLine("EXCEPTION: " + currentException.GetType());
                logger?.WriteTextBlock("EXCEPTION: ", currentException.StackTrace);
            }
            catch
            {
                // If anything goes wrong, ignore it. We want to report the original exception, not a telemetry problem
            }

            return(true);
        }
 private void ShellExited(object sender, EventArgs e)
 {
     _shellProcessMonitor.ProcessExited -= ShellExited;
     Logger?.WriteLine("Shell exited, stop debugging");
     this.Callback.OnDebuggerProcessExit(null);
 }
Beispiel #9
0
 public void Send(string cmd)
 {
     _logger?.WriteLine("<-" + cmd);
     _logger?.Flush();
     _asyncCommand.WriteLine(cmd);
 }
Beispiel #10
0
        public override void InitStreams(LaunchOptions options, out StreamReader reader, out StreamWriter writer)
        {
            LocalLaunchOptions localOptions = (LocalLaunchOptions)options;

            string debuggeeDir = System.IO.Path.GetDirectoryName(options.ExePath);

            string gdbStdInName  = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
            string gdbStdOutName = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());

            MakeGdbFifo(gdbStdInName);
            MakeGdbFifo(gdbStdOutName);

            // Setup the streams on the fifos as soon as possible.
            System.IO.FileStream gdbStdInStream  = new FileStream(gdbStdInName, FileMode.Open);
            System.IO.FileStream gdbStdOutStream = new FileStream(gdbStdOutName, FileMode.Open);

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

            // 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 gdb 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)
            //
            // NOTE: sudo launch requires sudo or the terminal will fail to launch. The first argument must then be the terminal path
            // TODO: this should be configurable in launch options to allow for other terminals with a default of gnome-terminal so the user can change the terminal
            // command. Note that this is trickier than it sounds since each terminal has its own set of parameters. For now, rely on remote for those scenarios
            string  terminalPath    = "/usr/bin/gnome-terminal";
            string  sudoPath        = "/usr/bin/sudo";
            Process terminalProcess = new Process();

            terminalProcess.StartInfo.CreateNoWindow   = false;
            terminalProcess.StartInfo.UseShellExecute  = false;
            terminalProcess.StartInfo.WorkingDirectory = debuggeeDir;
            terminalProcess.StartInfo.FileName         = !isRoot ? terminalPath : sudoPath;

            string argumentString = string.Format(System.Globalization.CultureInfo.InvariantCulture,
                                                  "--title DebuggerTerminal -x bash -c \"cd {0}; DbgTerm=`tty`; trap 'rm {2}; rm {3}' EXIT; {1} --interpreter=mi --tty=$DbgTerm < {2} > {3};",
                                                  debuggeeDir,
                                                  localOptions.MIDebuggerPath,
                                                  gdbStdInName,
                                                  gdbStdOutName
                                                  );

            terminalProcess.StartInfo.Arguments = !isRoot ? argumentString : String.Concat(terminalPath, " ", argumentString);
            Logger.WriteLine("LocalLinuxTransport command: " + terminalProcess.StartInfo.FileName + " " + terminalProcess.StartInfo.Arguments);

            if (localOptions.Environment != null)
            {
                foreach (EnvironmentEntry entry in localOptions.Environment)
                {
                    terminalProcess.StartInfo.Environment.Add(entry.Name, entry.Value);
                }
            }

            terminalProcess.Start();

            // 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
            writer = new StreamWriter(gdbStdInStream);
            reader = new StreamReader(gdbStdOutStream);
        }
Beispiel #11
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);
        }
Beispiel #12
0
 protected void Echo(string cmd)
 {
     Logger.WriteLine("<-" + cmd);
     _writer.WriteLine(cmd);
     _writer.Flush();
 }
        public override void InitStreams(LaunchOptions options, out StreamReader reader, out StreamWriter writer)
        {
            LocalLaunchOptions localOptions = (LocalLaunchOptions)options;

            if (!this.IsValidMiDebuggerPath(localOptions.MIDebuggerPath))
            {
                throw new Exception(MICoreResources.Error_InvalidMiDebuggerPath);
            }

            // Default working directory is next to the app
            string debuggeeDir;

            if (Path.IsPathRooted(options.ExePath) && File.Exists(options.ExePath))
            {
                debuggeeDir = System.IO.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 = "/";
                }
            }

            _gdbStdInName  = Path.Combine(Path.GetTempPath(), FifoPrefix + Path.GetRandomFileName());
            _gdbStdOutName = Path.Combine(Path.GetTempPath(), FifoPrefix + Path.GetRandomFileName());

            MakeGdbFifo(_gdbStdInName);
            MakeGdbFifo(_gdbStdOutName);

            _fifoWatcher                     = new FileSystemWatcher(Path.GetTempPath(), FifoPrefix + "*");
            _fifoWatcher.Deleted            += FifoWatcher_Deleted;
            _fifoWatcher.EnableRaisingEvents = true;

            // Setup the streams on the fifos as soon as possible.
            System.IO.FileStream gdbStdInStream  = new FileStream(_gdbStdInName, FileMode.Open);
            System.IO.FileStream gdbStdOutStream = new FileStream(_gdbStdOutName, FileMode.Open);

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

            // Check and see if gnome-terminal exists. If not, fall back to xterm
            string terminalCmd, bashCommandPrefix;

            if (File.Exists(GnomeTerminalPath))
            {
                terminalCmd       = GnomeTerminalPath;
                bashCommandPrefix = "--title DebuggerTerminal -x";
            }
            else
            {
                terminalCmd       = XTermPath;
                bashCommandPrefix = "-title DebuggerTerminal -e";
            }

            // 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 gdb 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)
            //
            // NOTE: sudo launch requires sudo or the terminal will fail to launch. The first argument must then be the terminal path
            // TODO: this should be configurable in launch options to allow for other terminals with a default of gnome-terminal so the user can change the terminal
            // command. Note that this is trickier than it sounds since each terminal has its own set of parameters. For now, rely on remote for those scenarios
            Process terminalProcess = new Process();

            terminalProcess.StartInfo.CreateNoWindow   = false;
            terminalProcess.StartInfo.UseShellExecute  = false;
            terminalProcess.StartInfo.WorkingDirectory = debuggeeDir;
            terminalProcess.StartInfo.FileName         = !isRoot ? terminalCmd : SudoPath;

            string debuggerCmd = localOptions.MIDebuggerPath;

            // If the system doesn't allow a non-root process to attach to another process, try to run GDB as root
            if (localOptions.ProcessId != 0 && !isRoot && GetRequiresRootAttach(localOptions))
            {
                // Prefer pkexec for a nice graphical prompt, but fall back to sudo if it's not available
                if (File.Exists(LocalLinuxTransport.PKExecPath))
                {
                    debuggerCmd = String.Concat(LocalLinuxTransport.PKExecPath, " ", debuggerCmd);
                }
                else if (File.Exists(LocalLinuxTransport.SudoPath))
                {
                    debuggerCmd = String.Concat(LocalLinuxTransport.SudoPath, " ", debuggerCmd);
                }
                else
                {
                    Debug.Fail("Root required to attach, but no means of elevating available!");
                }
            }

            string argumentString = string.Format(CultureInfo.InvariantCulture,
                                                  "{4} bash -c \"cd {0}; DbgTerm=`tty`; trap 'rm {2}; rm {3}' EXIT; {1} --interpreter=mi --tty=$DbgTerm < {2} > {3};\"",
                                                  debuggeeDir,
                                                  debuggerCmd,
                                                  _gdbStdInName,
                                                  _gdbStdOutName,
                                                  bashCommandPrefix
                                                  );

            terminalProcess.StartInfo.Arguments = !isRoot ? argumentString : String.Concat(terminalCmd, " ", argumentString);
            Logger?.WriteLine("LocalLinuxTransport command: " + terminalProcess.StartInfo.FileName + " " + terminalProcess.StartInfo.Arguments);

            if (localOptions.Environment != null)
            {
                foreach (EnvironmentEntry entry in localOptions.Environment)
                {
                    terminalProcess.StartInfo.Environment.Add(entry.Name, entry.Value);
                }
            }

            terminalProcess.Start();

            // 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
            writer = new StreamWriter(gdbStdInStream);
            reader = new StreamReader(gdbStdOutStream);
        }
 public void OnOutputLine(string line)
 {
     _logger?.WriteLine("[kill] ->" + line);
 }
Beispiel #15
0
        public void ProcessStdOutLine(string line)
        {
            if (line.Length == 0)
            {
                return;
            }
            else if (line == "(gdb)")
            {
                if (_consoleDebuggerInitializeCompletionSource != null)
                {
                    lock (_waitingOperations)
                    {
                        if (_consoleDebuggerInitializeCompletionSource != null)
                        {
                            _consoleDebuggerInitializeCompletionSource.TrySetResult(null);
                        }
                    }
                }
            }
            else
            {
                string token    = ParseToken(ref line);
                char   c        = line[0];
                string noprefix = line.Substring(1).Trim();

                if (token != null)
                {
                    // Look for event handlers registered on a specific Result id
                    if (c == '^')
                    {
                        uint id = uint.Parse(token, CultureInfo.InvariantCulture);
                        WaitingOperationDescriptor waitingOperation = null;
                        lock (_waitingOperations)
                        {
                            if (_waitingOperations.TryGetValue(id, out waitingOperation))
                            {
                                _waitingOperations.Remove(id);
                            }
                        }
                        if (waitingOperation != null)
                        {
                            Results results = MIResults.ParseCommandOutput(noprefix);
                            Logger.WriteLine(id + ": elapsed time " + (int)(DateTime.Now - waitingOperation.StartTime).TotalMilliseconds);
                            waitingOperation.OnComplete(results, this.MICommandFactory);
                            return;
                        }
                    }
                    // Check to see if we are just getting the echo of the command we sent
                    else if (c == '-')
                    {
                        uint id = uint.Parse(token, CultureInfo.InvariantCulture);
                        lock (_waitingOperations)
                        {
                            WaitingOperationDescriptor waitingOperation;
                            if (_waitingOperations.TryGetValue(id, out waitingOperation) &&
                                !waitingOperation.EchoReceived &&
                                line == waitingOperation.Command)
                            {
                                // This is just the echo. Ignore.
                                waitingOperation.EchoReceived = true;
                                return;
                            }
                        }
                    }
                }

                switch (c)
                {
                case '~':
                    OnDebuggeeOutput(noprefix);             // Console stream
                    break;

                case '^':
                    OnResult(noprefix, token);
                    break;

                case '*':
                    OnOutOfBand(noprefix);
                    break;

                case '&':
                    OnLogStreamOutput(noprefix);
                    break;

                case '=':
                    OnNotificationOutput(noprefix);
                    break;

                default:
                    OnDebuggeeOutput(line + '\n');
                    break;
                }
            }
        }
        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);
        }
Beispiel #17
0
        private void TransportLoop()
        {
            try
            {
                while (!_bQuit)
                {
                    string line = GetLine();
                    if (line == null)
                    {
                        break;
                    }

                    line = line.TrimEnd();
                    Logger?.WriteLine("->" + line);
                    Logger?.Flush();

                    try
                    {
                        if (_filterStdout)
                        {
                            line = FilterLine(line);
                        }
                        if (!String.IsNullOrWhiteSpace(line) && !line.StartsWith("-", StringComparison.Ordinal))
                        {
                            _callback.OnStdOutLine(line);
                        }
                    }
                    catch (ObjectDisposedException)
                    {
                        Debug.Assert(_bQuit);
                        break;
                    }
                }
                if (!_bQuit)
                {
                    OnReadStreamAborted();
                }
            }
            finally
            {
                lock (_locker)
                {
                    _bQuit = true;
                    _streamReadCancellationTokenSource.Dispose();

                    // If we are shutting down without notice from the debugger (e.g., the terminal
                    // where the debugger was hosted was closed), at this point it's possible that
                    // there is a thread blocked doing a read() syscall.
                    ForceDisposeStreamReader(_reader);

                    try
                    {
                        _writer.Dispose();
                    }
                    catch
                    {
                        // This can fail flush side effects if the debugger goes down. When this happens we don't want
                        // to crash OpenDebugAD7/VS. Stack:
                        //   System.IO.UnixFileStream.WriteNative(Byte[] array, Int32 offset, Int32 count)
                        //   System.IO.UnixFileStream.FlushWriteBuffer()
                        //   System.IO.UnixFileStream.Dispose(Boolean disposing)
                        //   System.IO.FileStream.Dispose(Boolean disposing)
                        //   System.IO.Stream.Close()
                        //   System.IO.StreamWriter.Dispose(Boolean disposing)
                        //   System.IO.TextWriter.Dispose()
                    }
                }
            }
        }
Beispiel #18
0
        /// <summary>
        /// Resolves the various file paths used by the AndroidDebugLauncher and returns an initialized InstallPaths object
        /// </summary>
        /// <param name="token">token to check for cancelation</param>
        /// <param name="launchOptions">[Required] launch options object</param>
        /// <param name="logger">logger object</param>
        /// <param name="targetEngine">target engine</param>
        /// <returns>[Required] Created InstallPaths object</returns>
        public static InstallPaths Resolve(CancellationToken token, AndroidLaunchOptions launchOptions, MICore.Logger logger, TargetEngine targetEngine)
        {
            var result = new InstallPaths();

            if (launchOptions.SDKRoot != null)
            {
                result.SDKRoot = launchOptions.SDKRoot;
            }
            else
            {
                result.SDKRoot = GetDirectoryFromRegistry(@"SOFTWARE\Android SDK Tools", "Path", checkBothBitnesses: true, externalProductName: LauncherResources.ProductName_SDK);
            }

            if (targetEngine != TargetEngine.Java)
            {
                string ndkRoot = launchOptions.NDKRoot;
                if (ndkRoot == null)
                {
                    ndkRoot = GetDirectoryFromRegistry(RegistryRoot.Value + @"\Setup\VS\SecondaryInstaller\AndroidNDK", "NDK_HOME", checkBothBitnesses: false, externalProductName: LauncherResources.ProductName_NDK);
                }

                NdkReleaseId ndkReleaseId            = new NdkReleaseId();
                string       ndkReleaseVersionFile   = Path.Combine(ndkRoot, "RELEASE.TXT");
                string       ndkSourcePropertiesFile = Path.Combine(ndkRoot, "source.properties");

                // NDK releases >= r11 have a source.properties file
                if (File.Exists(ndkSourcePropertiesFile))
                {
                    NdkReleaseId.TryParsePropertiesFile(ndkSourcePropertiesFile, out ndkReleaseId);
                }
                // NDK releases < r11 have a RELEASE.txt file
                else if (File.Exists(ndkReleaseVersionFile))
                {
                    NdkReleaseId.TryParseFile(ndkReleaseVersionFile, out ndkReleaseId);
                }
                else
                {
                    ThrowExternalFileNotFoundException(ndkReleaseVersionFile, LauncherResources.ProductName_NDK);
                }

                logger.WriteLine("Using NDK '{0}' from path '{1}'", ndkReleaseId, ndkRoot);

                // 32 vs 64-bit doesn't matter when comparing
                var r11 = new NdkReleaseId(11, 'a');
                // In NDK r11 and later, gdb is multi-arch and there's only one binary
                // in the prebuilt directory
                bool usePrebuiltGDB = ndkReleaseId.CompareVersion(r11) >= 0;
                IEnumerable <INDKFilePath> prebuiltGDBPath = NDKPrebuiltFilePath.GDBPaths();

                string targetArchitectureName = launchOptions.TargetArchitecture.ToNDKArchitectureName();
                IEnumerable <INDKFilePath> possibleGDBPaths;

                switch (launchOptions.TargetArchitecture)
                {
                case MICore.TargetArchitecture.X86:
                    possibleGDBPaths = usePrebuiltGDB ? prebuiltGDBPath : NDKToolChainFilePath.x86_GDBPaths();
                    break;

                case MICore.TargetArchitecture.X64:
                    possibleGDBPaths = usePrebuiltGDB ? prebuiltGDBPath : NDKToolChainFilePath.x64_GDBPaths();
                    break;

                case MICore.TargetArchitecture.ARM:
                    possibleGDBPaths = usePrebuiltGDB ? prebuiltGDBPath : NDKToolChainFilePath.ARM_GDBPaths();
                    break;

                case MICore.TargetArchitecture.ARM64:
                    possibleGDBPaths = usePrebuiltGDB ? prebuiltGDBPath : NDKToolChainFilePath.ARM64_GDBPaths();
                    break;

                default:
                    Debug.Fail("Should be impossible");
                    throw new InvalidOperationException();
                }

                INDKFilePath gdbMatchedPath;
                result.GDBPath = GetNDKFilePath(
                    string.Concat("Android-", targetArchitectureName, "-GDBPath"),
                    ndkRoot,
                    possibleGDBPaths,
                    out gdbMatchedPath
                    );
                if (launchOptions.TargetArchitecture == MICore.TargetArchitecture.X86 && gdbMatchedPath != null)
                {
                    var r10b = new NdkReleaseId(10, 'b');

                    // Before r10b, the 'windows-x86_64' ndk didn't support x86 debugging
                    if (ndkReleaseId.IsValid && ndkReleaseId.CompareVersion(r10b) < 0 && gdbMatchedPath.PartialFilePath.Contains(@"\windows-x86_64\"))
                    {
                        throw new LauncherException(Telemetry.LaunchFailureCode.NoReport, LauncherResources.Error_64BitNDKNotSupportedForX86);
                    }
                }

                IEnumerable <INDKFilePath> gdbServerPath = NDKPrebuiltFilePath.GDBServerPaths(targetArchitectureName);
                INDKFilePath gdbServerMatchedPath;
                result.GDBServerPath = GetNDKFilePath(
                    string.Concat("Android-", targetArchitectureName, "-GDBServerPath"),
                    ndkRoot,
                    gdbServerPath,
                    out gdbServerMatchedPath // not used
                    );

                token.ThrowIfCancellationRequested();
            }

            return(result);
        }