Beispiel #1
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;
                }
            }
        }