public int LoadSymbols() { IAction action = _actionRecorder.CreateToolAction(ActionType.DebugModuleLoadSymbols); ICancelableTask <int> loadSymbolsTask = _cancelableTaskFactory.Create( "Loading symbols...", task => _moduleFileLoader.LoadModuleFilesAsync(new[] { _lldbModule }, task, _moduleFileLoadRecorderFactory.Create( action))); if (!loadSymbolsTask.RunAndRecord(action)) { return(VSConstants.E_ABORT); } _engineHandler.OnSymbolsLoaded(Self, ModuleName, null, loadSymbolsTask.Result == VSConstants.S_OK, _program); // Returning E_FAIL causes Visual Studio to show a file dialog when attached // to a running program or crash dump. This dialog can only be used to select PDB // files. return(loadSymbolsTask.Result == VSConstants.E_FAIL ? VSConstants.S_OK : loadSymbolsTask.Result); }
// Query project information required to initialize the debugger. Will return false if the // action is canceled by the user. bool TrySetupQueries(IAsyncProject project, ActionRecorder actionRecorder, out SetupQueriesResult result) { var sdkConfig = _sdkConfigFactory.LoadOrDefault(); var action = actionRecorder.CreateToolAction(ActionType.DebugSetupQueries); Func <Task <SetupQueriesResult> > queryInformationTask = async delegate() { var runner = _cloudRunner.Intercept(action); var loadApplicationTask = LoadApplicationAsync(runner, await project.GetApplicationAsync()); var loadGameletsTask = _gameletClientFactory.Create(runner).ListGameletsAsync(); var loadTestAccountTask = LoadTestAccountAsync( runner, sdkConfig.OrganizationId, sdkConfig.ProjectId, await project.GetTestAccountAsync()); return(new SetupQueriesResult { Application = await loadApplicationTask, Gamelets = await loadGameletsTask, TestAccount = await loadTestAccountTask }); }; var task = _cancelableTaskFactory.Create("Querying project information...", queryInformationTask); if (!task.RunAndRecord(action)) { result = null; return(false); } result = task.Result; return(true); }
public async Task <ILldbAttachedProgram> LaunchAsync( ICancelable task, IDebugProcess2 process, Guid programId, uint?attachPid, DebuggerOptions.DebuggerOptions debuggerOptions, HashSet <string> libPaths, GrpcConnection grpcConnection, int localDebuggerPort, string targetIpAddress, int targetPort, IDebugEventCallback2 callback) { var launchSucceeded = false; Stopwatch launchTimer = Stopwatch.StartNew(); // This should be the first request to the DebuggerGrpcServer. Providing a retry wait // time allows us to connect to a DebuggerGrpcServer that is slow to start. Note that // we postpone sourcing .lldbinit until we are done with our initialization so that // the users can override our defaults. var lldbDebugger = _lldbDebuggerFactory.Create(grpcConnection, false, TimeSpan.FromSeconds(10)); if (lldbDebugger == null) { throw new AttachException(VSConstants.E_ABORT, ErrorStrings.FailedToCreateDebugger); } if (debuggerOptions[DebuggerOption.CLIENT_LOGGING] == DebuggerOptionState.ENABLED) { lldbDebugger.EnableLog("lldb", new List <string> { "default", "module" }); // TODO: Disable 'dwarf' logs until we can determine why this // causes LLDB to hang. // lldbDebugger.EnableLog("dwarf", new List<string> { "default" }); } if (_fastExpressionEvaluation) { lldbDebugger.EnableFastExpressionEvaluation(); } lldbDebugger.SetDefaultLLDBSettings(); // Apply .lldbinit after we set our settings so that the user can override our // defaults with a custom .lldbinit. LoadLocalLldbInit(lldbDebugger); // Add exec search paths, so that LLDB can find the executable and any dependent // libraries. If LLDB is able to find the files locally, it won't try to download // them from the remote server, saving valuable time on attach. foreach (string path in libPaths) { lldbDebugger.SetLibrarySearchPath(path); } lldbDebugger.SetAsync(true); SbPlatform lldbPlatform; switch (_launchOption) { case LaunchOption.AttachToGame: // Fall through. case LaunchOption.LaunchGame: lldbPlatform = CreateRemotePlatform(grpcConnection, lldbDebugger); if (lldbPlatform == null) { throw new AttachException(VSConstants.E_FAIL, ErrorStrings.FailedToCreateLldbPlatform); } task.ThrowIfCancellationRequested(); Trace.WriteLine("Attempting to connect debugger"); task.Progress.Report("Connecting to debugger"); string connectRemoteUrl = $"{_lldbConnectUrl}:{localDebuggerPort}"; string connectRemoteArgument = CreateConnectRemoteArgument(connectRemoteUrl, targetIpAddress, targetPort); SbPlatformConnectOptions lldbConnectOptions = _lldbPlatformConnectOptionsFactory.Create(connectRemoteArgument); IAction debugerWaitAction = _actionRecorder.CreateToolAction(ActionType.DebugWaitDebugger); bool TryConnectRemote() { if (lldbPlatform.ConnectRemote(lldbConnectOptions).Success()) { return(true); } VerifyGameIsReady(debugerWaitAction); return(false); } try { debugerWaitAction.Record(() => RetryWithTimeout( task, TryConnectRemote, _launchRetryDelay, _launchTimeout, launchTimer)); } catch (TimeoutException e) { throw new AttachException( VSConstants.E_ABORT, ErrorStrings.FailedToConnectDebugger(lldbConnectOptions.GetUrl()), e); } Trace.WriteLine("LLDB successfully connected"); break; case LaunchOption.AttachToCore: lldbPlatform = _lldbPlatformFactory.Create(_localLldbPlatformName, grpcConnection); if (lldbPlatform == null) { throw new AttachException(VSConstants.E_FAIL, ErrorStrings.FailedToCreateLldbPlatform); } break; default: throw new AttachException(VSConstants.E_ABORT, ErrorStrings.InvalidLaunchOption( _launchOption.ToString())); } lldbDebugger.SetSelectedPlatform(lldbPlatform); task.ThrowIfCancellationRequested(); task.Progress.Report("Debugger is attaching (this can take a while)"); RemoteTarget lldbTarget = null; if (_launchOption == LaunchOption.LaunchGame && !string.IsNullOrEmpty(_executableFullPath)) { var createExecutableTargetAction = _actionRecorder.CreateToolAction(ActionType.DebugCreateExecutableTarget); createExecutableTargetAction.Record( () => lldbTarget = CreateTarget(lldbDebugger, _executableFullPath)); } else { lldbTarget = CreateTarget(lldbDebugger, ""); } var lldbListener = CreateListener(grpcConnection); // This is required to catch breakpoint change events. lldbTarget.AddListener(lldbListener, EventType.STATE_CHANGED); var listenerSubscriber = new LldbListenerSubscriber(lldbListener); var eventHandler = new EventHandler <FileUpdateReceivedEventArgs>( (s, e) => ListenerSubscriberOnFileUpdateReceived(task, e)); listenerSubscriber.FileUpdateReceived += eventHandler; listenerSubscriber.Start(); try { if (_launchOption == LaunchOption.AttachToCore) { var loadCoreAction = _actionRecorder.CreateToolAction(ActionType.DebugLoadCore); SbProcess lldbDebuggerProcess = null; loadCoreAction.Record(() => lldbDebuggerProcess = LoadCore(lldbTarget, loadCoreAction)); await _taskContext.Factory.SwitchToMainThreadAsync(); return(_attachedProgramFactory.Create( process, programId, _debugEngine, callback, lldbDebugger, lldbTarget, listenerSubscriber, lldbDebuggerProcess, lldbDebugger.GetCommandInterpreter(), true, new NullExceptionManager(), _moduleSearchLogHolder, remotePid: 0)); } // Get process ID. uint processId = 0; switch (_launchOption) { case LaunchOption.AttachToGame: if (!attachPid.HasValue) { throw new AttachException(VSConstants.E_ABORT, ErrorStrings.FailedToRetrieveProcessId); } processId = attachPid.Value; break; case LaunchOption.LaunchGame: // Since we have no way of knowing when the remote process actually // starts, try a few times to get the pid. IAction debugWaitAction = _actionRecorder.CreateToolAction(ActionType.DebugWaitProcess); bool TryGetRemoteProcessId() { if (GetRemoteProcessId(_executableFileName, lldbPlatform, out processId)) { return(true); } VerifyGameIsReady(debugWaitAction); return(false); } try { debugWaitAction.Record(() => RetryWithTimeout( task, TryGetRemoteProcessId, _launchRetryDelay, _launchTimeout, launchTimer)); } catch (TimeoutException e) { throw new AttachException(VSConstants.E_ABORT, ErrorStrings.FailedToRetrieveProcessId, e); } break; } Trace.WriteLine("Attaching to pid " + processId); var debugAttachAction = _actionRecorder.CreateToolAction(ActionType.DebugAttach); SbProcess debuggerProcess = null; debugAttachAction.Record(() => { var moduleFileLoadRecorder = _moduleFileLoadRecorderFactory.Create(debugAttachAction); moduleFileLoadRecorder.RecordBeforeLoad(Array.Empty <SbModule>()); debuggerProcess = lldbTarget.AttachToProcessWithID(lldbListener, processId, out SbError lldbError); if (lldbError.Fail()) { throw new AttachException( VSConstants.E_ABORT, GetLldbAttachErrorDetails(lldbError, lldbPlatform, processId)); } RecordModules(lldbTarget, moduleFileLoadRecorder); }); var exceptionManager = _exceptionManagerFactory.Create(debuggerProcess); await _taskContext.Factory.SwitchToMainThreadAsync(); ILldbAttachedProgram attachedProgram = _attachedProgramFactory.Create( process, programId, _debugEngine, callback, lldbDebugger, lldbTarget, listenerSubscriber, debuggerProcess, lldbDebugger.GetCommandInterpreter(), false, exceptionManager, _moduleSearchLogHolder, processId); launchSucceeded = true; return(attachedProgram); } finally { // clean up the SBListener subscriber listenerSubscriber.FileUpdateReceived -= eventHandler; // stop the SBListener subscriber completely if the game failed to launch if (!launchSucceeded) { listenerSubscriber.Stop(); } } }
public async Task <IReadOnlyList <IDebugLaunchSettings> > QueryDebugTargetsAsync( IAsyncProject project, DebugLaunchOptions launchOptions) { try { // Make sure we can find the target executable. var targetPath = await project.GetTargetPathAsync(); if (!_fileSystem.File.Exists(targetPath)) { Trace.WriteLine($"Unable to find target executable: {targetPath}"); _dialogUtil.ShowError(ErrorStrings.UnableToFindTargetExecutable(targetPath)); return(new IDebugLaunchSettings[] { }); } _metrics.UseNewDebugSessionId(); var actionRecorder = new ActionRecorder(_metrics); var targetFileName = await project.GetTargetFileNameAsync(); var gameletCommand = (targetFileName + " " + await project.GetGameletLaunchArgumentsAsync()).Trim(); var launchParams = new LaunchParams() { Cmd = gameletCommand, RenderDoc = await project.GetLaunchRenderDocAsync(), Rgp = await project.GetLaunchRgpAsync(), SurfaceEnforcementMode = await project.GetSurfaceEnforcementAsync(), VulkanDriverVariant = await project.GetVulkanDriverVariantAsync(), QueryParams = await project.GetQueryParamsAsync(), Endpoint = await project.GetEndpointAsync() }; if (_sdkVersion != null && !string.IsNullOrEmpty(_sdkVersion.ToString())) { launchParams.SdkVersion = _sdkVersion.ToString(); } if (!TrySetupQueries(project, actionRecorder, out SetupQueriesResult setupQueriesResult)) { return(new IDebugLaunchSettings[] { }); } launchParams.ApplicationName = setupQueriesResult.Application.Name; launchParams.ApplicationId = setupQueriesResult.Application.Id; if (setupQueriesResult.TestAccount != null) { launchParams.TestAccount = setupQueriesResult.TestAccount.Name; launchParams.TestAccountGamerName = setupQueriesResult.TestAccount.GamerStadiaName; } DeployOnLaunchSetting deployOnLaunchAsync = await project.GetDeployOnLaunchAsync(); launchParams.Account = _credentialManager.LoadAccount(); // TODO: Enable PlayerEndpoint Launches for non-internal usage in VS. if (launchParams.Endpoint == StadiaEndpoint.PlayerEndpoint && launchParams.Account != null && !launchParams.Account.EndsWith("@sparklingsunset.com") && !launchParams.Account.EndsWith("@subtlesunset.com")) { throw new NotImplementedException( "Player Endpoints are not yet supported, please select " + "Test Client in the Project Properties instead."); } // TODO: Enable launch on any endpoint for external accounts. if (launchParams.Endpoint == StadiaEndpoint.AnyEndpoint && launchParams.Account != null && !launchParams.Account.EndsWith("@sparklingsunset.com") && !launchParams.Account.EndsWith("@subtlesunset.com")) { throw new NotImplementedException( "Launch on any player endpoint is not supported yet, please select " + "another endpoint in the Project Properties instead."); } bool launchGameApiEnabled = _yetiVsiService.Options.LaunchGameApiFlow == LaunchGameApiFlow.ENABLED; IGameletSelector gameletSelector = _gameletSelectorFactory.Create(launchGameApiEnabled, actionRecorder); if (!gameletSelector.TrySelectAndPrepareGamelet( targetPath, deployOnLaunchAsync, setupQueriesResult.Gamelets, setupQueriesResult.TestAccount, launchParams.Account, out Gamelet gamelet)) { return(new IDebugLaunchSettings[] { }); } launchParams.GameletName = gamelet.Name; launchParams.PoolId = gamelet.PoolId; launchParams.GameletSdkVersion = gamelet.GameletVersions.DevToolingVersion; launchParams.GameletEnvironmentVars = await project.GetGameletEnvironmentVariablesAsync(); // Prepare for debug launch using these settings. var debugLaunchSettings = new DebugLaunchSettings(launchOptions); debugLaunchSettings.Environment["PATH"] = await project.GetExecutablePathAsync(); debugLaunchSettings.LaunchOperation = DebugLaunchOperation.CreateProcess; debugLaunchSettings.CurrentDirectory = await project.GetAbsoluteRootPathAsync(); if (!launchOptions.HasFlag(DebugLaunchOptions.NoDebug)) { var parameters = _paramsFactory.Create(); parameters.TargetIp = new SshTarget(gamelet).GetString(); parameters.DebugSessionId = _metrics.DebugSessionId; debugLaunchSettings.Options = _paramsFactory.Serialize(parameters); } IAction action = actionRecorder.CreateToolAction(ActionType.RemoteDeploy); bool isDeployed = _cancelableTaskFactory.Create( TaskMessages.DeployingExecutable, async task => { await _remoteDeploy.DeployGameExecutableAsync( project, new SshTarget(gamelet), task, action); task.Progress.Report(TaskMessages.CustomDeployCommand); await _remoteDeploy.ExecuteCustomCommandAsync(project, gamelet, action); }).RunAndRecord(action); if (!isDeployed) { return(new IDebugLaunchSettings[] { }); } if (launchOptions.HasFlag(DebugLaunchOptions.NoDebug)) { if (_gameLauncher.LaunchGameApiEnabled || launchParams.Endpoint == StadiaEndpoint.PlayerEndpoint || launchParams.Endpoint == StadiaEndpoint.AnyEndpoint) { IVsiGameLaunch launch = _gameLauncher.CreateLaunch(launchParams); if (launch != null) { debugLaunchSettings.Arguments = _launchCommandFormatter.CreateWithLaunchName( launchParams, launch.LaunchName); } else { Trace.WriteLine("Unable to retrieve launch name from the launch api."); return(new IDebugLaunchSettings[] { }); } } else { debugLaunchSettings.Arguments = _launchCommandFormatter.CreateFromParams(launchParams); } debugLaunchSettings.Executable = Path.Combine(Environment.SystemDirectory, YetiConstants.Command); debugLaunchSettings.LaunchOptions = DebugLaunchOptions.NoDebug | DebugLaunchOptions.MergeEnvironment; } else { if (_yetiVsiService.DebuggerOptions[DebuggerOption.SKIP_WAIT_LAUNCH] == DebuggerOptionState.DISABLED) { launchParams.Debug = true; } // TODO: This should really be the game_client executable, since // the args we pass are for game_client as well. We just need to find another // way to pass the game executable. debugLaunchSettings.Executable = targetPath; debugLaunchSettings.LaunchDebugEngineGuid = YetiConstants.DebugEngineGuid; debugLaunchSettings.Arguments = _launchCommandFormatter.EncodeLaunchParams(launchParams); debugLaunchSettings.LaunchOptions = DebugLaunchOptions.MergeEnvironment; } return(new IDebugLaunchSettings[] { debugLaunchSettings }); } catch (Exception e) { Trace.WriteLine(e.ToString()); _dialogUtil.ShowError(e.Message, e.ToString()); return(new IDebugLaunchSettings[] { }); } }