private void OnStartDebuggingFailed(Exception exception) { Logger.Flush(); SendStartDebuggingError(exception); Dispose(); }
public WorkerThread(Logger logger) { Logger = logger; _opSet = new AutoResetEvent(false); _runningOpCompleteEvent = new ManualResetEvent(true); _postedOperations = new Queue <Operation>(); _thread = new Thread(new ThreadStart(ThreadFunc)); _thread.Name = "MIDebugger.PollThread"; _thread.Start(); }
public WorkerThread(Logger logger) { Logger = logger; _opSet = new AutoResetEvent(false); _runningOpCompleteEvent = new ManualResetEvent(true); _postedOperations = new Queue<Operation>(); _thread = new Thread(new ThreadStart(ThreadFunc)); _thread.Name = "MIDebugger.PollThread"; _thread.Start(); }
void IPlatformAppLauncher.Initialize(HostConfigurationStore configStore, IDeviceAppLauncherEventCallback eventCallback) { if (configStore == null) throw new ArgumentNullException("configStore"); if (eventCallback == null) throw new ArgumentNullException("eventCallback"); _eventCallback = eventCallback; RegistryRoot.Set(configStore.RegistryRoot); Logger = MICore.Logger.EnsureInitialized(configStore); }
void IPlatformAppLauncher.Initialize(HostConfigurationStore configStore, IDeviceAppLauncherEventCallback eventCallback) { if (configStore == null) { throw new ArgumentNullException("configStore"); } if (eventCallback == null) { throw new ArgumentNullException("eventCallback"); } _eventCallback = eventCallback; RegistryRoot.Set(configStore.RegistryRoot); Logger = MICore.Logger.EnsureInitialized(configStore); }
// Launches a process by means of the debug engine. // Normally, Visual Studio launches a program using the IDebugPortEx2::LaunchSuspended method and then attaches the debugger // to the suspended program. However, there are circumstances in which the debug engine may need to launch a program // (for example, if the debug engine is part of an interpreter and the program being debugged is an interpreted language), // in which case Visual Studio uses the IDebugEngineLaunch2::LaunchSuspended method // The IDebugEngineLaunch2::ResumeProcess method is called to start the process after the process has been successfully launched in a suspended state. int IDebugEngineLaunch2.LaunchSuspended(string pszServer, IDebugPort2 port, string exe, string args, string dir, string env, string options, enum_LAUNCH_FLAGS launchFlags, uint hStdInput, uint hStdOutput, uint hStdError, IDebugEventCallback2 ad7Callback, out IDebugProcess2 process) { Debug.Assert(_pollThread == null); Debug.Assert(_engineCallback == null); Debug.Assert(_debuggedProcess == null); Debug.Assert(_ad7ProgramId == Guid.Empty); // Check if the logger was enabled late. Logger.LoadMIDebugLogger(_configStore); process = null; _engineCallback = new EngineCallback(this, ad7Callback); Exception exception; try { bool noDebug = launchFlags.HasFlag(enum_LAUNCH_FLAGS.LAUNCH_NODEBUG); // Note: LaunchOptions.GetInstance can be an expensive operation and may push a wait message loop LaunchOptions launchOptions = LaunchOptions.GetInstance(_configStore, exe, args, dir, options, noDebug, _engineCallback, TargetEngine.Native, Logger); StartDebugging(launchOptions); EngineUtils.RequireOk(port.GetProcess(_debuggedProcess.Id, out process)); return(Constants.S_OK); } catch (Exception e) when(ExceptionHelper.BeforeCatch(e, Logger, reportOnlyCorrupting: true)) { exception = e; // Return from the catch block so that we can let the exception unwind - the stack can get kind of big } // If we just return the exception as an HRESULT, we will lose our message, so we instead send up an error event, and then // return E_ABORT. OnStartDebuggingFailed(exception); return(Constants.E_ABORT); }
// Sets the registry root currently in use by the DE. Different installations of Visual Studio can change where their registry information is stored // This allows the debugger to tell the engine where that location is. public int SetRegistryRoot(string registryRoot) { _configStore = new HostConfigurationStore(registryRoot); Logger = Logger.EnsureInitialized(_configStore); return(Constants.S_OK); }
// Attach the debug engine to a program. public int Attach(IDebugProgram2[] portProgramArray, IDebugProgramNode2[] programNodeArray, uint celtPrograms, IDebugEventCallback2 ad7Callback, enum_ATTACH_REASON dwReason) { Debug.Assert(_ad7ProgramId == Guid.Empty); Logger.LoadMIDebugLogger(_configStore); if (celtPrograms != 1) { Debug.Fail("SampleEngine only expects to see one program in a process"); throw new ArgumentException(); } IDebugProgram2 portProgram = portProgramArray[0]; Exception exception = null; try { IDebugProcess2 process; EngineUtils.RequireOk(portProgram.GetProcess(out process)); AD_PROCESS_ID processId = EngineUtils.GetProcessId(process); EngineUtils.RequireOk(portProgram.GetProgramId(out _ad7ProgramId)); // Attach can either be called to attach to a new process, or to complete an attach // to a launched process if (_pollThread == null) { if (processId.ProcessIdType != (uint)enum_AD_PROCESS_ID.AD_PROCESS_ID_SYSTEM) { Debug.Fail("Invalid process to attach to"); throw new ArgumentException(); } IDebugPort2 port; EngineUtils.RequireOk(process.GetPort(out port)); Debug.Assert(_engineCallback == null); Debug.Assert(_debuggedProcess == null); _engineCallback = new EngineCallback(this, ad7Callback); LaunchOptions launchOptions = CreateAttachLaunchOptions(processId.dwProcessId, port); if (port is IDebugUnixShellPort) { _unixPort = (IDebugUnixShellPort)port; } StartDebugging(launchOptions); } else { if (!EngineUtils.ProcIdEquals(processId, _debuggedProcess.Id)) { Debug.Fail("Asked to attach to a process while we are debugging"); return(Constants.E_FAIL); } } AD7EngineCreateEvent.Send(this); AD7ProgramCreateEvent.Send(this); this.ProgramCreateEventSent = true; return(Constants.S_OK); } catch (Exception e) when(ExceptionHelper.BeforeCatch(e, Logger, reportOnlyCorrupting: true)) { exception = e; } // If we just return the exception as an HRESULT, we will lose our message, so we instead send up an error event, and // return that the attach was canceled OnStartDebuggingFailed(exception); return(AD7_HRESULT.E_ATTACH_USER_CANCELED); }
// Launches a process by means of the debug engine. // Normally, Visual Studio launches a program using the IDebugPortEx2::LaunchSuspended method and then attaches the debugger // to the suspended program. However, there are circumstances in which the debug engine may need to launch a program // (for example, if the debug engine is part of an interpreter and the program being debugged is an interpreted language), // in which case Visual Studio uses the IDebugEngineLaunch2::LaunchSuspended method // The IDebugEngineLaunch2::ResumeProcess method is called to start the process after the process has been successfully launched in a suspended state. int IDebugEngineLaunch2.LaunchSuspended(string pszServer, IDebugPort2 port, string exe, string args, string dir, string env, string options, enum_LAUNCH_FLAGS launchFlags, uint hStdInput, uint hStdOutput, uint hStdError, IDebugEventCallback2 ad7Callback, out IDebugProcess2 process) { Debug.Assert(_pollThread == null); Debug.Assert(_engineCallback == null); Debug.Assert(_debuggedProcess == null); Debug.Assert(_ad7ProgramId == Guid.Empty); // Check if the logger was enabled late. Logger.LoadMIDebugLogger(_configStore); process = null; _engineCallback = new EngineCallback(this, ad7Callback); Exception exception; try { // Note: LaunchOptions.GetInstance can be an expensive operation and may push a wait message loop LaunchOptions launchOptions = LaunchOptions.GetInstance(_configStore, exe, args, dir, options, _engineCallback, TargetEngine.Native, Logger); // We are being asked to debug a process when we currently aren't debugging anything _pollThread = new WorkerThread(Logger); var cancellationTokenSource = new CancellationTokenSource(); using (cancellationTokenSource) { _pollThread.RunOperation(ResourceStrings.InitializingDebugger, cancellationTokenSource, (HostWaitLoop waitLoop) => { try { _debuggedProcess = new DebuggedProcess(true, launchOptions, _engineCallback, _pollThread, _breakpointManager, this, _configStore); } finally { // If there is an exception from the DebuggeedProcess constructor, it is our responsibility to dispose the DeviceAppLauncher, // otherwise the DebuggedProcess object takes ownership. if (_debuggedProcess == null && launchOptions.DeviceAppLauncher != null) { launchOptions.DeviceAppLauncher.Dispose(); } } _pollThread.PostedOperationErrorEvent += _debuggedProcess.OnPostedOperationError; return(_debuggedProcess.Initialize(waitLoop, cancellationTokenSource.Token)); }); } EngineUtils.RequireOk(port.GetProcess(_debuggedProcess.Id, out process)); return(Constants.S_OK); } catch (Exception e) when(ExceptionHelper.BeforeCatch(e, Logger, reportOnlyCorrupting: true)) { exception = e; // Return from the catch block so that we can let the exception unwind - the stack can get kind of big } // If we just return the exception as an HRESULT, we will loose our message, so we instead send up an error event, and then // return E_ABORT. Logger.Flush(); SendStartDebuggingError(exception); Dispose(); return(Constants.E_ABORT); }
/// <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); }
// Sets the registry root currently in use by the DE. Different installations of Visual Studio can change where their registry information is stored // This allows the debugger to tell the engine where that location is. int IDebugEngine2.SetRegistryRoot(string registryRoot) { _configStore = new HostConfigurationStore(registryRoot, EngineConstants.EngineId); Logger = Logger.EnsureInitialized(_configStore); return(Constants.S_OK); }