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(); }
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 } }
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(); }
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); }
/// <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); }
public void Send(string cmd) { _logger?.WriteLine("<-" + cmd); _logger?.Flush(); _asyncCommand.WriteLine(cmd); }
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); }
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); }
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); }
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); }
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() } } } }
/// <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); }