Exemple #1
0
        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[] { });
            }
        }