You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
This change adds a new field to the ProcessStartInfo struct in UBA trace files for a description of the task, called "breadcrumbs" which allow to diagnose and backtrace the origin of a UBA task. This is also hooked up to the distributed shader compilation (added as new field in FTaskCommandData) and a descriptive string is generated for each shader compile job batch. UbaVisualizer is updated accordingly to integrate this new data when hovering over a task in the timeline. This feature can be enabled via the new CVar "r.ShaderCompiler.DistributedJobDescriptionLevel" with values 0 (Disabled), 1 (Basic information), 2 (Adds shader format to each job). It's disabled by default. #rb dan.elksnitis, henrik.karlsson #rnx [CL 36346831 by laura hermanns in 5.5 branch]
2954 lines
89 KiB
C++
2954 lines
89 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "UbaSession.h"
|
|
#include "UbaBottleneck.h"
|
|
#include "UbaCompressedObjFileHeader.h"
|
|
#include "UbaConfig.h"
|
|
#include "UbaFileAccessor.h"
|
|
#include "UbaObjectFile.h"
|
|
#include "UbaProcess.h"
|
|
#include "UbaStorage.h"
|
|
#include "UbaDirectoryIterator.h"
|
|
#include "UbaApplicationRules.h"
|
|
#include "UbaPathUtils.h"
|
|
#include "UbaProtocol.h"
|
|
#include "UbaStorageUtils.h"
|
|
#include "UbaWorkManager.h"
|
|
|
|
#if PLATFORM_WINDOWS
|
|
#include "UbaWinBinDependencyParser.h"
|
|
#include <powerbase.h>
|
|
#pragma comment(lib, "Powrprof.lib")
|
|
#elif PLATFORM_MAC
|
|
#include "UbaMacBinDependencyParser.h"
|
|
#include <mach/vm_statistics.h>
|
|
#include <mach/mach_types.h>
|
|
#include <mach/mach_init.h>
|
|
#include <mach/mach_host.h>
|
|
#include <mach/mach.h>
|
|
extern char **environ;
|
|
#else // PLATFORM_LINUX
|
|
#include "UbaLinuxBinDependencyParser.h"
|
|
#endif
|
|
|
|
#define UBA_DEBUG_TRACK_DIR 0 // UBA_DEBUG_LOGGER
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
#if PLATFORM_WINDOWS
|
|
typedef struct _PROCESSOR_POWER_INFORMATION {
|
|
ULONG Number;
|
|
ULONG MaxMhz;
|
|
ULONG CurrentMhz;
|
|
ULONG MhzLimit;
|
|
ULONG MaxIdleState;
|
|
ULONG CurrentIdleState;
|
|
} PROCESSOR_POWER_INFORMATION, *PPROCESSOR_POWER_INFORMATION;
|
|
#endif
|
|
|
|
namespace uba
|
|
{
|
|
bool g_dummy;
|
|
|
|
ProcessStartInfo::ProcessStartInfo() = default;
|
|
ProcessStartInfo::~ProcessStartInfo() = default;
|
|
ProcessStartInfo::ProcessStartInfo(const ProcessStartInfo&) = default;
|
|
|
|
const tchar* ProcessStartInfo::GetDescription() const
|
|
{
|
|
if (description && *description)
|
|
return description;
|
|
const tchar* d = application;
|
|
if (const tchar* lps = TStrrchr(d, PathSeparator))
|
|
d = lps + 1;
|
|
if (const tchar* lps2 = TStrrchr(d, NonPathSeparator))
|
|
d = lps2 + 1;
|
|
return d;
|
|
}
|
|
|
|
ProcessHandle::ProcessHandle()
|
|
: m_process(nullptr)
|
|
{
|
|
}
|
|
ProcessHandle::~ProcessHandle()
|
|
{
|
|
if (m_process)
|
|
m_process->Release();
|
|
}
|
|
|
|
ProcessHandle::ProcessHandle(const ProcessHandle& o)
|
|
{
|
|
m_process = o.m_process;
|
|
if (m_process)
|
|
m_process->AddRef();
|
|
}
|
|
|
|
ProcessHandle::ProcessHandle(ProcessHandle&& o) noexcept
|
|
{
|
|
m_process = o.m_process;
|
|
o.m_process = nullptr;
|
|
}
|
|
|
|
ProcessHandle& ProcessHandle::operator=(const ProcessHandle& o)
|
|
{
|
|
if (&o == this)
|
|
return *this;
|
|
if (o.m_process)
|
|
o.m_process->AddRef();
|
|
if (m_process)
|
|
m_process->Release();
|
|
m_process = o.m_process;
|
|
return *this;
|
|
}
|
|
|
|
ProcessHandle& ProcessHandle::operator=(ProcessHandle&& o) noexcept
|
|
{
|
|
if (&o == this)
|
|
return *this;
|
|
if (o.m_process == m_process)
|
|
{
|
|
if (o.m_process)
|
|
{
|
|
o.m_process->Release();
|
|
o.m_process = nullptr;
|
|
}
|
|
return *this;
|
|
}
|
|
if (m_process)
|
|
m_process->Release();
|
|
m_process = o.m_process;
|
|
o.m_process = nullptr;
|
|
return *this;
|
|
}
|
|
|
|
const ProcessStartInfo& ProcessHandle::GetStartInfo() const
|
|
{
|
|
UBA_ASSERT(m_process);
|
|
return m_process->GetStartInfo();
|
|
}
|
|
u32 ProcessHandle::GetId() const
|
|
{
|
|
UBA_ASSERT(m_process);
|
|
return m_process->GetId();
|
|
}
|
|
u32 ProcessHandle::GetExitCode() const
|
|
{
|
|
UBA_ASSERT(m_process);
|
|
return m_process->GetExitCode();
|
|
}
|
|
bool ProcessHandle::HasExited() const
|
|
{
|
|
UBA_ASSERT(m_process);
|
|
return m_process->HasExited();
|
|
}
|
|
bool ProcessHandle::WaitForExit(u32 millisecondsTimeout) const
|
|
{
|
|
UBA_ASSERT(m_process);
|
|
return m_process->WaitForExit(millisecondsTimeout);
|
|
}
|
|
const Vector<ProcessLogLine>& ProcessHandle::GetLogLines() const
|
|
{
|
|
UBA_ASSERT(m_process);
|
|
return m_process->GetLogLines();
|
|
}
|
|
const Vector<u8>& ProcessHandle::GetTrackedInputs() const
|
|
{
|
|
UBA_ASSERT(m_process);
|
|
return m_process->GetTrackedInputs();
|
|
}
|
|
const Vector<u8>& ProcessHandle::GetTrackedOutputs() const
|
|
{
|
|
UBA_ASSERT(m_process);
|
|
return m_process->GetTrackedOutputs();
|
|
}
|
|
u64 ProcessHandle::GetTotalProcessorTime() const
|
|
{
|
|
UBA_ASSERT(m_process);
|
|
return m_process->GetTotalProcessorTime();
|
|
}
|
|
u64 ProcessHandle::GetTotalWallTime() const
|
|
{
|
|
UBA_ASSERT(m_process);
|
|
return m_process->GetTotalWallTime();
|
|
}
|
|
void ProcessHandle::Cancel(bool terminate) const
|
|
{
|
|
UBA_ASSERT(m_process);
|
|
return m_process->Cancel(terminate);
|
|
}
|
|
const tchar* ProcessHandle::GetExecutingHost() const
|
|
{
|
|
UBA_ASSERT(m_process);
|
|
return m_process->GetExecutingHost();
|
|
}
|
|
bool ProcessHandle::IsRemote() const
|
|
{
|
|
UBA_ASSERT(m_process);
|
|
return m_process->IsRemote();
|
|
}
|
|
ProcessExecutionType ProcessHandle::GetExecutionType() const
|
|
{
|
|
UBA_ASSERT(m_process);
|
|
return m_process->GetExecutionType();
|
|
}
|
|
ProcessHandle::ProcessHandle(Process* process)
|
|
{
|
|
m_process = process;
|
|
process->AddRef();
|
|
}
|
|
|
|
void SessionCreateInfo::Apply(Config& config)
|
|
{
|
|
const ConfigTable* tablePtr = config.GetTable(TC("Session"));
|
|
if (!tablePtr)
|
|
return;
|
|
const ConfigTable& table = *tablePtr;
|
|
table.GetValueAsString(rootDir, TC("RootDir"));
|
|
table.GetValueAsString(traceName, TC("TraceName"));
|
|
table.GetValueAsString(traceOutputFile, TC("TraceOutputFile"));
|
|
table.GetValueAsString(extraInfo, TC("ExtraInfo"));
|
|
table.GetValueAsBool(logToFile, TC("LogToFile"));
|
|
table.GetValueAsBool(useUniqueId, TC("UseUniqueId"));
|
|
table.GetValueAsBool(disableCustomAllocator, TC("DisableCustomAllocator"));
|
|
table.GetValueAsBool(launchVisualizer, TC("LaunchVisualizer"));
|
|
table.GetValueAsBool(allowMemoryMaps, TC("AllowMemoryMaps"));
|
|
table.GetValueAsBool(allowKeepFilesInMemory, TC("AllowKeepFilesInMemory"));
|
|
table.GetValueAsBool(allowOutputFiles, TC("AllowOutputFiles"));
|
|
table.GetValueAsBool(allowSpecialApplications, TC("AllowSpecialApplications"));
|
|
table.GetValueAsBool(suppressLogging, TC("SuppressLogging"));
|
|
table.GetValueAsBool(shouldWriteToDisk, TC("ShouldWriteToDisk"));
|
|
table.GetValueAsBool(traceEnabled, TC("TraceEnabled"));
|
|
table.GetValueAsBool(detailedTrace, TC("DetailedTrace"));
|
|
table.GetValueAsBool(traceChildProcesses, TC("TraceChildProcesses"));
|
|
table.GetValueAsBool(storeObjFilesCompressed, TC("StoreObjFilesCompressed"));
|
|
table.GetValueAsBool(extractObjFilesSymbols, TC("ExtractObjFilesSymbols"));
|
|
}
|
|
|
|
void Session::AddEnvironmentVariableNoLock(const tchar* key, const tchar* value)
|
|
{
|
|
m_environmentVariables.insert(m_environmentVariables.end(), key, key + TStrlen(key));
|
|
m_environmentVariables.push_back('=');
|
|
m_environmentVariables.insert(m_environmentVariables.end(), value, value + TStrlen(value));
|
|
m_environmentVariables.push_back(0);
|
|
}
|
|
|
|
bool Session::WriteDirectoryEntriesInternal(DirectoryTable::Directory& dir, const StringKey& dirKey, const tchar* dirPath, bool isRefresh, u32& outTableOffset)
|
|
{
|
|
if (dir.tableOffset != InvalidTableOffset && !isRefresh)
|
|
{
|
|
isRefresh = true;
|
|
}
|
|
|
|
auto& dirTable = m_directoryTable;
|
|
|
|
u32 volumeSerial = 0;
|
|
u32 dirAttributes = 0;
|
|
u64 fileIndex = 0;
|
|
|
|
Vector<u8> memoryBlock;
|
|
memoryBlock.resize(4096);
|
|
u64 written = 0;
|
|
u32 itemCount = 0;
|
|
|
|
StringKeyHasher hasher;
|
|
u32 dirPathLen = TStrlen(dirPath);
|
|
if (dirPathLen)
|
|
{
|
|
StringBuffer<> forHash;
|
|
forHash.Append(dirPath, dirPathLen);
|
|
if (CaseInsensitiveFs)
|
|
forHash.MakeLower();
|
|
hasher.Update(forHash.data, forHash.count);
|
|
}
|
|
|
|
#if UBA_DEBUG_TRACK_DIR
|
|
g_debugLogger.BeginScope();
|
|
auto dg = MakeGuard([]() { g_debugLogger.EndScope(); });
|
|
g_debugLogger.Info(TC("TRACKDIR %s\n"), dirPath);
|
|
#endif
|
|
|
|
|
|
BinaryWriter memoryWriter(memoryBlock.data(), 0, memoryBlock.size());
|
|
|
|
bool res = TraverseDir(m_logger, dirPathLen ? dirPath : TC("/"),
|
|
[&](const DirectoryEntry& e)
|
|
{
|
|
StringBuffer<256> fileNameForHash;
|
|
fileNameForHash.Append(PathSeparator).Append(e.name, e.nameLen);
|
|
if (CaseInsensitiveFs)
|
|
fileNameForHash.MakeLower();
|
|
|
|
StringKey fileKey = ToStringKey(hasher, fileNameForHash.data, fileNameForHash.count);
|
|
auto res = dir.files.try_emplace(fileKey, ~0u);
|
|
if (!res.second)
|
|
return;
|
|
UBA_ASSERT(e.attributes);
|
|
memoryWriter.WriteString(e.name, e.nameLen);
|
|
|
|
#if UBA_DEBUG_TRACK_DIR
|
|
g_debugLogger.Info(TC(" %s (Size: %llu, Key: %s, Id: %llu)\n"), e.name, e.size, KeyToString(fileKey).data, e.id);
|
|
#endif
|
|
|
|
u64 id = e.id;
|
|
if (id == 0xffffffffffffffffllu) // When using projfs we might not have the file yet and in that case we need to make this up.
|
|
id = ++m_fileIndexCounter;
|
|
|
|
res.first->second = u32(memoryWriter.GetPosition()); // Temporary offset that will be used further down to calculate the real offset
|
|
memoryWriter.WriteU64(e.lastWritten);
|
|
memoryWriter.WriteU32(e.attributes);
|
|
memoryWriter.WriteU32(e.volumeSerial);
|
|
memoryWriter.WriteU64(id);
|
|
memoryWriter.WriteU64(e.size);
|
|
|
|
FileEntryAdded(res.first->first, e.lastWritten, e.size);
|
|
|
|
|
|
++itemCount;
|
|
if (memoryWriter.GetPosition() > memoryBlock.size() - MaxPath)
|
|
{
|
|
memoryBlock.resize(memoryBlock.size() * 2);
|
|
memoryWriter.ChangeData(memoryBlock.data(), memoryBlock.size());
|
|
}
|
|
}, true,
|
|
[&](const DirectoryInfo& e)
|
|
{
|
|
volumeSerial = e.volumeSerial;
|
|
dirAttributes = e.attributes;
|
|
fileIndex = e.id;
|
|
});
|
|
if (!res)
|
|
return false;
|
|
|
|
written = memoryWriter.GetPosition();
|
|
|
|
u64 storageSize = sizeof(StringKey) + Get7BitEncodedCount(dir.tableOffset) + Get7BitEncodedCount(itemCount) + written;
|
|
|
|
u32 tableOffset;
|
|
|
|
SCOPED_WRITE_LOCK(dirTable.m_memoryLock, memoryLock);
|
|
u32 writePos = dirTable.m_memorySize;
|
|
BinaryWriter tableWriter(dirTable.m_memory + dirTable.m_memorySize);
|
|
|
|
if (isRefresh)
|
|
{
|
|
tableWriter.Write7BitEncoded(storageSize);
|
|
tableWriter.WriteStringKey(dirKey);
|
|
tableOffset = writePos + u32(tableWriter.GetPosition());
|
|
tableWriter.Write7BitEncoded(dir.tableOffset);
|
|
}
|
|
else
|
|
{
|
|
storageSize += sizeof(u32)*2 + sizeof(u64);
|
|
tableWriter.Write7BitEncoded(storageSize);
|
|
tableWriter.WriteStringKey(dirKey);
|
|
tableOffset = writePos + u32(tableWriter.GetPosition());
|
|
tableWriter.Write7BitEncoded(dir.tableOffset);
|
|
UBA_ASSERT(dirAttributes != 0);
|
|
tableWriter.WriteU32(dirAttributes);
|
|
tableWriter.WriteU32(volumeSerial);
|
|
tableWriter.WriteU64(fileIndex);
|
|
}
|
|
|
|
tableWriter.Write7BitEncoded(itemCount);
|
|
u32 filesOffset = writePos + u32(tableWriter.GetPosition());
|
|
tableWriter.WriteBytes(memoryBlock.data(), written);
|
|
dirTable.m_memorySize += u32(tableWriter.GetPosition());
|
|
UBA_ASSERT(dirTable.m_memorySize < DirTableMemSize);
|
|
|
|
memoryLock.Leave();
|
|
|
|
// Update offsets to be relative to full memory
|
|
for (auto& kv : dir.files)
|
|
kv.second = filesOffset + kv.second;
|
|
|
|
|
|
outTableOffset = tableOffset;
|
|
dir.tableOffset = tableOffset;
|
|
return true;
|
|
}
|
|
|
|
void Session::WriteDirectoryEntriesRecursive(const StringKey& dirKey, tchar* dirPath, u32& outTableOffset)
|
|
{
|
|
auto& dirTable = m_directoryTable;
|
|
SCOPED_WRITE_LOCK(dirTable.m_lookupLock, lookupLock);
|
|
auto res = dirTable.m_lookup.try_emplace(dirKey, dirTable.m_memoryBlock);
|
|
DirectoryTable::Directory& dir = res.first->second;
|
|
lookupLock.Leave();
|
|
|
|
SCOPED_WRITE_LOCK(dir.lock, dirLock);
|
|
|
|
if (dir.parseOffset == 1)
|
|
{
|
|
outTableOffset = dir.tableOffset;
|
|
return;
|
|
}
|
|
|
|
if (!WriteDirectoryEntriesInternal(dir, dirKey, dirPath, false, outTableOffset))
|
|
{
|
|
outTableOffset = InvalidTableOffset;
|
|
dir.parseOffset = 2;
|
|
}
|
|
else
|
|
{
|
|
dir.parseOffset = 1;
|
|
}
|
|
|
|
u64 dirlen = TStrlen(dirPath);
|
|
|
|
if (!dirlen) // This is for non-windows.. '/' is actually empty to get hashes correct
|
|
return;
|
|
|
|
// scan backwards first
|
|
tchar* rit = (tchar*)dirPath + dirlen - 2;
|
|
while (rit > dirPath)
|
|
{
|
|
if (*rit != PathSeparator)
|
|
{
|
|
--rit;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (rit <= dirPath) // There are no path separators left, this is the drive
|
|
{
|
|
*(dirPath) = 0;
|
|
return;
|
|
}
|
|
|
|
*(rit) = 0;
|
|
|
|
StringBuffer<> parentDirForHash;
|
|
parentDirForHash.Append(dirPath, u64(rit - dirPath));
|
|
if (CaseInsensitiveFs)
|
|
parentDirForHash.MakeLower();
|
|
StringKey parentKey = ToStringKey(parentDirForHash);
|
|
|
|
// Traverse through ancestors and populate them, this is an optimization
|
|
u32 parentOffset;
|
|
WriteDirectoryEntriesRecursive(parentKey, dirPath, parentOffset);
|
|
}
|
|
|
|
u32 Session::WriteDirectoryEntries(const StringKey& dirKey, tchar* dirPath, u32* outTableOffset)
|
|
{
|
|
auto& dirTable = m_directoryTable;
|
|
u32 temp;
|
|
if (!outTableOffset)
|
|
outTableOffset = &temp;
|
|
WriteDirectoryEntriesRecursive(dirKey, dirPath, *outTableOffset);
|
|
SCOPED_READ_LOCK(dirTable.m_memoryLock, memoryLock);
|
|
return dirTable.m_memorySize;
|
|
}
|
|
|
|
u32 Session::AddFileMapping(StringKey fileNameKey, const tchar* fileName, const tchar* newFileName, u64 fileSize)
|
|
{
|
|
UBA_ASSERT(fileNameKey != StringKeyZero);
|
|
SCOPED_WRITE_LOCK(m_fileMappingTableLookupLock, lookupLock);
|
|
auto insres = m_fileMappingTableLookup.try_emplace(fileNameKey);
|
|
FileMappingEntry& entry = insres.first->second;
|
|
lookupLock.Leave();
|
|
|
|
SCOPED_WRITE_LOCK(entry.lock, entryCs);
|
|
|
|
if (entry.handled)
|
|
{
|
|
entryCs.Leave();
|
|
SCOPED_READ_LOCK(m_fileMappingTableMemLock, lookupCs2);
|
|
return entry.success ? m_fileMappingTableSize : 0;
|
|
}
|
|
|
|
entry.size = fileSize;
|
|
entry.isDir = false;
|
|
entry.success = true;
|
|
entry.mapping = {};
|
|
entry.handled = true;
|
|
|
|
SCOPED_WRITE_LOCK(m_fileMappingTableMemLock, lock);
|
|
BinaryWriter writer(m_fileMappingTableMem, m_fileMappingTableSize);
|
|
writer.WriteStringKey(fileNameKey);
|
|
writer.WriteString(newFileName);
|
|
writer.Write7BitEncoded(fileSize);
|
|
u32 newSize = (u32)writer.GetPosition();
|
|
m_fileMappingTableSize = (u32)newSize;
|
|
return newSize;
|
|
}
|
|
|
|
bool Session::CreateMemoryMapFromFile(MemoryMap& out, StringKey fileNameKey, const tchar* fileName, bool isCompressed, u64 alignment)
|
|
{
|
|
TimerScope ts(Stats().waitMmapFromFile);
|
|
|
|
SCOPED_WRITE_LOCK(m_fileMappingTableLookupLock, lookupLock);
|
|
auto insres = m_fileMappingTableLookup.try_emplace(fileNameKey);
|
|
FileMappingEntry& entry = insres.first->second;
|
|
lookupLock.Leave();
|
|
|
|
SCOPED_WRITE_LOCK(entry.lock, entryCs);
|
|
|
|
if (entry.handled)
|
|
{
|
|
entryCs.Leave();
|
|
if (!entry.success)
|
|
return false;
|
|
out.size = entry.size;
|
|
if (entry.mapping.IsValid())
|
|
Storage::GetMappingString(out.name, entry.mapping, entry.mappingOffset);
|
|
else
|
|
out.name.Append(entry.isDir ? TC("$d") : TC("$f"));
|
|
return true;
|
|
}
|
|
|
|
ts.Cancel();
|
|
TimerScope ts2(Stats().createMmapFromFile);
|
|
|
|
out.size = 0;
|
|
|
|
entry.handled = true;
|
|
|
|
bool isDir = false;
|
|
FileHandle fileHandle = uba::CreateFileW(fileName, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, DefaultAttributes());
|
|
if (fileHandle == InvalidFileHandle)
|
|
{
|
|
u32 error = GetLastError();
|
|
if (error == ERROR_ACCESS_DENIED || error == ERROR_PATH_NOT_FOUND) // Probably directory? .. path not found can be returned if path is the drive ('e:\' etc)
|
|
{
|
|
fileHandle = uba::CreateFileW(fileName, 0, 0x00000007, 0x00000003, FILE_FLAG_BACKUP_SEMANTICS);
|
|
if (fileHandle == InvalidFileHandle)
|
|
return m_logger.Error(TC("Failed to open file %s (%s)"), fileName, LastErrorToText().data);
|
|
|
|
isDir = true;
|
|
}
|
|
else
|
|
return m_logger.Error(TC("Failed to open file %s (%u)"), fileName, error);
|
|
}
|
|
auto _ = MakeGuard([&](){ uba::CloseFile(fileName, fileHandle); });
|
|
|
|
u64 size = 0;
|
|
|
|
if (!isDir)
|
|
{
|
|
if (isCompressed)
|
|
{
|
|
if (!ReadFile(m_logger, fileName, fileHandle, &size, sizeof(u64)))
|
|
return m_logger.Error(TC("Failed to read first bytes from file %s (%s)"), fileName, LastErrorToText().data);
|
|
}
|
|
else
|
|
{
|
|
FileInformation info;
|
|
if (!GetFileInformationByHandle(info, m_logger, fileName, fileHandle))
|
|
return false;
|
|
|
|
size = info.size;
|
|
}
|
|
}
|
|
|
|
if (isDir || size == 0)
|
|
{
|
|
if (isDir)
|
|
out.name.Append(TC("$d"));
|
|
else
|
|
out.name.Append(TC("$f"));
|
|
}
|
|
else
|
|
{
|
|
auto mappedView = m_fileMappingBuffer.AllocAndMapView(MappedView_Transient, size, alignment, fileName, false);
|
|
auto unmapGuard = MakeGuard([&](){ m_fileMappingBuffer.UnmapView(mappedView, fileName); });
|
|
|
|
if (isCompressed)
|
|
{
|
|
if (!m_storage.DecompressFileToMemory(fileName, fileHandle, mappedView.memory, size, TC("MappedMemory")))
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (!ReadFile(m_logger, fileName, fileHandle, mappedView.memory, size))
|
|
return false;
|
|
}
|
|
|
|
unmapGuard.Execute();
|
|
|
|
entry.mappingOffset = mappedView.offset;
|
|
Storage::GetMappingString(out.name, mappedView.handle, mappedView.offset);
|
|
entry.mapping = mappedView.handle;
|
|
}
|
|
|
|
entry.success = true;
|
|
|
|
{
|
|
SCOPED_WRITE_LOCK(m_fileMappingTableMemLock, lock);
|
|
BinaryWriter writer(m_fileMappingTableMem, m_fileMappingTableSize);
|
|
writer.WriteStringKey(fileNameKey);
|
|
writer.WriteString(out.name);
|
|
writer.Write7BitEncoded(size);
|
|
m_fileMappingTableSize = (u32)writer.GetPosition();
|
|
}
|
|
|
|
entry.isDir = isDir;
|
|
entry.size = size;
|
|
|
|
out.size = size;
|
|
return true;
|
|
}
|
|
|
|
bool Session::CreateMemoryMapFromView(MemoryMap& out, StringKey fileNameKey, const tchar* fileName, const CasKey& casKey, u64 alignment)
|
|
{
|
|
//StringBuffer<> workName;
|
|
//u32 len = TStrlen(fileName);
|
|
//workName.Append(TC("MM:")).Append(len > 30 ? fileName + (len - 30) : fileName);
|
|
//TrackWorkScope tws(*m_workManager, workName.data);
|
|
|
|
SCOPED_WRITE_LOCK(m_fileMappingTableLookupLock, lookupLock);
|
|
auto insres = m_fileMappingTableLookup.try_emplace(fileNameKey);
|
|
FileMappingEntry& entry = insres.first->second;
|
|
lookupLock.Leave();
|
|
|
|
SCOPED_WRITE_LOCK(entry.lock, entryCs);
|
|
|
|
if (entry.handled)
|
|
{
|
|
entryCs.Leave();
|
|
if (!entry.success)
|
|
return false;
|
|
out.size = entry.size;
|
|
if (entry.mapping.IsValid())
|
|
Storage::GetMappingString(out.name, entry.mapping, entry.mappingOffset);
|
|
else
|
|
out.name.Append(entry.isDir ? TC("$d") : TC("$f"));
|
|
return true;
|
|
}
|
|
|
|
out.size = 0;
|
|
|
|
entry.handled = true;
|
|
|
|
MappedView mappedViewRead = m_storage.MapView(casKey, fileName);
|
|
if (!mappedViewRead.handle.IsValid())
|
|
return false;
|
|
|
|
u64 size = InvalidValue;
|
|
|
|
if (mappedViewRead.isCompressed)
|
|
{
|
|
auto mvrg = MakeGuard([&](){ m_storage.UnmapView(mappedViewRead, fileName); });
|
|
u8* readMemory = mappedViewRead.memory;
|
|
size = *(u64*)readMemory;
|
|
readMemory += 8;
|
|
|
|
if (size == 0)
|
|
{
|
|
out.name.Append(TC("$f"));
|
|
}
|
|
else
|
|
{
|
|
auto mappedViewWrite = m_fileMappingBuffer.AllocAndMapView(MappedView_Transient, size, alignment, fileName);
|
|
auto unmapGuard = MakeGuard([&](){ m_fileMappingBuffer.UnmapView(mappedViewWrite, fileName); });
|
|
|
|
if (!m_storage.DecompressMemoryToMemory(readMemory, mappedViewWrite.memory, size, fileName, TC("TransientMapping")))
|
|
return false;
|
|
unmapGuard.Execute();
|
|
|
|
entry.mappingOffset = mappedViewWrite.offset;
|
|
Storage::GetMappingString(out.name, mappedViewWrite.handle, mappedViewWrite.offset);
|
|
entry.mapping = mappedViewWrite.handle;
|
|
}
|
|
mvrg.Execute();
|
|
}
|
|
else
|
|
{
|
|
UBA_ASSERT(mappedViewRead.memory == nullptr);
|
|
entry.mappingOffset = mappedViewRead.offset;
|
|
Storage::GetMappingString(out.name, mappedViewRead.handle, mappedViewRead.offset);
|
|
entry.mapping = mappedViewRead.handle;
|
|
size = mappedViewRead.size;
|
|
}
|
|
entry.success = true;
|
|
|
|
{
|
|
SCOPED_WRITE_LOCK(m_fileMappingTableMemLock, lock);
|
|
BinaryWriter writer(m_fileMappingTableMem, m_fileMappingTableSize);
|
|
writer.WriteStringKey(fileNameKey);
|
|
writer.WriteString(out.name);
|
|
writer.Write7BitEncoded(size);
|
|
m_fileMappingTableSize = (u32)writer.GetPosition();
|
|
}
|
|
|
|
entry.isDir = false;
|
|
entry.size = size;
|
|
|
|
out.size = size;
|
|
return true;
|
|
}
|
|
|
|
bool GetDirKey(StringKey& outDirKey, StringBufferBase& outDirName, const tchar*& outLastSlash, const StringView& fileName)
|
|
{
|
|
outLastSlash = TStrrchr(fileName.data, PathSeparator);
|
|
UBA_ASSERTF(outLastSlash, TC("Can't get dir key for path %s"), fileName.data);
|
|
if (!outLastSlash)
|
|
return false;
|
|
|
|
u64 dirLen = u64(outLastSlash - fileName.data);
|
|
outDirName.Append(fileName.data, dirLen);
|
|
outDirKey = CaseInsensitiveFs ? ToStringKeyLower(outDirName) : ToStringKey(outDirName);
|
|
return true;
|
|
}
|
|
|
|
bool Session::RegisterCreateFileForWrite(StringKey fileNameKey, const StringView& fileName, bool registerRealFile, u64 fileSize, u64 lastWriteTime, bool invalidateStorage)
|
|
{
|
|
// Remote is not updating its own directory table
|
|
if (m_runningRemote)
|
|
return true;
|
|
|
|
auto& dirTable = m_directoryTable;
|
|
|
|
StringKey dirKey;
|
|
const tchar* lastSlash;
|
|
StringBuffer<> dirName;
|
|
if (!GetDirKey(dirKey, dirName, lastSlash, fileName))
|
|
return true;
|
|
|
|
#if 0//_DEBUG // Bring this back, turned off right now because a few lines above the call to this method we add a mapping
|
|
{
|
|
SCOPED_WRITE_LOCK(m_fileMappingTableLookupLock, lookupLock);
|
|
auto findIt = m_fileMappingTableLookup.find(fileNameKey);
|
|
if (findIt != m_fileMappingTableLookup.end())
|
|
{
|
|
FileMappingEntry& entry = findIt->second;
|
|
lookupLock.Leave();
|
|
SCOPED_WRITE_LOCK(entry.lock, entryCs);
|
|
UBA_ASSERT(!entry.mapping);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
bool shouldWriteToDisk = registerRealFile && ShouldWriteToDisk(fileName);
|
|
|
|
// When not writing to disk we need to populate lookup before adding non-written files.. otherwise they will be lost once lookup is actually populated
|
|
if (!shouldWriteToDisk)
|
|
{
|
|
u32 res = WriteDirectoryEntries(dirKey, dirName.data);
|
|
UBA_ASSERT(res); (void)res;
|
|
}
|
|
|
|
SCOPED_READ_LOCK(dirTable.m_lookupLock, lookupCs);
|
|
auto findIt = dirTable.m_lookup.find(dirKey);
|
|
if (findIt == dirTable.m_lookup.end())
|
|
return true;
|
|
|
|
DirectoryTable::Directory& dir = findIt->second;
|
|
lookupCs.Leave();
|
|
|
|
SCOPED_WRITE_LOCK(dir.lock, dirLock);
|
|
|
|
// To prevent race where code creating dir manage to add to lookup but then got here later than this thread.
|
|
while (dir.parseOffset == 0)
|
|
{
|
|
dirLock.Leave();
|
|
Sleep(1);
|
|
dirLock.Enter();
|
|
}
|
|
|
|
// Directory was attempted to be added when it didn't exist. It is still added to dirtable lookup but we set parseOffset to 2.
|
|
// If adding a file, clearly it does exist.. so let's reparse it.
|
|
if (dir.parseOffset == 2)
|
|
{
|
|
dirLock.Leave();
|
|
u32 res = WriteDirectoryEntries(dirKey, dirName.data);
|
|
UBA_ASSERT(res); (void)res;
|
|
dirLock.Enter();
|
|
}
|
|
UBA_ASSERT(dir.parseOffset == 1);
|
|
|
|
if (fileNameKey == StringKeyZero)
|
|
{
|
|
StringBuffer<> forKey;
|
|
forKey.Append(fileName);
|
|
if (CaseInsensitiveFs)
|
|
forKey.MakeLower();
|
|
fileNameKey = ToStringKey(forKey);
|
|
}
|
|
auto insres = dir.files.try_emplace(fileNameKey, ~u32(0));
|
|
|
|
u64 fileIndex = InvalidValue;
|
|
u32 attributes = 0;
|
|
u32 volumeSerial = 0;
|
|
|
|
if (shouldWriteToDisk)
|
|
{
|
|
FileInformation info;
|
|
if (!GetFileInformation(info, m_logger, fileName.data))
|
|
return m_logger.Error(TC("Failed to get file information for %s while checking file added for write. This should not happen! (%s)"), fileName.data, LastErrorToText().data);
|
|
|
|
attributes = info.attributes;
|
|
volumeSerial = info.volumeSerialNumber;
|
|
lastWriteTime = info.lastWriteTime;
|
|
if (IsDirectory(attributes))
|
|
fileSize = 0;
|
|
else
|
|
fileSize = info.size;
|
|
fileIndex = info.index;
|
|
}
|
|
else
|
|
{
|
|
// TODO: Do we need more code here?
|
|
attributes = DefaultAttributes();
|
|
volumeSerial = 1;
|
|
fileIndex = ++m_fileIndexCounter;
|
|
}
|
|
|
|
// Check if new write is actually a write. The file might just have been open with write permissions and then actually never written to.
|
|
// We check this by using lastWriteTime. If it hasn't change, directory table is already up-to-date
|
|
if (!insres.second && insres.first->second != ~u32(0))
|
|
{
|
|
BinaryReader reader(dirTable.m_memory + insres.first->second);
|
|
u64 oldLastWriteTime = reader.ReadU64();
|
|
|
|
if (lastWriteTime == oldLastWriteTime)
|
|
{
|
|
#if !PLATFORM_WINDOWS
|
|
reader.Skip(sizeof(u32) * 2);
|
|
u64 oldFileIndex = reader.ReadU64();
|
|
UBA_ASSERT(oldFileIndex == fileIndex); // Checking so it is really the same file
|
|
#endif
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// There are directory crawlers happening in parallel so we need to really make sure to invalidate this one since a crawler can actually
|
|
// hit this file with information from a query before it was written.. and then it will turn it back to "verified" using old info
|
|
if (registerRealFile && invalidateStorage)
|
|
m_storage.InvalidateCachedFileInfo(fileNameKey);
|
|
|
|
FileEntryAdded(fileNameKey, lastWriteTime, fileSize);
|
|
|
|
u8 temp[1024];
|
|
u64 written;
|
|
u64 entryPos;
|
|
{
|
|
BinaryWriter writer(temp, 0, sizeof(temp));
|
|
writer.WriteStringKey(dirKey);
|
|
writer.Write7BitEncoded(dir.tableOffset); // Previous entry for same directory
|
|
writer.Write7BitEncoded(1); // Count
|
|
writer.WriteString(lastSlash + 1);
|
|
entryPos = writer.GetPosition();
|
|
writer.WriteU64(lastWriteTime);
|
|
writer.WriteU32(attributes);
|
|
writer.WriteU32(volumeSerial);
|
|
writer.WriteU64(fileIndex);
|
|
writer.WriteU64(fileSize);
|
|
written = writer.GetPosition();
|
|
}
|
|
|
|
#if UBA_DEBUG_TRACK_DIR
|
|
g_debugLogger.Info(TC("TRACKADD %s (Size: %llu, Key: %s, Id: %llu)\n"), fileName.data, fileSize, KeyToString(fileNameKey).data, fileIndex);
|
|
#endif
|
|
|
|
|
|
SCOPED_WRITE_LOCK(dirTable.m_memoryLock, memoryLock);
|
|
u8* startPos = dirTable.m_memory + dirTable.m_memorySize;
|
|
BinaryWriter writer(startPos);
|
|
writer.Write7BitEncoded(written); // Storage size
|
|
insres.first->second = dirTable.m_memorySize + u32(writer.GetPosition() + entryPos); // Storing position to last write time
|
|
u32 tableOffset = u32(writer.GetPosition()) + sizeof(StringKey);
|
|
writer.WriteBytes(temp, written);
|
|
dir.tableOffset = dirTable.m_memorySize + tableOffset;
|
|
dirTable.m_memorySize += u32(writer.GetPosition());
|
|
UBA_ASSERT(dirTable.m_memorySize < DirTableMemSize);
|
|
return true;
|
|
}
|
|
|
|
u32 Session::RegisterDeleteFile(StringKey fileNameKey, const StringView& fileName)
|
|
{
|
|
// Remote is not updating its own directory table
|
|
if (m_runningRemote)
|
|
return GetDirectoryTableSize();
|
|
|
|
auto& dirTable = m_directoryTable;
|
|
|
|
StringKey dirKey;
|
|
const tchar* lastSlash;
|
|
StringBuffer<> dirName;
|
|
if (!GetDirKey(dirKey, dirName, lastSlash, fileName))
|
|
return InvalidTableOffset;
|
|
|
|
SCOPED_READ_LOCK(dirTable.m_lookupLock, lookupCs);
|
|
auto res = dirTable.m_lookup.find(dirKey);
|
|
if (res == dirTable.m_lookup.end())
|
|
return 0;
|
|
DirectoryTable::Directory& dir = res->second;
|
|
lookupCs.Leave();
|
|
SCOPED_WRITE_LOCK(dir.lock, dirLock);
|
|
|
|
while (dir.parseOffset == 0)
|
|
{
|
|
dirLock.Leave();
|
|
Sleep(1);
|
|
dirLock.Enter();
|
|
}
|
|
UBA_ASSERT(dir.parseOffset == 1);
|
|
|
|
if (fileNameKey == StringKeyZero)
|
|
{
|
|
StringBuffer<> forKey;
|
|
forKey.Append(fileName);
|
|
if (CaseInsensitiveFs)
|
|
forKey.MakeLower();
|
|
fileNameKey = ToStringKey(forKey);
|
|
}
|
|
|
|
// Does not exist, no need adding to file table
|
|
if (dir.files.erase(fileNameKey) == 0)
|
|
return 0;
|
|
|
|
u8 temp[1024];
|
|
u64 written;
|
|
{
|
|
BinaryWriter writer(temp, 0, sizeof(temp));
|
|
writer.WriteStringKey(dirKey);
|
|
writer.Write7BitEncoded(dir.tableOffset); // Previous entry for same directory
|
|
writer.Write7BitEncoded(1); // Count
|
|
writer.WriteString(lastSlash + 1);
|
|
writer.WriteU32(0);
|
|
writer.WriteU32(0);
|
|
writer.WriteU64(0);
|
|
writer.WriteU64(0);
|
|
writer.WriteU64(0);
|
|
written = writer.GetPosition();
|
|
}
|
|
|
|
#if UBA_DEBUG_TRACK_DIR
|
|
g_debugLogger.Info(TC("TRACKDEL %s (Key: %s)\n"), fileName.data, KeyToString(fileNameKey).data);
|
|
#endif
|
|
|
|
SCOPED_WRITE_LOCK(dirTable.m_memoryLock, memoryLock);
|
|
u8* startPos = dirTable.m_memory + dirTable.m_memorySize;
|
|
BinaryWriter writer(startPos);
|
|
writer.Write7BitEncoded(written); // Storage size
|
|
u32 tableOffset = u32(writer.GetPosition()) + sizeof(StringKey);
|
|
writer.WriteBytes(temp, written);
|
|
dir.tableOffset = dirTable.m_memorySize + tableOffset;
|
|
dirTable.m_memorySize += u32(writer.GetPosition());
|
|
UBA_ASSERT(dirTable.m_memorySize < DirTableMemSize);
|
|
return dirTable.m_memorySize;
|
|
}
|
|
|
|
bool Session::CopyImports(Vector<BinaryModule>& out, const tchar* library, tchar* applicationDir, tchar* applicationDirEnd, UnorderedSet<TString>& handledImports, const char* const* loaderPaths)
|
|
{
|
|
if (!handledImports.insert(library).second)
|
|
return true;
|
|
TSprintf_s(applicationDirEnd, 512 - (applicationDirEnd - applicationDir), TC("%s"), library);
|
|
const tchar* applicationName = applicationDir;
|
|
u32 attr = GetFileAttributesW(applicationName); // TODO: Use attributes table
|
|
tchar temp[512];
|
|
tchar temp2[512];
|
|
StringBuffer<512> temp3;
|
|
bool result = true;
|
|
|
|
if (attr == INVALID_FILE_ATTRIBUTES)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
if (!SearchPathW(NULL, library, NULL, 512, temp, NULL))
|
|
return true; // TODO: We have to return true here because there are scenarios where failing is actually ok (it seems it can return false on crt shim libraries such as api-ms-win-crt*)
|
|
#elif PLATFORM_MAC
|
|
if (!loaderPaths)
|
|
return m_logger.Error("Failed to find file %s", applicationName);
|
|
for (auto it = loaderPaths; *it; ++it)
|
|
{
|
|
StringBuffer<> absolutePath;
|
|
absolutePath.Append(applicationDir, applicationDirEnd - applicationDir).Append(*it).EnsureEndsWithSlash().Append(library);
|
|
FixPath(absolutePath.data, nullptr, 0, temp3.Clear());
|
|
attr = GetFileAttributesW(temp3.data);
|
|
if (attr == INVALID_FILE_ATTRIBUTES)
|
|
continue;
|
|
memcpy(temp, temp3.data, temp3.count+1);
|
|
break;
|
|
}
|
|
if (attr == INVALID_FILE_ATTRIBUTES)
|
|
return m_logger.Error("Failed to find file %s", applicationName);
|
|
//UBA_ASSERTF(false, TC("DIR NOT FOUND: %s"), applicationName);
|
|
#else
|
|
return m_logger.Error("Code path not implemented for linux!");
|
|
#endif
|
|
|
|
applicationName = temp;
|
|
attr = DefaultAttributes();
|
|
|
|
tchar* lastSlash = TStrrchr(temp, PathSeparator);
|
|
UBA_ASSERTF(lastSlash, TC("No slash found in path %s"), temp);
|
|
u64 applicationDirLen = u64(lastSlash + 1 - temp);
|
|
memcpy(temp2, temp, applicationDirLen * sizeof(tchar));
|
|
applicationDir = temp2;
|
|
applicationDirEnd = temp2 + applicationDirLen;
|
|
}
|
|
|
|
FixPath(applicationName, nullptr, 0, temp3.Clear());
|
|
|
|
bool isSystem = StartsWith(applicationName, m_systemPath.data);
|
|
if (isSystem && IsKnownSystemFile(applicationName))
|
|
return true;
|
|
|
|
out.push_back({ library, temp3.data, attr, isSystem });
|
|
|
|
StringBuffer<> errorStr;
|
|
FindImports(applicationName, [&](const tchar* importName, bool isKnown, const char* const* importLoaderPaths)
|
|
{
|
|
if (result && !isKnown)
|
|
result = CopyImports(out, importName, applicationDir, applicationDirEnd, handledImports, importLoaderPaths);
|
|
}, errorStr);
|
|
if (errorStr.count)
|
|
return m_logger.Error(errorStr.data);
|
|
|
|
// This code is needed if application is compiled with tsan
|
|
//strcpy(applicationDirEnd, "libclang_rt.tsan.so");
|
|
//out.push_back({ "libclang_rt.tsan.so", applicationDir, S_IRUSR | S_IWUSR });
|
|
return result;
|
|
}
|
|
|
|
Session::Session(const SessionCreateInfo& info, const tchar* logPrefix, bool runningRemote, WorkManager* workManager)
|
|
: m_storage(info.storage)
|
|
, m_logger(info.logWriter, logPrefix)
|
|
, m_workManager(workManager)
|
|
, m_directoryTable(&m_directoryTableMemory)
|
|
, m_fileMappingBuffer(m_logger, workManager)
|
|
, m_processCommunicationAllocator(m_logger, TC("CommunicationAllocator"))
|
|
, m_trace(info.logWriter)
|
|
{
|
|
if (info.useUniqueId)
|
|
{
|
|
time_t rawtime;
|
|
time(&rawtime);
|
|
tm ti;
|
|
localtime_s(&ti, &rawtime);
|
|
m_id.Appendf(TC("%02i%02i%02i_%02i%02i%02i"), ti.tm_year - 100,ti.tm_mon+1,ti.tm_mday, ti.tm_hour, ti.tm_min, ti.tm_sec);
|
|
}
|
|
else
|
|
{
|
|
m_id.Append(TC("Debug"));
|
|
}
|
|
|
|
UBA_ASSERTF(info.rootDir && *info.rootDir, TC("No root dir set when creating session"));
|
|
m_rootDir.count = GetFullPathNameW(info.rootDir, m_rootDir.capacity, m_rootDir.data, NULL);
|
|
m_rootDir.Replace('/', PathSeparator).EnsureEndsWithSlash();
|
|
|
|
m_runningRemote = runningRemote;
|
|
m_disableCustomAllocator = info.disableCustomAllocator;
|
|
m_allowMemoryMaps = info.allowMemoryMaps;
|
|
m_allowKeepFilesInMemory = info.allowKeepFilesInMemory;
|
|
m_allowOutputFiles = info.allowOutputFiles;
|
|
m_allowSpecialApplications = info.allowSpecialApplications;
|
|
m_suppressLogging = info.suppressLogging;
|
|
if (!info.allowMemoryMaps)
|
|
m_keepOutputFileMemoryMapsThreshold = 0;
|
|
else
|
|
m_keepOutputFileMemoryMapsThreshold = info.keepOutputFileMemoryMapsThreshold;
|
|
m_shouldWriteToDisk = info.shouldWriteToDisk;
|
|
UBA_ASSERTF(m_shouldWriteToDisk || m_allowMemoryMaps, TC("Can't disable both should write to disk and allow memory maps"));
|
|
|
|
m_storeObjFilesCompressed = info.storeObjFilesCompressed;
|
|
|
|
m_detailedTrace = info.detailedTrace;
|
|
m_traceChildProcesses = info.traceChildProcesses;
|
|
m_logToFile = info.logToFile;
|
|
if (info.extraInfo)
|
|
m_extraInfo = info.extraInfo;
|
|
|
|
if (info.deleteSessionsOlderThanSeconds)
|
|
{
|
|
StringBuffer<> sessionsDir;
|
|
sessionsDir.Append(m_rootDir).Append(TC("sessions"));
|
|
|
|
u64 systemTimeAsFileTime = GetSystemTimeAsFileTime();
|
|
|
|
TraverseDir(m_logger, sessionsDir.data,
|
|
[&](const DirectoryEntry& e)
|
|
{
|
|
u64 seconds = GetFileTimeAsSeconds(systemTimeAsFileTime - e.lastWritten);
|
|
if (seconds <= info.deleteSessionsOlderThanSeconds)
|
|
return;
|
|
|
|
if (IsDirectory(e.attributes)) // on macos we get a ".ds_store" file created by the os
|
|
{
|
|
StringBuffer<> sessionDir(sessionsDir);
|
|
sessionDir.EnsureEndsWithSlash().Append(e.name);
|
|
DeleteAllFiles(m_logger, sessionDir.data);
|
|
}
|
|
});
|
|
}
|
|
|
|
m_sessionDir.Append(m_rootDir).Append(TC("sessions")).Append(PathSeparator).Append(m_id).Append(PathSeparator);
|
|
m_sessionBinDir.Append(m_sessionDir).Append(TC("bin"));
|
|
m_sessionOutputDir.Append(m_sessionDir).Append(TC("output"));
|
|
m_sessionLogDir.Append(m_sessionDir).Append(TC("log"));
|
|
|
|
if (m_runningRemote)
|
|
{
|
|
m_storage.CreateDirectory(m_sessionBinDir.data);
|
|
m_storage.CreateDirectory(m_sessionOutputDir.data);
|
|
}
|
|
|
|
m_tempPath.Append(m_sessionDir).Append(TC("temp"));
|
|
m_storage.CreateDirectory(m_tempPath.data);
|
|
m_tempPath.EnsureEndsWithSlash();
|
|
|
|
m_sessionBinDir.EnsureEndsWithSlash();
|
|
m_sessionOutputDir.EnsureEndsWithSlash();
|
|
|
|
m_storage.CreateDirectory(m_sessionLogDir.data);
|
|
m_sessionLogDir.EnsureEndsWithSlash();
|
|
|
|
if (info.traceOutputFile)
|
|
m_traceOutputFile.Append(info.traceOutputFile);
|
|
}
|
|
|
|
bool Session::Create(const SessionCreateInfo& info)
|
|
{
|
|
#if UBA_DEBUG_LOGGER
|
|
StartDebugLogger(m_logger, StringBuffer<512>().Append(m_sessionDir).Append(TC("SessionDebug.log")).data);
|
|
#endif
|
|
|
|
#if PLATFORM_WINDOWS
|
|
m_systemPath.count = GetEnvironmentVariableW(TC("SystemRoot"), m_systemPath.data, m_systemPath.capacity);
|
|
#else
|
|
m_systemPath.Append(TC("/nonexistingpath"));
|
|
#endif
|
|
|
|
m_fileMappingTableHandle = uba::CreateMemoryMappingW(m_logger, PAGE_READWRITE, FileMappingTableMemSize);
|
|
UBA_ASSERT(m_fileMappingTableHandle.IsValid());
|
|
m_fileMappingTableMem = MapViewOfFile(m_fileMappingTableHandle, FILE_MAP_WRITE, 0, FileMappingTableMemSize);
|
|
UBA_ASSERT(m_fileMappingTableMem);
|
|
|
|
m_directoryTableHandle = uba::CreateMemoryMappingW(m_logger, PAGE_READWRITE, DirTableMemSize);
|
|
UBA_ASSERT(m_directoryTableHandle.IsValid());
|
|
m_directoryTableMem = MapViewOfFile(m_directoryTableHandle, FILE_MAP_WRITE, 0, DirTableMemSize);
|
|
UBA_ASSERT(m_directoryTableMem);
|
|
|
|
m_directoryTable.m_memory = m_directoryTableMem;
|
|
m_directoryTable.m_lookup.reserve(30000);
|
|
m_fileMappingTableLookup.reserve(70000);
|
|
|
|
m_fileMappingBuffer.AddTransient(TC("FileMappings"));
|
|
|
|
u64 reserveSize = CommunicationMemSize * 512;
|
|
if (!m_processCommunicationAllocator.Init(CommunicationMemSize, reserveSize))
|
|
return false;
|
|
|
|
if (!CreateProcessJobObject())
|
|
return false;
|
|
|
|
// Environment variables that should stay local when building remote (not replicated)
|
|
#if PLATFORM_WINDOWS
|
|
m_localEnvironmentVariables.insert(TC("SystemRoot"));
|
|
m_localEnvironmentVariables.insert(TC("SystemDrive"));
|
|
m_localEnvironmentVariables.insert(TC("NUMBER_OF_PROCESSORS"));
|
|
m_localEnvironmentVariables.insert(TC("PROCESSOR_ARCHITECTURE"));
|
|
m_localEnvironmentVariables.insert(TC("PROCESSOR_IDENTIFIER"));
|
|
m_localEnvironmentVariables.insert(TC("PROCESSOR_LEVEL"));
|
|
m_localEnvironmentVariables.insert(TC("PROCESSOR_REVISION"));
|
|
#endif
|
|
|
|
StringBuffer<> traceName;
|
|
if (info.traceName && *info.traceName)
|
|
traceName.Append(info.traceName);
|
|
else if (info.launchVisualizer || !m_traceOutputFile.IsEmpty() || info.traceEnabled)
|
|
{
|
|
traceName.Append(m_id);
|
|
|
|
OwnerInfo ownerInfo = GetOwnerInfo();
|
|
if (ownerInfo.pid)
|
|
traceName.Appendf(TC("_%s%u"), ownerInfo.id, ownerInfo.pid);
|
|
}
|
|
|
|
if (!traceName.IsEmpty())
|
|
StartTrace(IsWindows ? traceName.data : nullptr); // non-windows named shared memory not implemented (only needed for UbaVisualizer which you can't run on linux either way)
|
|
|
|
#if PLATFORM_WINDOWS
|
|
if (info.launchVisualizer)
|
|
{
|
|
HMODULE currentModule = GetModuleHandle(NULL);
|
|
GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCWSTR)&g_dummy, ¤tModule);
|
|
tchar fileName[512];
|
|
GetModuleFileNameW(currentModule, fileName, 512);
|
|
uba::StringBuffer<> launcherCmd;
|
|
launcherCmd.Append(TC("\""));
|
|
launcherCmd.AppendDir(fileName);
|
|
launcherCmd.Append(TC("\\UbaVisualizer.exe\""));
|
|
launcherCmd.Append(TC(" -named=")).Append(traceName);
|
|
STARTUPINFOW si;
|
|
memset(&si, 0, sizeof(si));
|
|
PROCESS_INFORMATION pi;
|
|
m_logger.Info(TC("Starting visualizer: %s"), launcherCmd.data);
|
|
DWORD creationFlags = DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP;
|
|
CreateProcessW(NULL, launcherCmd.data, NULL, NULL, false, creationFlags, NULL, NULL, &si, &pi);
|
|
CloseHandle(pi.hThread);
|
|
CloseHandle(pi.hProcess);
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
Session::~Session()
|
|
{
|
|
StopTrace(m_traceOutputFile.data);
|
|
|
|
CancelAllProcessesAndWait();
|
|
FlushDeadProcesses();
|
|
|
|
//for (auto& i : m_fileMappingTableLookup)
|
|
// CloseHandle(i.second.mapping);
|
|
|
|
UnmapViewOfFile(m_fileMappingTableMem, FileMappingTableMemSize, TC("FileMappingTable"));
|
|
CloseFileMapping(m_fileMappingTableHandle);
|
|
|
|
UnmapViewOfFile(m_directoryTableMem, DirTableMemSize, TC("DirectoryTable"));
|
|
CloseFileMapping(m_directoryTableHandle);
|
|
|
|
//#if !UBA_DEBUG
|
|
//u32 count;
|
|
//DeleteAllFiles(m_logger, m_sessionDir.data, count);
|
|
//#endif
|
|
|
|
#if UBA_DEBUG_LOGGER
|
|
StopDebugLogger();
|
|
#endif
|
|
}
|
|
|
|
void Session::CancelAllProcessesAndWait(bool terminate)
|
|
{
|
|
bool isEmpty = false;
|
|
bool isFirst = true;
|
|
while (!isEmpty)
|
|
{
|
|
Vector<ProcessHandle> processes;
|
|
{
|
|
SCOPED_WRITE_LOCK(m_processesLock, lock);
|
|
isEmpty = m_processes.empty();
|
|
processes.reserve(m_processes.size());
|
|
for (auto& pair : m_processes)
|
|
processes.push_back(pair.second);
|
|
}
|
|
|
|
if (isFirst)
|
|
{
|
|
isFirst = false;
|
|
if (!processes.empty())
|
|
m_logger.Info(TC("Cancelling %llu processes and wait for them to exit"), processes.size());
|
|
++m_logger.isMuted;
|
|
}
|
|
|
|
for (auto& process : processes)
|
|
process.Cancel(true);
|
|
|
|
#if PLATFORM_WINDOWS
|
|
if (m_processJobObject != NULL)
|
|
{
|
|
SCOPED_WRITE_LOCK(m_processJobObjectLock, lock);
|
|
CloseHandle(m_processJobObject);
|
|
m_processJobObject = NULL;
|
|
}
|
|
#endif
|
|
|
|
for (auto& process : processes)
|
|
process.WaitForExit(100000);
|
|
}
|
|
|
|
--m_logger.isMuted;
|
|
}
|
|
|
|
ProcessHandle Session::RunProcess(const ProcessStartInfo& startInfo, bool async, bool enableDetour)
|
|
{
|
|
FlushDeadProcesses();
|
|
ValidateStartInfo(startInfo);
|
|
return InternalRunProcess(startInfo, async, nullptr, enableDetour);
|
|
}
|
|
|
|
void Session::ValidateStartInfo(const ProcessStartInfo& startInfo)
|
|
{
|
|
UBA_ASSERTF(startInfo.workingDir && *startInfo.workingDir, TC("Working dir must be set when spawning process"));
|
|
UBA_ASSERTF(!TStrchr(startInfo.workingDir, '~'), TC("WorkingDir path must use long name (%s)"), startInfo.workingDir);
|
|
}
|
|
|
|
ProcessHandle Session::InternalRunProcess(const ProcessStartInfo& startInfo, bool async, ProcessImpl* parent, bool enableDetour)
|
|
{
|
|
auto& si = const_cast<ProcessStartInfo&>(startInfo);
|
|
si.useCustomAllocator &= !m_disableCustomAllocator;
|
|
const tchar* originalLogFile = si.logFile;
|
|
|
|
u32 processId = CreateProcessId();
|
|
|
|
StringBuffer<> logFile;
|
|
if (si.logFile && *si.logFile)
|
|
{
|
|
if (TStrchr(si.logFile, PathSeparator) == nullptr)
|
|
{
|
|
logFile.Append(m_sessionLogDir).Append(si.logFile);
|
|
si.logFile = logFile.data;
|
|
}
|
|
}
|
|
else if (m_logToFile)
|
|
{
|
|
logFile.Append(m_sessionLogDir);
|
|
GenerateNameForProcess(logFile, startInfo.arguments, processId);
|
|
logFile.Append(TC(".log"));
|
|
si.logFile = logFile.data;
|
|
}
|
|
|
|
if (!si.rules)
|
|
si.rules = GetRules(si);
|
|
|
|
void* env = GetProcessEnvironmentVariables();
|
|
auto process = new ProcessImpl(*this, processId, parent);
|
|
ProcessHandle h(process);
|
|
if (!process->Start(startInfo, m_runningRemote, env, async, enableDetour))
|
|
return {};
|
|
|
|
si.logFile = originalLogFile;
|
|
return h;
|
|
}
|
|
|
|
void Session::RefreshDirectory(const tchar* dirName)
|
|
{
|
|
UBA_ASSERT(!m_runningRemote);
|
|
|
|
StringBuffer<> dirPath;
|
|
FixPath2(dirName, nullptr, 0, dirPath.data, dirPath.capacity, &dirPath.count);
|
|
StringKey dirKey = CaseInsensitiveFs ? ToStringKeyLower(dirPath) : ToStringKey(dirPath);
|
|
|
|
auto& dirTable = m_directoryTable;
|
|
SCOPED_READ_LOCK(dirTable.m_lookupLock, lookupLock);
|
|
auto res = dirTable.m_lookup.find(dirKey);
|
|
if (res == dirTable.m_lookup.end())
|
|
return;
|
|
DirectoryTable::Directory& dir = res->second;
|
|
lookupLock.Leave();
|
|
SCOPED_WRITE_LOCK(dir.lock, dirLock);
|
|
|
|
while (dir.parseOffset == 0)
|
|
{
|
|
dirLock.Leave();
|
|
Sleep(1);
|
|
dirLock.Enter();
|
|
}
|
|
UBA_ASSERT(dir.parseOffset == 1);
|
|
|
|
StringKeyHasher hasher;
|
|
hasher.Update(dirPath.data, TStrlen(dirPath.data));
|
|
m_directoryTable.PopulateDirectory(hasher, dir);
|
|
|
|
u32 tableOffset;
|
|
WriteDirectoryEntriesInternal(dir, dirKey, dirPath.data, true, tableOffset);
|
|
}
|
|
|
|
StringKey GetKeyAndFixedName(StringBuffer<>& fixedFilePath, const tchar* filePath)
|
|
{
|
|
StringBuffer<> workingDir;
|
|
if (!IsAbsolutePath(filePath))
|
|
{
|
|
GetCurrentDirectoryW(workingDir);
|
|
workingDir.EnsureEndsWithSlash();
|
|
}
|
|
FixPath2(filePath, workingDir.data, workingDir.count, fixedFilePath.data, fixedFilePath.capacity, &fixedFilePath.count);
|
|
|
|
StringKey dirKey;
|
|
StringBuffer<> dirNameForHash;
|
|
const tchar* baseFileName;
|
|
GetDirKey(dirKey, dirNameForHash, baseFileName, fixedFilePath);
|
|
|
|
if (CaseInsensitiveFs)
|
|
dirNameForHash.MakeLower();
|
|
|
|
StringKeyHasher hasher;
|
|
hasher.Update(dirNameForHash.data, dirNameForHash.count);
|
|
|
|
StringBuffer<128> baseFileNameForHash;
|
|
baseFileNameForHash.Append(baseFileName);
|
|
if (CaseInsensitiveFs)
|
|
baseFileNameForHash.MakeLower();
|
|
return ToStringKey(hasher, baseFileNameForHash.data, baseFileNameForHash.count);
|
|
}
|
|
|
|
bool Session::RegisterNewFile(const tchar* filePath)
|
|
{
|
|
UBA_ASSERT(!m_runningRemote);
|
|
StringBuffer<> fixedFilePath;
|
|
auto key = GetKeyAndFixedName(fixedFilePath, filePath);
|
|
return RegisterCreateFileForWrite(key, fixedFilePath, true);
|
|
}
|
|
|
|
void Session::RegisterDeleteFile(const tchar* filePath)
|
|
{
|
|
UBA_ASSERT(!m_runningRemote);
|
|
StringBuffer<> fixedFilePath;
|
|
auto key = GetKeyAndFixedName(fixedFilePath, filePath);
|
|
RegisterDeleteFile(key, fixedFilePath);
|
|
}
|
|
|
|
void Session::RegisterCustomService(CustomServiceFunction&& function)
|
|
{
|
|
m_customServiceFunction = function;
|
|
}
|
|
|
|
void Session::RegisterGetNextProcess(GetNextProcessFunction&& function)
|
|
{
|
|
m_getNextProcessFunction = function;
|
|
}
|
|
|
|
const tchar* Session::GetId() { return m_id.data; }
|
|
Storage& Session::GetStorage() { return m_storage; }
|
|
MutableLogger& Session::GetLogger() { return m_logger; }
|
|
LogWriter& Session::GetLogWriter() { return m_logger.m_writer; }
|
|
Trace& Session::GetTrace() { return m_trace; }
|
|
|
|
const ApplicationRules* Session::GetRules(const ProcessStartInfo& si)
|
|
{
|
|
u32 exeNameStart = 0;
|
|
u32 exeNameEnd = TStrlen(si.application);
|
|
const tchar* lastSeparator = TStrrchr(si.application, PathSeparator);
|
|
if (lastSeparator)
|
|
exeNameStart = u32(lastSeparator - si.application + 1);
|
|
else if (si.application[exeNameStart] == '"')
|
|
++exeNameStart;
|
|
if (si.application[exeNameEnd - 1] == '"')
|
|
--exeNameEnd;
|
|
StringBuffer<128> exeName;
|
|
exeName.Append(si.application + exeNameStart, exeNameEnd - exeNameStart);
|
|
|
|
auto rules = GetApplicationRules();
|
|
|
|
while (true)
|
|
{
|
|
for (u32 i = 1;; ++i)
|
|
{
|
|
const tchar* app = rules[i].app;
|
|
if (!app)
|
|
break;
|
|
if (!exeName.Equals(app))
|
|
continue;
|
|
return GetApplicationRules()[i].rules;
|
|
}
|
|
|
|
if (!exeName.Equals(TC("dotnet.exe")))
|
|
return GetApplicationRules()[0].rules;
|
|
|
|
u32 firstArgumentStart = 0;
|
|
u32 firstArgumentEnd = 0;
|
|
bool quoted = false;
|
|
for (u32 i = 0, e = TStrlen(si.arguments); i != e; ++i)
|
|
{
|
|
tchar c = si.arguments[i];
|
|
if (firstArgumentEnd)
|
|
{
|
|
if (c == '\\')
|
|
firstArgumentStart = i + 1;
|
|
if ((quoted && c != '"') || (!quoted && c != ' ' && c != '\t'))
|
|
continue;
|
|
firstArgumentEnd = i;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if (c == ' ' || c == '\t')
|
|
{
|
|
++firstArgumentStart;
|
|
continue;
|
|
}
|
|
if (c == '"')
|
|
{
|
|
++firstArgumentStart;
|
|
quoted = true;
|
|
}
|
|
firstArgumentEnd = firstArgumentStart + 1;
|
|
}
|
|
}
|
|
exeName.Clear().Append(si.arguments + firstArgumentStart, firstArgumentEnd - firstArgumentStart);
|
|
}
|
|
return GetApplicationRules()[0].rules;
|
|
}
|
|
|
|
const tchar* Session::GetTempPath()
|
|
{
|
|
return m_tempPath.data;
|
|
}
|
|
|
|
const tchar* Session::GetRootDir()
|
|
{
|
|
return m_rootDir.data;
|
|
}
|
|
|
|
u32 Session::CreateProcessId()
|
|
{
|
|
return ++m_processIdCounter;
|
|
}
|
|
|
|
void Session::ProcessAdded(Process& process, u32 sessionId)
|
|
{
|
|
u32 processId = process.GetId();
|
|
|
|
if (!process.IsChild() || m_traceChildProcesses)
|
|
m_trace.ProcessAdded(sessionId, processId, process.GetStartInfo().GetDescription());
|
|
|
|
SCOPED_WRITE_LOCK(m_processesLock, lock);
|
|
bool success = m_processes.try_emplace(processId, ProcessHandle(&process)).second;
|
|
UBA_ASSERT(success);(void)success;
|
|
}
|
|
|
|
void Session::ProcessExited(ProcessImpl& process, u64 executionTime)
|
|
{
|
|
const tchar* application = process.GetStartInfo().application;
|
|
StringBuffer<> applicationName;
|
|
applicationName.AppendFileName(application);
|
|
if (applicationName.count > 21)
|
|
applicationName[21] = 0;
|
|
|
|
u32 id = process.GetId();
|
|
|
|
if (!process.IsChild() || m_traceChildProcesses)
|
|
{
|
|
StackBinaryWriter<1024> writer;
|
|
process.m_processStats.Write(writer);
|
|
process.m_storageStats.Write(writer);
|
|
process.m_kernelStats.Write(writer);
|
|
u32 exitCode = process.GetExitCode();
|
|
Vector<ProcessLogLine> emptyLines;
|
|
auto& logLines = (exitCode != 0 || m_detailedTrace) ? process.m_logLines : emptyLines;
|
|
m_trace.ProcessExited(id, exitCode, writer.GetData(), writer.GetPosition(), logLines, process.GetStartInfo().breadcrumbs);
|
|
SCOPED_WRITE_LOCK(m_processStatsLock, lock);
|
|
m_processStats.Add(process.m_processStats);
|
|
m_stats.Add(process.m_sessionStats);
|
|
}
|
|
|
|
SCOPED_WRITE_LOCK(m_processesLock, lock);
|
|
m_deadProcesses.emplace_back(&process); // Here to prevent Process thread call trigger a delete of Process which causes a deadlock
|
|
auto& stats = m_applicationStats[applicationName.data];
|
|
stats.count++;
|
|
stats.time += executionTime;
|
|
auto count = m_processes.erase(id);
|
|
UBA_ASSERT(count == 1);(void)count;
|
|
}
|
|
|
|
void Session::FlushDeadProcesses()
|
|
{
|
|
SCOPED_WRITE_LOCK(m_processesLock, lock);
|
|
Vector<ProcessHandle> deadProcesses;
|
|
deadProcesses.swap(m_deadProcesses);
|
|
lock.Leave();
|
|
}
|
|
|
|
bool Session::GetInitResponse(InitResponse& out, const InitMessage& msg)
|
|
{
|
|
out.directoryTableHandle = m_directoryTableHandle.ToU64();
|
|
{
|
|
SCOPED_READ_LOCK(m_directoryTable.m_memoryLock, l);
|
|
out.directoryTableSize = (u32)m_directoryTable.m_memorySize;
|
|
}
|
|
{
|
|
SCOPED_READ_LOCK(m_directoryTable.m_lookupLock, l);
|
|
out.directoryTableCount = (u32)m_directoryTable.m_lookup.size();
|
|
}
|
|
out.mappedFileTableHandle = m_fileMappingTableHandle.ToU64();
|
|
{
|
|
SCOPED_READ_LOCK(m_fileMappingTableMemLock, l);
|
|
out.mappedFileTableSize = m_fileMappingTableSize;
|
|
}
|
|
{
|
|
SCOPED_READ_LOCK(m_fileMappingTableLookupLock, l);
|
|
out.mappedFileTableCount = (u32)m_fileMappingTableLookup.size();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
u32 Session::GetDirectoryTableSize()
|
|
{
|
|
SCOPED_READ_LOCK(m_directoryTable.m_memoryLock, lock);
|
|
return m_directoryTable.m_memorySize;
|
|
}
|
|
|
|
u32 Session::GetFileMappingSize()
|
|
{
|
|
SCOPED_READ_LOCK(m_fileMappingTableMemLock, lock);
|
|
return m_fileMappingTableSize;
|
|
}
|
|
|
|
SessionStats& Session::Stats()
|
|
{
|
|
if (SessionStats* s = SessionStats::GetCurrent())
|
|
return *s;
|
|
return m_stats;
|
|
}
|
|
|
|
u32 Session::GetActiveProcessCount()
|
|
{
|
|
SCOPED_READ_LOCK(m_processesLock, cs);
|
|
return u32(m_processes.size());
|
|
}
|
|
|
|
void Session::PrintProcessStats(ProcessStats& stats, const tchar* logName)
|
|
{
|
|
m_logger.Info(TC(" -- %s --"), logName);
|
|
stats.Print(m_logger);
|
|
}
|
|
|
|
void Session::StartTrace(const tchar* traceName)
|
|
{
|
|
if (traceName)
|
|
LoggerWithWriter(m_logger.m_writer).Info(TC("---- Starting trace: %s ----"), traceName);
|
|
else
|
|
LoggerWithWriter(m_logger.m_writer).Info(TC("---- Starting trace ----"));
|
|
|
|
m_trace.StartWrite(traceName, m_detailedTrace ? 512*1024*1024 : 128*1024*1024);
|
|
|
|
tchar buf[256];
|
|
if (!GetComputerNameW(buf, sizeof_array(buf)))
|
|
TStrcpy_s(buf, 256, TC("LOCAL"));
|
|
StringBuffer<> systemInfo;
|
|
GetSystemInfo(systemInfo);
|
|
m_trace.SessionAdded(0, {}, buf, systemInfo.data);
|
|
m_traceThreadEvent.Create(true);
|
|
m_traceThread.Start([this]() { ThreadTraceLoop(); return 0; });
|
|
}
|
|
|
|
bool Session::StopTrace(const tchar* writeFile)
|
|
{
|
|
StopTraceThread();
|
|
return m_trace.StopWrite(writeFile);
|
|
}
|
|
|
|
void Session::StopTraceThread()
|
|
{
|
|
m_traceThreadEvent.Set();
|
|
m_traceThread.Wait();
|
|
}
|
|
|
|
void Session::PrintSummary(Logger& logger)
|
|
{
|
|
logger.BeginScope();
|
|
logger.Info(TC(" ------- Detours stats summary -------"));
|
|
m_processStats.Print(logger);
|
|
logger.Info(TC(""));
|
|
|
|
MultiMap<u64, std::pair<const TString*, u32>> sortedApps;
|
|
for (auto& pair : m_applicationStats)
|
|
sortedApps.insert({pair.second.time, {&pair.first, pair.second.count}});
|
|
for (auto i=sortedApps.rbegin(), e=sortedApps.rend(); i!=e; ++i)
|
|
{
|
|
const TString& name = *i->second.first;
|
|
u64 time = i->first;
|
|
u32 count = i->second.second;
|
|
logger.Info(TC(" %-21s %5u %9s"), name.c_str(), count, TimeToText(time).str);
|
|
}
|
|
logger.Info(TC(""));
|
|
|
|
logger.Info(TC(" ------- Session stats summary -------"));
|
|
|
|
PrintSessionStats(logger);
|
|
logger.EndScope();
|
|
}
|
|
|
|
bool Session::GetBinaryModules(Vector<BinaryModule>& out, const tchar* application)
|
|
{
|
|
const tchar* applicationName = application;
|
|
|
|
if (tchar* lastSlash = TStrrchr((tchar*)application, PathSeparator))
|
|
applicationName = lastSlash + 1;
|
|
|
|
u64 applicationDirLen = u64(applicationName - application);
|
|
tchar applicationDir[512];
|
|
UBA_ASSERT(applicationDirLen < 512);
|
|
memcpy(applicationDir, application, applicationDirLen * sizeof(tchar));
|
|
tchar* applicationDirEnd = applicationDir + applicationDirLen;
|
|
|
|
UnorderedSet<TString> handledImports;
|
|
return CopyImports(out, applicationName, applicationDir, applicationDirEnd, handledImports, nullptr);
|
|
}
|
|
|
|
void Session::Free(Vector<BinaryModule>& v)
|
|
{
|
|
v.resize(0);
|
|
v.shrink_to_fit();
|
|
}
|
|
|
|
bool Session::IsRarelyRead(ProcessImpl& process, const StringBufferBase& fileName) const
|
|
{
|
|
return process.m_startInfo.rules->IsRarelyRead(fileName);
|
|
}
|
|
|
|
bool Session::IsRarelyReadAfterWritten(ProcessImpl& process, const StringView& fileName) const
|
|
{
|
|
return process.m_startInfo.rules->IsRarelyReadAfterWritten(fileName);
|
|
}
|
|
|
|
bool Session::IsKnownSystemFile(const tchar* applicationName)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
return uba::IsKnownSystemFile(applicationName);
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool Session::ShouldWriteToDisk(const StringView& fileName)
|
|
{
|
|
if (m_shouldWriteToDisk)
|
|
return true;
|
|
return fileName.EndsWith(TC(".h"));
|
|
}
|
|
|
|
bool Session::PrepareProcess(ProcessStartInfoHolder& startInfo, bool isChild, StringBufferBase& outRealApplication, const tchar*& outRealWorkingDir)
|
|
{
|
|
if (StartsWith(startInfo.application, TC("ubacopy")))
|
|
return true;
|
|
if (IsAbsolutePath(startInfo.application))
|
|
return true;
|
|
if (!SearchPathForFile(m_logger, outRealApplication.Clear(), startInfo.application, startInfo.workingDir))
|
|
return false;
|
|
startInfo.applicationStr = outRealApplication.data;
|
|
startInfo.application = startInfo.applicationStr.c_str();
|
|
return true;
|
|
}
|
|
|
|
u32 Session::GetMemoryMapAlignment(const StringView& fileName) const
|
|
{
|
|
// It is not necessarily better to make mem maps of everything.. only things that are read more than once in the build.
|
|
// Reason is because there is additional overhead to use memory mappings.
|
|
// Upside is that all things that are memory mapped can be stored compressed in cas storage so it saves space.
|
|
|
|
if (fileName.EndsWith(TC(".pch")))
|
|
return 64 * 1024; // pch needs 64k alignment
|
|
if (fileName.EndsWith(TC(".h")) || fileName.EndsWith(TC(".inl")) || fileName.EndsWith(TC(".gch")))
|
|
return 4 * 1024; // clang seems to need 4k alignment? Is it a coincidence it works or what is happening inside the code? (msvc works with alignment 1byte here)
|
|
if (fileName.EndsWith(TC(".h.obj")))
|
|
return 4 * 1024;
|
|
//if (fileName.EndsWith(TC(".lib")))
|
|
// return 4 * 1024;
|
|
//if (fileName.EndsWith(TC(".rc2.res")))
|
|
// return 64;
|
|
//if (fileName.EndsWith(TC(".rsp")))
|
|
// return 4 * 1024; // rsp is read over and over again
|
|
//if (fileName.EndsWith(TC(".h.obj")) || fileName.EndsWith(TC(".lib")))
|
|
// return 4 * 1024; // rsp is read over and over again
|
|
return 0;
|
|
}
|
|
|
|
void* Session::GetProcessEnvironmentVariables()
|
|
{
|
|
SCOPED_WRITE_LOCK(m_environmentVariablesLock, lock);
|
|
if (!m_environmentVariables.empty())
|
|
return m_environmentVariables.data();
|
|
|
|
#if PLATFORM_WINDOWS
|
|
auto HandleEnvironmentVar = [&](const tchar* env)
|
|
{
|
|
StringBuffer<> varName;
|
|
varName.Append(env, TStrchr(env, '=') - env);
|
|
const tchar* varValue = env + varName.count + 1;
|
|
|
|
if (m_runningRemote && varName.Equals(TC("PATH")))
|
|
{
|
|
AddEnvironmentVariableNoLock(TC("PATH"), TC("c:\\noenvironment"));
|
|
return;
|
|
}
|
|
if (varName.Equals(TC("TEMP")) || varName.Equals(TC("TMP")))
|
|
{
|
|
AddEnvironmentVariableNoLock(varName.data, m_tempPath.data);
|
|
return;
|
|
}
|
|
if (varName.Equals(TC("_CL_")) || varName.Equals(TC("CL")))
|
|
{
|
|
return;
|
|
}
|
|
|
|
AddEnvironmentVariableNoLock(varName.data, varValue);
|
|
};
|
|
|
|
if (m_environmentMemory.empty())
|
|
{
|
|
auto strs = GetEnvironmentStringsW();
|
|
for (auto env = strs; *env; env += TStrlen(env) + 1)
|
|
HandleEnvironmentVar(env);
|
|
FreeEnvironmentStrings(strs);
|
|
}
|
|
else
|
|
{
|
|
BinaryReader reader(m_environmentMemory.data(), 0, m_environmentMemory.size());
|
|
while (reader.GetLeft())
|
|
HandleEnvironmentVar(reader.ReadString().c_str());
|
|
}
|
|
AddEnvironmentVariableNoLock(TC("MSBUILDDISABLENODEREUSE"), TC("1")); // msbuild will reuse existing helper nodes but since those are not detoured we can't let that happen
|
|
AddEnvironmentVariableNoLock(TC("DOTNET_CLI_USE_MSBUILD_SERVER"), TC("0")); // Disable msbuild server
|
|
AddEnvironmentVariableNoLock(TC("DOTNET_CLI_TELEMETRY_OPTOUT"), TC("1")); // Stop talking to telemetry service
|
|
#else
|
|
auto HandleEnvironmentVar = [&](const tchar* env)
|
|
{
|
|
if (StartsWith(env, "TMPDIR="))
|
|
return;
|
|
|
|
if (!StartsWith(env, "PATH="))
|
|
{
|
|
m_environmentVariables.insert(m_environmentVariables.end(), env, env + TStrlen(env) + 1);
|
|
return;
|
|
}
|
|
|
|
TString paths;
|
|
|
|
const char* start = env + 5;
|
|
const char* it = start;
|
|
bool isLast = false;
|
|
while (!isLast)
|
|
{
|
|
if (*it != ':')
|
|
{
|
|
if (*it)
|
|
{
|
|
++it;
|
|
continue;
|
|
}
|
|
isLast = true;
|
|
}
|
|
|
|
const char* s = start;
|
|
const char* e = it;
|
|
start = ++it;
|
|
|
|
if (StartsWith(s, "/mnt/"))
|
|
continue;
|
|
if (!paths.empty())
|
|
paths += ":";
|
|
paths.append(s, e);
|
|
}
|
|
AddEnvironmentVariableNoLock("PATH", paths.c_str());
|
|
};
|
|
|
|
if (m_environmentMemory.empty())
|
|
{
|
|
int i = 0;
|
|
while (char* env = environ[i++])
|
|
HandleEnvironmentVar(env);
|
|
}
|
|
else
|
|
{
|
|
BinaryReader reader(m_environmentMemory.data(), 0, m_environmentMemory.size());
|
|
while (reader.GetLeft())
|
|
HandleEnvironmentVar(reader.ReadString().c_str());
|
|
}
|
|
AddEnvironmentVariableNoLock("TMPDIR", m_tempPath.data);
|
|
#endif
|
|
|
|
AddEnvironmentVariableNoLock(TC("UBA_DETOURED"), TC("1"));
|
|
|
|
m_environmentVariables.push_back(0);
|
|
return m_environmentVariables.data();
|
|
}
|
|
|
|
bool Session::CreateFile(CreateFileResponse& out, const CreateFileMessage& msg)
|
|
{
|
|
const StringBufferBase& fileName = msg.fileName;
|
|
const StringKey& fileNameKey = msg.fileNameKey;
|
|
|
|
auto tableSizeGuard = MakeGuard([&]()
|
|
{
|
|
out.mappedFileTableSize = GetFileMappingSize();
|
|
out.directoryTableSize = GetDirectoryTableSize();
|
|
});
|
|
|
|
if ((msg.access & ~FileAccess_Read) == 0)
|
|
{
|
|
if (!IsWindows)
|
|
{
|
|
out.fileName.Append(fileName);
|
|
return true;
|
|
}
|
|
|
|
if (fileName.EndsWith(TC(".dll")) || fileName.EndsWith(TC(".exe")))
|
|
{
|
|
UBA_ASSERTF(IsAbsolutePath(fileName.data), TC("Got bad filename from process %s"), fileName.data);
|
|
AddFileMapping(fileNameKey, fileName.data, TC("#"));
|
|
out.fileName.Append(TC("#"));
|
|
return true;
|
|
}
|
|
|
|
if (m_allowMemoryMaps)
|
|
{
|
|
if (u64 alignment = GetMemoryMapAlignment(fileName))
|
|
{
|
|
MemoryMap map;
|
|
if (CreateMemoryMapFromFile(map, fileNameKey, fileName.data, false, alignment))
|
|
{
|
|
out.size = map.size;
|
|
out.fileName.Append(map.name);
|
|
}
|
|
else
|
|
{
|
|
out.fileName.Append(fileName);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (!IsRarelyRead(msg.process, fileName))
|
|
{
|
|
AddFileMapping(fileNameKey, fileName.data, TC("#"));
|
|
out.fileName.Append(TC("#"));
|
|
return true;
|
|
}
|
|
|
|
out.fileName.Append(fileName);
|
|
return true;
|
|
}
|
|
|
|
// if ((message.Access & FileAccess.Write) != 0)
|
|
m_storage.ReportFileWrite(fileNameKey, fileName.data);
|
|
|
|
if (m_runningRemote && !fileName.StartsWith(m_tempPath.data))
|
|
{
|
|
SCOPED_WRITE_LOCK(m_outputFilesLock, lock);
|
|
auto insres = m_outputFiles.try_emplace(fileName.data);
|
|
if (insres.second)
|
|
{
|
|
out.fileName.Append(m_sessionOutputDir).Append(KeyToString(fileNameKey));
|
|
insres.first->second = out.fileName.data;
|
|
}
|
|
else
|
|
{
|
|
out.fileName.Append(insres.first->second.c_str());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
out.fileName.Append(fileName);
|
|
}
|
|
|
|
SCOPED_WRITE_LOCK(m_activeFilesLock, lock);
|
|
u32 wantsOnCloseId = m_wantsOnCloseIdCounter++;
|
|
out.closeId = wantsOnCloseId;
|
|
auto insres = m_activeFiles.try_emplace(wantsOnCloseId);
|
|
if (!insres.second)
|
|
return m_logger.Error(TC("TRYING TO ADD %s twice!"), out.fileName.data);
|
|
|
|
insres.first->second.name = fileName.data;
|
|
insres.first->second.nameKey = fileNameKey;
|
|
return true;
|
|
}
|
|
|
|
void RemoveWrittenFile(ProcessImpl& process, const TString& name)
|
|
{
|
|
SCOPED_WRITE_LOCK(process.m_writtenFilesLock, writtenLock);
|
|
auto& writtenFiles = process.m_writtenFiles;
|
|
auto findIt = writtenFiles.find(name);
|
|
if (findIt == writtenFiles.end())
|
|
return;
|
|
FileMappingHandle h = findIt->second.mappingHandle;
|
|
writtenFiles.erase(findIt);
|
|
writtenLock.Leave();
|
|
|
|
if (h.IsValid())
|
|
CloseFileMapping(h);
|
|
}
|
|
|
|
bool Session::CloseFile(CloseFileResponse& out, const CloseFileMessage& msg)
|
|
{
|
|
SCOPED_WRITE_LOCK(m_activeFilesLock, lock);
|
|
auto findIt = m_activeFiles.find(msg.closeId);
|
|
if (findIt == m_activeFiles.end())
|
|
return m_logger.Error(TC("This should not happen. Got unknown closeId %u - %s"), msg.closeId, msg.fileName.data);
|
|
|
|
ActiveFile file = findIt->second;
|
|
m_activeFiles.erase(msg.closeId);
|
|
lock.Leave();
|
|
|
|
bool registerRealFile = true;
|
|
u64 fileSize = 0;
|
|
u64 lastWriteTime = 0;
|
|
|
|
if (!msg.success)
|
|
{
|
|
return true;
|
|
}
|
|
if (msg.deleteOnClose)
|
|
{
|
|
RemoveWrittenFile(msg.process, file.name);
|
|
}
|
|
else
|
|
{
|
|
StringKey key = file.nameKey;
|
|
const tchar* name = file.name.c_str();
|
|
const tchar* msgName = msg.fileName.data;
|
|
if (!msg.newName.IsEmpty())
|
|
{
|
|
UBA_ASSERT(!msg.deleteOnClose);
|
|
RemoveWrittenFile(msg.process, file.name);
|
|
name = msg.newName.data;
|
|
key = msg.newNameKey;
|
|
if (!m_runningRemote)
|
|
msgName = msg.newName.data;
|
|
}
|
|
|
|
SCOPED_WRITE_LOCK(msg.process.m_writtenFilesLock, writtenLock);
|
|
auto insres = msg.process.m_writtenFiles.try_emplace(name);
|
|
WrittenFile& writtenFile = insres.first->second;
|
|
|
|
UBA_ASSERTF(writtenFile.owner == nullptr || writtenFile.owner == &msg.process || !m_allowOutputFiles, TC("File %s changed owner.. should not happen"), name);
|
|
writtenFile.owner = &msg.process;
|
|
writtenFile.attributes = msg.attributes;
|
|
|
|
bool addMapping = true;
|
|
if (!insres.second)
|
|
{
|
|
if (writtenFile.name != msgName)
|
|
{
|
|
UBA_ASSERT(msg.mappingHandle == 0 && !writtenFile.mappingHandle.IsValid());
|
|
writtenFile.name = msgName;
|
|
}
|
|
|
|
if (!msg.mappingHandle || msg.mappingHandle == writtenFile.originalMappingHandle)
|
|
{
|
|
if (msg.mappingWritten)
|
|
{
|
|
writtenFile.mappingWritten = msg.mappingWritten;
|
|
writtenFile.lastWriteTime = GetSystemTimeAsFileTime();
|
|
}
|
|
addMapping = false;
|
|
}
|
|
else
|
|
{
|
|
if (writtenFile.mappingHandle.IsValid())
|
|
{
|
|
CloseFileMapping(writtenFile.mappingHandle);
|
|
writtenFile.mappingHandle = {};
|
|
}
|
|
}
|
|
}
|
|
|
|
if (addMapping)
|
|
{
|
|
writtenFile.name = msgName;
|
|
FileMappingHandle mappingHandle;
|
|
if (msg.mappingHandle)
|
|
{
|
|
FileMappingHandle source;
|
|
source.FromU64(msg.mappingHandle);
|
|
if (!DuplicateFileMapping(msg.process.m_nativeProcessHandle, source, GetCurrentProcessHandle(), &mappingHandle, FILE_MAP_READ, false, 0))
|
|
return m_logger.Error(TC("Failed to duplicate file mapping handle for %s"), name);
|
|
}
|
|
writtenFile.key = key;
|
|
writtenFile.mappingHandle = mappingHandle;
|
|
writtenFile.mappingWritten = msg.mappingWritten;
|
|
writtenFile.originalMappingHandle = msg.mappingHandle;
|
|
writtenFile.lastWriteTime = GetSystemTimeAsFileTime();
|
|
}
|
|
|
|
if (writtenFile.mappingHandle.IsValid())
|
|
{
|
|
registerRealFile = false;
|
|
fileSize = writtenFile.mappingWritten;
|
|
lastWriteTime = writtenFile.lastWriteTime;
|
|
}
|
|
|
|
if (msg.process.m_extractExports && msg.process.m_startInfo.rules->ShouldExtractSymbols(file.name))
|
|
if (!ExtractSymbolsFromObjectFile(msg, name, fileSize))
|
|
return false;
|
|
}
|
|
|
|
if (!msg.newName.IsEmpty())
|
|
{
|
|
RegisterDeleteFile(file.nameKey, file.name);
|
|
RegisterCreateFileForWrite(msg.newNameKey, msg.newName, registerRealFile, fileSize, lastWriteTime);
|
|
}
|
|
else if (msg.deleteOnClose)
|
|
RegisterDeleteFile(file.nameKey, file.name);
|
|
else
|
|
RegisterCreateFileForWrite(file.nameKey, file.name, registerRealFile, fileSize, lastWriteTime);
|
|
|
|
out.directoryTableSize = GetDirectoryTableSize();
|
|
return true;
|
|
}
|
|
|
|
bool Session::DeleteFile(DeleteFileResponse& out, const DeleteFileMessage& msg)
|
|
{
|
|
if (msg.closeId != 0)
|
|
{
|
|
SCOPED_WRITE_LOCK(m_activeFilesLock, lock);
|
|
m_activeFiles.erase(msg.closeId);
|
|
}
|
|
|
|
{
|
|
SCOPED_WRITE_LOCK(m_outputFilesLock, lock);
|
|
m_outputFiles.erase(msg.fileName.data);
|
|
}
|
|
|
|
RemoveWrittenFile(msg.process, msg.fileName.data);
|
|
|
|
out.result = uba::DeleteFileW(msg.fileName.data);
|
|
out.errorCode = GetLastError();
|
|
out.directoryTableSize = RegisterDeleteFile(msg.fileNameKey, msg.fileName);
|
|
return true;
|
|
}
|
|
|
|
bool Session::CopyFile(CopyFileResponse& out, const CopyFileMessage& msg)
|
|
{
|
|
out.fromName.Append(msg.fromName);
|
|
out.toName.Append(msg.toName);
|
|
|
|
SCOPED_WRITE_LOCK(m_activeFilesLock, lock);
|
|
u32 closeId = m_wantsOnCloseIdCounter++;
|
|
if (!m_activeFiles.try_emplace(closeId, ActiveFile{ msg.toName.data, msg.toKey }).second)
|
|
{
|
|
m_logger.Error(TC("SHOULD NOT HAPPEN"));
|
|
}
|
|
out.closeId = closeId;
|
|
return true;
|
|
}
|
|
|
|
bool Session::MoveFile(MoveFileResponse& out, const MoveFileMessage& msg)
|
|
{
|
|
out.result = MoveFileExW(msg.fromName.data, msg.toName.data, msg.flags);
|
|
out.errorCode = ERROR_SUCCESS;
|
|
if (!out.result)
|
|
out.errorCode = GetLastError();
|
|
RegisterCreateFileForWrite(msg.toKey, msg.toName, true);
|
|
out.directoryTableSize = RegisterDeleteFile(msg.fromKey, msg.fromName);
|
|
return true;
|
|
}
|
|
|
|
bool Session::Chmod(ChmodResponse& out, const ChmodMessage& msg)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
UBA_ASSERT(false); // This is not used
|
|
#else
|
|
out.errorCode = 0;
|
|
if (chmod(msg.fileName.data, (mode_t)msg.fileMode) == 0)
|
|
{
|
|
RegisterCreateFileForWrite(msg.fileNameKey, msg.fileName, true);
|
|
return true;
|
|
}
|
|
out.errorCode = errno;
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
bool Session::CreateDirectory(CreateDirectoryResponse& out, const CreateDirectoryMessage& msg)
|
|
{
|
|
out.result = uba::CreateDirectoryW(msg.name.data);
|
|
if (out.result)
|
|
{
|
|
StringKey dirKey;
|
|
const tchar* lastSlash;
|
|
StringBuffer<> dirName;
|
|
if (!GetDirKey(dirKey, dirName, lastSlash, msg.name))
|
|
return true;
|
|
// Both these functions need to be called. otherwise we can get created directories that does not end up in directory table
|
|
RegisterCreateFileForWrite(msg.nameKey, msg.name, true);
|
|
WriteDirectoryEntries(dirKey, dirName.data);
|
|
}
|
|
else
|
|
{
|
|
out.errorCode = GetLastError();
|
|
}
|
|
|
|
out.directoryTableSize = GetDirectoryTableSize();
|
|
return true;
|
|
}
|
|
|
|
bool Session::RemoveDirectory(RemoveDirectoryResponse& out, const RemoveDirectoryMessage& msg)
|
|
{
|
|
out.result = uba::RemoveDirectoryW(msg.name.data);
|
|
if (out.result)
|
|
RegisterDeleteFile(msg.nameKey, msg.name);
|
|
else
|
|
out.errorCode = GetLastError();
|
|
out.directoryTableSize = GetDirectoryTableSize();
|
|
return true;
|
|
}
|
|
|
|
bool Session::GetFullFileName(GetFullFileNameResponse& out, const GetFullFileNameMessage& msg)
|
|
{
|
|
UBA_ASSERTF(false, TC("SHOULD NOT HAPPEN (only remote).. %s"), msg.fileName.data);
|
|
return false;
|
|
}
|
|
|
|
bool Session::GetLongPathName(GetLongPathNameResponse& out, const GetLongPathNameMessage& msg)
|
|
{
|
|
UBA_ASSERTF(false, TC("SHOULD NOT HAPPEN (only remote).. %s"), msg.fileName.data);
|
|
return false;
|
|
}
|
|
|
|
bool Session::GetListDirectoryInfo(ListDirectoryResponse& out, tchar* dirName, const StringKey& dirKey)
|
|
{
|
|
u32 tableOffset;
|
|
u32 tableSize = WriteDirectoryEntries(dirKey, dirName, &tableOffset);
|
|
out.tableOffset = tableOffset;
|
|
out.tableSize = tableSize;
|
|
return true;
|
|
}
|
|
|
|
bool Session::WriteFilesToDisk(ProcessImpl& process, WrittenFile** files, u32 fileCount)
|
|
{
|
|
|
|
auto writeFile = [&](WrittenFile& file)
|
|
{
|
|
bool shouldEvictFromMemory = IsRarelyReadAfterWritten(process, file.name) || file.mappingWritten > m_keepOutputFileMemoryMapsThreshold;
|
|
|
|
if (ShouldWriteToDisk(file.name))
|
|
{
|
|
// This is to kill I/O when writing lots of pdb/dlls in parallel
|
|
#if PLATFORM_WINDOWS
|
|
constexpr u32 bottleneckMax = 32;
|
|
bool shouldBottleneck = false;//useOverlap;
|
|
static Bottleneck bottleneck(bottleneckMax);
|
|
auto bng = MakeGuard([&]() { if (shouldBottleneck) bottleneck.Leave(); });
|
|
#endif
|
|
|
|
#if PLATFORM_WINDOWS
|
|
shouldBottleneck = true;
|
|
bottleneck.Enter();
|
|
#endif
|
|
|
|
u64 fileSize = file.mappingWritten;
|
|
u8* mem = MapViewOfFile(file.mappingHandle, FILE_MAP_READ, 0, fileSize);
|
|
if (!mem)
|
|
return m_logger.Error(TC("Failed to map view of filehandle for read %s (%s)"), file.name.c_str(), LastErrorToText().data);
|
|
|
|
//PrefetchVirtualMemory(mem, fileSize);
|
|
|
|
auto memClose = MakeGuard([&](){ UnmapViewOfFile(mem, fileSize, file.name.c_str()); });
|
|
|
|
if (m_storeObjFilesCompressed && process.m_startInfo.rules->StoreFileCompressed(file.name))
|
|
{
|
|
Storage::WriteResult res;
|
|
CompressedObjFileHeader header { CalculateCasKey(mem, fileSize, true, m_workManager, file.name.c_str()) };
|
|
|
|
if (!m_storage.WriteCompressed(res, TC("MemoryMap"), InvalidFileHandle, mem, fileSize, file.name.c_str(), &header, sizeof(header), file.lastWriteTime))
|
|
return false;
|
|
|
|
shouldEvictFromMemory = false; // Can't evict without properly update filemappingtable.. the file on disk does now not match what was registered for write
|
|
}
|
|
else
|
|
{
|
|
|
|
// Seems like best combo (for windows at least) is to use writes with overlap and max 16 at the same time.
|
|
// On one machine we get twice as fast without overlap if no bottleneck. On another machine (ntfs compression on) we get twice as slow without overlap
|
|
// Both machines behaves well with overlap AND bottleneck. Both machine are 128 logical core thread rippers.
|
|
bool useFileMapForWrite = fileSize > 0; // ::CreateFileMappingW does not work for zero-length files
|
|
bool useOverlap = false;//fileSize > 8 * 1024 * 1024;
|
|
|
|
|
|
u32 attributes = DefaultAttributes();
|
|
if (useOverlap)
|
|
attributes |= FILE_FLAG_OVERLAPPED;
|
|
|
|
FileAccessor destinationFile(m_logger, file.name.c_str());
|
|
|
|
if (useFileMapForWrite)
|
|
{
|
|
if (!destinationFile.CreateMemoryWrite(false, attributes, fileSize, m_tempPath.data))
|
|
return false;
|
|
MapMemoryCopy(destinationFile.GetData(), mem, fileSize);
|
|
}
|
|
else
|
|
{
|
|
if (!destinationFile.CreateWrite(false, attributes, fileSize, m_tempPath.data))
|
|
return false;
|
|
|
|
#if PLATFORM_WINDOWS
|
|
//shouldBottleneck = fileSize > 64 * 1024 * 1024;
|
|
//if (shouldBottleneck)
|
|
// bottleneck.Enter();
|
|
#endif
|
|
|
|
if (!destinationFile.Write(mem, fileSize))
|
|
return false;
|
|
}
|
|
|
|
if (u64 time = file.lastWriteTime)
|
|
if (!SetFileLastWriteTime(destinationFile.GetHandle(), time))
|
|
return m_logger.Error(TC("Failed to set file time on filehandle for %s"), file.name.c_str());
|
|
|
|
if (!destinationFile.Close(file.lastWriteTime ? nullptr : &file.lastWriteTime))
|
|
return false;
|
|
}
|
|
|
|
// There are directory crawlers happening in parallel so we need to really make sure to invalidate this one since a crawler can actually
|
|
// hit this file with information from a query before it was written.. and then it will turn it back to "verified" using old info
|
|
m_storage.InvalidateCachedFileInfo(file.key);
|
|
}
|
|
else
|
|
{
|
|
// Delete existing file to make sure it is not picked up (since it is out of date)
|
|
uba::DeleteFileW(file.name.c_str());
|
|
}
|
|
|
|
if (shouldEvictFromMemory)
|
|
{
|
|
m_workManager->AddWork([mh = file.mappingHandle]()
|
|
{
|
|
CloseFileMapping(mh);
|
|
}, 1, TC("CFM"));
|
|
//CloseFileMapping(file.mappingHandle);
|
|
}
|
|
else
|
|
{
|
|
StringBuffer<> name;
|
|
Storage::GetMappingString(name, file.mappingHandle, 0);
|
|
SCOPED_WRITE_LOCK(m_fileMappingTableLookupLock, lookupLock);
|
|
auto insres = m_fileMappingTableLookup.try_emplace(file.key);
|
|
FileMappingEntry& entry = insres.first->second;
|
|
lookupLock.Leave();
|
|
SCOPED_WRITE_LOCK(entry.lock, entryCs);
|
|
entry.handled = true;
|
|
entry.mapping = file.mappingHandle;
|
|
entry.mappingOffset = 0;
|
|
entry.size = file.mappingWritten;
|
|
entry.isDir = false;
|
|
entry.success = true;
|
|
|
|
SCOPED_WRITE_LOCK(m_fileMappingTableMemLock, lock);
|
|
BinaryWriter writer(m_fileMappingTableMem, m_fileMappingTableSize);
|
|
writer.WriteStringKey(file.key);
|
|
writer.WriteString(name);
|
|
writer.Write7BitEncoded(file.mappingWritten);
|
|
u32 newSize = (u32)writer.GetPosition();
|
|
m_fileMappingTableSize = (u32)newSize;
|
|
}
|
|
file.mappingHandle = {};
|
|
return true;
|
|
};
|
|
|
|
for (u32 i=0; i!=fileCount; ++i)
|
|
if (!writeFile(*files[i]))
|
|
return false;
|
|
/*
|
|
Event events[32];
|
|
UBA_ASSERT(fileCount < 32);
|
|
|
|
|
|
for (u32 i=0; i!=fileCount; ++i)
|
|
{
|
|
events[i].Create(true);
|
|
|
|
m_workManager->AddWork([&, ii = i]()
|
|
{
|
|
writeFile(*files[ii]);
|
|
events[ii].Set();
|
|
}, 1, TC(""));
|
|
}
|
|
for (u32 i=0; i!=fileCount; ++i)
|
|
events[i].IsSet();
|
|
*/
|
|
return true;
|
|
}
|
|
|
|
bool Session::AllocFailed(Process& process, const tchar* allocType, u32 error)
|
|
{
|
|
m_logger.Warning(TC("Allocation failed in %s (%s).. process will sleep and try again"), allocType, LastErrorToText(error).data);
|
|
return true;
|
|
}
|
|
|
|
bool Session::GetNextProcess(Process& process, bool& outNewProcess, NextProcessInfo& outNextProcess, u32 prevExitCode, BinaryReader& statsReader)
|
|
{
|
|
if (!m_getNextProcessFunction)
|
|
{
|
|
outNewProcess = false;
|
|
return true;
|
|
}
|
|
|
|
outNewProcess = m_getNextProcessFunction(process, outNextProcess, prevExitCode);
|
|
if (!outNewProcess)
|
|
return true;
|
|
|
|
m_trace.ProcessEnvironmentUpdated(process.GetId(), outNextProcess.description.c_str(), statsReader.GetPositionData(), statsReader.GetLeft());
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Session::CustomMessage(Process& process, BinaryReader& reader, BinaryWriter& writer)
|
|
{
|
|
u32 recvSize = reader.ReadU32();
|
|
u32* sendSize = (u32*)writer.AllocWrite(4);
|
|
void* sendData = writer.GetData() + writer.GetPosition();
|
|
u32 written = 0;
|
|
if (m_customServiceFunction)
|
|
written = m_customServiceFunction(process, reader.GetPositionData(), recvSize, sendData, u32(writer.GetCapacityLeft()));
|
|
*sendSize = written;
|
|
writer.AllocWrite(written);
|
|
return true;
|
|
}
|
|
|
|
bool Session::SHGetKnownFolderPath(Process& process, BinaryReader& reader, BinaryWriter& writer)
|
|
{
|
|
UBA_ASSERT(false); // Should only be called on UbaSessionClient
|
|
return false;
|
|
}
|
|
|
|
bool Session::HostRun(BinaryReader& reader, BinaryWriter& writer)
|
|
{
|
|
#if !PLATFORM_WINDOWS
|
|
|
|
Vector<TString> args;
|
|
while (reader.GetLeft())
|
|
args.push_back(reader.ReadString());
|
|
bool success = false;
|
|
|
|
StringBuffer<> command;
|
|
for (auto& arg : args)
|
|
{
|
|
if (command.count)
|
|
command.Append(' ');
|
|
command.Append(arg);
|
|
}
|
|
|
|
char result[4096];
|
|
if (FILE* fp = popen(command.data, "r"))
|
|
{
|
|
char* dest = result;
|
|
errno = 0;
|
|
while (true)
|
|
{
|
|
if (!fgets(dest, sizeof(result) - (dest - result), fp))
|
|
{
|
|
success = errno == 0;
|
|
if (!success)
|
|
snprintf(result, sizeof(result), "fgets failed with command: %s", command.data);
|
|
break;
|
|
}
|
|
dest += strlen(dest);
|
|
}
|
|
pclose(fp);
|
|
}
|
|
else
|
|
snprintf(result, sizeof(result), "popen failed with command: %s", command.data);
|
|
writer.WriteBool(success);
|
|
writer.WriteString(result);
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
void Session::FileEntryAdded(StringKey fileNameKey, u64 lastWritten, u64 size)
|
|
{
|
|
}
|
|
|
|
bool Session::FlushWrittenFiles(ProcessImpl& process)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool Session::UpdateEnvironment(ProcessImpl& process, const tchar* reason, bool resetStats)
|
|
{
|
|
if (!resetStats)
|
|
return true;
|
|
StackBinaryWriter<16 * 1024> writer;
|
|
process.m_processStats.Write(writer);
|
|
process.m_sessionStats.Write(writer);
|
|
process.m_storageStats.Write(writer);
|
|
process.m_kernelStats.Write(writer);
|
|
m_trace.ProcessEnvironmentUpdated(process.GetId(), reason, writer.GetData(), writer.GetPosition());
|
|
process.m_processStats = {};
|
|
process.m_sessionStats = {};
|
|
process.m_storageStats = {};
|
|
process.m_kernelStats = {};
|
|
return true;
|
|
}
|
|
|
|
bool Session::LogLine(ProcessImpl& process, const tchar* line, LogEntryType logType)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void Session::PrintSessionStats(Logger& logger)
|
|
{
|
|
u64 mappingBufferSize;
|
|
u32 mappingBufferCount;
|
|
m_fileMappingBuffer.GetSizeAndCount(MappedView_Transient, mappingBufferSize, mappingBufferCount);
|
|
logger.Info(TC(" DirectoryTable %7u %9s"), u32(m_directoryTable.m_lookup.size()), BytesToText(GetDirectoryTableSize()).str);
|
|
logger.Info(TC(" MappingTable %7u %9s"), u32(m_fileMappingTableLookup.size()), BytesToText(GetFileMappingSize()).str);
|
|
logger.Info(TC(" MappingBuffer %7u %9s"), mappingBufferCount, BytesToText(mappingBufferSize).str);
|
|
m_stats.Print(logger);
|
|
logger.Info(TC(""));
|
|
}
|
|
|
|
bool Session::CreateProcessJobObject()
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
m_processJobObject = CreateJobObject(nullptr, nullptr);
|
|
if (!m_processJobObject)
|
|
return m_logger.Error(TC("Failed to create process job object"));
|
|
JOBOBJECT_EXTENDED_LIMIT_INFORMATION info = { };
|
|
info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_BREAKAWAY_OK | JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
|
|
SetInformationJobObject(m_processJobObject, JobObjectExtendedLimitInformation, &info, sizeof(info));
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
|
|
void Session::GetSystemInfo(StringBufferBase& out)
|
|
{
|
|
u32 cpuCount = GetLogicalProcessorCount();
|
|
u32 cpuGroupCount = GetProcessorGroupCount();
|
|
|
|
StringBuffer<128> cpuCountStr;
|
|
if (cpuGroupCount != 1)
|
|
cpuCountStr.AppendValue(cpuGroupCount).Append('x');
|
|
cpuCountStr.AppendValue(cpuCount/cpuGroupCount);
|
|
|
|
u64 totalMemoryInKilobytes = 0;
|
|
StringBuffer<128> hzStr;
|
|
|
|
#if PLATFORM_WINDOWS
|
|
GetPhysicallyInstalledSystemMemory(&totalMemoryInKilobytes);
|
|
|
|
{
|
|
u32 maxMHz = 0;
|
|
DWORD valueSize = 4;
|
|
const tchar* key = TC("HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0");
|
|
LSTATUS res = RegGetValueW(HKEY_LOCAL_MACHINE, key, TC("~MHz"), RRF_RT_REG_DWORD, NULL, &maxMHz, &valueSize);
|
|
if (res != ERROR_SUCCESS)
|
|
{
|
|
// This will not always be the same and since we use the system info as part of key for client uniqueness it is annoying to get multiple sessions for same instance
|
|
Vector<PROCESSOR_POWER_INFORMATION> procInfos;
|
|
procInfos.resize(cpuCount);
|
|
if (CallNtPowerInformation(ProcessorInformation, NULL, 0, procInfos.data(), cpuCount*sizeof(PROCESSOR_POWER_INFORMATION)) == STATUS_SUCCESS)
|
|
maxMHz = procInfos[0].MaxMhz;
|
|
}
|
|
hzStr.Appendf(TC(" @ %.1fGHz"), float(maxMHz) / 1000.0f);
|
|
}
|
|
|
|
#else
|
|
u64 throwAway;
|
|
GetMemoryInfo(throwAway, totalMemoryInKilobytes);
|
|
totalMemoryInKilobytes /= 1024;
|
|
// TODO get freq from /proc/cpuinfo
|
|
hzStr.Append(" @ ?GHz");
|
|
#endif
|
|
|
|
const tchar* capacityStr = TC("NoLimit");
|
|
u64 capacity = m_storage.GetStorageCapacity();
|
|
BytesToText temp(capacity);
|
|
if (capacity)
|
|
capacityStr = temp.str;
|
|
out.Appendf(TC("CPU:%s%s Mem:%ugb Cas:%s"), cpuCountStr.data, hzStr.data, u32(totalMemoryInKilobytes/(1024*1024)), capacityStr);
|
|
|
|
StringBuffer<128> zone;
|
|
if (m_storage.GetZone(zone))
|
|
out.Append(TC(" Zone:")).Append(zone);
|
|
|
|
#if PLATFORM_WINDOWS
|
|
if (!IsRunningWine())
|
|
{
|
|
DWORD value = 0;
|
|
DWORD valueSize = 4;
|
|
const tchar* fsKey = TC("SYSTEM\\CurrentControlSet\\Control\\FileSystem");
|
|
LSTATUS res = RegGetValueW(HKEY_LOCAL_MACHINE, fsKey, TC("NtfsDisableLastAccessUpdate"), RRF_RT_REG_DWORD, NULL, &value, &valueSize);
|
|
if (res != ERROR_SUCCESS)
|
|
{
|
|
m_logger.Detail(TC("Failed to retreive ntfs registry key (%i)"), res);
|
|
}
|
|
else
|
|
{
|
|
u32 lastAccessSettingsValue = value & 0xf;
|
|
if (lastAccessSettingsValue == 0 || lastAccessSettingsValue == 2)
|
|
out.Append(TC(" NtfsLastAccessEnabled"));
|
|
}
|
|
value = 0;
|
|
res = RegGetValueW(HKEY_LOCAL_MACHINE, fsKey, TC("NtfsDisable8dot3NameCreation"), RRF_RT_REG_DWORD, NULL, &value, &valueSize);
|
|
if (res == ERROR_SUCCESS)
|
|
if (value == 0)
|
|
out.Append(TC(" NtfsShortNamesEnabled"));
|
|
}
|
|
else
|
|
{
|
|
StringBuffer<> testDir;
|
|
testDir.Append(m_rootDir).Append(TC("UbaTestShortNames"));
|
|
::RemoveDirectory(testDir.data);
|
|
wchar_t shortName[1024];
|
|
if (::CreateDirectoryW(testDir.data, NULL))
|
|
if (GetShortPathName(testDir.data, shortName, 1024) != 0 && !Contains(shortName, TC("UbaTestShortNames")))
|
|
out.Append(TC(" NtfsShortNamesEnabled"));
|
|
}
|
|
#endif
|
|
|
|
if (!m_extraInfo.empty())
|
|
out.Append(m_extraInfo);
|
|
|
|
#if UBA_DEBUG
|
|
out.Append(TC(" DEBUG"));
|
|
#endif
|
|
}
|
|
|
|
bool Session::GetMemoryInfo(u64& outAvailable, u64& outTotal)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
|
|
MEMORYSTATUSEX memStatus = { sizeof(memStatus) };
|
|
if (!GlobalMemoryStatusEx(&memStatus))
|
|
{
|
|
outAvailable = 0;
|
|
outTotal = 0;
|
|
return m_logger.Error(TC("Failed to get global memory status (%s)"), LastErrorToText().data);
|
|
}
|
|
|
|
// Page file can grow and we want to use the absolute max size to figure out when we need to wait to start new processes
|
|
if (m_maxPageSize == ~u64(0))
|
|
{
|
|
m_maxPageSize = 0;
|
|
wchar_t str[1024];
|
|
DWORD strBytes = sizeof(str);
|
|
LSTATUS res = RegGetValueW(HKEY_LOCAL_MACHINE, TC("SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Memory Management"), TC("PagingFiles"), RRF_RT_REG_MULTI_SZ, NULL, str, &strBytes);
|
|
if (res == ERROR_SUCCESS)
|
|
{
|
|
wchar_t* line = str;
|
|
for (; size_t lineLen = wcslen(line); line += lineLen + 1)
|
|
{
|
|
if (lineLen < 3)
|
|
continue;
|
|
|
|
u64 maxSizeMb = 0;
|
|
|
|
StringBuffer<8> drive;
|
|
drive.Append(line, 3); // Get drive root path
|
|
|
|
if (drive[0] == '?') // Drive '?' can exist when "Automatically manage paging file size for all drives"..
|
|
{
|
|
// We can use ExistingPageFiles registry key to figure out which drive...
|
|
// This key can contain multiple page files normally.. have no idea if it can contain multiple when drive is '?'.. but for now, just use the first
|
|
wchar_t str2[1024];
|
|
DWORD str2Bytes = sizeof(str2);
|
|
res = RegGetValueW(HKEY_LOCAL_MACHINE, TC("SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Memory Management"), TC("ExistingPageFiles"), RRF_RT_REG_MULTI_SZ, NULL, str2, &str2Bytes);
|
|
if (res != ERROR_SUCCESS)
|
|
continue;
|
|
|
|
auto colon = wcschr(str2, ':'); // Path is something like \??\C:\pagefile.sys or similar.. let's search for : and use character in front of it.
|
|
if (colon == nullptr || colon == str2)
|
|
continue;
|
|
|
|
drive[0] = colon[-1];
|
|
}
|
|
else
|
|
{
|
|
const wchar_t* maxSizeStr = wcsrchr(line, ' ');
|
|
|
|
if (!maxSizeStr || !StringBuffer<32>(maxSizeStr + 1).Parse(maxSizeMb))
|
|
{
|
|
m_logger.Warning(TC("Unrecognized page file information format (please report): %s"), line);
|
|
continue;
|
|
}
|
|
|
|
if (maxSizeMb) // Custom set page file size
|
|
{
|
|
m_maxPageSize += maxSizeMb * 1024 * 1024;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Max possible system-managed page file
|
|
maxSizeMb = Max(u64(memStatus.ullTotalPhys) * 3, 4ull * 1024 * 1024 * 1024);
|
|
|
|
// Check if disk is limiting factor of system-managed page file
|
|
// Page file can be max 1/8 of volume size and ofc not more than free space
|
|
ULARGE_INTEGER totalNumberOfBytes;
|
|
ULARGE_INTEGER totalNumberOfFreeBytes;
|
|
if (!GetDiskFreeSpaceExW(drive.data, NULL, &totalNumberOfBytes, &totalNumberOfFreeBytes))
|
|
return m_logger.Error(TC("GetDiskFreeSpaceExW failed to get information about %s (%s)"), drive.data, LastErrorToText().data);
|
|
|
|
u64 maxDiskPageFileSize = Min(totalNumberOfBytes.QuadPart / 8, totalNumberOfFreeBytes.QuadPart);
|
|
m_maxPageSize += Min(maxDiskPageFileSize, maxSizeMb);
|
|
}
|
|
}
|
|
}
|
|
|
|
u64 currentPageSize = memStatus.ullTotalPageFile - memStatus.ullTotalPhys;
|
|
if (currentPageSize < m_maxPageSize)
|
|
{
|
|
outTotal = memStatus.ullTotalPhys + m_maxPageSize;
|
|
outAvailable = memStatus.ullAvailPageFile + (m_maxPageSize - currentPageSize);
|
|
}
|
|
else
|
|
{
|
|
outTotal = memStatus.ullTotalPageFile;
|
|
outAvailable = memStatus.ullAvailPageFile;
|
|
}
|
|
#elif PLATFORM_MAC
|
|
vm_size_t page_size;
|
|
mach_port_t mach_port;
|
|
mach_msg_type_number_t count;
|
|
vm_statistics64_data_t vm_stats;
|
|
|
|
mach_port = mach_host_self();
|
|
count = sizeof(vm_stats) / sizeof(natural_t);
|
|
if (KERN_SUCCESS == host_page_size(mach_port, &page_size) &&
|
|
KERN_SUCCESS == host_statistics64(mach_port, HOST_VM_INFO,
|
|
(host_info64_t)&vm_stats, &count))
|
|
{
|
|
outAvailable = (int64_t)vm_stats.free_count * (int64_t)page_size;
|
|
outTotal = vm_stats.wire_count + vm_stats.active_count + vm_stats.inactive_count + vm_stats.free_count;
|
|
// long long used_memory = ((int64_t)vm_stats.active_count +
|
|
// (int64_t)vm_stats.inactive_count +
|
|
// (int64_t)vm_stats.wire_count) * (int64_t)page_size;
|
|
}
|
|
|
|
#else
|
|
static long mem = sysconf(_SC_PHYS_PAGES) * sysconf(_SC_PAGE_SIZE);
|
|
outTotal = mem;
|
|
outAvailable = mem;
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
void Session::WriteSummary(BinaryWriter& writer, const Function<void(Logger& logger)>& summaryFunc)
|
|
{
|
|
struct SummaryLogWriter : public LogWriter
|
|
{
|
|
SummaryLogWriter(BinaryWriter& w) : writer(w) {}
|
|
virtual void BeginScope() override {}
|
|
virtual void EndScope() override {}
|
|
virtual void Log(LogEntryType type, const tchar* str, u32 strLen, const tchar* prefix, u32 prefixLen) override
|
|
{
|
|
writer.WriteString(str, strLen);
|
|
++count;
|
|
}
|
|
|
|
BinaryWriter& writer;
|
|
u32 count = 0;
|
|
};
|
|
u32* lineCount = (u32*)writer.AllocWrite(4);
|
|
|
|
SummaryLogWriter logWriter(writer);
|
|
LoggerWithWriter logger(logWriter, TC(""));
|
|
|
|
summaryFunc(logger);
|
|
|
|
*lineCount = logWriter.count;
|
|
}
|
|
|
|
float Session::UpdateCpuLoad()
|
|
{
|
|
u64 idleTime = 0;
|
|
u64 totalTime = 0;
|
|
|
|
#if PLATFORM_WINDOWS
|
|
u64 kernelTime, userTime;
|
|
if (!GetSystemTimes((FILETIME*)&idleTime, (FILETIME*)&kernelTime, (FILETIME*)&userTime))
|
|
return m_cpuLoad;
|
|
totalTime = kernelTime + userTime;
|
|
#elif PLATFORM_LINUX
|
|
int fd = open("/proc/stat", O_RDONLY | O_CLOEXEC);
|
|
if (fd != -1)
|
|
{
|
|
char buffer[512];
|
|
int size = read(fd, buffer, sizeof_array(buffer) - 1);
|
|
if (size != -1)
|
|
{
|
|
buffer[size] = 0;
|
|
if (char* endl = strchr(buffer, '\n'))
|
|
{
|
|
u64 values[16];
|
|
u32 valueCount = 0;
|
|
*endl = 0;
|
|
char* parsePos = buffer;
|
|
char* space = nullptr;
|
|
do
|
|
{
|
|
space = strchr(parsePos, ' ');
|
|
if (space)
|
|
*space = 0;
|
|
values[valueCount++] = strtoull(parsePos, nullptr, 10);
|
|
|
|
parsePos = space + 1;
|
|
} while (space);
|
|
|
|
// user: normal processes executing in user mode
|
|
// nice: niced processes executing in user mode
|
|
// system: processes executing in kernel mode
|
|
// idle: twiddling thumbs
|
|
// iowait: waiting for I/O to complete
|
|
// irq: servicing interrupts
|
|
// softirq: servicing softirqs
|
|
if (valueCount > 6)
|
|
{
|
|
u64 work = values[0] + values[1] + values[2];
|
|
idleTime = values[3] + values[4] + values[5] + values[6];
|
|
totalTime = work + idleTime;
|
|
}
|
|
}
|
|
}
|
|
close(fd);
|
|
}
|
|
// TODO: Read "/proc/stat" to get cpu cycles
|
|
#else // PLATFORM_MAC
|
|
mach_msg_type_number_t CpuMsgCount = 0;
|
|
processor_flavor_t CpuInfoType = PROCESSOR_CPU_LOAD_INFO;;
|
|
natural_t CpuCount = 0;
|
|
processor_cpu_load_info_t CpuData;
|
|
host_t host = mach_host_self();
|
|
|
|
int res = 0;
|
|
u64 work = 0;
|
|
idleTime = 0;
|
|
|
|
res = host_processor_info(host, CpuInfoType, &CpuCount, (processor_info_array_t *)&CpuData, &CpuMsgCount);
|
|
if(res != KERN_SUCCESS)
|
|
{
|
|
return m_logger.Error(TC("Kernel error: %s"), mach_error_string(res));
|
|
}
|
|
|
|
for(int i = 0; i < (int)CpuCount; i++)
|
|
{
|
|
work += CpuData[i].cpu_ticks[CPU_STATE_SYSTEM];
|
|
work += CpuData[i].cpu_ticks[CPU_STATE_USER];
|
|
work += CpuData[i].cpu_ticks[CPU_STATE_NICE];
|
|
idleTime += CpuData[i].cpu_ticks[CPU_STATE_IDLE];
|
|
}
|
|
|
|
totalTime = work + idleTime;
|
|
#endif
|
|
|
|
u64 totalTimeSinceLastTime = totalTime - m_previousTotalCpuTime;
|
|
u64 idleTimeSinceLastTime = idleTime - m_previousIdleCpuTime;
|
|
|
|
float cpuLoad = 1.0f - ((totalTimeSinceLastTime > 0) ? (float(idleTimeSinceLastTime) / float(totalTimeSinceLastTime)) : 0);
|
|
|
|
m_previousTotalCpuTime = totalTime;
|
|
m_previousIdleCpuTime = idleTime;
|
|
|
|
// TODO: This is the wrong solution.. but can't repro the bad values some people get
|
|
if (cpuLoad >= 0 && cpuLoad <= 1.0f)
|
|
m_cpuLoad = cpuLoad;
|
|
|
|
return m_cpuLoad;
|
|
}
|
|
|
|
bool Session::ExtractSymbolsFromObjectFile(const CloseFileMessage& msg, const tchar* fileName, u64 fileSize)
|
|
{
|
|
if (!msg.mappingHandle)
|
|
return m_logger.Error(TC("Can't extract symbols from obj file that is written directly to disk (%s writing %s)"), msg.process.m_startInfo.application, fileName);
|
|
|
|
FileMappingHandle source;
|
|
source.FromU64(msg.mappingHandle);
|
|
FileMappingHandle objectFileMappingHandle;
|
|
|
|
if (!DuplicateFileMapping(msg.process.m_nativeProcessHandle, source, GetCurrentProcessHandle(), &objectFileMappingHandle, FILE_MAP_ALL_ACCESS, false, 0))
|
|
return m_logger.Error(TC("Failed to duplicate file mapping handle for %s"), fileName);
|
|
auto ofmh = MakeGuard([&]() { CloseFileMapping(objectFileMappingHandle); });
|
|
|
|
u8* mem = MapViewOfFile(objectFileMappingHandle, FILE_MAP_ALL_ACCESS, 0, fileSize);
|
|
if (!mem)
|
|
return m_logger.Error(TC("Failed to map view of filehandle for read %s (%s)"), fileName, LastErrorToText().data);
|
|
auto memClose = MakeGuard([&](){ UnmapViewOfFile(mem, fileSize, fileName); });
|
|
|
|
ObjectFile* objectFile = ObjectFile::Parse(m_logger, mem, fileSize, fileName);
|
|
if (!objectFile)
|
|
return false;
|
|
auto ofg = MakeGuard([&]() { delete objectFile; });
|
|
|
|
if (!objectFile->StripExports(m_logger))
|
|
return false;
|
|
|
|
const tchar* lastDot = TStrrchr(fileName, '.');
|
|
UBA_ASSERT(lastDot);
|
|
StringBuffer<> exportsFile;
|
|
exportsFile.Append(fileName, lastDot - fileName).Append(TC(".exi"));
|
|
|
|
MemoryBlock memoryBlock(8*1024*1024);
|
|
if (!objectFile->WriteImportsAndExports(m_logger, memoryBlock))
|
|
return false;
|
|
|
|
FileMappingHandle symHandle = CreateMemoryMappingW(m_logger, PAGE_READWRITE, memoryBlock.writtenSize);
|
|
if (!symHandle.IsValid())
|
|
return false;
|
|
|
|
u8* mem2 = MapViewOfFile(symHandle, FILE_MAP_ALL_ACCESS, 0, memoryBlock.writtenSize);
|
|
if (!mem2)
|
|
return false;
|
|
|
|
MapMemoryCopy(mem2, memoryBlock.memory, memoryBlock.writtenSize);
|
|
UnmapViewOfFile(mem2, memoryBlock.writtenSize, TC(""));
|
|
|
|
StringKey symFileKey = CaseInsensitiveFs ? ToStringKeyLower(exportsFile) : ToStringKey(exportsFile);
|
|
u64 lastWriteTime = GetSystemTimeAsFileTime();
|
|
|
|
if (!RegisterCreateFileForWrite(symFileKey, exportsFile, false, memoryBlock.writtenSize, lastWriteTime))
|
|
return false;
|
|
|
|
auto insres = msg.process.m_writtenFiles.try_emplace(exportsFile.data);
|
|
WrittenFile& writtenFile = insres.first->second;
|
|
|
|
UBA_ASSERT(writtenFile.owner == nullptr || writtenFile.owner == &msg.process);
|
|
writtenFile.key = symFileKey;
|
|
writtenFile.owner = &msg.process;
|
|
writtenFile.attributes = msg.attributes;
|
|
writtenFile.mappingHandle = symHandle;
|
|
writtenFile.mappingWritten = memoryBlock.writtenSize;
|
|
writtenFile.lastWriteTime = lastWriteTime;
|
|
writtenFile.name = insres.first->first;
|
|
|
|
return true;
|
|
}
|
|
|
|
void Session::ThreadTraceLoop()
|
|
{
|
|
while (true)
|
|
{
|
|
TraceSessionUpdate();
|
|
if (m_traceThreadEvent.IsSet(500))
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Session::TraceSessionUpdate()
|
|
{
|
|
}
|
|
|
|
void GenerateNameForProcess(StringBufferBase& out, const tchar* arguments, u32 counterSuffix)
|
|
{
|
|
const tchar* start = arguments;
|
|
const tchar* it = arguments;
|
|
StringBuffer<> temp;
|
|
while (true)
|
|
{
|
|
if (*it != ' ' && *it != 0)
|
|
{
|
|
++it;
|
|
continue;
|
|
}
|
|
temp.Clear();
|
|
temp.Append(start, u64(it - start));
|
|
if (!temp.Contains(TC(".rsp")) && !temp.Contains(TC(".bat")))
|
|
{
|
|
if (*it == 0)
|
|
break;
|
|
++it;
|
|
start = it;
|
|
continue;
|
|
}
|
|
out.AppendFileName(temp.data);
|
|
if (out.data[out.count -1] == '"')
|
|
out.Resize(out.count -1);
|
|
break;
|
|
}
|
|
|
|
if (out.IsEmpty())
|
|
out.Append(TC("NoGoodName"));
|
|
|
|
if (counterSuffix)
|
|
out.Appendf(TC("_%03u"), counterSuffix);
|
|
}
|
|
|
|
bool GetZone(StringBufferBase& outZone)
|
|
{
|
|
outZone.count = GetEnvironmentVariableW(TC("UBA_ZONE"), outZone.data, outZone.capacity);
|
|
if (outZone.count)
|
|
return true;
|
|
|
|
// TODO: Remove.
|
|
#if PLATFORM_MAC
|
|
if (!GetComputerNameW(outZone.data, outZone.capacity))
|
|
return false;
|
|
|
|
outZone.count = TStrlen(outZone.data);
|
|
if (outZone.StartsWith(TC("dc4-mac")) || outZone.StartsWith(TC("rdu-mac")))
|
|
{
|
|
outZone.Resize(7);
|
|
return true;
|
|
}
|
|
outZone.count = 0;
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
}
|