mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-06 10:00:16 +02:00
Merge pull request #5835 from Plagman/plagman/cache_thread_mr
DiskCache: offload Store to a WorkQueueThread
This commit is contained in:
13 files changed
+518
-123
No files matched your search
@@ -70,6 +70,7 @@ set(SRCS
|
|||||||
Utils/LongJump.cpp
|
Utils/LongJump.cpp
|
||||||
Utils/Telemetry.cpp
|
Utils/Telemetry.cpp
|
||||||
Utils/Threads.cpp
|
Utils/Threads.cpp
|
||||||
|
Utils/WorkQueueThread.cpp
|
||||||
Utils/Profiler.cpp)
|
Utils/Profiler.cpp)
|
||||||
|
|
||||||
if (ARCHITECTURE_arm64)
|
if (ARCHITECTURE_arm64)
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ namespace DiskCache {
|
|||||||
if (!ReadOnly) {
|
if (!ReadOnly) {
|
||||||
Modes = Modes | File::FileModes::WRITE | File::FileModes::CREATE;
|
Modes = Modes | File::FileModes::WRITE | File::FileModes::CREATE;
|
||||||
}
|
}
|
||||||
FD = fextl::make_unique<File::File>(FileName.c_str(), Modes);
|
FD = fextl::make_unique<File::File>(FileName.c_str(), Modes, false);
|
||||||
if (!FD->IsValid()) {
|
if (!FD->IsValid()) {
|
||||||
FD.reset();
|
FD.reset();
|
||||||
return false;
|
return false;
|
||||||
@@ -51,7 +51,7 @@ namespace DiskCache {
|
|||||||
|
|
||||||
bool Valid = false;
|
bool Valid = false;
|
||||||
bool TookLock = false;
|
bool TookLock = false;
|
||||||
ssize_t Size = FD->Seek(0, File::SeekOp::END);
|
ssize_t Size = FD->Size();
|
||||||
|
|
||||||
if (Size < FOZ_REF_MAGIC_SIZE && !ReadOnly) {
|
if (Size < FOZ_REF_MAGIC_SIZE && !ReadOnly) {
|
||||||
if (!FD->Lock(OPEN_LOCK_TIMEOUT_MS)) {
|
if (!FD->Lock(OPEN_LOCK_TIMEOUT_MS)) {
|
||||||
@@ -59,16 +59,15 @@ namespace DiskCache {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
TookLock = true;
|
TookLock = true;
|
||||||
// seek in case someone else made it while we waited above
|
// check size again in case someone else made it while we waited
|
||||||
Size = FD->Seek(0, File::SeekOp::END);
|
Size = FD->Size();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Size == 0 && !ReadOnly) {
|
if (Size == 0 && !ReadOnly) {
|
||||||
Valid = FD->Write(MesaFOZ::stream_reference_magic_and_version, FOZ_REF_MAGIC_SIZE) == FOZ_REF_MAGIC_SIZE;
|
Valid = FD->PWrite(MesaFOZ::stream_reference_magic_and_version, FOZ_REF_MAGIC_SIZE, 0) == FOZ_REF_MAGIC_SIZE;
|
||||||
} else {
|
} else {
|
||||||
FD->Seek(0, File::SeekOp::BEGIN);
|
|
||||||
uint8_t magic[FOZ_REF_MAGIC_SIZE];
|
uint8_t magic[FOZ_REF_MAGIC_SIZE];
|
||||||
if (FD->Read(magic, FOZ_REF_MAGIC_SIZE) == FOZ_REF_MAGIC_SIZE &&
|
if (FD->PRead(magic, FOZ_REF_MAGIC_SIZE, 0) == FOZ_REF_MAGIC_SIZE &&
|
||||||
memcmp(magic, MesaFOZ::stream_reference_magic_and_version, FOZ_REF_MAGIC_SIZE - 1) == 0) {
|
memcmp(magic, MesaFOZ::stream_reference_magic_and_version, FOZ_REF_MAGIC_SIZE - 1) == 0) {
|
||||||
int version = magic[FOZ_REF_MAGIC_SIZE - 1];
|
int version = magic[FOZ_REF_MAGIC_SIZE - 1];
|
||||||
Valid = version <= MesaFOZ::FOSSILIZE_FORMAT_VERSION && version >= MesaFOZ::FOSSILIZE_FORMAT_MIN_COMPAT_VERSION;
|
Valid = version <= MesaFOZ::FOSSILIZE_FORMAT_VERSION && version >= MesaFOZ::FOSSILIZE_FORMAT_MIN_COMPAT_VERSION;
|
||||||
@@ -85,41 +84,34 @@ namespace DiskCache {
|
|||||||
return Valid;
|
return Valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FOZFile::ReadNextBlob(MesaFOZ::foz_payload_key& OutKey, MesaFOZ::foz_payload_header& OutHeader, fextl::vector<uint8_t>& OutBlob) {
|
ssize_t FOZFile::Size() {
|
||||||
if (FD->Read(OutKey.bytes, sizeof(OutKey.bytes)) != sizeof(OutKey.bytes)) {
|
return FD ? FD->Size() : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FOZFile::ReadAll(fextl::vector<uint8_t>& Out) {
|
||||||
|
ssize_t FileSize = Size();
|
||||||
|
if (FileSize < FOZ_REF_MAGIC_SIZE) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (FD->Read(&OutHeader, sizeof(OutHeader)) != sizeof(OutHeader)) {
|
Out.resize((size_t)FileSize - FOZ_REF_MAGIC_SIZE);
|
||||||
return false;
|
return FD->PRead(Out.data(), Out.size(), FOZ_REF_MAGIC_SIZE) == (ssize_t)Out.size();
|
||||||
}
|
|
||||||
OutBlob.resize(OutHeader.payload_size);
|
|
||||||
if (FD->Read(OutBlob.data(), OutBlob.size()) != (ssize_t)OutBlob.size()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FOZFile::ReadBlob(uint64_t Offset, std::span<uint8_t> OutBlob) {
|
bool FOZFile::ReadBlob(uint64_t Offset, std::span<uint8_t> OutBlob) {
|
||||||
ssize_t SeekRet = FD->Seek(Offset, File::SeekOp::BEGIN);
|
if (FD->PRead(OutBlob.data(), OutBlob.size(), Offset) != (ssize_t)OutBlob.size()) {
|
||||||
if (SeekRet < 0) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (FD->Read(OutBlob.data(), OutBlob.size()) != (ssize_t)OutBlob.size()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FOZFile::WriteBlob(const MesaFOZ::foz_payload_key& Key, std::span<const std::span<const uint8_t>> BlobChunks, uint64_t& OutBlobOffset) {
|
bool FOZFile::WriteBlob(const MesaFOZ::foz_payload_key& Key, std::span<const std::span<const uint8_t>> BlobChunks, uint64_t& OutBlobOffset) {
|
||||||
uint64_t WriteOffset = 0;
|
ssize_t FileSize = FD->Size();
|
||||||
ssize_t SeekRet = FD->Seek(0, File::SeekOp::END);
|
if (FileSize < 0) {
|
||||||
if (SeekRet < 0) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
WriteOffset = (uint64_t)SeekRet;
|
uint64_t WriteOffset = (uint64_t)FileSize;
|
||||||
|
|
||||||
if (FD->Write(Key.bytes, sizeof(Key.bytes)) != sizeof(Key.bytes)) {
|
if (FD->PWrite(Key.bytes, sizeof(Key.bytes), WriteOffset) != sizeof(Key.bytes)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
WriteOffset += sizeof(Key.bytes);
|
WriteOffset += sizeof(Key.bytes);
|
||||||
@@ -134,7 +126,7 @@ namespace DiskCache {
|
|||||||
.crc = 0, // todo? maybe
|
.crc = 0, // todo? maybe
|
||||||
.uncompressed_size = (uint32_t)TotalBlobSize};
|
.uncompressed_size = (uint32_t)TotalBlobSize};
|
||||||
|
|
||||||
if (FD->Write(&ScratchHeader, sizeof(ScratchHeader)) != sizeof(ScratchHeader)) {
|
if (FD->PWrite(&ScratchHeader, sizeof(ScratchHeader), WriteOffset) != sizeof(ScratchHeader)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
WriteOffset += sizeof(ScratchHeader);
|
WriteOffset += sizeof(ScratchHeader);
|
||||||
@@ -145,9 +137,10 @@ namespace DiskCache {
|
|||||||
if (Chunk.size() == 0) {
|
if (Chunk.size() == 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (FD->Write(Chunk.data(), Chunk.size()) != (ssize_t)Chunk.size()) {
|
if (FD->PWrite(Chunk.data(), Chunk.size(), WriteOffset) != (ssize_t)Chunk.size()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
WriteOffset += Chunk.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -166,19 +159,40 @@ namespace DiskCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void IndexedDB::PopulateIndex(Index& CacheIndex) {
|
void IndexedDB::PopulateIndex(Index& CacheIndex) {
|
||||||
MesaFOZ::foz_payload_key Key;
|
fextl::vector<uint8_t> Data;
|
||||||
MesaFOZ::foz_payload_header Header;
|
if (!IndexFOZ.ReadAll(Data)) {
|
||||||
fextl::vector<uint8_t> Blob;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
while (IndexFOZ.ReadNextBlob(Key, Header, Blob)) {
|
ssize_t CacheFOZSize = CacheFOZ.Size();
|
||||||
if (Blob.size() != sizeof(MesaFOZ::mesa_index_db_file_entry)) {
|
if (CacheFOZSize < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t* IndexDataStart = Data.data();
|
||||||
|
const size_t IndexDataSize = Data.size();
|
||||||
|
size_t ReadOffset = 0;
|
||||||
|
while (ReadOffset + sizeof(MesaFOZ::foz_payload_key) + sizeof(MesaFOZ::foz_payload_header) <= IndexDataSize) {
|
||||||
|
const auto* FOZKey = reinterpret_cast<const MesaFOZ::foz_payload_key*>(IndexDataStart + ReadOffset);
|
||||||
|
ReadOffset += sizeof(MesaFOZ::foz_payload_key);
|
||||||
|
const auto* FOZHeader = reinterpret_cast<const MesaFOZ::foz_payload_header*>(IndexDataStart + ReadOffset);
|
||||||
|
ReadOffset += sizeof(MesaFOZ::foz_payload_header);
|
||||||
|
|
||||||
|
if (FOZHeader->payload_size != sizeof(MesaFOZ::mesa_index_db_file_entry) || ReadOffset + FOZHeader->payload_size > IndexDataSize) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
MesaFOZ::mesa_index_db_file_entry* IndexEntry = (MesaFOZ::mesa_index_db_file_entry*)Blob.data();
|
const auto* IndexBlobPayload = reinterpret_cast<const MesaFOZ::mesa_index_db_file_entry*>(IndexDataStart + ReadOffset);
|
||||||
if (IndexEntry->hash != XXH3_64bits(Key.bytes, FOSSILIZE_BLOB_HASH_LENGTH)) {
|
ReadOffset += FOZHeader->payload_size;
|
||||||
|
|
||||||
|
if (IndexBlobPayload->hash != XXH3_64bits(FOZKey->bytes, FOSSILIZE_BLOB_HASH_LENGTH)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
CacheIndex.insert({IndexEntry->hash, {this, IndexEntry->cache_db_file_offset, IndexEntry->size}});
|
// skip corrupt (carefully) so we don't have to figure that out in the hot path later
|
||||||
|
if (IndexBlobPayload->cache_db_file_offset > (uint64_t)CacheFOZSize ||
|
||||||
|
IndexBlobPayload->size > (uint64_t)CacheFOZSize - IndexBlobPayload->cache_db_file_offset) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
CacheIndex.insert({IndexBlobPayload->hash, {this, IndexBlobPayload->cache_db_file_offset, IndexBlobPayload->size}});
|
||||||
}
|
}
|
||||||
// could truncate/delete index if we don't end up perfectly at end here
|
// could truncate/delete index if we don't end up perfectly at end here
|
||||||
}
|
}
|
||||||
@@ -187,15 +201,18 @@ namespace DiskCache {
|
|||||||
return CacheFOZ.ReadBlob(Offset, OutBlob);
|
return CacheFOZ.ReadBlob(Offset, OutBlob);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IndexedDB::StoreCacheBlob(const MesaFOZ::foz_payload_key& Key, std::span<const std::span<const uint8_t>> BlobChunks, Index& Index) {
|
bool IndexedDB::StoreCacheBlob(const MesaFOZ::foz_payload_key& Key, std::span<const uint8_t> Blob, Index& Index, std::mutex& IndexMutex) {
|
||||||
if (ReadOnly) {
|
if (ReadOnly) {
|
||||||
// shouldn't happen
|
// shouldn't happen
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
uint64_t Hash = XXH3_64bits(Key.bytes, FOSSILIZE_BLOB_HASH_LENGTH);
|
uint64_t Hash = XXH3_64bits(Key.bytes, FOSSILIZE_BLOB_HASH_LENGTH);
|
||||||
if (Index.contains(Hash)) {
|
{
|
||||||
// shouldn't really happen.. assert or something?
|
std::lock_guard Guard(IndexMutex);
|
||||||
return true;
|
if (Index.contains(Hash)) {
|
||||||
|
// shouldn't really happen.. assert or something?
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!CacheFOZ.Lock(STORE_LOCK_TIMEOUT_MS) || !IndexFOZ.Lock(STORE_LOCK_TIMEOUT_MS)) {
|
if (!CacheFOZ.Lock(STORE_LOCK_TIMEOUT_MS) || !IndexFOZ.Lock(STORE_LOCK_TIMEOUT_MS)) {
|
||||||
@@ -205,6 +222,7 @@ namespace DiskCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// write cache side first so we get offset for index
|
// write cache side first so we get offset for index
|
||||||
|
std::span<const uint8_t> BlobChunks[] = {Blob};
|
||||||
uint64_t BlobOffset = 0;
|
uint64_t BlobOffset = 0;
|
||||||
if (!CacheFOZ.WriteBlob(Key, BlobChunks, BlobOffset)) {
|
if (!CacheFOZ.WriteBlob(Key, BlobChunks, BlobOffset)) {
|
||||||
CacheFOZ.Unlock();
|
CacheFOZ.Unlock();
|
||||||
@@ -212,13 +230,8 @@ namespace DiskCache {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t TotalBlobSize = 0;
|
|
||||||
for (const std::span<const uint8_t>& Chunk : BlobChunks) {
|
|
||||||
TotalBlobSize += Chunk.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
MesaFOZ::mesa_index_db_file_entry IndexEntry {.hash = Hash,
|
MesaFOZ::mesa_index_db_file_entry IndexEntry {.hash = Hash,
|
||||||
.size = (uint32_t)TotalBlobSize,
|
.size = (uint32_t)Blob.size(),
|
||||||
.last_access_time = 0, // todo..
|
.last_access_time = 0, // todo..
|
||||||
.cache_db_file_offset = BlobOffset};
|
.cache_db_file_offset = BlobOffset};
|
||||||
|
|
||||||
@@ -233,7 +246,8 @@ namespace DiskCache {
|
|||||||
CacheFOZ.Unlock();
|
CacheFOZ.Unlock();
|
||||||
IndexFOZ.Unlock();
|
IndexFOZ.Unlock();
|
||||||
|
|
||||||
Index[Hash] = {this, BlobOffset, (uint32_t)TotalBlobSize};
|
std::lock_guard Guard(IndexMutex);
|
||||||
|
Index[Hash] = {this, BlobOffset, (uint32_t)Blob.size()};
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,13 +314,16 @@ namespace DiskCache {
|
|||||||
// advance to next
|
// advance to next
|
||||||
RONames.remove_prefix(Delim + 1);
|
RONames.remove_prefix(Delim + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (IsWritingDiskCache()) {
|
||||||
|
Writer = fextl::make_unique<WorkQueueThread>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<CodeHitData> DiskCache::Lookup(Core::InternalThreadState* Thread, const ExecutableFileSectionInfo& Region, uint64_t GuestRIP) {
|
std::optional<CodeHitData> DiskCache::Lookup(Core::InternalThreadState* Thread, const ExecutableFileSectionInfo& Region, uint64_t GuestRIP) {
|
||||||
if (!IsReadingDiskCache()) {
|
if (!IsReadingDiskCache()) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
std::lock_guard Guard(Lock);
|
|
||||||
uint64_t ModuleOffset = GuestRIP - Region.FileStartVA;
|
uint64_t ModuleOffset = GuestRIP - Region.FileStartVA;
|
||||||
|
|
||||||
// todo move key making to a helper once we have options and stuff (see Store)
|
// todo move key making to a helper once we have options and stuff (see Store)
|
||||||
@@ -314,12 +331,18 @@ namespace DiskCache {
|
|||||||
memcpy(Key.bytes, &ModuleOffset, sizeof(ModuleOffset));
|
memcpy(Key.bytes, &ModuleOffset, sizeof(ModuleOffset));
|
||||||
|
|
||||||
uint64_t Hash = XXH3_64bits(Key.bytes, FOSSILIZE_BLOB_HASH_LENGTH);
|
uint64_t Hash = XXH3_64bits(Key.bytes, FOSSILIZE_BLOB_HASH_LENGTH);
|
||||||
auto It = Index.find(Hash);
|
|
||||||
if (It == Index.end()) {
|
IndexEntry Entry;
|
||||||
// definite miss
|
{
|
||||||
return std::nullopt;
|
std::lock_guard Guard(IndexLock);
|
||||||
|
auto It = Index.find(Hash);
|
||||||
|
if (It == Index.end()) {
|
||||||
|
// definite miss
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
// we can't hold onto the iterator, the map may shift while we don't hold the lock
|
||||||
|
Entry = It->second;
|
||||||
}
|
}
|
||||||
const IndexEntry& Entry = It->second;
|
|
||||||
// found a key hash match, could still be a miss, read the blob and verify more
|
// found a key hash match, could still be a miss, read the blob and verify more
|
||||||
CodeHitData HitData;
|
CodeHitData HitData;
|
||||||
HitData.Blob.resize(Entry.Size);
|
HitData.Blob.resize(Entry.Size);
|
||||||
@@ -405,6 +428,25 @@ namespace DiskCache {
|
|||||||
return HitData;
|
return HitData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline bool IsRelocationInBlock(const FEXCore::CPU::Relocation& Reloc, const CPU::CPUBackend::CompiledCode& CompiledCode) {
|
||||||
|
return Reloc.Header.Offset >= CompiledCode.HostCodeOffset && Reloc.Header.Offset < CompiledCode.HostCodeOffset + CompiledCode.Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DiskCache::CacheStoreWorkItem final : WorkQueueThread::WorkItem {
|
||||||
|
DiskCache* Self;
|
||||||
|
IndexedDB* DB;
|
||||||
|
MesaFOZ::foz_payload_key Key;
|
||||||
|
fextl::vector<uint8_t> Blob;
|
||||||
|
CacheStoreWorkItem(DiskCache* Self, IndexedDB* DB, const MesaFOZ::foz_payload_key& Key, fextl::vector<uint8_t>&& Blob)
|
||||||
|
: Self(Self)
|
||||||
|
, DB(DB)
|
||||||
|
, Key(Key)
|
||||||
|
, Blob(std::move(Blob)) {}
|
||||||
|
void Run() override {
|
||||||
|
DB->StoreCacheBlob(Key, Blob, Self->Index, Self->IndexLock);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
bool DiskCache::Store(Core::InternalThreadState* Thread, const ExecutableFileSectionInfo& Region, uint64_t GuestRIP,
|
bool DiskCache::Store(Core::InternalThreadState* Thread, const ExecutableFileSectionInfo& Region, uint64_t GuestRIP,
|
||||||
std::span<const uint8_t> GuestCode, const CPU::CPUBackend::CompiledCode& CompiledCode,
|
std::span<const uint8_t> GuestCode, const CPU::CPUBackend::CompiledCode& CompiledCode,
|
||||||
std::span<const FEXCore::CPU::Relocation> Relocations, const Frontend::Decoder::DecodedBlockInformation* DecodedBlockInfo) {
|
std::span<const FEXCore::CPU::Relocation> Relocations, const Frontend::Decoder::DecodedBlockInformation* DecodedBlockInfo) {
|
||||||
@@ -414,13 +456,12 @@ namespace DiskCache {
|
|||||||
if (!DecodedBlockInfo) {
|
if (!DecodedBlockInfo) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
std::lock_guard Guard(Lock);
|
|
||||||
|
|
||||||
// check for any reloc targets outside of our jurisdiction
|
// check for any reloc targets outside of our jurisdiction
|
||||||
// todo what are they exactly? caching those blocks is great when it works, so need to figure this out and make finer-grained if we can
|
// todo what are they exactly? caching those blocks is great when it works, so need to figure this out and make finer-grained if we can
|
||||||
if (RelocationFilter) {
|
if (RelocationFilter) {
|
||||||
for (const auto& Reloc : Relocations) {
|
for (const auto& Reloc : Relocations) {
|
||||||
if (Reloc.Header.Offset < CompiledCode.HostCodeOffset || Reloc.Header.Offset >= CompiledCode.HostCodeOffset + CompiledCode.Size) {
|
if (!IsRelocationInBlock(Reloc, CompiledCode)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (Reloc.Header.Type != CPU::RelocationTypes::RELOC_GUEST_RIP_LITERAL && Reloc.Header.Type != CPU::RelocationTypes::RELOC_GUEST_RIP_MOVE) {
|
if (Reloc.Header.Type != CPU::RelocationTypes::RELOC_GUEST_RIP_LITERAL && Reloc.Header.Type != CPU::RelocationTypes::RELOC_GUEST_RIP_MOVE) {
|
||||||
@@ -438,23 +479,70 @@ namespace DiskCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pack entrypoints to disk format
|
uint32_t SmallRelocCount = 0;
|
||||||
fextl::vector<BlobEntryPoint> CacheEntryPoints;
|
uint32_t ThunkRelocCount = 0;
|
||||||
CacheEntryPoints.reserve(CompiledCode.EntryPoints.size());
|
|
||||||
|
|
||||||
for (auto [GuestAddr, HostAddr] : CompiledCode.EntryPoints) {
|
|
||||||
CacheEntryPoints.push_back({GuestAddr - Region.FileStartVA, uint32_t(HostAddr - CompiledCode.BlockBegin)});
|
|
||||||
}
|
|
||||||
|
|
||||||
// pack relocations to disk format
|
|
||||||
fextl::vector<BlobSmallRelocation> SmallRelocs;
|
|
||||||
fextl::vector<BlobThunkRelocation> ThunkRelocs;
|
|
||||||
|
|
||||||
// todo discover sizes first and reserve vecs?
|
|
||||||
|
|
||||||
for (const auto& Reloc : Relocations) {
|
for (const auto& Reloc : Relocations) {
|
||||||
// relocs aren't cleared every time if IsGeneratingCache, so filter just in case
|
// relocs aren't cleared every time if IsGeneratingCache, so filter just in case
|
||||||
if (Reloc.Header.Offset < CompiledCode.HostCodeOffset || Reloc.Header.Offset >= CompiledCode.HostCodeOffset + CompiledCode.Size) {
|
if (!IsRelocationInBlock(Reloc, CompiledCode)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (Reloc.Header.Type == CPU::RelocationTypes::RELOC_NAMED_THUNK_MOVE) {
|
||||||
|
ThunkRelocCount++;
|
||||||
|
} else {
|
||||||
|
SmallRelocCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t EntryPointCount = (uint32_t)CompiledCode.EntryPoints.size();
|
||||||
|
const uint32_t TouchedGuestPagesCount = DecodedBlockInfo ? (uint32_t)DecodedBlockInfo->CodePages.size() : 0;
|
||||||
|
|
||||||
|
const size_t HeaderOffset = 0;
|
||||||
|
const size_t HostCodeOffset = HeaderOffset + sizeof(BlobFixedHeader);
|
||||||
|
const size_t EntryPointsOffset = HostCodeOffset + CompiledCode.Size;
|
||||||
|
const size_t SmallRelocsOffset = EntryPointsOffset + EntryPointCount * sizeof(BlobEntryPoint);
|
||||||
|
const size_t ThunkRelocsOffset = SmallRelocsOffset + SmallRelocCount * sizeof(BlobSmallRelocation);
|
||||||
|
const size_t TouchedGuestPagesOffset = ThunkRelocsOffset + ThunkRelocCount * sizeof(BlobThunkRelocation);
|
||||||
|
const size_t GuestCodeOffset = TouchedGuestPagesOffset + TouchedGuestPagesCount * sizeof(int64_t);
|
||||||
|
const size_t TotalSize = GuestCodeOffset + GuestCode.size();
|
||||||
|
|
||||||
|
// we'll copy everything into here and pass it to the Writer, then return to caller quickly
|
||||||
|
fextl::vector<uint8_t> Blob;
|
||||||
|
Blob.resize(TotalSize);
|
||||||
|
uint8_t* BlobData = Blob.data();
|
||||||
|
|
||||||
|
uint64_t ModuleOffset = GuestRIP - Region.FileStartVA;
|
||||||
|
|
||||||
|
// todo also copy/hash options that affect codegen into the key
|
||||||
|
// todo should try to keep the key ascii i think?
|
||||||
|
MesaFOZ::foz_payload_key Key = {};
|
||||||
|
memcpy(Key.bytes, &ModuleOffset, sizeof(ModuleOffset));
|
||||||
|
|
||||||
|
BlobFixedHeader Header {
|
||||||
|
.GuestSize = (uint32_t)GuestCode.size(),
|
||||||
|
.HostSize = (uint32_t)CompiledCode.Size,
|
||||||
|
.EntryPointCount = EntryPointCount,
|
||||||
|
.SmallRelocCount = SmallRelocCount,
|
||||||
|
.ThunkRelocCount = ThunkRelocCount,
|
||||||
|
.TouchedGuestPagesCount = TouchedGuestPagesCount,
|
||||||
|
.GuestHash = XXH3_128bits(GuestCode.data(), GuestCode.size()),
|
||||||
|
};
|
||||||
|
memcpy(BlobData + HeaderOffset, &Header, sizeof(Header));
|
||||||
|
memcpy(BlobData + HostCodeOffset, CompiledCode.BlockBegin, CompiledCode.Size);
|
||||||
|
|
||||||
|
// pack and relocate entrypoints
|
||||||
|
auto* EntryPoints = reinterpret_cast<BlobEntryPoint*>(BlobData + EntryPointsOffset);
|
||||||
|
uint32_t EntryIdx = 0;
|
||||||
|
for (auto [GuestAddr, HostAddr] : CompiledCode.EntryPoints) {
|
||||||
|
EntryPoints[EntryIdx++] = {GuestAddr - Region.FileStartVA, uint32_t(HostAddr - CompiledCode.BlockBegin)};
|
||||||
|
}
|
||||||
|
|
||||||
|
// pack relocations
|
||||||
|
auto* SmallRelocs = reinterpret_cast<BlobSmallRelocation*>(BlobData + SmallRelocsOffset);
|
||||||
|
auto* ThunkRelocs = reinterpret_cast<BlobThunkRelocation*>(BlobData + ThunkRelocsOffset);
|
||||||
|
uint32_t SmallIdx = 0;
|
||||||
|
uint32_t ThunkIdx = 0;
|
||||||
|
for (const auto& Reloc : Relocations) {
|
||||||
|
if (!IsRelocationInBlock(Reloc, CompiledCode)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// re-relocate :harold:
|
// re-relocate :harold:
|
||||||
@@ -468,7 +556,7 @@ namespace DiskCache {
|
|||||||
SmallReloc.Offset = LocalOffset;
|
SmallReloc.Offset = LocalOffset;
|
||||||
SmallReloc.Type = uint8_t(Reloc.Header.Type);
|
SmallReloc.Type = uint8_t(Reloc.Header.Type);
|
||||||
SmallReloc.Named.Symbol = uint32_t(Reloc.NamedSymbolLiteral.Symbol);
|
SmallReloc.Named.Symbol = uint32_t(Reloc.NamedSymbolLiteral.Symbol);
|
||||||
SmallRelocs.push_back(SmallReloc);
|
SmallRelocs[SmallIdx++] = SmallReloc;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case CPU::RelocationTypes::RELOC_GUEST_RIP_LITERAL: {
|
case CPU::RelocationTypes::RELOC_GUEST_RIP_LITERAL: {
|
||||||
@@ -476,7 +564,7 @@ namespace DiskCache {
|
|||||||
SmallReloc.Offset = LocalOffset;
|
SmallReloc.Offset = LocalOffset;
|
||||||
SmallReloc.Type = uint8_t(Reloc.Header.Type);
|
SmallReloc.Type = uint8_t(Reloc.Header.Type);
|
||||||
SmallReloc.RIPLiteral.GuestRIP = Reloc.GuestRIP.GuestRIP - GuestRIP;
|
SmallReloc.RIPLiteral.GuestRIP = Reloc.GuestRIP.GuestRIP - GuestRIP;
|
||||||
SmallRelocs.push_back(SmallReloc);
|
SmallRelocs[SmallIdx++] = SmallReloc;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case CPU::RelocationTypes::RELOC_GUEST_RIP_MOVE: {
|
case CPU::RelocationTypes::RELOC_GUEST_RIP_MOVE: {
|
||||||
@@ -485,7 +573,7 @@ namespace DiskCache {
|
|||||||
SmallReloc.Type = uint8_t(Reloc.Header.Type);
|
SmallReloc.Type = uint8_t(Reloc.Header.Type);
|
||||||
SmallReloc.RIPMove.RegisterIndex = Reloc.GuestRIP.RegisterIndex;
|
SmallReloc.RIPMove.RegisterIndex = Reloc.GuestRIP.RegisterIndex;
|
||||||
SmallReloc.RIPMove.GuestRIP = Reloc.GuestRIP.GuestRIP - GuestRIP;
|
SmallReloc.RIPMove.GuestRIP = Reloc.GuestRIP.GuestRIP - GuestRIP;
|
||||||
SmallRelocs.push_back(SmallReloc);
|
SmallRelocs[SmallIdx++] = SmallReloc;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case CPU::RelocationTypes::RELOC_NAMED_THUNK_MOVE: {
|
case CPU::RelocationTypes::RELOC_NAMED_THUNK_MOVE: {
|
||||||
@@ -493,50 +581,25 @@ namespace DiskCache {
|
|||||||
BigReloc.Offset = LocalOffset;
|
BigReloc.Offset = LocalOffset;
|
||||||
BigReloc.RegisterIndex = Reloc.NamedThunkMove.RegisterIndex;
|
BigReloc.RegisterIndex = Reloc.NamedThunkMove.RegisterIndex;
|
||||||
memcpy(BigReloc.SymbolHash, &Reloc.NamedThunkMove.Symbol, sizeof(BigReloc.SymbolHash));
|
memcpy(BigReloc.SymbolHash, &Reloc.NamedThunkMove.Symbol, sizeof(BigReloc.SymbolHash));
|
||||||
ThunkRelocs.push_back(BigReloc);
|
ThunkRelocs[ThunkIdx++] = BigReloc;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pack touched pages, relative to GuestRIP
|
// relocate touched pages relative to GuestRIP
|
||||||
// in theory we could save some size here, unlikely we need all 64bits
|
// in theory we could save some size here, unlikely we need all 64bits
|
||||||
fextl::vector<int64_t> GuestPageOffsets;
|
auto* PageOffsets = reinterpret_cast<int64_t*>(BlobData + TouchedGuestPagesOffset);
|
||||||
if (DecodedBlockInfo) {
|
uint32_t PageIdx = 0;
|
||||||
GuestPageOffsets.reserve(DecodedBlockInfo->CodePages.size());
|
for (auto GuestPage : DecodedBlockInfo->CodePages) {
|
||||||
for (auto& GuestPage : DecodedBlockInfo->CodePages) {
|
PageOffsets[PageIdx++] = GuestPage - GuestRIP;
|
||||||
GuestPageOffsets.push_back(GuestPage - GuestRIP);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t ModuleOffset = GuestRIP - Region.FileStartVA;
|
memcpy(BlobData + GuestCodeOffset, GuestCode.data(), GuestCode.size());
|
||||||
|
|
||||||
// todo also copy/hash options that affect codegen into the key
|
// hand the rest off to the writer thread
|
||||||
// todo should try to keep the key ascii i think?
|
Writer->QueueWork(fextl::make_unique<CacheStoreWorkItem>(this, RWCacheDB.get(), Key, std::move(Blob)));
|
||||||
MesaFOZ::foz_payload_key Key = {};
|
return true;
|
||||||
memcpy(Key.bytes, &ModuleOffset, sizeof(ModuleOffset));
|
|
||||||
|
|
||||||
BlobFixedHeader Header {
|
|
||||||
.GuestSize = (uint32_t)GuestCode.size(),
|
|
||||||
.HostSize = (uint32_t)CompiledCode.Size,
|
|
||||||
.EntryPointCount = (uint32_t)CacheEntryPoints.size(),
|
|
||||||
.SmallRelocCount = (uint32_t)SmallRelocs.size(),
|
|
||||||
.ThunkRelocCount = (uint32_t)ThunkRelocs.size(),
|
|
||||||
.TouchedGuestPagesCount = (uint32_t)GuestPageOffsets.size(),
|
|
||||||
.GuestHash = XXH3_128bits(GuestCode.data(), GuestCode.size()),
|
|
||||||
};
|
|
||||||
|
|
||||||
std::span<const uint8_t> BlobChunks[] = {
|
|
||||||
{(const uint8_t*)&Header, sizeof(Header)},
|
|
||||||
{(const uint8_t*)CompiledCode.BlockBegin, CompiledCode.Size},
|
|
||||||
{(const uint8_t*)CacheEntryPoints.data(), CacheEntryPoints.size() * sizeof(BlobEntryPoint)},
|
|
||||||
{(const uint8_t*)SmallRelocs.data(), SmallRelocs.size() * sizeof(BlobSmallRelocation)},
|
|
||||||
{(const uint8_t*)ThunkRelocs.data(), ThunkRelocs.size() * sizeof(BlobThunkRelocation)},
|
|
||||||
{(const uint8_t*)GuestPageOffsets.data(), GuestPageOffsets.size() * sizeof(int64_t)},
|
|
||||||
GuestCode,
|
|
||||||
};
|
|
||||||
|
|
||||||
return RWCacheDB->StoreCacheBlob(Key, BlobChunks, Index);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace DiskCache
|
} // namespace DiskCache
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
#include <FEXCore/Utils/WorkQueueThread.h>
|
||||||
|
#include <FEXCore/Utils/LogManager.h>
|
||||||
|
|
||||||
|
namespace FEXCore {
|
||||||
|
|
||||||
|
WorkQueueThread::WorkQueueThread() {
|
||||||
|
Thread = FEXCore::Threads::Thread::Create(ThreadEntry, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
WorkQueueThread::~WorkQueueThread() {
|
||||||
|
{
|
||||||
|
std::unique_lock lk {Mutex};
|
||||||
|
Stop = true;
|
||||||
|
}
|
||||||
|
CV.notify_one();
|
||||||
|
|
||||||
|
if (Thread && Thread->joinable()) {
|
||||||
|
Thread->join(nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WorkQueueThread::QueueWork(fextl::unique_ptr<WorkItem> Work) {
|
||||||
|
{
|
||||||
|
std::unique_lock lk {Mutex};
|
||||||
|
Queue.push_back(std::move(Work));
|
||||||
|
}
|
||||||
|
CV.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WorkQueueThread::ThreadProc() {
|
||||||
|
while (true) {
|
||||||
|
fextl::unique_ptr<WorkItem> Work;
|
||||||
|
{
|
||||||
|
std::unique_lock lk {Mutex};
|
||||||
|
while (!(Stop || !Queue.empty())) {
|
||||||
|
CV.wait(lk);
|
||||||
|
}
|
||||||
|
if (Queue.empty()) {
|
||||||
|
// nothing to do? must be stopping
|
||||||
|
LOGMAN_THROW_A_FMT(Stop, "WorkQueueThread wakes up empty but no Stop?");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Work = std::move(Queue.front());
|
||||||
|
Queue.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
|
Work->Run();
|
||||||
|
// Work is destroyed here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace FEXCore
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "Interface/Core/CPUBackend.h"
|
#include "Interface/Core/CPUBackend.h"
|
||||||
#include "FEXCore/Config/Config.h"
|
#include "FEXCore/Config/Config.h"
|
||||||
#include "FEXCore/Utils/File.h"
|
#include "FEXCore/Utils/File.h"
|
||||||
|
#include "FEXCore/Utils/WorkQueueThread.h"
|
||||||
#include "FEXCore/fextl/memory.h"
|
#include "FEXCore/fextl/memory.h"
|
||||||
#include <FEXCore/fextl/string.h>
|
#include <FEXCore/fextl/string.h>
|
||||||
#include <FEXCore/fextl/unordered_set.h>
|
#include <FEXCore/fextl/unordered_set.h>
|
||||||
@@ -123,7 +124,8 @@ namespace DiskCache {
|
|||||||
}
|
}
|
||||||
return FD->Unlock();
|
return FD->Unlock();
|
||||||
}
|
}
|
||||||
bool ReadNextBlob(MesaFOZ::foz_payload_key& OutKey, MesaFOZ::foz_payload_header& OutHeader, fextl::vector<uint8_t>& OutBlob);
|
ssize_t Size();
|
||||||
|
bool ReadAll(fextl::vector<uint8_t>& Out); // from first blob
|
||||||
bool ReadBlob(uint64_t Offset, std::span<uint8_t> OutBlob);
|
bool ReadBlob(uint64_t Offset, std::span<uint8_t> OutBlob);
|
||||||
bool WriteBlob(const MesaFOZ::foz_payload_key& Key, std::span<const std::span<const uint8_t>> BlobChunks, uint64_t& OutBlobOffset);
|
bool WriteBlob(const MesaFOZ::foz_payload_key& Key, std::span<const std::span<const uint8_t>> BlobChunks, uint64_t& OutBlobOffset);
|
||||||
|
|
||||||
@@ -140,11 +142,11 @@ namespace DiskCache {
|
|||||||
bool Open(const fextl::string& CacheDBName, bool ReadOnly);
|
bool Open(const fextl::string& CacheDBName, bool ReadOnly);
|
||||||
void PopulateIndex(Index& CacheIndex);
|
void PopulateIndex(Index& CacheIndex);
|
||||||
bool ReadCacheBlob(uint64_t Offset, std::span<uint8_t> OutBlob);
|
bool ReadCacheBlob(uint64_t Offset, std::span<uint8_t> OutBlob);
|
||||||
bool StoreCacheBlob(const MesaFOZ::foz_payload_key& Key, std::span<const std::span<const uint8_t>> BlobChunks, Index& CacheIndex);
|
bool StoreCacheBlob(const MesaFOZ::foz_payload_key& Key, std::span<const uint8_t> Blob, Index& CacheIndex, std::mutex& IndexMutex);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// give up after 2ms of trying to store - when we have an async thread we can increase this
|
// stores run on the Writer, so returning quick isn't as important
|
||||||
static constexpr uint32_t STORE_LOCK_TIMEOUT_MS = 2;
|
static constexpr uint32_t STORE_LOCK_TIMEOUT_MS = 1000;
|
||||||
|
|
||||||
FOZFile CacheFOZ;
|
FOZFile CacheFOZ;
|
||||||
FOZFile IndexFOZ;
|
FOZFile IndexFOZ;
|
||||||
@@ -174,7 +176,11 @@ namespace DiskCache {
|
|||||||
fextl::vector<fextl::unique_ptr<IndexedDB>> ROCacheDBs;
|
fextl::vector<fextl::unique_ptr<IndexedDB>> ROCacheDBs;
|
||||||
fextl::unique_ptr<IndexedDB> RWCacheDB;
|
fextl::unique_ptr<IndexedDB> RWCacheDB;
|
||||||
Index Index;
|
Index Index;
|
||||||
std::mutex Lock;
|
std::mutex IndexLock;
|
||||||
|
struct CacheStoreWorkItem;
|
||||||
|
|
||||||
|
// the Writer holds references to all this stuff above and needs to be last
|
||||||
|
fextl::unique_ptr<WorkQueueThread> Writer;
|
||||||
|
|
||||||
FEX_CONFIG_OPT(EnableDiskCache, DISKCACHE);
|
FEX_CONFIG_OPT(EnableDiskCache, DISKCACHE);
|
||||||
FEX_CONFIG_OPT(RelocationFilter, DISKCACHERELOCATIONFILTER);
|
FEX_CONFIG_OPT(RelocationFilter, DISKCACHERELOCATIONFILTER);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <FEXCore/fextl/allocator.h>
|
#include <FEXCore/fextl/allocator.h>
|
||||||
#include <FEXCore/fextl/string.h>
|
#include <FEXCore/fextl/string.h>
|
||||||
#include <FEXCore/Utils/EnumOperators.h>
|
#include <FEXCore/Utils/EnumOperators.h>
|
||||||
|
#include "FEXCore/Utils/LogManager.h"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
@@ -11,6 +12,7 @@
|
|||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <sys/file.h>
|
#include <sys/file.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
#else
|
#else
|
||||||
#define WIN32_LEAN_AND_MEAN
|
#define WIN32_LEAN_AND_MEAN
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
@@ -43,7 +45,8 @@ public:
|
|||||||
|
|
||||||
File() = default;
|
File() = default;
|
||||||
|
|
||||||
File(const char* Filepath, FileModes Modes) {
|
File(const char* Filepath, FileModes Modes, bool Seekable = true)
|
||||||
|
: Seekable {Seekable} {
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
auto Disp = TranslateModes(Modes);
|
auto Disp = TranslateModes(Modes);
|
||||||
Handle = open(Filepath, Disp, DEFAULT_USER_PERMS);
|
Handle = open(Filepath, Disp, DEFAULT_USER_PERMS);
|
||||||
@@ -75,6 +78,10 @@ public:
|
|||||||
* @return The number of bytes actually written or -1 on error.
|
* @return The number of bytes actually written or -1 on error.
|
||||||
*/
|
*/
|
||||||
ssize_t Write(const void* Buffer, size_t Bytes) {
|
ssize_t Write(const void* Buffer, size_t Bytes) {
|
||||||
|
if (!Seekable) {
|
||||||
|
LOGMAN_THROW_A_FMT(false, "Can't use non-positioned ops on a non-seekable file!");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
return write(Handle, Buffer, Bytes);
|
return write(Handle, Buffer, Bytes);
|
||||||
#else
|
#else
|
||||||
@@ -101,6 +108,10 @@ public:
|
|||||||
* @return The number of bytes read or -1 on error.
|
* @return The number of bytes read or -1 on error.
|
||||||
*/
|
*/
|
||||||
ssize_t Read(void* Buffer, size_t Bytes) {
|
ssize_t Read(void* Buffer, size_t Bytes) {
|
||||||
|
if (!Seekable) {
|
||||||
|
LOGMAN_THROW_A_FMT(false, "Can't use non-positioned ops on a non-seekable file!");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
return read(Handle, Buffer, Bytes);
|
return read(Handle, Buffer, Bytes);
|
||||||
#else
|
#else
|
||||||
@@ -114,6 +125,48 @@ public:
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ssize_t PRead(void* Buffer, size_t Bytes, uint64_t Offset) {
|
||||||
|
if (Seekable) {
|
||||||
|
LOGMAN_THROW_A_FMT(false, "Can't use positioned ops on a seekable file!");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
#ifndef _WIN32
|
||||||
|
return pread(Handle, Buffer, Bytes, Offset);
|
||||||
|
#else
|
||||||
|
DWORD BytesRead {};
|
||||||
|
OVERLAPPED Overlapped {};
|
||||||
|
Overlapped.Offset = static_cast<DWORD>(Offset);
|
||||||
|
Overlapped.OffsetHigh = static_cast<DWORD>(Offset >> 32);
|
||||||
|
auto Result = ReadFile(Handle, Buffer, Bytes, &BytesRead, &Overlapped);
|
||||||
|
if (Result) {
|
||||||
|
return BytesRead;
|
||||||
|
}
|
||||||
|
// Some error, match Linux side.
|
||||||
|
return -1;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t PWrite(const void* Buffer, size_t Bytes, uint64_t Offset) {
|
||||||
|
if (Seekable) {
|
||||||
|
LOGMAN_THROW_A_FMT(false, "Can't use positioned ops on a seekable file!");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
#ifndef _WIN32
|
||||||
|
return pwrite(Handle, Buffer, Bytes, Offset);
|
||||||
|
#else
|
||||||
|
DWORD BytesWritten {};
|
||||||
|
OVERLAPPED Overlapped {};
|
||||||
|
Overlapped.Offset = static_cast<DWORD>(Offset);
|
||||||
|
Overlapped.OffsetHigh = static_cast<DWORD>(Offset >> 32);
|
||||||
|
auto Result = WriteFile(Handle, Buffer, Bytes, &BytesWritten, &Overlapped);
|
||||||
|
if (Result) {
|
||||||
|
return BytesWritten;
|
||||||
|
}
|
||||||
|
// Some error, match Linux side.
|
||||||
|
return -1;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
bool Lock(uint32_t TimeoutMS) {
|
bool Lock(uint32_t TimeoutMS) {
|
||||||
for (uint32_t i = 0;; ++i) {
|
for (uint32_t i = 0;; ++i) {
|
||||||
if (TryLock()) {
|
if (TryLock()) {
|
||||||
@@ -202,6 +255,22 @@ public:
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ssize_t Size() {
|
||||||
|
#ifndef _WIN32
|
||||||
|
struct stat st;
|
||||||
|
if (fstat(Handle, &st) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return st.st_size;
|
||||||
|
#else
|
||||||
|
LARGE_INTEGER FileSize;
|
||||||
|
if (!GetFileSizeEx(Handle, &FileSize)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return FileSize.QuadPart;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Seek the file pointer location.
|
* @brief Seek the file pointer location.
|
||||||
*
|
*
|
||||||
@@ -211,6 +280,10 @@ public:
|
|||||||
* @return The current file pointer location or -1.
|
* @return The current file pointer location or -1.
|
||||||
*/
|
*/
|
||||||
ssize_t Seek(ssize_t Distance, SeekOp Op) {
|
ssize_t Seek(ssize_t Distance, SeekOp Op) {
|
||||||
|
if (!Seekable) {
|
||||||
|
LOGMAN_THROW_A_FMT(false, "Can't use non-positioned ops on a non-seekable file!");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
return lseek(Handle, Distance, TranslateSeek(Op));
|
return lseek(Handle, Distance, TranslateSeek(Op));
|
||||||
#else
|
#else
|
||||||
@@ -227,10 +300,11 @@ public:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
File(FileHandleType Handle, bool ShouldClose)
|
File(FileHandleType Handle, bool ShouldClose, bool Seekable = true)
|
||||||
: ShouldClose {ShouldClose}
|
: ShouldClose {ShouldClose}
|
||||||
, IsValidHandle {true}
|
, IsValidHandle {true}
|
||||||
, Handle {Handle} {}
|
, Handle {Handle}
|
||||||
|
, Seekable {Seekable} {}
|
||||||
private:
|
private:
|
||||||
bool TryLock() {
|
bool TryLock() {
|
||||||
if (Locked) {
|
if (Locked) {
|
||||||
@@ -257,6 +331,7 @@ private:
|
|||||||
bool IsValidHandle {};
|
bool IsValidHandle {};
|
||||||
|
|
||||||
FileHandleType Handle {};
|
FileHandleType Handle {};
|
||||||
|
bool Seekable = true;
|
||||||
bool Locked = false;
|
bool Locked = false;
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
static constexpr int DEFAULT_USER_PERMS = S_IRWXU | S_IRWXG | S_IRWXO;
|
static constexpr int DEFAULT_USER_PERMS = S_IRWXU | S_IRWXG | S_IRWXO;
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <FEXCore/Utils/Threads.h>
|
||||||
|
#include <FEXCore/fextl/deque.h>
|
||||||
|
#include <FEXCore/fextl/memory.h>
|
||||||
|
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
namespace FEXCore {
|
||||||
|
|
||||||
|
class WorkQueueThread {
|
||||||
|
public:
|
||||||
|
// destroyed after Run()
|
||||||
|
struct WorkItem {
|
||||||
|
virtual ~WorkItem() = default;
|
||||||
|
virtual void Run() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
WorkQueueThread();
|
||||||
|
~WorkQueueThread();
|
||||||
|
|
||||||
|
void QueueWork(fextl::unique_ptr<WorkItem> Work);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// static function for the ::Thread to refer to
|
||||||
|
static void* ThreadEntry(void* Self) {
|
||||||
|
static_cast<WorkQueueThread*>(Self)->ThreadProc();
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
void ThreadProc();
|
||||||
|
|
||||||
|
std::mutex Mutex;
|
||||||
|
std::condition_variable CV;
|
||||||
|
fextl::deque<fextl::unique_ptr<WorkItem>> Queue;
|
||||||
|
bool Stop = false;
|
||||||
|
|
||||||
|
fextl::unique_ptr<FEXCore::Threads::Thread> Thread;
|
||||||
|
};
|
||||||
|
} // namespace FEXCore
|
||||||
@@ -36,6 +36,7 @@ $end_info$
|
|||||||
#include "Common/Exception.h"
|
#include "Common/Exception.h"
|
||||||
#include "Common/ImageTracker.h"
|
#include "Common/ImageTracker.h"
|
||||||
#include "Common/InvalidationTracker.h"
|
#include "Common/InvalidationTracker.h"
|
||||||
|
#include "Common/Threads.h"
|
||||||
#include "Common/OvercommitTracker.h"
|
#include "Common/OvercommitTracker.h"
|
||||||
#include "Common/TSOHandlerConfig.h"
|
#include "Common/TSOHandlerConfig.h"
|
||||||
#include "Common/CPUFeatures.h"
|
#include "Common/CPUFeatures.h"
|
||||||
@@ -581,6 +582,7 @@ NTSTATUS ProcessInit() {
|
|||||||
InitSyscalls();
|
InitSyscalls();
|
||||||
|
|
||||||
FEX::Windows::InitCRTProcess();
|
FEX::Windows::InitCRTProcess();
|
||||||
|
FEX::Windows::SetupThreadHandlers();
|
||||||
const auto ExecutablePath = FEX::Windows::GetExecutableFilePath();
|
const auto ExecutablePath = FEX::Windows::GetExecutableFilePath();
|
||||||
const auto ExecutableName = FEX::Windows::BaseName(ExecutablePath);
|
const auto ExecutableName = FEX::Windows::BaseName(ExecutablePath);
|
||||||
FEX::Config::LoadConfig(fextl::string {ExecutableName}, _environ, FEX::ReadPortabilityInformation());
|
FEX::Config::LoadConfig(fextl::string {ExecutableName}, _environ, FEX::ReadPortabilityInformation());
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ add_library(CommonWindows STATIC
|
|||||||
SHMStats.cpp
|
SHMStats.cpp
|
||||||
InvalidationTracker.cpp
|
InvalidationTracker.cpp
|
||||||
ImageTracker.cpp
|
ImageTracker.cpp
|
||||||
Logging.cpp)
|
Logging.cpp
|
||||||
|
Threads.cpp)
|
||||||
|
|
||||||
target_link_libraries(CommonWindows FEXCore_Base)
|
target_link_libraries(CommonWindows FEXCore_Base)
|
||||||
target_include_directories(CommonWindows PRIVATE "${CMAKE_SOURCE_DIR}/Source/Windows/include/")
|
target_include_directories(CommonWindows PRIVATE "${CMAKE_SOURCE_DIR}/Source/Windows/include/")
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
#include "Threads.h"
|
||||||
|
#include "CRT/CRT.h"
|
||||||
|
|
||||||
|
#include <FEXCore/Utils/LogManager.h>
|
||||||
|
#include <FEXCore/Utils/Threads.h>
|
||||||
|
#include <FEXCore/fextl/memory.h>
|
||||||
|
|
||||||
|
#include <winternl.h>
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
namespace FEX::Windows {
|
||||||
|
namespace WinThreadImpl {
|
||||||
|
class Thread final : public FEXCore::Threads::Thread {
|
||||||
|
public:
|
||||||
|
Thread(FEXCore::Threads::ThreadFunc Func, void* Arg)
|
||||||
|
: UserFunc {Func}
|
||||||
|
, UserArg {Arg} {
|
||||||
|
// hide everything from guest, don't initialize anything, we'll do that manually in RunThread()
|
||||||
|
const ULONG CreateFlags = THREAD_CREATE_FLAGS_SKIP_THREAD_ATTACH | THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER |
|
||||||
|
THREAD_CREATE_FLAGS_SKIP_LOADER_INIT | THREAD_CREATE_FLAGS_BYPASS_PROCESS_FREEZE;
|
||||||
|
NTSTATUS Status = NtCreateThreadEx(&Handle, THREAD_ALL_ACCESS, nullptr, GetCurrentProcess(),
|
||||||
|
reinterpret_cast<PRTL_THREAD_START_ROUTINE>(&Thread::RunThread), this, CreateFlags, 0, 0, 0, nullptr);
|
||||||
|
if (Status < 0) {
|
||||||
|
LogMan::Msg::EFmt("NtCreateThreadEx failed: 0x{:x}", static_cast<uint32_t>(Status));
|
||||||
|
Handle = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool joinable() override {
|
||||||
|
return Handle != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool join(void** ret) override {
|
||||||
|
if (!Handle) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
NtWaitForSingleObject(Handle, FALSE, nullptr);
|
||||||
|
if (ret) {
|
||||||
|
*ret = ReturnValue;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool detach() override {
|
||||||
|
// almost no-op, it's already detached, just deref handle
|
||||||
|
if (Handle) {
|
||||||
|
NtClose(Handle);
|
||||||
|
Handle = nullptr;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsSelf() override {
|
||||||
|
return TID == GetCurrentThreadId();
|
||||||
|
}
|
||||||
|
|
||||||
|
~Thread() override {
|
||||||
|
if (Handle) {
|
||||||
|
NtClose(Handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void RunThread(Thread* This) {
|
||||||
|
This->TID = GetCurrentThreadId();
|
||||||
|
// do initialization we skipped earlier here around the user entrypoint
|
||||||
|
FEX::Windows::InitCRTThread();
|
||||||
|
This->ReturnValue = This->UserFunc(This->UserArg);
|
||||||
|
FEX::Windows::DeinitCRTThread();
|
||||||
|
NtTerminateThread(GetCurrentThread(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
FEXCore::Threads::ThreadFunc UserFunc;
|
||||||
|
void* UserArg;
|
||||||
|
HANDLE Handle {};
|
||||||
|
DWORD TID {};
|
||||||
|
void* ReturnValue {};
|
||||||
|
};
|
||||||
|
|
||||||
|
fextl::unique_ptr<FEXCore::Threads::Thread> CreateThread(FEXCore::Threads::ThreadFunc Func, void* Arg) {
|
||||||
|
return fextl::make_unique<Thread>(Func, Arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CleanupAfterFork() {}
|
||||||
|
} // namespace WinThreadImpl
|
||||||
|
|
||||||
|
void SetupThreadHandlers() {
|
||||||
|
FEXCore::Threads::Pointers Ptrs = {
|
||||||
|
.CreateThread = WinThreadImpl::CreateThread,
|
||||||
|
.CleanupAfterFork = WinThreadImpl::CleanupAfterFork,
|
||||||
|
};
|
||||||
|
FEXCore::Threads::Thread::SetInternalPointers(Ptrs);
|
||||||
|
}
|
||||||
|
} // namespace FEX::Windows
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace FEX::Windows {
|
||||||
|
void SetupThreadHandlers();
|
||||||
|
} // namespace FEX::Windows
|
||||||
@@ -111,10 +111,16 @@ DLLEXPORT_FUNC(HANDLE, CreateFileW,
|
|||||||
DLLEXPORT_FUNC(WINBOOL, WriteFile,
|
DLLEXPORT_FUNC(WINBOOL, WriteFile,
|
||||||
(HANDLE hFile, const void* lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped)) {
|
(HANDLE hFile, const void* lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped)) {
|
||||||
IO_STATUS_BLOCK IOSB;
|
IO_STATUS_BLOCK IOSB;
|
||||||
|
LARGE_INTEGER ByteOffset;
|
||||||
|
PLARGE_INTEGER ByteOffsetPtr = nullptr;
|
||||||
if (lpOverlapped) {
|
if (lpOverlapped) {
|
||||||
UNIMPLEMENTED();
|
if (lpOverlapped->hEvent) {
|
||||||
|
UNIMPLEMENTED();
|
||||||
|
}
|
||||||
|
ByteOffset.QuadPart = (static_cast<LONGLONG>(lpOverlapped->OffsetHigh) << 32) | lpOverlapped->Offset;
|
||||||
|
ByteOffsetPtr = &ByteOffset;
|
||||||
}
|
}
|
||||||
NTSTATUS Status = NtWriteFile(hFile, nullptr, nullptr, nullptr, &IOSB, lpBuffer, nNumberOfBytesToWrite, nullptr, nullptr);
|
NTSTATUS Status = NtWriteFile(hFile, nullptr, nullptr, nullptr, &IOSB, lpBuffer, nNumberOfBytesToWrite, ByteOffsetPtr, nullptr);
|
||||||
if (lpNumberOfBytesWritten) {
|
if (lpNumberOfBytesWritten) {
|
||||||
*lpNumberOfBytesWritten = static_cast<DWORD>(IOSB.Information);
|
*lpNumberOfBytesWritten = static_cast<DWORD>(IOSB.Information);
|
||||||
}
|
}
|
||||||
@@ -135,6 +141,18 @@ DLLEXPORT_FUNC(WINBOOL, WriteConsoleW,
|
|||||||
UNIMPLEMENTED();
|
UNIMPLEMENTED();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DLLEXPORT_FUNC(WINBOOL, GetFileSizeEx, (HANDLE hFile, PLARGE_INTEGER lpFileSize)) {
|
||||||
|
IO_STATUS_BLOCK IOSB;
|
||||||
|
FILE_STANDARD_INFORMATION StandardInfo;
|
||||||
|
if (NTSTATUS Status = NtQueryInformationFile(hFile, &IOSB, &StandardInfo, sizeof(StandardInfo), FileStandardInformation); Status) {
|
||||||
|
return WinAPIReturn(Status);
|
||||||
|
}
|
||||||
|
if (lpFileSize) {
|
||||||
|
*lpFileSize = StandardInfo.EndOfFile;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
DLLEXPORT_FUNC(WINBOOL, SetFilePointerEx, (HANDLE hFile, LARGE_INTEGER liDistanceToMove, PLARGE_INTEGER lpNewFilePointer, DWORD dwMoveMethod)) {
|
DLLEXPORT_FUNC(WINBOOL, SetFilePointerEx, (HANDLE hFile, LARGE_INTEGER liDistanceToMove, PLARGE_INTEGER lpNewFilePointer, DWORD dwMoveMethod)) {
|
||||||
IO_STATUS_BLOCK IOSB;
|
IO_STATUS_BLOCK IOSB;
|
||||||
FILE_POSITION_INFORMATION PositionInfo;
|
FILE_POSITION_INFORMATION PositionInfo;
|
||||||
@@ -173,10 +191,17 @@ DLLEXPORT_FUNC(WINBOOL, SetFilePointerEx, (HANDLE hFile, LARGE_INTEGER liDistanc
|
|||||||
DLLEXPORT_FUNC(WINBOOL, ReadFile,
|
DLLEXPORT_FUNC(WINBOOL, ReadFile,
|
||||||
(HANDLE hFile, void* lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped)) {
|
(HANDLE hFile, void* lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped)) {
|
||||||
IO_STATUS_BLOCK IOSB;
|
IO_STATUS_BLOCK IOSB;
|
||||||
|
LARGE_INTEGER ByteOffset;
|
||||||
|
PLARGE_INTEGER ByteOffsetPtr = nullptr;
|
||||||
if (lpOverlapped) {
|
if (lpOverlapped) {
|
||||||
UNIMPLEMENTED();
|
if (lpOverlapped->hEvent) {
|
||||||
|
// no actual async-overlapped, just enough for PRead
|
||||||
|
UNIMPLEMENTED();
|
||||||
|
}
|
||||||
|
ByteOffset.QuadPart = (static_cast<LONGLONG>(lpOverlapped->OffsetHigh) << 32) | lpOverlapped->Offset;
|
||||||
|
ByteOffsetPtr = &ByteOffset;
|
||||||
}
|
}
|
||||||
NTSTATUS Status = NtReadFile(hFile, nullptr, nullptr, nullptr, &IOSB, lpBuffer, nNumberOfBytesToRead, nullptr, nullptr);
|
NTSTATUS Status = NtReadFile(hFile, nullptr, nullptr, nullptr, &IOSB, lpBuffer, nNumberOfBytesToRead, ByteOffsetPtr, nullptr);
|
||||||
if (lpNumberOfBytesRead) {
|
if (lpNumberOfBytesRead) {
|
||||||
*lpNumberOfBytesRead = static_cast<DWORD>(IOSB.Information);
|
*lpNumberOfBytesRead = static_cast<DWORD>(IOSB.Information);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ $end_info$
|
|||||||
#include "Common/TSOHandlerConfig.h"
|
#include "Common/TSOHandlerConfig.h"
|
||||||
#include "Common/ImageTracker.h"
|
#include "Common/ImageTracker.h"
|
||||||
#include "Common/InvalidationTracker.h"
|
#include "Common/InvalidationTracker.h"
|
||||||
|
#include "Common/Threads.h"
|
||||||
#include "Common/OvercommitTracker.h"
|
#include "Common/OvercommitTracker.h"
|
||||||
#include "Common/CPUFeatures.h"
|
#include "Common/CPUFeatures.h"
|
||||||
#include "Common/Logging.h"
|
#include "Common/Logging.h"
|
||||||
@@ -515,6 +516,7 @@ public:
|
|||||||
|
|
||||||
void BTCpuProcessInit() {
|
void BTCpuProcessInit() {
|
||||||
FEX::Windows::InitCRTProcess();
|
FEX::Windows::InitCRTProcess();
|
||||||
|
FEX::Windows::SetupThreadHandlers();
|
||||||
const auto ExecutablePath = FEX::Windows::GetExecutableFilePath();
|
const auto ExecutablePath = FEX::Windows::GetExecutableFilePath();
|
||||||
const auto ExecutableName = FEX::Windows::BaseName(ExecutablePath);
|
const auto ExecutableName = FEX::Windows::BaseName(ExecutablePath);
|
||||||
FEX::Config::LoadConfig(fextl::string {ExecutableName}, _environ, FEX::ReadPortabilityInformation());
|
FEX::Config::LoadConfig(fextl::string {ExecutableName}, _environ, FEX::ReadPortabilityInformation());
|
||||||
|
|||||||
@@ -407,6 +407,23 @@ typedef enum _SECTION_INHERIT {
|
|||||||
ViewUnmap = 2,
|
ViewUnmap = 2,
|
||||||
} SECTION_INHERIT;
|
} SECTION_INHERIT;
|
||||||
|
|
||||||
|
typedef NTSTATUS(WINAPI* PRTL_THREAD_START_ROUTINE)(void* Parameter);
|
||||||
|
|
||||||
|
typedef struct _PS_ATTRIBUTE {
|
||||||
|
ULONG_PTR Attribute;
|
||||||
|
SIZE_T Size;
|
||||||
|
union {
|
||||||
|
ULONG_PTR Value;
|
||||||
|
void* ValuePtr;
|
||||||
|
};
|
||||||
|
PSIZE_T ReturnLength;
|
||||||
|
} PS_ATTRIBUTE, *PPS_ATTRIBUTE;
|
||||||
|
|
||||||
|
typedef struct _PS_ATTRIBUTE_LIST {
|
||||||
|
SIZE_T TotalLength;
|
||||||
|
PS_ATTRIBUTE Attributes[1];
|
||||||
|
} PS_ATTRIBUTE_LIST, *PPS_ATTRIBUTE_LIST;
|
||||||
|
|
||||||
/* definitions of bits in the Feature set for the x86 processors */
|
/* definitions of bits in the Feature set for the x86 processors */
|
||||||
#define CPU_FEATURE_VME 0x00000005 /* Virtual 86 Mode Extensions */
|
#define CPU_FEATURE_VME 0x00000005 /* Virtual 86 Mode Extensions */
|
||||||
#define CPU_FEATURE_TSC 0x00000002 /* Time Stamp Counter available */
|
#define CPU_FEATURE_TSC 0x00000002 /* Time Stamp Counter available */
|
||||||
@@ -518,6 +535,12 @@ NTSTATUS WINAPI NtAllocateVirtualMemoryEx(HANDLE, PVOID*, SIZE_T*, ULONG, ULONG,
|
|||||||
NTSTATUS WINAPI NtAllocateVirtualMemory(HANDLE, PVOID*, ULONG_PTR, SIZE_T*, ULONG, ULONG);
|
NTSTATUS WINAPI NtAllocateVirtualMemory(HANDLE, PVOID*, ULONG_PTR, SIZE_T*, ULONG, ULONG);
|
||||||
NTSTATUS WINAPI NtContinue(PCONTEXT, BOOLEAN);
|
NTSTATUS WINAPI NtContinue(PCONTEXT, BOOLEAN);
|
||||||
NTSTATUS WINAPI NtCreateSection(HANDLE*, ACCESS_MASK, const OBJECT_ATTRIBUTES*, const LARGE_INTEGER*, ULONG, ULONG, HANDLE);
|
NTSTATUS WINAPI NtCreateSection(HANDLE*, ACCESS_MASK, const OBJECT_ATTRIBUTES*, const LARGE_INTEGER*, ULONG, ULONG, HANDLE);
|
||||||
|
#define THREAD_CREATE_FLAGS_SKIP_THREAD_ATTACH 0x00000002
|
||||||
|
#define THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER 0x00000004
|
||||||
|
#define THREAD_CREATE_FLAGS_SKIP_LOADER_INIT 0x00000020
|
||||||
|
#define THREAD_CREATE_FLAGS_BYPASS_PROCESS_FREEZE 0x00000040
|
||||||
|
NTSTATUS WINAPI NtCreateThreadEx(HANDLE*, ACCESS_MASK, OBJECT_ATTRIBUTES*, HANDLE, PRTL_THREAD_START_ROUTINE, void*, ULONG, ULONG_PTR,
|
||||||
|
SIZE_T, SIZE_T, PPS_ATTRIBUTE_LIST);
|
||||||
NTSTATUS WINAPI NtDelayExecution(BOOLEAN, const LARGE_INTEGER*);
|
NTSTATUS WINAPI NtDelayExecution(BOOLEAN, const LARGE_INTEGER*);
|
||||||
NTSTATUS WINAPI NtDuplicateObject(HANDLE, HANDLE, HANDLE, PHANDLE, ACCESS_MASK, ULONG, ULONG);
|
NTSTATUS WINAPI NtDuplicateObject(HANDLE, HANDLE, HANDLE, PHANDLE, ACCESS_MASK, ULONG, ULONG);
|
||||||
NTSTATUS WINAPI NtFlushInstructionCache(HANDLE, LPCVOID, SIZE_T);
|
NTSTATUS WINAPI NtFlushInstructionCache(HANDLE, LPCVOID, SIZE_T);
|
||||||
@@ -538,6 +561,7 @@ NTSTATUS WINAPI NtReadFile(HANDLE, HANDLE, PIO_APC_ROUTINE, PVOID, PIO_STATUS_BL
|
|||||||
NTSTATUS WINAPI NtSetContextThread(HANDLE, const CONTEXT*);
|
NTSTATUS WINAPI NtSetContextThread(HANDLE, const CONTEXT*);
|
||||||
NTSTATUS WINAPI NtSuspendThread(HANDLE, PULONG);
|
NTSTATUS WINAPI NtSuspendThread(HANDLE, PULONG);
|
||||||
NTSTATUS WINAPI NtTerminateProcess(HANDLE, LONG);
|
NTSTATUS WINAPI NtTerminateProcess(HANDLE, LONG);
|
||||||
|
NTSTATUS WINAPI NtTerminateThread(HANDLE, LONG);
|
||||||
NTSTATUS WINAPI NtUnlockFile(HANDLE, PIO_STATUS_BLOCK, PLARGE_INTEGER, PLARGE_INTEGER, ULONG);
|
NTSTATUS WINAPI NtUnlockFile(HANDLE, PIO_STATUS_BLOCK, PLARGE_INTEGER, PLARGE_INTEGER, ULONG);
|
||||||
NTSTATUS WINAPI NtWriteFile(HANDLE, HANDLE, PIO_APC_ROUTINE, PVOID, PIO_STATUS_BLOCK, const void*, ULONG, PLARGE_INTEGER, PULONG);
|
NTSTATUS WINAPI NtWriteFile(HANDLE, HANDLE, PIO_APC_ROUTINE, PVOID, PIO_STATUS_BLOCK, const void*, ULONG, PLARGE_INTEGER, PULONG);
|
||||||
void WINAPI ProcessPendingCrossProcessEmulatorWork();
|
void WINAPI ProcessPendingCrossProcessEmulatorWork();
|
||||||
|
|||||||
Reference in new issue
Block a user