Esempio n. 1
0
        public void Dispose()
        {
            if (!m_disposed)
            {
                using (var semaphoreReleaser = m_syncSemaphore.AcquireSemaphore())
                {
                    StopWaiting(semaphoreReleaser);
                    WaitUntilErrorAndOutputEof(true, semaphoreReleaser).GetAwaiter().GetResult();

                    if (m_processInjector != null)
                    {
                        // We may have already called Stop() in CompletionCallback, but that's okay.
                        m_processInjector.Stop().GetAwaiter().GetResult();
                        m_processInjector.Dispose();
                        m_processInjector = null;
                    }

                    if (m_processHandle != null)
                    {
                        m_processHandle.Dispose();
                        m_processHandle = null;
                    }

                    if (m_job != null)
                    {
                        m_job.Dispose();
                        m_job = null;
                    }
                }

                m_syncSemaphore.Dispose();

                m_disposed = true;
            }
        }
Esempio n. 2
0
        private async void CompletionCallback(object context, bool timedOut)
        {
            if (timedOut)
            {
                Volatile.Write(ref m_timedout, true);

                // Attempt to dump the timed out process before killing it
                if (!m_processHandle.IsInvalid && !m_processHandle.IsClosed && m_workingDirectory != null)
                {
                    DumpFileDirectory = m_timeoutDumpDirectory;

                    Exception dumpCreationException;

                    try
                    {
                        Directory.CreateDirectory(DumpFileDirectory);
                    }
                    catch (Exception ex)
                    {
                        DumpCreationException = ex;
                    }

                    if (DumpCreationException == null &&
                        !ProcessDumper.TryDumpProcessAndChildren(
                            parentProcessId: m_processId,
                            dumpDirectory: DumpFileDirectory,
                            primaryDumpCreationException: out dumpCreationException))
                    {
                        DumpCreationException = dumpCreationException;
                    }
                }

                Kill(ExitCodes.Timeout);

                using (await m_syncSemaphore.AcquireAsync())
                {
                    if (m_processWaitHandle != null)
                    {
                        await m_processWaitHandle;
                        m_exited = true;
                    }
                }
            }
            else
            {
                m_exited = true;
            }

            using (var semaphoreReleaser = await m_syncSemaphore.AcquireAsync())
            {
                Contract.Assume(m_waiting, "CompletionCallback should only be triggered once.");
                StopWaiting(semaphoreReleaser);
            }

            try
            {
                await Task.Run(
                    async() =>
                {
                    // Before waiting on anything, we call the processExiting callback.
                    // This callback happens to be responsible for triggering or forcing
                    // cleanup of all processes in this job. We can't finish waiting on pipe EOF
                    // (error, output, report, and process-injector pipes) until all handles
                    // to the write-sides are closed.
                    Func <Task> processExiting = m_processExitingAsync;
                    if (processExiting != null)
                    {
                        await processExiting();
                    }

                    using (var semaphoreReleaser = await m_syncSemaphore.AcquireAsync())
                    {
                        // Error and output pipes: Finish reading and then expect EOF (see above).
                        await WaitUntilErrorAndOutputEof(false, semaphoreReleaser);

                        // Stop process injection service. This finishes reading the injector control pipe (for injection requests).
                        // Since we don't get to the 'end' of the pipe until all child-processes holding on to it exit, we must
                        // perform this wait after processExiting() above.
                        if (m_processInjector != null)
                        {
                            // Stop() discards all unhandled requests. That is only safe to do since we are assuming that all processes
                            // in the job have exited (so those requests aren't relevant anymore)
                            await m_processInjector.Stop();
                            m_hasDetoursFailures = m_processInjector.HasDetoursInjectionFailures;
                            m_processInjector.Dispose();
                            m_processInjector = null;
                        }
                    }

                    // Now, callback for additional cleanup (can safely wait on extra pipes, such as the SandboxedProcess report pipe,
                    // if processExiting causes process tree teardown; see above).
                    var processExited = m_processExited;
                    if (processExited != null)
                    {
                        await processExited();
                    }
                });
            }
            catch (Exception exception)
            {
                // Something above may fail and that has to be observed. Unfortunately, throwing a normal exception in a continuation
                // just means someone has to observe the continuation. So we tug on some bootstraps by killing the process here.
                // TODO: It'd be nice if we had a FailFast equivalent that went through AppDomain.UnhandledExceptionEvent for logging.
                ExceptionHandling.OnFatalException(exception, "FailFast in DetouredProcess completion callback");
            }
        }
Esempio n. 3
0
        public void Start(
            Guid payloadGuid,
            ArraySegment <byte> payloadData,
            SafeFileHandle inheritableReportHandle,
            string dllNameX64,
            string dllNameX86)
        {
            using (m_syncSemaphore.AcquireSemaphore())
            {
                if (m_starting || m_disposed)
                {
                    throw new InvalidOperationException("Cannot invoke start process more than once or after this instance has been Disposed.");
                }

                m_starting = true;

                // The process creation flags
                // We use CREATE_DEFAULT_ERROR_MODE to ensure that the hard error mode of the child process (i.e., GetErrorMode)
                // is deterministic. Inheriting error mode is the default, but there may be some concurrent operation that temporarily
                // changes it (process global). The CLR has been observed to do so.
                // We use CREATE_NO_WINDOW in case BuildXL is attached to a console windows to prevent child processes from messing up
                // the console window. If BuildXL itself is started without a console window the flag is not set to prevent creating
                // extra conhost.exe processes.
                int creationFlags =
                    ((s_consoleWindow == IntPtr.Zero && !this.m_disableConHostSharing) ?
                     0 : Native.Processes.ProcessUtilities.CREATE_NO_WINDOW) | Native.Processes.ProcessUtilities.CREATE_DEFAULT_ERROR_MODE;

                SafeFileHandle standardInputWritePipeHandle = null;
                SafeFileHandle standardOutputReadPipeHandle = null;
                SafeFileHandle standardErrorReadPipeHandle  = null;

                try
                {
                    // set up the environment block parameter
                    var environmentHandle = default(GCHandle);
                    var payloadHandle     = default(GCHandle);

                    SafeFileHandle   hStdInput    = null;
                    SafeFileHandle   hStdOutput   = null;
                    SafeFileHandle   hStdError    = null;
                    SafeThreadHandle threadHandle = null;
                    try
                    {
                        IntPtr environmentPtr = IntPtr.Zero;
                        if (m_unicodeEnvironmentBlock != null)
                        {
                            creationFlags    |= Native.Processes.ProcessUtilities.CREATE_UNICODE_ENVIRONMENT;
                            environmentHandle = GCHandle.Alloc(m_unicodeEnvironmentBlock, GCHandleType.Pinned);
                            environmentPtr    = environmentHandle.AddrOfPinnedObject();
                        }

                        Pipes.CreateInheritablePipe(
                            Pipes.PipeInheritance.InheritRead,
                            Pipes.PipeFlags.WriteSideAsync,
                            readHandle: out hStdInput,
                            writeHandle: out standardInputWritePipeHandle);
                        Pipes.CreateInheritablePipe(
                            Pipes.PipeInheritance.InheritWrite,
                            Pipes.PipeFlags.ReadSideAsync,
                            readHandle: out standardOutputReadPipeHandle,
                            writeHandle: out hStdOutput);
                        Pipes.CreateInheritablePipe(
                            Pipes.PipeInheritance.InheritWrite,
                            Pipes.PipeFlags.ReadSideAsync,
                            readHandle: out standardErrorReadPipeHandle,
                            writeHandle: out hStdError);

                        // We want a per-process job primarily. If nested job support is not available, then we make sure to not have a BuildXL-level job.
                        if (JobObject.OSSupportsNestedJobs)
                        {
                            JobObject.SetTerminateOnCloseOnCurrentProcessJob();
                        }

                        // Initialize the injector
                        m_processInjector = new ProcessTreeContext(payloadGuid, inheritableReportHandle, payloadData, dllNameX64, dllNameX86, m_loggingContext);

                        // If path remapping is enabled then we wrap the job object in a container, so the filter drivers get
                        // configured (and they get cleaned up when the container is disposed)
                        if (m_containerConfiguration.IsIsolationEnabled)
                        {
                            m_job = new Container(
                                name: null,
                                containerConfiguration: m_containerConfiguration,
                                loggingContext: m_loggingContext);
                        }
                        else
                        {
                            m_job = new JobObject(null);
                        }

                        // We want the effects of SEM_NOGPFAULTERRORBOX on all children (but can't set that with CreateProcess).
                        // That's not set otherwise (even if set in this process) due to CREATE_DEFAULT_ERROR_MODE above.
                        m_job.SetLimitInformation(terminateOnClose: true, failCriticalErrors: false);

                        m_processInjector.Listen();

                        if (m_containerConfiguration.IsIsolationEnabled)
                        {
                            // After calling SetLimitInformation, start up the container if present
                            // This will throw if the container is not set up properly
                            m_job.StartContainerIfPresent();
                        }

                        // The call to the CreateDetouredProcess below will add a newly created process to the job.
                        System.Diagnostics.Stopwatch m_startUpTimeWatch = System.Diagnostics.Stopwatch.StartNew();
                        var detouredProcessCreationStatus =
                            Native.Processes.ProcessUtilities.CreateDetouredProcess(
                                m_commandLine,
                                creationFlags,
                                environmentPtr,
                                m_workingDirectory,
                                hStdInput,
                                hStdOutput,
                                hStdError,
                                m_job,
                                m_processInjector.Injector,
                                m_containerConfiguration.IsIsolationEnabled,
                                out m_processHandle,
                                out threadHandle,
                                out m_processId,
                                out int errorCode);
                        m_startUpTimeWatch.Stop();
                        m_startUpTime = m_startUpTimeWatch.ElapsedMilliseconds;

                        if (detouredProcessCreationStatus != CreateDetouredProcessStatus.Succeeded)
                        {
                            // TODO: Indicating user vs. internal errors (and particular phase failures e.g. adding to job object or injecting detours)
                            //       is good progress on the transparency into these failures. But consider making this indication visible beyond this
                            //       function without throwing exceptions; consider returning a structured value or logging events.
                            string message;
                            if (detouredProcessCreationStatus.IsDetoursSpecific())
                            {
                                message = string.Format(
                                    CultureInfo.InvariantCulture,
                                    "Internal error during process creation: {0:G}",
                                    detouredProcessCreationStatus);
                            }
                            else if (detouredProcessCreationStatus == CreateDetouredProcessStatus.ProcessCreationFailed)
                            {
                                message = "Process creation failed";
                            }
                            else
                            {
                                message = string.Format(
                                    CultureInfo.InvariantCulture,
                                    "Process creation failed: {0:G}",
                                    detouredProcessCreationStatus);
                            }

                            throw new BuildXLException(
                                      message,
                                      new NativeWin32Exception(errorCode));
                        }

                        // TODO: We should establish good post-conditions for CreateDetouredProcess. As a temporary measure, it would be nice
                        //       to determine if we are sometimes getting invalid process handles with retVal == true. So for now we differentiate
                        //       that possible case with a unique error string.
                        if (m_processHandle.IsInvalid)
                        {
                            throw new BuildXLException("Unable to start or detour a process (process handle invalid)", new NativeWin32Exception(errorCode));
                        }
                    }
                    finally
                    {
                        if (environmentHandle.IsAllocated)
                        {
                            environmentHandle.Free();
                        }

                        if (payloadHandle.IsAllocated)
                        {
                            payloadHandle.Free();
                        }

                        if (hStdInput != null && !hStdInput.IsInvalid)
                        {
                            hStdInput.Dispose();
                        }

                        if (hStdOutput != null && !hStdOutput.IsInvalid)
                        {
                            hStdOutput.Dispose();
                        }

                        if (hStdError != null && !hStdError.IsInvalid)
                        {
                            hStdError.Dispose();
                        }

                        if (inheritableReportHandle != null && !inheritableReportHandle.IsInvalid)
                        {
                            inheritableReportHandle.Dispose();
                        }

                        if (threadHandle != null && !threadHandle.IsInvalid)
                        {
                            threadHandle.Dispose();
                        }
                    }

                    var standardInputStream = new FileStream(standardInputWritePipeHandle, FileAccess.Write, m_bufferSize, isAsync: true);
                    m_standardInputWriter = new StreamWriter(standardInputStream, m_standardInputEncoding, m_bufferSize)
                    {
                        AutoFlush = true
                    };

                    var standardOutputFile = AsyncFileFactory.CreateAsyncFile(
                        standardOutputReadPipeHandle,
                        FileDesiredAccess.GenericRead,
                        ownsHandle: true,
                        kind: FileKind.Pipe);
                    m_outputReader = new AsyncPipeReader(standardOutputFile, m_outputDataReceived, m_standardOutputEncoding, m_bufferSize);
                    m_outputReader.BeginReadLine();

                    var standardErrorFile = AsyncFileFactory.CreateAsyncFile(
                        standardErrorReadPipeHandle,
                        FileDesiredAccess.GenericRead,
                        ownsHandle: true,
                        kind: FileKind.Pipe);
                    m_errorReader = new AsyncPipeReader(standardErrorFile, m_errorDataReceived, m_standardErrorEncoding, m_bufferSize);
                    m_errorReader.BeginReadLine();

                    Contract.Assert(!m_processHandle.IsInvalid);
                    m_processWaitHandle = new SafeWaitHandleFromSafeHandle(m_processHandle);

                    m_waiting = true;

                    TimeSpan timeout = m_timeout ?? Timeout.InfiniteTimeSpan;
                    m_registeredWaitHandle = ThreadPool.RegisterWaitForSingleObject(
                        m_processWaitHandle,
                        CompletionCallback,
                        null,
                        timeout,
                        true);

                    m_started = true;
                }
                catch (Exception)
                {
                    // Dispose pipe handles in case they are not assigned to streams.
                    if (m_standardInputWriter == null)
                    {
                        standardInputWritePipeHandle?.Dispose();
                    }

                    if (m_outputReader == null)
                    {
                        standardOutputReadPipeHandle?.Dispose();
                    }

                    if (m_errorReader == null)
                    {
                        standardErrorReadPipeHandle?.Dispose();
                    }

                    throw;
                }
            }
        }