Files
UnrealEngine/Engine/Source/Programs/UnrealBuildAccelerator/Common/Private/UbaProcess.cpp
2025-05-18 13:04:45 +08:00

2297 lines
72 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "UbaProcess.h"
#include "UbaFileAccessor.h"
#include "UbaProtocol.h"
#include "UbaProcessStats.h"
#include "UbaProcessUtils.h"
#include "UbaApplicationRules.h"
#if PLATFORM_WINDOWS
#include "UbaDetoursPayload.h"
#include <winternl.h>
#include <Psapi.h>
#include <detours/detours.h>
#else
#include <wchar.h>
#include <poll.h>
#include <stdio.h>
#include <spawn.h>
// These headers are used for tracking child and beyond
// processes and making sure they clean up properly
// Linux uses PR_SET_CHILD_SUBREAPER
// Mac has to roll it's own solution
#if PLATFORM_LINUX
#include <sys/prctl.h>
#include <sys/resource.h>
#elif PLATFORM_MAC
#include <sys/types.h>
#include <sys/sysctl.h>
#endif
extern char **environ;
#endif
#define UBA_DEBUG_TRACK_PROCESS 0 // UBA_DEBUG_LOGGER
#if !PLATFORM_WINDOWS
#define EXCEPTION_ACCESS_VIOLATION 128 + SIGSEGV
#define STATUS_STACK_BUFFER_OVERRUN 128 + SIGSEGV
#endif
//////////////////////////////////////////////////////////////////////////////
#define UBA_EXIT_CODE(x) (9000 + x)
namespace uba
{
static constexpr const tchar g_extractExportsStr[] = TC("/extractexports");
void Process::AddRef()
{
++m_refCount;
}
void Process::Release()
{
UBA_ASSERT(m_refCount);
if (!--m_refCount)
delete this;
}
struct ProcessImpl::PipeReader
{
PipeReader(ProcessImpl& p, LogEntryType lt) : process(p), logType(lt) {}
~PipeReader()
{
if (!currentString.empty())
process.InternalLogLine(false, TString(currentString), logType);
}
void ReadData(char* buf, u32 readCount)
{
char* startPos = buf;
while (true)
{
char* endOfLine = strchr(startPos, '\n');
if (!endOfLine)
{
currentString.append(TString(startPos, startPos + strlen(startPos)));
return;
}
char* newStart = endOfLine + 1;
if (endOfLine > buf && endOfLine[-1] == '\r')
--endOfLine;
currentString.append(TString(startPos, endOfLine));
process.InternalLogLine(false, TString(currentString), logType);
currentString.clear();
startPos = newStart;
}
}
ProcessImpl& process;
LogEntryType logType;
TString currentString;
};
struct ImageInfo
{
CasKey key;
u64 fileSize = 0;
bool is64Bit = true;
bool isX64 = false;
bool isArm64 = false;
bool isDotnet = false;
};
bool GetImageInfo(ImageInfo& out, Logger& logger, const tchar* application, bool calculateCas)
{
FileAccessor fa(logger, application);
if (!fa.OpenMemoryRead())
return false;
out.fileSize = fa.GetSize();
u8* data = fa.GetData();
if (calculateCas)
{
CasKeyHasher hasher;
hasher.Update(data, out.fileSize);
out.key = ToCasKey(hasher, false);
}
if (data[0] == 'M' && data[1] == 'Z')
{
u32 offset = *(u32*)(data + 0x3c);
u32* signaturePos = (u32*)(data + offset);
out.is64Bit = *signaturePos == 0x00004550;
u16 machine = *(u16*)(signaturePos + 1);
out.isX64 = machine == 0x8664;
out.isArm64 = machine == 0xaa64;
if (out.fileSize > offset + 0x18 + 0x70 + 4)
out.isDotnet = *(u32*)(data + offset + 0x18 + 0x70);
}
return fa.Close();
}
ProcessImpl::ProcessImpl(Session& session, u32 id, ProcessImpl* parent, bool detourEnabled)
: m_session(session)
, m_parentProcess(parent)
, m_id(id)
, m_comMemory(detourEnabled ? m_session.m_processCommunicationAllocator.Alloc(TC("")) : FileMappingAllocator::Allocation())
#if !PLATFORM_WINDOWS
, m_cancelEvent(*(m_comMemory.memory ? (new (m_comMemory.memory) SharedEvent) : (SharedEvent*)nullptr))
, m_writeEvent(*(m_comMemory.memory ? (new (m_comMemory.memory + sizeof(SharedEvent)) SharedEvent) : (SharedEvent*)nullptr))
, m_readEvent(*(m_comMemory.memory ? (new (m_comMemory.memory + sizeof(SharedEvent)*2) SharedEvent) : (SharedEvent*)nullptr))
#endif
, m_detourEnabled(detourEnabled)
, m_shared(parent ? parent->m_shared : *new Shared)
{
if (m_comMemory.memory)
{
m_cancelEvent.Create(true);
m_writeEvent.Create(false);
m_readEvent.Create(false);
}
}
ProcessImpl::~ProcessImpl()
{
UBA_ASSERT(m_refCount == 0);
{
#if !PLATFORM_WINDOWS
SCOPED_FUTEX(m_comMemoryLock, lock);
#endif
if (m_comMemory.memory)
m_cancelEvent.Set();
}
m_messageThread.Wait();
if (m_comMemory.memory)
{
#if !PLATFORM_WINDOWS
m_cancelEvent.~SharedEvent();
m_writeEvent.~SharedEvent();
m_readEvent.~SharedEvent();
#endif
m_session.m_processCommunicationAllocator.Free(m_comMemory);
}
if (!m_parentProcess)
{
for (auto& pair : m_shared.writtenFiles)
if (pair.second.mappingHandle.IsValid())
CloseFileMapping(m_session.m_logger, pair.second.mappingHandle, pair.second.name.c_str());
ClearTempFiles();
delete &m_shared;
}
}
bool ProcessImpl::Start(const ProcessStartInfo& startInfo, bool runningRemote, void* environment, bool async)
{
m_startTime = GetTime();
m_startInfo = startInfo;
m_runningRemote = runningRemote;
FixPathSeparators(m_startInfo.workingDirStr.data());
FixPathSeparators(m_startInfo.logFileStr.data());
if (IsAbsolutePath(m_startInfo.application))
{
StringBuffer<256> temp2;
FixPath(m_startInfo.applicationStr.data(), nullptr, 0, temp2);
m_startInfo.applicationStr = temp2.data;
m_startInfo.application = m_startInfo.applicationStr.data();
}
else
FixPathSeparators(m_startInfo.applicationStr.data());
m_startInfo.Expand();
m_extractExports = Contains(m_startInfo.arguments, g_extractExportsStr, true);
StringBuffer<> realApplication(m_startInfo.application);
const tchar* realWorkingDir = m_startInfo.workingDir;
if (!m_session.PrepareProcess(*this, m_parentProcess != nullptr, realApplication, realWorkingDir))
{
m_exitCode = 44324;
return false;
}
m_realApplication = realApplication.data;
m_realWorkingDir = realWorkingDir;
if (realWorkingDir == startInfo.workingDir)
m_realWorkingDir = m_startInfo.workingDir;
if (m_parentProcess)
m_waitForParent.Create(true);
UBA_ASSERT(m_startInfo.rules);
// If running remote we can't use mspdbsrv (not supported yet).. so instead embed information in .obj file
// TODO: This should be placed somewhere else it feels like.
#if PLATFORM_WINDOWS
if (runningRemote && (m_startInfo.rules->index == SpecialRulesIndex_ClExe || m_startInfo.rules->index == SpecialRulesIndex_LinkExe))
{
tchar* pos = nullptr;
if (Contains(m_startInfo.argumentsStr.data(), L"/FS ", true, (const tchar**)&pos))
memcpy(pos, L"/Z7", 6);
}
#endif
m_session.ProcessAdded(*this, 0);
if (async)
m_messageThread.Start([this, environment]()
{
ThreadRun(environment);
ThreadExit();
return 0;
}, startInfo.description);
else
{
// This is needed to handle cltr-c. Otherwise this thread might exit before the detoured process which in turn might do things after ctrl-c which can cause deadlocks in detoured process
#if !PLATFORM_WINDOWS
sigset_t newMask;
sigset_t oldMask;
sigemptyset(&newMask);
sigaddset(&newMask, SIGINT);
pthread_sigmask(SIG_BLOCK, &newMask, &oldMask);
#endif
ThreadRun(environment);
ThreadExit();
#if !PLATFORM_WINDOWS
pthread_sigmask(SIG_BLOCK, &oldMask, nullptr);
#endif
}
return true;
}
bool ProcessImpl::IsActive()
{
if (m_nativeProcessHandle == InvalidProcHandle)
{
//m_session.m_logger.Info(TC("IsActive false 1"), LastErrorToText().data);
return false;
}
#if PLATFORM_WINDOWS
DWORD waitRes = WaitForSingleObject((HANDLE)m_nativeProcessHandle, 0);
if (waitRes == WAIT_TIMEOUT)
return true;
if (waitRes != WAIT_OBJECT_0)
{
m_session.m_logger.Error(TC("WaitForSingleObject failed on handle %llu id %u returning %u (%s)"), u64(m_nativeProcessHandle), m_nativeProcessId, waitRes, LastErrorToText().data);
return false;
}
DWORD exitCode = STILL_ACTIVE;
if (!GetExitCodeProcess((HANDLE)m_nativeProcessHandle, &exitCode))
{
m_nativeProcessExitCode = ~0u;
m_session.m_logger.Error(TC("GetExitCodeProcess failed (%s)"), LastErrorToText().data);
return false;
}
if (exitCode == STILL_ACTIVE)
return true;
if (!m_gotExitMessage && exitCode != EXCEPTION_ACCESS_VIOLATION && exitCode != STATUS_STACK_BUFFER_OVERRUN)
{
StringBuffer<> err;
if (m_messageCount == 0) // This is bad.. bad binaries?
{
err.Append(TCV("ERROR: Process did not start properly. "));
ImageInfo imageInfo;
bool machineIsArm = IsArmBinary;
if (exitCode == 1398)
err.Appendf(TC("UbaDetours.dll has a different version than " UBA_BINARY));
else if (!GetImageInfo(imageInfo, m_session.m_logger, m_realApplication.c_str(), true))
err.Appendf(TC("Failed to load %s"), m_realApplication.c_str());
else if (!imageInfo.is64Bit)
err.Append(TCV("Doesn't seem to be a 64-bit executable"));
else if (imageInfo.isDotnet)
err.Append(TCV("Dotnet binary"));
else if (!imageInfo.isArm64 && !imageInfo.isX64)
err.Append(TCV("Unknown image architecture"));
else if (!machineIsArm && imageInfo.isArm64)
err.Appendf(TC("Machine is x64 and image is arm64"));
if (exitCode != 1398)
err.Appendf(TC(" (GetExitCodeProcess returned 0x%x (%s Size: %llu, CasKey: %s)"), exitCode, m_realApplication.c_str(), imageInfo.fileSize, CasKeyString(imageInfo.key).str);
}
if (err.IsEmpty())
err.Appendf(TC("ERROR: Process %llu %s (%s) not active but did not get exit message. Received %u messages (GetExitCodeProcess returned 0x%x)"), u64(m_nativeProcessHandle), m_startInfo.GetDescription(), m_realApplication.c_str(), m_messageCount, exitCode);
LogLine(false, err.data, LogEntryType_Error);
m_nativeProcessExitCode = UBA_EXIT_CODE(666);
}
return false;
#else
if (m_parentProcess && m_parentProcess->m_nativeProcessId != 0) // Can't do wait on grandchildren on Linux.. but since we use PR_SET_CHILD_SUBREAPER we should once parent is gone and child is orphaned
return true;
#if PLATFORM_MAC
if (m_parentProcess && m_gotExitMessage) // TODO: We need a timeout here... if child crashes we will never get exit message
return false;
#endif
siginfo_t signalInfo;
while (m_nativeProcessId != 0)
{
memset(&signalInfo, 0, sizeof(signalInfo));
int res = waitid(P_PID, (unsigned int)m_nativeProcessId, &signalInfo, WEXITED | WNOHANG | WNOWAIT);
if (res)
{
UBA_ASSERT(res == -1);
if (errno == EINTR)
continue;
if (errno == ECHILD) // This should not happen, but let's return true on this since we can't use waitid on processes that are not our children
return true;
UBA_ASSERTF(false, "waitid failed with error: %u (%s)", errno, strerror(errno));
break;
}
else
{
if (signalInfo.si_pid != (pid_t)m_nativeProcessId)
return true;
const char* codeType = nullptr;
const char* extraString = "";
switch (signalInfo.si_code)
{
case CLD_KILLED:
codeType = "killed";
break;
case CLD_DUMPED:
codeType = "killed";
extraString = " (dumped core)";
break;
case CLD_STOPPED:
codeType = "stopped";
break;
case CLD_TRAPPED:
codeType = "trapped";
break;
case CLD_CONTINUED:
codeType = "continued";
break;
}
u32 nativeProcessId = m_nativeProcessId;
m_nativeProcessId = 0;
m_nativeProcessExitCode = signalInfo.si_status;
if (!codeType) // Is null if graceful exit (CLD_EXITED)
break;
StringBuffer<> err;
err.Appendf(TC("Process %u (%s) %s by signal %i. Received %u messages. Execution time: %s."), nativeProcessId, m_startInfo.GetDescription(), codeType, signalInfo.si_status, m_messageCount, TimeToText(GetTime() - m_startTime).str);
LogLine(false, err.data, LogEntryType_Error);
m_nativeProcessExitCode = UBA_EXIT_CODE(666); // We do exit code 666 to trigger non-uba retry on the outside
return false;
}
}
// There is a small race condition between this process polling and exit message.
// Detoured process can't wait for exit message response and then close the shared memory because it might end up closing another process memory
// .. so solution is to do one more poll from here to make sure we pick up the message before leaving.
if (!m_gotExitMessage)
{
if (m_doOneExtraCheckForExitMessage)
{
m_doOneExtraCheckForExitMessage = false;
return true;
}
StringBuffer<> err;
err.Appendf(TC("ERROR: Process %u (%s) not active but did not get exit message. Received %u messages. Signal code: %i. Exit value or signal: %i. Execution time: %s."), m_nativeProcessId, m_startInfo.GetDescription(), m_messageCount, signalInfo.si_code, signalInfo.si_status, TimeToText(GetTime() - m_startTime).str);
LogLine(false, err.data, LogEntryType_Error);
m_nativeProcessExitCode = UBA_EXIT_CODE(666);
}
//m_session.m_logger.Info(TC("IsActive false (no parent)"), LastErrorToText().data);
return false;
#endif
}
bool ProcessImpl::IsCancelled()
{
#if PLATFORM_WINDOWS
return m_cancelEvent.IsSet(0);
#else
return m_cancelled; // can't use cancel event since memory might have been returned
#endif
}
bool ProcessImpl::HasFailedMessage()
{
return !m_messageSuccess;
}
bool ProcessImpl::WaitForExit(u32 millisecondsTimeout)
{
return m_messageThread.Wait(millisecondsTimeout);
}
u64 ProcessImpl::GetTotalWallTime() const
{
return m_processStats.wallTime;
}
u64 ProcessImpl::GetTotalProcessorTime() const
{
return m_processStats.cpuTime;
}
void ProcessImpl::Cancel(bool terminate)
{
#if PLATFORM_WINDOWS
m_cancelEvent.Set();
#else
m_cancelled = true;
SCOPED_FUTEX(m_comMemoryLock, lock);
if (m_comMemory.memory)
m_cancelEvent.Set();
#endif
}
bool ProcessImpl::WaitForRead(PipeReader& outReader, PipeReader& errReader)
{
while (true)
{
if (m_readEvent.IsSet(1000))
break;
#if !PLATFORM_WINDOWS
PollStdPipes(outReader, errReader, 0);
#endif
if (!IsActive())
return m_readEvent.IsSet(0); // Do one more check
if (IsCancelled())
return false;
}
return true;
}
void ProcessImpl::SetWritten()
{
m_writeEvent.Set();
}
void ProcessImpl::ThreadRun(void* environment)
{
KernelStatsScope kernelStatsScope(m_kernelStats);
StorageStatsScope storageStatsScope(m_storageStats);
SessionStatsScope sessionStatsScope(m_sessionStats);
if (HandleSpecialApplication())
return;
if (!m_session.ProcessThreadStart(*this))
{
m_logLines.push_back({ TString(TC("Session::ProcessThreadStart failed")), LogEntryType_Error });
return;
}
u8* comMemory = m_comMemory.memory;
u64 comMemorySize = CommunicationMemSize;
#if !PLATFORM_WINDOWS
comMemory += sizeof(SharedEvent) * 3;
comMemorySize -= sizeof(SharedEvent) * 3;
#endif
u32 retryCount = 0; // Do not allow retry
u32 exitCode = ~0u;
while (!IsCancelled())
{
exitCode = InternalCreateProcess(environment, m_comMemory.handle, m_comMemory.offset);
if (exitCode == 0)
{
PipeReader outReader(*this, LogEntryType_Info);
PipeReader errReader(*this, LogEntryType_Error);
bool loop = m_detourEnabled;
while (loop && WaitForRead(outReader, errReader))
{
u64 startTime = GetTime();
BinaryReader reader(comMemory, 0, comMemorySize);
BinaryWriter writer(comMemory, 0, comMemorySize);
loop = HandleMessage(reader, writer);
SetWritten();
m_processStats.hostTotalTime += GetTime() - startTime;
++m_messageCount;
}
#if !PLATFORM_WINDOWS
while (PollStdPipes(outReader, errReader, 500) && !IsCancelled())
continue;
#endif
}
m_processStats.exitTime = GetTime();
bool cancelled = IsCancelled();
if (exitCode == 0)
exitCode = InternalExitProcess(cancelled);
// For some reason a parent can exit before a child. I've seen it happen on ClangEditor win64 and also with some experimental features enabled on msvc
// We need to wait also because we need all written files to be added in shared file system
WaitForChildrenExit();
if (!cancelled)
if (m_startInfo.writeOutputFilesOnFail || m_startInfo.rules->IsExitCodeSuccess(m_nativeProcessExitCode))
if (!WriteFilesToDisk(true))
exitCode = UBA_EXIT_CODE(1);
if (m_parentProcess)
{
m_parentProcess->m_processStats.Add(m_processStats);
m_parentProcess->m_sessionStats.Add(m_sessionStats);
m_parentProcess->m_storageStats.Add(m_storageStats);
m_parentProcess->m_kernelStats.Add(m_kernelStats);
}
if (exitCode == 0 && !m_messageSuccess)
exitCode = UBA_EXIT_CODE(1);
bool isChild = m_parentProcess != nullptr;
if (cancelled || isChild)
break;
if (retryCount == 0)
break;
--retryCount;
if (exitCode == EXCEPTION_ACCESS_VIOLATION)
m_session.m_logger.Warning(TC("Process exited with access violation. Will do one retry."));
else if (exitCode == STATUS_STACK_BUFFER_OVERRUN)
m_session.m_logger.Warning(TC("Process exited with stack buffer overflow. Will do one retry."));
else
break;
m_logLines.clear();
m_trackedInputs.clear();
m_shared.writtenFiles.clear();
ClearTempFiles();
}
#if PLATFORM_WINDOWS
if (m_accountingJobObject)
{
JOBOBJECT_BASIC_ACCOUNTING_INFORMATION accountingInformation = {};
if (QueryInformationJobObject(m_accountingJobObject, JOBOBJECTINFOCLASS::JobObjectBasicAccountingInformation, &accountingInformation, sizeof(accountingInformation), NULL))
m_processStats.cpuTime = accountingInformation.TotalUserTime.QuadPart + accountingInformation.TotalKernelTime.QuadPart;
CloseHandle(m_accountingJobObject);
}
#endif
if (IsCancelled())
m_exitCode = ProcessCancelExitCode;
else
m_exitCode = exitCode;
}
void ProcessImpl::ThreadExit()
{
m_processStats.wallTime = GetTime() - m_startTime;
KernelStats::GetGlobal().Add(m_kernelStats);
#if UBA_DEBUG_TRACK_PROCESS
m_session.m_debugLogger->Info(TC("ProcessExitedStart (%u)"), m_id);
#endif
UBA_ASSERT(IsCancelled() || !m_parentProcess || !m_parentProcess->m_hasExited);
m_hasExited = true;
if (m_comMemory.memory)
{
#if !PLATFORM_WINDOWS
SCOPED_FUTEX(m_comMemoryLock, lock);
m_cancelEvent.~SharedEvent();
m_writeEvent.~SharedEvent();
m_readEvent.~SharedEvent();
#endif
m_session.m_processCommunicationAllocator.Free(m_comMemory);
m_comMemory = {};
}
if (!m_parentProcess)
ClearTempFiles();
if (m_startInfo.exitedFunc)
{
auto exitedFunc = m_startInfo.exitedFunc;
auto userData = m_startInfo.userData;
m_startInfo.exitedFunc = nullptr;
m_startInfo.userData = nullptr;
ProcessHandle h;
h.m_process = this;
ProcessExitedResponse exitedResponse = ProcessExitedResponse_None;
exitedFunc(userData, h, exitedResponse);
h.m_process = nullptr;
}
UBA_ASSERT(m_refCount);
if (m_processStats.exitTime)
m_processStats.exitTime = GetTime() - m_processStats.exitTime;
// Must be done last to make sure shutdown is not racing
m_session.ProcessExited(*this, m_processStats.wallTime);
#if UBA_DEBUG_TRACK_PROCESS
m_session.m_debugLogger->Info(TC("ProcessExitedDone (%u)"), m_id);
#endif
}
bool ProcessImpl::HandleSpecialApplication()
{
#if PLATFORM_WINDOWS
if (!Equals(m_startInfo.application, TC("ubacopy")))
return false;
const tchar* fromFileBegin = m_startInfo.arguments;
const tchar* fromFileEnd = TStrchr(fromFileBegin, '\"');
UBA_ASSERT(fromFileEnd);
if (!fromFileEnd)
return false;
const tchar* toFileBegin = TStrchr(fromFileEnd + 1, '\"');
UBA_ASSERT(toFileBegin);
if (!toFileBegin)
return false;
++toFileBegin;
const tchar* toFileEnd = TStrchr(toFileBegin, '\"');
UBA_ASSERT(toFileEnd);
if (!toFileEnd)
return false;
m_processStats.wallTime = GetTime() - m_startTime;
StringBuffer<> workDir(m_startInfo.workingDir);
workDir.EnsureEndsWithSlash();
StringBuffer<> fromName;
StringBuffer<> toName;
StringBuffer<> temp;
temp.Append(fromFileBegin, fromFileEnd - fromFileBegin);
FixPath(temp.data, workDir.data, workDir.count, fromName);
temp.Clear().Append(toFileBegin, toFileEnd - toFileBegin);
FixPath(temp.data, workDir.data, workDir.count, toName);
DWORD oldAttributes = GetFileAttributes(toName.data);
if (oldAttributes != INVALID_FILE_ATTRIBUTES && (oldAttributes & FILE_ATTRIBUTE_READONLY))
SetFileAttributes(toName.data, oldAttributes & (~FILE_ATTRIBUTE_READONLY));
if (!CopyFileW(fromName.data, toName.data, false))
{
m_exitCode = GetLastError();
temp.Clear().Appendf(TC("Failed to copy %s to %s (%s)"), fromName.data, toName.data, LastErrorToText(m_exitCode).data);
m_logLines.push_back({ temp.ToString(), LogEntryType_Error });
return true;
}
SetFileAttributes(toName.data, DefaultAttributes());
StringKey toKey = ToStringKeyLower(toName);
m_session.RegisterCreateFileForWrite(toKey, toName, true);
//m_writtenFiles.try_emplace(name);
//WrittenFile& writtenFile = m_writtenFiles[toName.data];
//writtenFile.key = toKey;
//writtenFile.name = toName.data;
//writtenFile.owner = this;
//writtenFile.attributes = DefaultAttributes();
StackBinaryWriter<1024> trackedInputs;
trackedInputs.WriteString(fromName);
m_trackedInputs.resize(trackedInputs.GetPosition());
memcpy(m_trackedInputs.data(), trackedInputs.GetData(), trackedInputs.GetPosition());
StackBinaryWriter<1024> trackedOutputs;
trackedOutputs.WriteString(toName);
m_trackedOutputs.resize(trackedOutputs.GetPosition());
memcpy(m_trackedOutputs.data(), trackedOutputs.GetData(), trackedOutputs.GetPosition());
m_exitCode = 0;
return true;
#else
return false;
#endif
}
bool ProcessImpl::HandleMessage(BinaryReader& reader, BinaryWriter& writer)
{
MessageType messageType = (MessageType)reader.ReadByte();
switch (messageType)
{
#define UBA_PROCESS_MESSAGE(type) case MessageType_##type: return Handle##type(reader, writer);
UBA_PROCESS_MESSAGES
#undef UBA_PROCESS_MESSAGE
}
return CancelWithError(m_session.m_logger.Error(TC("Unknown message type %u"), messageType));
}
void ProcessImpl::LogLine(bool printInSession, TString&& line, LogEntryType logType)
{
if (IsCancelled())// || m_startInfo.stdoutHandle != INVALID_HANDLE_VALUE)
return;
if (printInSession)
m_session.m_logger.Log(LogEntryType_Warning, line.c_str(), u32(line.size()));
if (m_startInfo.logLineFunc)
m_startInfo.logLineFunc(m_startInfo.logLineUserData, line.c_str(), u32(line.size()), logType);
SCOPED_FUTEX(m_logLinesLock, l);
m_logLines.push_back({ std::move(line), logType });
}
bool ProcessImpl::HandleInit(BinaryReader& reader, BinaryWriter& writer)
{
InitMessage msg { *this };
InitResponse response;
m_messageSuccess = m_session.GetInitResponse(response, msg) && m_messageSuccess;
writer.WriteBool(m_echoOn);
writer.WriteBool(m_parentProcess != nullptr);
writer.WriteString(m_startInfo.application);
writer.WriteString(m_startInfo.workingDir);
writer.WriteU64(response.directoryTableHandle);
writer.WriteU32(response.directoryTableSize);
writer.WriteU32(response.directoryTableCount);
writer.WriteU64(response.mappedFileTableHandle);
writer.WriteU32(response.mappedFileTableSize);
writer.WriteU32(response.mappedFileTableCount);
if (!m_startInfo.rootsHandle)
{
writer.WriteU16(0);
return true;
}
auto rootsEntry = m_session.GetRootsEntry(m_startInfo.rootsHandle);
if (!rootsEntry)
return CancelWithError();
writer.WriteU16(u16(rootsEntry->memory.size()));
writer.WriteBytes(rootsEntry->memory.data(), rootsEntry->memory.size());
return true;
}
bool ProcessImpl::HandleCreateFile(BinaryReader& reader, BinaryWriter& writer)
{
CreateFileMessage msg { *this };
reader.ReadString(msg.fileName);
msg.fileNameKey = reader.ReadStringKey();
msg.access = (FileAccess)reader.ReadByte();
CreateFileResponse response;
m_messageSuccess = m_session.CreateFile(response, msg) && m_messageSuccess;
writer.WriteString(response.fileName);
writer.WriteU64(response.size);
writer.WriteU32(response.closeId);
writer.WriteU32(response.mappedFileTableSize);
writer.WriteU32(response.directoryTableSize);
return true;
}
bool ProcessImpl::HandleGetFullFileName(BinaryReader& reader, BinaryWriter& writer)
{
GetFullFileNameMessage msg { *this };
reader.ReadString(msg.fileName);
msg.fileNameKey = reader.ReadStringKey();
msg.loaderPathsSize = reader.ReadU16();
msg.loaderPaths = reader.GetPositionData();
GetFullFileNameResponse response;
m_messageSuccess = m_session.GetFullFileName(response, msg) && m_messageSuccess;
writer.WriteString(response.fileName);
writer.WriteString(response.virtualFileName);
writer.WriteU32(response.mappedFileTableSize);
return true;
}
bool ProcessImpl::HandleGetLongPathName(BinaryReader& reader, BinaryWriter& writer)
{
GetLongPathNameMessage msg { *this };
reader.ReadString(msg.fileName);
GetLongPathNameResponse response;
m_messageSuccess = m_session.GetLongPathName(response, msg) && m_messageSuccess;
writer.WriteU32(response.errorCode);
writer.WriteString(response.fileName);
return true;
}
bool ProcessImpl::HandleCloseFile(BinaryReader& reader, BinaryWriter& writer)
{
CloseFileMessage msg { *this };
reader.ReadString(msg.fileName);
msg.closeId = reader.ReadU32();
msg.attributes = DefaultAttributes(); // reader.ReadFileAttributes(); TODO
msg.deleteOnClose = reader.ReadBool();
msg.success = reader.ReadBool();
msg.mappingHandle = FileMappingHandle::FromU64(reader.ReadU64());
msg.mappingWritten = reader.ReadU64();
msg.newNameKey = reader.ReadStringKey();
if (msg.newNameKey != StringKeyZero)
reader.ReadString(msg.newName);
CloseFileResponse response;
m_messageSuccess = m_session.CloseFile(response, msg) && m_messageSuccess;
writer.WriteU32(response.directoryTableSize);
return true;
}
bool ProcessImpl::HandleDeleteFile(BinaryReader& reader, BinaryWriter& writer)
{
DeleteFileMessage msg { *this };
reader.ReadString(msg.fileName);
msg.fileNameKey = reader.ReadStringKey();
msg.closeId = reader.ReadU32();
DeleteFileResponse response;
m_messageSuccess = m_session.DeleteFile(response, msg) && m_messageSuccess;
writer.WriteBool(response.result);
writer.WriteU32(response.errorCode);
writer.WriteU32(response.directoryTableSize);
return true;
}
bool ProcessImpl::HandleCopyFile(BinaryReader& reader, BinaryWriter& writer)
{
CopyFileMessage msg{ *this };
msg.fromKey = reader.ReadStringKey();
reader.ReadString(msg.fromName);
msg.toKey = reader.ReadStringKey();
reader.ReadString(msg.toName);
CopyFileResponse response;
m_messageSuccess = m_session.CopyFile(response, msg) && m_messageSuccess;
writer.WriteString(response.fromName);
writer.WriteString(response.toName);
writer.WriteU32(response.closeId);
writer.WriteU32(response.errorCode);
writer.WriteU32(response.directoryTableSize);
return true;
}
bool ProcessImpl::HandleMoveFile(BinaryReader& reader, BinaryWriter& writer)
{
MoveFileMessage msg { *this };
msg.fromKey = reader.ReadStringKey();
reader.ReadString(msg.fromName);
msg.toKey = reader.ReadStringKey();
reader.ReadString(msg.toName);
msg.flags = reader.ReadU32();
MoveFileResponse response;
m_messageSuccess = m_session.MoveFile(response, msg) && m_messageSuccess;
writer.WriteBool(response.result);
writer.WriteU32(response.errorCode);
writer.WriteU32(response.directoryTableSize);
return true;
}
bool ProcessImpl::HandleChmod(BinaryReader& reader, BinaryWriter& writer)
{
ChmodMessage msg { *this };
msg.fileNameKey = reader.ReadStringKey();
reader.ReadString(msg.fileName);
msg.fileMode = reader.ReadU32();
ChmodResponse response;
m_messageSuccess = m_session.Chmod(response, msg) && m_messageSuccess;
writer.WriteU32(response.errorCode);
return true;
}
bool ProcessImpl::HandleCreateDirectory(BinaryReader& reader, BinaryWriter& writer)
{
CreateDirectoryMessage msg;
msg.nameKey = reader.ReadStringKey();
reader.ReadString(msg.name);
CreateDirectoryResponse response;
m_messageSuccess = m_session.CreateDirectory(response, msg) && m_messageSuccess;
writer.WriteBool(response.result);
writer.WriteU32(response.errorCode);
writer.WriteU32(response.directoryTableSize);
return true;
}
bool ProcessImpl::HandleRemoveDirectory(BinaryReader& reader, BinaryWriter& writer)
{
RemoveDirectoryMessage msg;
msg.nameKey = reader.ReadStringKey();
reader.ReadString(msg.name);
RemoveDirectoryResponse response;
m_messageSuccess = m_session.RemoveDirectory(response, msg) && m_messageSuccess;
writer.WriteBool(response.result);
writer.WriteU32(response.errorCode);
writer.WriteU32(response.directoryTableSize);
return true;
}
bool ProcessImpl::HandleListDirectory(BinaryReader& reader, BinaryWriter& writer)
{
ListDirectoryMessage msg;
reader.ReadString(msg.directoryName);
msg.directoryNameKey = reader.ReadStringKey();
ListDirectoryResponse response;
m_messageSuccess = m_session.GetListDirectoryInfo(response, msg.directoryName, msg.directoryNameKey) && m_messageSuccess;
writer.WriteU32(response.tableSize);
writer.WriteU32(response.tableOffset);
return true;
}
bool ProcessImpl::HandleUpdateTables(BinaryReader& reader, BinaryWriter& writer)
{
writer.WriteU32(m_session.GetDirectoryTableSize());
writer.WriteU32(m_session.GetFileMappingSize());
if (m_tempFilesModified)
{
// This should be very rare (lld-link.exe uses it for mt.exe).. if we get too many temp files we'll need to rethink this design
SCOPED_READ_LOCK(m_shared.tempFilesLock, l);
writer.WriteU32(u32(m_shared.tempFiles.size()));
for (auto& kv : m_shared.tempFiles)
{
writer.WriteStringKey(kv.first);
writer.WriteU64(kv.second.mappingWritten);
}
}
else
writer.WriteU32(0);
return HandleGetWrittenFiles(reader, writer);
}
bool ProcessImpl::HandleGetWrittenFiles(BinaryReader& reader, BinaryWriter& writer)
{
u32& writtenCount = *(u32*)writer.AllocWrite(4);
u32 count = 0;
SCOPED_FUTEX_READ(m_shared.writtenFilesLock, lock);
for (auto& kv : m_shared.writtenFiles)
{
WrittenFile& wf = kv.second;
if (wf.owner == this)
continue;
u64 fileSize;
if (wf.mappingHandle.IsValid())
fileSize = wf.mappingWritten;
else if (!FileExists(m_session.m_logger, wf.backedName.c_str(), &fileSize))
continue;
else if (wf.name == wf.backedName)
continue;
writer.WriteStringKey(kv.first);
writer.WriteString(wf.name);
writer.WriteString(wf.backedName);
writer.WriteU64(wf.mappingHandle.ToU64());
writer.WriteU64(fileSize);
++count;
}
writtenCount = count;
return true;
}
bool ProcessImpl::HandleCreateProcess(BinaryReader& reader, BinaryWriter& writer)
{
#if PLATFORM_LINUX
// This process will become the parent of a process if it becomes orphaned
static bool subreaper = []() { prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0); return true; }();
#endif
TString applicationStr = reader.ReadString();
TString commandLineWithoutApplication = reader.ReadLongString();
UBA_ASSERTF(!applicationStr.empty() && (applicationStr[0] != '\"' || applicationStr[1] != '\"'), TC("Invalid application name: %s"), applicationStr.c_str());
StringBuffer<> currentDir;
reader.ReadString(currentDir);
if (currentDir.IsEmpty())
currentDir.Append(m_startInfo.workingDir);
bool startSuspended = reader.ReadBool();
bool isChild = reader.ReadBool();
StringBuffer<> temp;
ProcessStartInfo info;
info.application = applicationStr.c_str();
info.arguments = commandLineWithoutApplication.c_str();
info.workingDir = currentDir.data;
info.logFile = InternalGetChildLogFile(temp);
info.priorityClass = m_startInfo.priorityClass;
info.logLineUserData = this;
info.logLineFunc = [](void* userData, const tchar* line, u32 length, LogEntryType type) { ((ProcessImpl*)userData)->LogLine(false, TString(line, length), type); };
info.startSuspended = startSuspended;
info.rootsHandle = m_startInfo.rootsHandle;
ProcessImpl* parent = isChild ? this : nullptr;
ProcessHandle h = m_session.InternalRunProcess(info, true, parent, true);
if (!h.m_process)
{
// TODO: Do we need to log something here? This failure is most likely a cause of something else so error logging might show somewhere else
writer.WriteU32(0); // childProcessId
return true;
}
u32 childProcessId = ~0u;
if (isChild)
{
m_childProcesses.push_back(h);
childProcessId = u32(m_childProcesses.size());
}
auto& process = *(ProcessImpl*)h.m_process;
process.m_echoOn = m_echoOn;
const char* detoursLib = m_session.m_detoursLibrary[IsArmBinary].c_str();
u32 detoursLibLen = u32(m_session.m_detoursLibrary[IsArmBinary].size());
writer.WriteU32(childProcessId);
writer.WriteU32(info.rules->index);
writer.WriteU32(detoursLibLen);
writer.WriteBytes(detoursLib, detoursLibLen);
writer.WriteString(m_realWorkingDir);
writer.WriteString(process.m_realApplication);
#if !PLATFORM_WINDOWS
writer.WriteU64(process.m_comMemory.handle.uid);
writer.WriteU32(process.m_comMemory.offset);
writer.WriteString(info.logFile);
#endif
#if UBA_DEBUG_TRACK_PROCESS
m_session.m_debugLogger->Info(TC("CreateChildProcess (%u creating child %u at index %u) %s %s (%s)"), m_id, process.m_id, childProcessId-1, process.m_realApplication.c_str(), commandLineWithoutApplication.c_str(), info.logFile);
#endif
return true;
}
bool ProcessImpl::HandleStartProcess(BinaryReader& reader, BinaryWriter& writer)
{
u32 processId = reader.ReadU32();
UBA_ASSERT(processId > 0 && processId <= m_childProcesses.size());
auto& process = *(ProcessImpl*)m_childProcesses[processId - 1].m_process;
bool result = reader.ReadBool();
u32 lastError = reader.ReadU32();
auto setWaitForParent = MakeGuard([&process]() { process.m_waitForParent.Set(); });
if (!result)
{
#if PLATFORM_WINDOWS
if (lastError == ERROR_FILENAME_EXCED_RANGE) // This is a command line issue so we dont' want uba to be blamed just because it outputs below logging entry
return true;
#endif
m_session.m_logger.Logf(LogEntryType_Info, TC("Detoured process failed to start child process - %s. %s (Working dir: %s)"), LastErrorToText(lastError).data, process.m_realApplication.c_str(), process.m_realWorkingDir);
return true;
}
#if PLATFORM_WINDOWS
HANDLE nativeProcessHandle = (HANDLE)reader.ReadU64();
u32 nativeProcessId = reader.ReadU32();
HANDLE nativeThreadHandle = (HANDLE)reader.ReadU64();
if (nativeProcessHandle)
{
DuplicateHandle((HANDLE)m_nativeProcessHandle, nativeProcessHandle, GetCurrentProcess(), (HANDLE*)&process.m_nativeProcessHandle, 0, false, DUPLICATE_SAME_ACCESS);
if (!process.m_nativeProcessHandle || process.m_nativeProcessHandle == InvalidProcHandle)
return CancelWithError(m_session.m_logger.Error(TC("Failed to duplicate handle for child process")));
DuplicateHandle((HANDLE)m_nativeProcessHandle, nativeThreadHandle, GetCurrentProcess(), &process.m_nativeThreadHandle, 0, false, DUPLICATE_SAME_ACCESS);
if (!process.m_nativeThreadHandle || process.m_nativeThreadHandle == INVALID_HANDLE_VALUE)
return CancelWithError(m_session.m_logger.Error(TC("Failed to duplicate handle for child thread")));
process.m_nativeProcessId = nativeProcessId;
}
#else
u64 nativeProcessHandle = reader.ReadU64();
u32 nativeProcessId = reader.ReadU32();
u64 nativeThreadHandle = reader.ReadU64();
process.m_nativeProcessHandle = (ProcHandle)nativeProcessHandle;
process.m_nativeProcessId = nativeProcessId;
#endif
#if UBA_DEBUG_TRACK_PROCESS
m_session.m_debugLogger->Info(TC("WaitForChildProcessReady (%u waiting for child %u at index %u)"), m_id, process.m_id, processId-1);
#endif
setWaitForParent.Execute();
#if UBA_DEBUG_TRACK_PROCESS
m_session.m_debugLogger->Info(TC("WaitForChildProcessReadyDone (%u waiting for child %u at index %u)"), m_id, process.m_id, processId-1);
#endif
#if PLATFORM_WINDOWS
// TOOD: This is ugly, should use event or something instead.. right now we make sure to wait and not return until we know payload has been uploaded etc
// It is a very uncommon usecase that processes starts suspended.. some process in ninja/cmake does it when building clang/llvm
if (process.m_startInfo.startSuspended)
while (process.m_nativeThreadHandle)
Sleep(1);
#endif
return true;
}
bool ProcessImpl::HandleExitChildProcess(BinaryReader& reader, BinaryWriter& writer)
{
u32 nativeProcessId = reader.ReadU32();
for (auto& child : m_childProcesses)
{
auto& process = *(ProcessImpl*)child.m_process;
if (process.m_nativeProcessId != nativeProcessId)
continue;
process.m_parentReportedExit = true;
return true;
}
UBA_ASSERT(false);
return true;
}
bool ProcessImpl::HandleCreateTempFile(BinaryReader& reader, BinaryWriter& writer)
{
CreateTempFile(reader, m_nativeProcessHandle, m_startInfo.application);
return true;
}
bool ProcessImpl::HandleOpenTempFile(BinaryReader& reader, BinaryWriter& writer)
{
OpenTempFile(reader, writer, m_startInfo.application);
return true;
}
bool ProcessImpl::HandleVirtualAllocFailed(BinaryReader& reader, BinaryWriter& writer)
{
StringBuffer<> allocType;
reader.ReadString(allocType);
u32 error = reader.ReadU32();
m_session.AllocFailed(*this, allocType.data, error);
return true;
}
bool ProcessImpl::HandleLog(BinaryReader& reader, BinaryWriter& writer)
{
bool printInSession = reader.ReadBool();
bool isError = reader.ReadBool();
TString line = reader.ReadString();
LogEntryType entryType = isError ? LogEntryType_Error : LogEntryType_Info;
if (!m_session.LogLine(*this, line.c_str(), entryType))
return CancelWithError();
InternalLogLine(printInSession, std::move(line), entryType);
return true;
}
bool ProcessImpl::HandleEchoOn(BinaryReader& reader, BinaryWriter& writer)
{
m_echoOn = reader.ReadBool();
return true;
}
bool ProcessImpl::HandleInputDependencies(BinaryReader& reader, BinaryWriter& writer)
{
UBA_ASSERT(m_startInfo.trackInputs);
if (u64 reserveSize = reader.Read7BitEncoded())
m_trackedInputs.reserve(m_trackedInputs.size() + reserveSize);
u32 toRead = reader.ReadU32();
u8* pos = m_trackedInputs.data() + m_trackedInputs.size();
m_trackedInputs.resize(m_trackedInputs.size() + toRead);
reader.ReadBytes(pos, toRead);
return true;
}
bool ProcessImpl::HandleExit(BinaryReader& reader, BinaryWriter& writer)
{
m_gotExitMessage = true;
m_nativeProcessExitCode = reader.ReadU32();
StringBuffer<> logName;
reader.ReadString(logName);
ProcessStats stats;
stats.Read(reader, ~0u);
KernelStats kernelStats;
kernelStats.Read(reader, ~0u);
m_processStats.Add(stats);
m_kernelStats.Add(kernelStats);
return false;
}
bool ProcessImpl::HandleFlushWrittenFiles(BinaryReader& reader, BinaryWriter& writer)
{
WriteFilesToDisk(false);
bool result = m_session.FlushWrittenFiles(*this);
writer.WriteBool(result);
return true;
}
bool ProcessImpl::HandleUpdateEnvironment(BinaryReader& reader, BinaryWriter& writer)
{
StringBuffer<> reason;
reader.ReadString(reason);
bool resetStats = reader.ReadBool();
bool result = m_session.UpdateEnvironment(*this, reason, resetStats);
writer.WriteBool(result);
return true;
}
bool ProcessImpl::HandleGetNextProcess(BinaryReader& reader, BinaryWriter& writer)
{
u32 prevExitCode = reader.ReadU32();
NextProcessInfo nextProcess;
StackBinaryWriter<16 * 1024> statsWriter;
ProcessStats processStats;
processStats.Read(reader, TraceVersion);
processStats.Add(m_processStats);
processStats.startupTime = m_processStats.startupTime;
processStats.wallTime = GetTime() - m_startTime;
processStats.cpuTime = 0;
processStats.hostTotalTime = m_processStats.hostTotalTime;
KernelStats kernelStats;
kernelStats.Read(reader, TraceVersion);
kernelStats.Add(m_kernelStats);
processStats.Write(statsWriter);
if (m_runningRemote)
m_sessionStats.Write(statsWriter);
m_storageStats.Write(statsWriter);
kernelStats.Write(statsWriter);
BinaryReader statsReader(statsWriter.GetData(), 0, statsWriter.GetPosition());
WriteFilesToDisk(false);
bool newProcess = false;
m_exitCode = prevExitCode;
m_messageSuccess = m_session.GetNextProcess(*this, newProcess, nextProcess, prevExitCode, statsReader) && m_messageSuccess;
writer.WriteBool(newProcess);
m_exitCode = ~0u;
if (!newProcess)
return true;
m_startInfo.argumentsStr = nextProcess.arguments;
m_startInfo.descriptionStr = nextProcess.description;
m_startInfo.logFileStr = nextProcess.logFile;
m_startInfo.arguments = m_startInfo.argumentsStr.c_str();
m_startInfo.description = m_startInfo.descriptionStr.c_str();
m_startInfo.logFile = m_startInfo.logFileStr.c_str();
m_childProcesses.clear();
m_logLines.clear();
m_trackedInputs.clear();
m_trackedOutputs.clear();
m_shared.writtenFiles.clear();
ClearTempFiles();
m_processStats = {};
m_sessionStats = {};
m_storageStats = {};
m_kernelStats = {};
m_startTime = GetTime();
writer.WriteString(nextProcess.arguments);
writer.WriteString(nextProcess.workingDir);
writer.WriteString(nextProcess.description);
writer.WriteString(nextProcess.logFile);
return true;
}
bool ProcessImpl::HandleCustom(BinaryReader& reader, BinaryWriter& writer)
{
m_session.CustomMessage(*this, reader, writer);
return true;
}
bool ProcessImpl::HandleSHGetKnownFolderPath(BinaryReader& reader, BinaryWriter& writer)
{
m_session.SHGetKnownFolderPath(*this, reader, writer);
return true;
}
bool ProcessImpl::HandleRpcCommunication(BinaryReader& reader, BinaryWriter& writer)
{
//m_session.RpcCommunication(*this, reader, writer);
return true;
}
bool ProcessImpl::HandleHostRun(BinaryReader& reader, BinaryWriter& writer)
{
u16 size = reader.ReadU16();
BinaryReader reader2(reader.GetPositionData(), 0, size);
m_session.HostRun(reader2, writer);
return true;
}
bool ProcessImpl::HandleResolveCallstack(BinaryReader& reader, BinaryWriter& writer)
{
m_session.GetSymbols(m_startInfo.application, m_isArmBinary, reader, writer);
return true;
}
bool ProcessImpl::HandleCheckRemapping(BinaryReader& reader, BinaryWriter& writer)
{
m_session.CheckRemapping(*this, reader, writer);
return true;
}
bool ProcessImpl::HandleTakeFileOwnership(BinaryReader& reader, BinaryWriter& writer)
{
StringKey fileNameKey = reader.ReadStringKey();
StringBuffer<> fileName;
reader.ReadString(fileName);
bool isOwner = false;
FileMappingHandle mappingHandle;
u64 mappingWritten = 0;
SCOPED_FUTEX(m_shared.writtenFilesLock, lock);
auto findIt = m_shared.writtenFiles.find(fileNameKey);
if (findIt != m_shared.writtenFiles.end())
{
WrittenFile& file = findIt->second;
isOwner = file.owner == this;
mappingHandle = file.mappingHandle;
mappingWritten = file.mappingWritten;
file.owner = this;
file.originalMappingHandle = {};
}
writer.WriteBool(isOwner);
writer.WriteU64(mappingHandle.ToU64());
writer.WriteU64(mappingWritten);
return true;
}
bool ProcessImpl::HandleRunSpecialProgram(BinaryReader& reader, BinaryWriter& writer)
{
m_session.RunSpecialProgram(*this, reader, writer);
return true;
}
bool ProcessImpl::CreateTempFile(BinaryReader& reader, ProcHandle nativeProcessHandle, const tchar* application)
{
StringKey key = reader.ReadStringKey();
StringBuffer<> fileName;
reader.ReadString(fileName);
FileMappingHandle mappingHandle = FileMappingHandle::FromU64(reader.ReadU64());
u64 mappingHandleSize = reader.ReadU64();
FileMappingHandle newHandle;
if (!DuplicateFileMapping(m_session.m_logger, nativeProcessHandle, mappingHandle, GetCurrentProcessHandle(), newHandle, 0, false, DUPLICATE_SAME_ACCESS, fileName.data))
{
m_session.m_logger.Error(TC("Failed to duplicate handle for temp file (%s)"), fileName.data);
return true;
}
SCOPED_WRITE_LOCK(m_shared.tempFilesLock, tempLock);
auto insres = m_shared.tempFiles.try_emplace(key, WrittenFile{ nullptr, StringKeyZero, fileName.data, fileName.data, newHandle, mappingHandleSize, mappingHandle });
if (insres.second)
return true;
++m_tempFilesModified;
WrittenFile& tempFile = insres.first->second;
FileMappingHandle oldMapping = tempFile.mappingHandle;
tempFile.mappingHandle = newHandle;
tempFile.mappingWritten = mappingHandleSize;
tempLock.Leave();
CloseFileMapping(m_session.m_logger, oldMapping, fileName.data);
return true;
}
bool ProcessImpl::OpenTempFile(BinaryReader& reader, BinaryWriter& writer, const tchar* application)
{
StringKey fileKey = reader.ReadStringKey();
StringBuffer<> fileName;
reader.ReadString(fileName);
u64 mappingHandle = 0;
u64 mappingWritten = 0;
SCOPED_READ_LOCK(m_shared.tempFilesLock, lock);
auto findIt = m_shared.tempFiles.find(fileKey);
if (findIt != m_shared.tempFiles.end())
{
mappingHandle = findIt->second.mappingHandle.ToU64();
mappingWritten = findIt->second.mappingWritten;
}
writer.WriteU64(mappingHandle);
writer.WriteU64(mappingWritten);
return true;
}
bool ProcessImpl::WriteFilesToDisk(bool isExiting)
{
Vector<WrittenFile*> files;
TimerScope ts(m_processStats.writeFiles);
SCOPED_FUTEX(m_shared.writtenFilesLock, lock);
for (auto& kv : m_shared.writtenFiles)
{
// TODO: This requires some more logic. In some cases we have long living batch files as parent and then maybe we want to write out files to disk
// But for now we only let the root process write out all output files
if (m_parentProcess)
continue;
kv.second.owner = nullptr;
if (kv.second.mappingHandle.IsValid())
files.push_back(&kv.second);
}
// We want file count to match number of files.. and it is actually fine that count is 0
m_processStats.writeFiles.count += u32(files.size() - 1);
if (!m_session.WriteFilesToDisk(*this, files.data(), u32(files.size())))
return false;
if (m_startInfo.trackInputs)
{
u64 totalBytes = 0;
for (auto& kv : m_shared.writtenFiles)
totalBytes += GetStringWriteSize(kv.second.name.c_str(), kv.second.name.size());
m_trackedOutputs.resize(totalBytes);
BinaryWriter writer(m_trackedOutputs.data(), 0, totalBytes);
for (auto& kv : m_shared.writtenFiles)
writer.WriteString(kv.second.name);
}
return true;
}
const tchar* ProcessImpl::InternalGetChildLogFile(StringBufferBase& temp)
{
if (!*m_startInfo.logFile)
return TC("");
temp.Append(m_startInfo.logFile);
if (TStrcmp(temp.data + temp.count - 4, TC(".log")) == 0)
temp.Resize(temp.count - 4);
temp.Appendf(TC("_CHILD%03u.log"), u32(m_childProcesses.size()));
return temp.data;
}
#if PLATFORM_WINDOWS
/** Disables Power Throttling in the provided process, to ensure P-Cores are preferred over E Cores on hybrid architecture intel platforms */
void DisableProcessPowerThrottling(HANDLE ProcessHandle)
{
PROCESS_POWER_THROTTLING_STATE PowerThrottling;
RtlZeroMemory(&PowerThrottling, sizeof(PowerThrottling));
// Enable PowerThrottling policies for the process
// and disable power throttling by setting the state mask to 0
PowerThrottling.Version = PROCESS_POWER_THROTTLING_CURRENT_VERSION;
PowerThrottling.ControlMask = PROCESS_POWER_THROTTLING_EXECUTION_SPEED;
PowerThrottling.StateMask = 0;
SetProcessInformation(ProcessHandle, ProcessPowerThrottling, &PowerThrottling, sizeof(PowerThrottling));
}
#endif
u32 ProcessImpl::InternalCreateProcess(void* environment, FileMappingHandle communicationHandle, u64 communicationOffset)
{
SCOPED_FUTEX(m_initLock, initLock);
Logger& logger = m_session.m_logger;
#if PLATFORM_WINDOWS
HANDLE readPipe = INVALID_HANDLE_VALUE;
auto readPipeGuard = MakeGuard([&]() { CloseHandle(readPipe); });
bool allowCustomAllocator = true;
if (!m_parentProcess)
{
if (IsRunningArm())
{
SCOPED_FUTEX(m_session.m_isX64ApplicationLock, lock);
auto insres = m_session.m_isX64Application.try_emplace(m_realApplication);
if (insres.second)
{
ImageInfo imageInfo;
if (!GetImageInfo(imageInfo, m_session.m_logger, m_realApplication.c_str(), false))
return UBA_EXIT_CODE(19);
insres.first->second = imageInfo.isX64;
}
m_isArmBinary = !insres.first->second;
if (!m_isArmBinary)
allowCustomAllocator = false;
}
const char* detoursLib = m_session.m_detoursLibrary[m_isArmBinary].c_str();
if (!*detoursLib)
detoursLib = UBA_DETOURS_LIBRARY_ANSI;
StringBuffer<> application(m_realApplication);
m_session.VirtualizePath(application, m_startInfo.rootsHandle);
TString commandLine;
if (application.EndsWith(TCV(".bat")) && !IsRunningWine()) // If there are quotes around arguments used by batch file we need to quote the entire thing on windows
commandLine = TString(TC("\"\"")) + application.data + TC("\" ") + m_startInfo.arguments + TC("\"");
else
commandLine = TString(TC("\"")) + application.data + TC("\" ") + m_startInfo.arguments;
if (m_extractExports)
{
const tchar* pos = nullptr;
Contains(commandLine.c_str(), g_extractExportsStr, true, &pos);
commandLine.erase(pos - commandLine.c_str(), sizeof_array(g_extractExportsStr));
}
LPCSTR dlls[] = { detoursLib };
STARTUPINFOEX siex;
STARTUPINFO& si = siex.StartupInfo;
ZeroMemory(&siex, sizeof(STARTUPINFOEX));
si.cb = sizeof(STARTUPINFOEX);
PROCESS_INFORMATION processInfo;
ZeroMemory(&processInfo, sizeof(processInfo));
DWORD creationFlags = CREATE_DEFAULT_ERROR_MODE | CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_PROCESS_GROUP | m_startInfo.priorityClass;
BOOL inheritHandles = false;
SIZE_T attributesBufferSize = 0;
::InitializeProcThreadAttributeList(nullptr, 1, 0, &attributesBufferSize);
u8 attributesBuffer[128];
if (sizeof(attributesBuffer) < attributesBufferSize)
{
logger.Error(TC("Attributes buffer is too small, needs to be at least %llu"), u64(attributesBufferSize));
return UBA_EXIT_CODE(2);
}
PPROC_THREAD_ATTRIBUTE_LIST attributes = reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(attributesBuffer);
if (!::InitializeProcThreadAttributeList(attributes, 1, 0, &attributesBufferSize))
{
logger.Error(TC("InitializeProcThreadAttributeList failed (%s)"), LastErrorToText().data);
return UBA_EXIT_CODE(3);
}
auto destroyAttr = MakeGuard([&]() { ::DeleteProcThreadAttributeList(attributes); });
siex.lpAttributeList = attributes;
creationFlags |= EXTENDED_STARTUPINFO_PRESENT;
SCOPED_FUTEX_READ(m_session.m_processJobObjectLock, jobObjectLock);
if (!m_session.m_processJobObject)
{
m_cancelEvent.Set();
return ProcessCancelExitCode;
}
bool isDetachedProcess = m_startInfo.rules->AllowDetach() && m_detourEnabled;
HANDLE hJob = CreateJobObject(nullptr, nullptr);
JOBOBJECT_EXTENDED_LIMIT_INFORMATION info = { };
info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_BREAKAWAY_OK;
SetInformationJobObject(hJob, JobObjectExtendedLimitInformation, &info, sizeof(info));
m_accountingJobObject = hJob;
HANDLE jobs[] = { m_session.m_processJobObject, m_accountingJobObject };
if (!::UpdateProcThreadAttribute(attributes, 0, PROC_THREAD_ATTRIBUTE_JOB_LIST, jobs, sizeof(jobs), nullptr, nullptr))
{
logger.Error(TC("UpdateProcThreadAttribute failed when setting job list (%s)"), LastErrorToText().data);
return UBA_EXIT_CODE(4);
}
if (isDetachedProcess)
creationFlags |= DETACHED_PROCESS;
else
creationFlags |= CREATE_NO_WINDOW;
u32 retryCount = 0;
while (!IsCancelled())
{
LPCWSTR workingDir = *m_realWorkingDir ? m_realWorkingDir : NULL;
if (m_detourEnabled)
{
if (DetourCreateProcessWithDlls(m_realApplication.c_str(), (tchar*)commandLine.c_str(), NULL, NULL, inheritHandles, creationFlags, environment, workingDir, &si, &processInfo, sizeof_array(dlls), dlls, NULL))
{
DisableProcessPowerThrottling(processInfo.hProcess);
break;
}
}
else
{
SECURITY_ATTRIBUTES saAttr;
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
HANDLE writePipe;
if (!CreatePipe(&readPipe, &writePipe, &saAttr, 0))
{
logger.Error(TC("CreatePipe failed"));
return UBA_EXIT_CODE(18);
}
auto writePipeGuard = MakeGuard([&]() { CloseHandle(writePipe); });
if (!SetHandleInformation(readPipe, HANDLE_FLAG_INHERIT, 0))
{
logger.Error(TC("SetHandleInformation failed"));
return UBA_EXIT_CODE(18);
}
si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
si.hStdError = writePipe;
si.hStdOutput = writePipe;
si.dwFlags |= STARTF_USESTDHANDLES;
if (CreateProcessW(m_realApplication.c_str(), (tchar*)commandLine.c_str(), NULL, NULL, TRUE, creationFlags, environment, workingDir, &si, &processInfo))
{
DisableProcessPowerThrottling(processInfo.hProcess);
break;
}
}
DWORD error = GetLastError();
if (error == ERROR_ACCESS_DENIED || error == ERROR_INTERNAL_ERROR)
{
// We have no idea why this is happening.. but it seems to recover when retrying.
// Could it be related to two process spawning at the exact same time or something?
// It happens extremely rarely and can happen on both host and remotes
bool retry = retryCount++ < 5;
const tchar* errorText = error == ERROR_ACCESS_DENIED ? TC("access denied") : TC("internal error");
logger.Logf(retry ? LogEntryType_Info : LogEntryType_Error, TC("DetourCreateProcessWithDllEx failed with %s, retrying %s (Working dir: %s)"), errorText, commandLine.c_str(), workingDir);
if (!retry)
return UBA_EXIT_CODE(5);
Sleep(100 + (rand() % 200)); // We have no idea
ZeroMemory(&processInfo, sizeof(processInfo));
continue;
}
else if (error == ERROR_WRITE_PROTECT) // AWS shutting down
{
m_cancelEvent.Set();
return ProcessCancelExitCode;
}
LastErrorToText lett(error);
const tchar* errorText = lett.data;
if (error == ERROR_INVALID_HANDLE)
errorText = TC("Can't detour a 32-bit target process from a 64-bit parent process.");
if (!IsCancelled())
{
if (error == ERROR_DIRECTORY)
logger.Error(TC("HOW CAN THIS HAPPEN? '%s'"), workingDir);
logger.Error(TC("DetourCreateProcessWithDllEx failed: %s (Working dir: %s). Exit code: %u - %s"), commandLine.c_str(), workingDir, error, errorText);
}
return UBA_EXIT_CODE(6);
}
//auto closeThreadHandle = MakeGuard([&]() { CloseHandle(pi.hThread); });
//auto closeProcessHandle = MakeGuard([&]() { CloseHandle(pi.hProcess); });
destroyAttr.Execute();
m_nativeProcessHandle = (ProcHandle)(u64)processInfo.hProcess;
m_nativeProcessId = processInfo.dwProcessId;
m_nativeThreadHandle = processInfo.hThread;
#if UBA_DEBUG_TRACK_PROCESS
m_session.m_debugLogger->Info(TC("CreateRealProcess (%u) %s %s"), m_id, m_realApplication.c_str(), m_startInfo.arguments);
#endif
}
else
{
#if UBA_DEBUG_TRACK_PROCESS
m_session.m_debugLogger->Info(TC("WaitingForParentReady (%u)"), m_id);
#endif
WaitForParent();
if (m_nativeProcessHandle == InvalidProcHandle) // Failed to create the child process
return UBA_EXIT_CODE(7);
#if UBA_DEBUG_TRACK_PROCESS
m_session.m_debugLogger->Info(TC("WaitingForParentReadyDone (%u)"), m_id);
#endif
m_extractExports = m_parentProcess->m_extractExports;
}
auto closeThreadHandle = MakeGuard([&]() { CloseHandle(m_nativeThreadHandle); m_nativeThreadHandle = 0; });
if (m_detourEnabled)
{
HANDLE hostProcess;
HANDLE currentProcess = GetCurrentProcess();
if (!DuplicateHandle(currentProcess, currentProcess, (HANDLE)m_nativeProcessHandle, &hostProcess, 0, FALSE, DUPLICATE_SAME_ACCESS))
{
if (!IsCancelled())
logger.Error(TC("Failed to duplicate host process handle for process (%s)"), LastErrorToText().data);
return UBA_EXIT_CODE(8);
}
DetoursPayload payload;
payload.processId = m_id;
payload.hostProcess = hostProcess;
payload.cancelEvent = m_cancelEvent.GetHandle();
payload.writeEvent = m_writeEvent.GetHandle();
payload.readEvent = m_readEvent.GetHandle();
payload.communicationHandle = communicationHandle.mh;
payload.communicationOffset = communicationOffset;
payload.rulesIndex = m_startInfo.rules->index;
payload.version = ProcessMessageVersion;
payload.runningRemote = m_runningRemote;
payload.allowKeepFilesInMemory = m_session.m_allowKeepFilesInMemory;
payload.allowOutputFiles = m_session.m_allowOutputFiles;
payload.suppressLogging = m_session.m_suppressLogging;
payload.isChild = m_parentProcess != nullptr;
payload.trackInputs = m_startInfo.trackInputs;
payload.useCustomAllocator = allowCustomAllocator && m_startInfo.useCustomAllocator && m_startInfo.rules->AllowMiMalloc();
payload.reportAllExceptions = m_startInfo.reportAllExceptions || m_startInfo.rules->ReportAllExceptions();
payload.isRunningWine = IsRunningWine();
payload.readIntermediateFilesCompressed = m_session.m_readIntermediateFilesCompressed;
payload.uiLanguage = m_startInfo.uiLanguage;
if (*m_startInfo.logFile)
{
#if !UBA_DEBUG_LOG_ENABLED
static bool runOnce = [&]() { logger.Warning(TC("Build has log files disabled so no logs will be produced")); return false; }();
#endif
payload.logFile.Append(m_startInfo.logFile);
}
if (!DetourCopyPayloadToProcess((HANDLE)m_nativeProcessHandle, DetoursPayloadGuid, &payload, sizeof(payload)))
{
logger.Error(TC("Failed to copy payload to process (%s)"), LastErrorToText().data);
return UBA_EXIT_CODE(9);
}
}
bool affinitySet = false;
if (!m_messageThread.Wait(0))
{
GroupAffinity aff;
if (m_messageThread.GetGroupAffinity(aff))
affinitySet = SetThreadGroupAffinity(m_nativeThreadHandle, aff);
}
if (!affinitySet)
{
if (!AlternateThreadGroupAffinity(m_nativeThreadHandle))
{
logger.Error(TC("Failed to set thread group affinity to process"));//% ls. (% ls)"), commandLine.c_str(), LastErrorToText().data);
return UBA_EXIT_CODE(10);
}
}
m_processStats.startupTime = GetTime() - m_startTime;
if (!m_startInfo.startSuspended && ResumeThread(m_nativeThreadHandle) == -1)
{
logger.Error(TC("Failed to resume thread for"));//% ls. (% ls)", commandLine.c_str(), LastErrorToText().data);
return UBA_EXIT_CODE(11);
}
closeThreadHandle.Execute();
if (!m_detourEnabled)
{
PipeReader pipeReader(*this, LogEntryType_Info);
TString currentString;
while (true)
{
DWORD exitCode = STILL_ACTIVE;
GetExitCodeProcess((HANDLE)m_nativeProcessHandle, &exitCode);
while (true)
{
DWORD totalBytesAvailable = 0;
if (!PeekNamedPipe(readPipe, NULL, 0, NULL, &totalBytesAvailable, NULL))
break;
if (totalBytesAvailable == 0)
break;
char buf[4096];
DWORD readCount = 0;
if (!::ReadFile(readPipe, buf, sizeof(buf) - 1, &readCount, NULL))
break;
buf[readCount] = 0;
pipeReader.ReadData(buf, readCount);
}
if (exitCode != STILL_ACTIVE)
break;
WaitForSingleObject((HANDLE)m_nativeProcessHandle, 1000);
}
}
#else // #if PLATFORM_WINDOWS
if (!m_parentProcess)
{
const char* realApplication = m_realApplication.c_str();
StringBuffer<> tempApplication;
if (m_realApplication.find(' ') != TString::npos)
{
tempApplication.Append('\"').Append(m_realApplication).Append('\"');
realApplication = tempApplication.data;
}
Vector<TString> arguments;
if (!ParseArguments(m_startInfo.arguments, [&](const tchar* arg, u32 argLen)
{
if (Equals(arg, g_extractExportsStr))
return;
arguments.push_back(TString(arg, argLen));
}))
{
logger.Error("Failed to parse arguments: %s", m_startInfo.arguments);
return UBA_EXIT_CODE(16);
}
Vector<const char*> arguments2;
arguments2.reserve(arguments.size() + 2);
StringBuffer<512> application(m_startInfo.application);
m_session.VirtualizePath(application, m_startInfo.rootsHandle);
arguments2.push_back(application.data);
for (auto& s : arguments)
arguments2.push_back(s.data());
arguments2.push_back(nullptr);
auto argsArray = arguments2.data();
posix_spawnattr_t attr;
int res = posix_spawnattr_init(&attr);
UBA_ASSERTF(res == 0, TC("posix_spawnattr_init (%s)"), strerror(errno));
auto attrGuard = MakeGuard([&]() { posix_spawnattr_destroy(&attr); });
// We set process group because we want to make sure that all processes get killed when ctrl-c is pressed
res = posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETPGROUP);
UBA_ASSERTF(res == 0, TC("posix_spawnattr_setflags (%s)"), strerror(errno));
res = posix_spawnattr_setpgroup(&attr, getpgrp());
UBA_ASSERTF(res == 0, TC("posix_spawnattr_setpgroup (%s)"), strerror(errno));
posix_spawn_file_actions_t fileActions;
res = posix_spawn_file_actions_init(&fileActions);
UBA_ASSERTF(res == 0, TC("posix_spawn_file_actions_init (%s)"), strerror(errno));
auto actionsGuard = MakeGuard([&]() { posix_spawn_file_actions_destroy(&fileActions); });
if (*m_realWorkingDir)
{
#if PLATFORM_MAC
posix_spawn_file_actions_addchdir_np(&fileActions, m_realWorkingDir);
#else
//UBA_ASSERT(false); // TODO: Revisit
#endif
}
StringBuffer<128> comIdVar;
StringBuffer<512> workingDir;
StringBuffer<32> rulesStr;
StringBuffer<512> logFile;
StringBuffer<512> ldLibraryPath;
StringBuffer<512> detoursVar;
StringBuffer<32> processVar;
Vector<const char*> envvars;
const char* it = (const char*)environment;
while (*it)
{
const char* s = it;
envvars.push_back(s);
it += TStrlen(s) + 1;
}
int outPipe[2] = { -1, -1 };
int errPipe[2] = { -1, -1 };
auto pipeGuard0 = MakeGuard([&]() { if (outPipe[0] != -1) close(outPipe[0]); if (errPipe[0] != -1) close(errPipe[0]); });
auto pipeGuard1 = MakeGuard([&]() { if (outPipe[1] != -1) close(outPipe[1]); if (errPipe[1] != -1) close(errPipe[1]); });
if (m_detourEnabled)
{
const char* detoursLib = m_session.m_detoursLibrary[IsArmBinary].c_str();
if (*detoursLib)
{
#if PLATFORM_LINUX
//if (strchr(detoursLib, ' '))
{
const char* lastSlash = strrchr(detoursLib, '/');
UBA_ASSERT(lastSlash);
StringBuffer<> ldLibPath;
ldLibPath.Append(detoursLib, lastSlash - detoursLib);
ldLibraryPath.Append("LD_LIBRARY_PATH=").Append(ldLibPath);
detoursLib = lastSlash + 1;
}
#endif
}
else
detoursLib = "./" UBA_DETOURS_LIBRARY;
#if PLATFORM_LINUX
detoursVar.Append("LD_PRELOAD=").Append(detoursLib);
#else
detoursVar.Append("DYLD_INSERT_LIBRARIES=").Append(detoursLib);
#endif
processVar.Append("UBA_SESSION_PROCESS=").AppendValue(getpid());
comIdVar.Append("UBA_COMID=").AppendValue(communicationHandle.uid).Append('+').AppendValue(communicationOffset);
workingDir.Append("UBA_CWD=").Append(m_realWorkingDir);
rulesStr.Append("UBA_RULES=").AppendValue(m_startInfo.rules->index);
if (*m_startInfo.logFile)
{
#if !UBA_DEBUG_LOG_ENABLED
static bool runOnce = [&]() { logger.Warning(TC("Build has log files disabled so no logs will be produced")); return false; }();
#endif
logFile.Append("UBA_LOGFILE=").Append(m_startInfo.logFile);
}
//envvars.push_back("LD_DEBUG=bindings");
if (ldLibraryPath.count)
envvars.push_back(ldLibraryPath.data);
envvars.push_back(detoursVar.data);
envvars.push_back(processVar.data);
envvars.push_back(comIdVar.data);
envvars.push_back(workingDir.data);
envvars.push_back(rulesStr.data);
if (m_runningRemote)
envvars.push_back("UBA_REMOTE=1");
if (!logFile.IsEmpty())
envvars.push_back(logFile.data);
}
envvars.push_back(nullptr);
if (true)
{
if (pipe(outPipe) || pipe(errPipe))
{
logger.Error("pipe failed (%s)", strerror(errno));
return UBA_EXIT_CODE(18);
}
res = posix_spawn_file_actions_addclose(&fileActions, outPipe[0]);
UBA_ASSERTF(!res, "posix_spawn_file_actions_addclose outPipe[0] failed: %i", res);
res = posix_spawn_file_actions_addclose(&fileActions, errPipe[0]);
UBA_ASSERTF(!res, "posix_spawn_file_actions_addclose errPipe[1] failed: %i", res);
res = posix_spawn_file_actions_adddup2(&fileActions, outPipe[1], 1);
UBA_ASSERTF(!res, "posix_spawn_file_actions_adddup2 outPipe[1] failed: %i", res);
res = posix_spawn_file_actions_adddup2(&fileActions, errPipe[1], 2);
UBA_ASSERTF(!res, "posix_spawn_file_actions_adddup2 errPipe[1] failed: %i", res);
res = posix_spawn_file_actions_addclose(&fileActions, outPipe[1]);
UBA_ASSERTF(!res, "posix_spawn_file_actions_addclose outPipe[1] failed: %i", res);
res = posix_spawn_file_actions_addclose(&fileActions, errPipe[1]);
UBA_ASSERTF(!res, "posix_spawn_file_actions_addclose errPipe[1] failed: %i", res);
}
u32 retryCount = 0;
pid_t processID;
while (true)
{
res = posix_spawnp(&processID, m_realApplication.c_str(), &fileActions, &attr, (char**)argsArray, (char**)envvars.data());
if (res == 0)
break;
if (errno == ETXTBSY && retryCount < 5)
{
logger.Warning(TC("posix_spawn failed with ETXTBSY, will retry %s %s (Working dir: %s)"), m_realApplication.c_str(), m_startInfo.arguments, m_realWorkingDir);
Sleep(2000);
++retryCount;
continue;
}
logger.Error(TC("posix_spawn failed: %s %s (Working dir: %s) -> %i (%s)"), m_realApplication.c_str(), m_startInfo.arguments, m_realWorkingDir, res, strerror(errno));
return UBA_EXIT_CODE(12);
}
errno = 0;
int prio = getpriority(PRIO_PROCESS, processID);
if (prio != -1)
{
errno = 0;
if (setpriority(PRIO_PROCESS, processID, prio + 2) == -1)
{
UBA_ASSERTF(errno == ESRCH || errno == EPERM, TC("setpriority failed: %s. pid: %i prio: %i (%s)"), m_realApplication.c_str(), processID, prio + 2, strerror(errno));
}
}
m_processStats.startupTime = GetTime() - m_startTime;
m_nativeProcessHandle = (ProcHandle)1;
m_nativeProcessId = u32(processID);
pipeGuard1.Execute();
m_stdOutPipe = outPipe[0];
m_stdErrPipe = errPipe[0];
pipeGuard0.Cancel();
#if UBA_DEBUG_TRACK_PROCESS
m_session.m_debugLogger->Info(TC("CreateRealProcess (%u) %s %.100s"), m_id, m_realApplication.c_str(), m_startInfo.arguments);
#endif
}
else
{
#if UBA_DEBUG_TRACK_PROCESS
m_session.m_debugLogger->Info(TC("WaitingForParent (%u) %.100s"), m_id, m_realApplication.c_str());
#endif
//logger.Info("Waiting for parent");
WaitForParent();
//logger.Info("DONE waiting on parent");
if (m_nativeProcessHandle == InvalidProcHandle) // Failed to create the child process
return UBA_EXIT_CODE(7);
}
#endif
return 0;
}
u32 ProcessImpl::InternalExitProcess(bool cancel)
{
SCOPED_FUTEX(m_initLock, lock);
Logger& logger = m_session.m_logger;
ProcHandle handle = m_nativeProcessHandle;
if (handle == InvalidProcHandle)
return ~0u;
if (m_parentProcess)
WaitForParent();
m_nativeProcessHandle = InvalidProcHandle;
#if PLATFORM_WINDOWS
auto closeHandleGuard = MakeGuard([&]()
{
IO_COUNTERS ioCounters;
if (GetProcessIoCounters((HANDLE)handle, &ioCounters))
{
m_processStats.iopsRead = ioCounters.ReadOperationCount;
m_processStats.iopsWrite = ioCounters.WriteOperationCount;
m_processStats.iopsOther = ioCounters.OtherOperationCount;
}
CloseHandle((HANDLE)handle);
});
PROCESS_MEMORY_COUNTERS mem;
if (GetProcessMemoryInfo((HANDLE)handle, &mem, sizeof(mem)))
m_processStats.peakMemory = mem.PeakWorkingSetSize;
bool hadTimeout = false;
if (cancel)
TerminateProcess((HANDLE)handle, ProcessCancelExitCode);
else
{
while (true)
{
DWORD res = WaitForSingleObject((HANDLE)handle, 120 * 1000);
if (res == WAIT_OBJECT_0)
{
break;
}
if (res == WAIT_TIMEOUT)
{
if (!hadTimeout && m_nativeProcessExitCode != STILL_ACTIVE)
{
hadTimeout = true;
const tchar* gotMessage = m_gotExitMessage ? TC("Got") : TC("Did not get");
const tchar* isCancelledNewCheck = IsCancelled() ? TC("true") : TC("false");
logger.Info(TC("WaitForSingleObject timed out after 120 seconds waiting for process %s to exit (Exit code %u, %s ExitMessage and wrote %u files. Cancelled: %s. Runtime: %s). Will terminate and wait again"), m_startInfo.GetDescription(), m_nativeProcessExitCode, gotMessage, u32(m_shared.writtenFiles.size()), isCancelledNewCheck, TimeToText(GetTime() - m_startTime).str);
TerminateProcess((HANDLE)handle, m_nativeProcessExitCode);
continue;
}
logger.Error(TC("WaitForSingleObject failed while waiting for process %s to exit even after terminating it (%s)"), m_startInfo.GetDescription(), LastErrorToText().data);
}
else if (res == WAIT_FAILED)
logger.Error(TC("WaitForSingleObject failed while waiting for process to exit (%s)"), LastErrorToText().data);
else if (res == WAIT_ABANDONED)
logger.Error(TC("Abandoned, this should never happen"));
TerminateProcess((HANDLE)handle, UBA_EXIT_CODE(13));
return UBA_EXIT_CODE(13);
}
}
bool res = true;
if (!hadTimeout)
{
DWORD nativeExitCode = 0;
res = GetExitCodeProcess((HANDLE)handle, (DWORD*)&nativeExitCode);
if (!res && GetLastError() == ERROR_INVALID_HANDLE) // Was already terminated
return ~0u;
if (m_gotExitMessage || !m_detourEnabled)
m_nativeProcessExitCode = nativeExitCode;
}
if (res || cancel)
return m_nativeProcessExitCode;
logger.Warning(TC("GetExitCodeProcess failed (%s)"), LastErrorToText().data);
return UBA_EXIT_CODE(14);
#else
if (m_stdOutPipe != -1)
close(m_stdOutPipe);
if (m_stdErrPipe != -1)
close(m_stdErrPipe);
auto g = MakeGuard([this]() { m_nativeProcessId = 0; });
if (cancel)
{
if (m_nativeProcessId)
kill((pid_t)m_nativeProcessId, -1);
return m_nativeProcessExitCode;
}
if (m_parentProcess != nullptr) // We can't wait for grandchildren.. if we got here the parent reported the child as exited
return 0;
// Process should have been waited on here because of IsActive
int status = 0;
while (m_nativeProcessId)
{
int res = waitpid((pid_t)m_nativeProcessId, &status, 0);
if (res == -1)
{
logger.Error(TC("waitpid failed on %u (%s)"), m_nativeProcessId, strerror(errno));
return UBA_EXIT_CODE(15);
}
if (WIFEXITED(status))
{
m_nativeProcessExitCode = WEXITSTATUS(status);
break;
}
if (WIFSIGNALED(status))
{
//logger.Info(TC("SIGNALED"));
m_nativeProcessExitCode = WTERMSIG(status);
break;
}
Sleep(1);
}
return m_nativeProcessExitCode;
#endif
}
void ProcessImpl::InternalLogLine(bool printInSession, TString&& line, LogEntryType logType)
{
m_session.DevirtualizeString(line, m_startInfo.rootsHandle, true, TC("LogLine"));
LogLine(printInSession, std::move(line), logType);
}
#if !PLATFORM_WINDOWS
bool ProcessImpl::PollStdPipes(PipeReader& outReader, PipeReader& errReader, int timeoutMs)
{
if (m_stdOutPipe == -1)
return false;
auto pipeGuard = MakeGuard([&]() { close(m_stdOutPipe); m_stdOutPipe = -1; close(m_stdErrPipe); m_stdErrPipe = -1; });
PipeReader* pipeReaders[] = { &outReader, &errReader };
pollfd plist[] = { {m_stdOutPipe,POLLIN, 0}, {m_stdErrPipe,POLLIN, 0} };
int rval = poll(plist, sizeof_array(plist), timeoutMs);
if (rval < 0)
{
#if PLATFORM_MAC
m_session.m_logger.Error(TC("pipe polling error with %i (%s)"), rval, strerror(errno));
#endif
return false;
}
bool hasRead = false;
for (int i=0;i!=2;++i)
{
if (plist[i].revents & POLLERR) // If there is an error on any of them we hang up
return m_session.m_logger.Error(TC("pipe polling error"));
if (!(plist[i].revents & POLLIN))
continue;
char buffer[1024];
if (int bytesRead = read(plist[i].fd, buffer, sizeof_array(buffer) - 1))
{
hasRead = true;
buffer[bytesRead] = 0;
pipeReaders[i]->ReadData(buffer, bytesRead);
}
}
if (!hasRead)
if (plist[0].revents & POLLHUP && plist[1].revents & POLLHUP)
return false;
pipeGuard.Cancel();
return true;
}
#endif
void ProcessImpl::ClearTempFiles()
{
for (auto& pair : m_shared.tempFiles)
if (pair.second.mappingHandle.IsValid())
CloseFileMapping(m_session.m_logger, pair.second.mappingHandle, pair.second.name.c_str());
m_shared.tempFiles.clear();
}
void ProcessImpl::WaitForParent()
{
u64 startTime = GetTime();
while (!m_waitForParent.IsSet(500) && !IsCancelled())
{
if (TimeToMs(GetTime() - startTime) > 120 * 1000) //
{
startTime = GetTime();
m_session.m_logger.Error(TC("Waiting for parent process in createprocess has now taken more than 120 seconds."));
}
}
}
void ProcessImpl::WaitForChildrenExit()
{
for (auto& child : m_childProcesses)
{
auto& childProcess = *(ProcessImpl*)child.m_process;
childProcess.m_waitForParent.Set();
while (!childProcess.m_hasExited && !IsCancelled())
Sleep(10);
}
}
bool ProcessImpl::CancelWithError(bool dummy)
{
m_cancelEvent.Set();
return false;
}
}