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; } } }