Merge pull request #4773 from Sonicadvance1/extended_volatile_metadata

Implement support for additional provided volatile metadata
This commit is contained in:
Ryan Houdek authored and GitHub committed 2025-08-22 15:15:50 -07:00
commit 6310c20217
10 files changed
+441 -13

No files matched your search

@@ -522,6 +522,27 @@
"Desc": [
"Disables inline syscalls in order to support seccomp handling"
]
},
"ExtendedVolatileMetadata": {
"Type": "str",
"Default": "",
"Desc": [
"Configuration provided volatile metadata. Only implemented for WoW64/arm64ec.",
"Limited in its use but can be handy.",
"Extends on top of what Microsoft has for volatile metadata, but also supported for WoW64.",
"Colon delimited modules, then semi-colon delimited instructions, then comma delimited ranges",
"Default disables TSO in the module, unless instructions overlap the range",
"<module>;<offset begin>-<offset-end>,...;<instruction offset to force TSO>,...:<another>",
"examples:",
" * Disable TSO for a full module: Just provide the module name:",
" `hl2_linux`",
" * Disable TSO for a part of the module:",
" `hl2_linux;<offset begin>-<offset-end>`",
" * Disable TSO for a part of the module, but enable TSO for some instructions within the module",
" `hl2_linux;<offset begin>-<offset-end>;<instruction offset>,<instruction offset>`",
" * Disable TSO for multiple modules",
" `hl2_linux:libsdl2.so`"
]
}
}
},
@@ -34,10 +34,23 @@ public:
Interval Interval; ///< The interval that the query offset is enclosed by, or the next interval if `Enclosed` is false
};
using const_iterator = typename fextl::vector<Interval>::const_iterator;
const_iterator begin() const {
return Intervals.begin();
}
const_iterator end() const {
return Intervals.end();
}
void Clear() {
Intervals.clear();
}
bool Empty() const {
return Intervals.empty();
}
void Insert(Interval Entry) {
if (Entry.Offset == Entry.End) {
return;
+3 -1
View File
@@ -9,7 +9,9 @@ set(SRCS
HostFeatures.cpp
JSONPool.cpp
StringUtil.cpp
SHMStats.cpp)
SHMStats.cpp
VolatileMetadata.cpp
)
if (NOT MINGW_BUILD)
list (APPEND SRCS
+120
View File
@@ -0,0 +1,120 @@
// SPDX-License-Identifier: MIT
#include "Common/VolatileMetadata.h"
namespace FEX::VolatileMetadata {
fextl::unordered_map<fextl::string, ExtendedVolatileMetadata> ParseExtendedVolatileMetadata(std::string_view ListOfDescriptors) {
// Parsing: `<module>;<address begin>-<address-end>,<more addresses>;<instruction offset to force TSO>:`
if (ListOfDescriptors.empty()) {
return {};
}
fextl::unordered_map<fextl::string, ExtendedVolatileMetadata> ExtendedMetaData {};
auto current_module = ExtendedMetaData.end();
for (size_t module_offset = 0; module_offset != ListOfDescriptors.npos;) {
size_t end_of_module = ListOfDescriptors.find(":", module_offset);
std::string_view module_config = ListOfDescriptors.substr(module_offset, end_of_module - module_offset);
if (module_config.empty()) {
module_offset = end_of_module == ListOfDescriptors.npos ? ListOfDescriptors.npos : end_of_module + 1;
continue;
}
size_t end_of_name = module_config.find(";");
size_t end_of_address_ranges = module_config.npos;
size_t end_of_individual_inst = module_config.npos;
// Module name handling
{
std::string_view section_str = module_config.substr(0, end_of_name);
if (section_str.empty()) {
module_offset = end_of_module == ListOfDescriptors.npos ? ListOfDescriptors.npos : end_of_module + 1;
continue;
}
current_module = ExtendedMetaData
.insert_or_assign(fextl::string(section_str),
ExtendedVolatileMetadata {
.ModuleTSODisabled = true,
})
.first;
}
// Address range handling
if (end_of_name != module_config.npos) {
end_of_address_ranges = module_config.find(";", end_of_name + 1);
std::string_view section_str = module_config.substr(end_of_name + 1, end_of_address_ranges - (end_of_name + 1));
if (section_str.empty()) {
module_offset = end_of_module == ListOfDescriptors.npos ? ListOfDescriptors.npos : end_of_module + 1;
continue;
}
current_module->second.ModuleTSODisabled = false;
// Walk all the address ranges provided.
for (size_t non_tso_region_offset = 0; non_tso_region_offset != section_str.npos;) {
size_t end_of_region_substr = section_str.find(",", non_tso_region_offset);
std::string_view tso_region_view = section_str.substr(non_tso_region_offset, end_of_region_substr - non_tso_region_offset);
if (tso_region_view.empty()) {
non_tso_region_offset = end_of_region_substr == section_str.npos ? section_str.npos : end_of_region_substr + 1;
continue;
}
uint64_t begin {}, end {};
char* str_end;
begin = std::strtoull(tso_region_view.data(), &str_end, 16);
LOGMAN_THROW_A_FMT(tso_region_view.data() != str_end, "Couldn't parse begin {}", tso_region_view);
// Skip `-` separator.
++str_end;
LOGMAN_THROW_A_FMT(str_end != tso_region_view.end(), "Couldn't parse end {}", tso_region_view);
auto str_begin = str_end;
end = std::strtoull(str_begin, &str_end, 16);
LOGMAN_THROW_A_FMT(str_begin != str_end, "Couldn't parse end {}", tso_region_view);
current_module->second.VolatileValidRanges.Insert({begin, end});
non_tso_region_offset = end_of_region_substr == section_str.npos ? section_str.npos : end_of_region_substr + 1;
}
}
// Individual instruction handling
if (end_of_address_ranges != module_config.npos) {
end_of_individual_inst = module_config.find(";", end_of_address_ranges + 1);
std::string_view section_str = module_config.substr(end_of_address_ranges + 1, end_of_individual_inst);
if (section_str.empty()) {
module_offset = end_of_module == ListOfDescriptors.npos ? ListOfDescriptors.npos : end_of_module + 1;
continue;
}
for (size_t force_tso_region_offset = 0; force_tso_region_offset != section_str.npos;) {
size_t end_of_region_substr = section_str.find(",", force_tso_region_offset);
std::string_view tso_region_view = section_str.substr(force_tso_region_offset, end_of_region_substr - force_tso_region_offset);
if (tso_region_view.empty()) {
force_tso_region_offset = end_of_region_substr == section_str.npos ? section_str.npos : end_of_region_substr + 1;
continue;
}
uint64_t offset {};
char* str_end;
offset = std::strtoull(tso_region_view.data(), &str_end, 16);
LOGMAN_THROW_A_FMT(tso_region_view.data() != str_end, "Couldn't parse offset {}", tso_region_view);
current_module->second.VolatileInstructions.insert(offset);
force_tso_region_offset = end_of_region_substr == section_str.npos ? section_str.npos : end_of_region_substr + 1;
}
}
module_offset = end_of_module == ListOfDescriptors.npos ? ListOfDescriptors.npos : end_of_module + 1;
}
return ExtendedMetaData;
}
} // namespace FEX::VolatileMetadata
+16
View File
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: MIT
#pragma once
#include <FEXCore/Utils/IntervalList.h>
#include <FEXCore/fextl/unordered_map.h>
#include <FEXCore/fextl/set.h>
#include <FEXCore/fextl/string.h>
namespace FEX::VolatileMetadata {
struct ExtendedVolatileMetadata {
FEXCore::IntervalList<uint64_t> VolatileValidRanges;
fextl::set<uint64_t> VolatileInstructions;
bool ModuleTSODisabled;
};
fextl::unordered_map<fextl::string, ExtendedVolatileMetadata> ParseExtendedVolatileMetadata(std::string_view ListOfDescriptors);
} // namespace FEX::VolatileMetadata
+30 -9
View File
@@ -43,6 +43,7 @@ $end_info$
#include "DummyHandlers.h"
#include "BTInterface.h"
#include "Windows/Common/SHMStats.h"
#include "Windows/Common/VolatileMetadata.h"
#include <cstdint>
#include <cstdio>
@@ -80,6 +81,7 @@ void* WineSyscallDispatcher;
uint64_t WineNtContinueSyscallId;
uint64_t WineNtAllocateVirtualMemorySyscallId;
uint64_t WineNtProtectVirtualMemorySyscallId;
static fextl::unordered_map<fextl::string, FEX::VolatileMetadata::ExtendedVolatileMetadata> ExtendedMetaData {};
NTSTATUS NtContinueNative(ARM64_NT_CONTEXT* NativeContext, BOOLEAN Alert);
NTSTATUS NtAllocateVirtualMemoryNative(HANDLE, PVOID*, ULONG_PTR, SIZE_T*, ULONG, ULONG);
@@ -267,11 +269,10 @@ void InitSyscalls() {
PatchCallChecker();
}
void LoadImageVolatileMetadata(uint64_t Address) {
const auto Module = reinterpret_cast<HMODULE>(Address);
IMAGE_NT_HEADERS* Nt = RtlImageNtHeader(Module);
uint64_t EndAddress = Address + Nt->OptionalHeader.SizeOfImage;
void LoadImageVolatileMetadata(fextl::set<uint64_t>& VolatileInstructions, FEXCore::IntervalList<uint64_t>& VolatileValidRanges,
HMODULE Module, IMAGE_NT_HEADERS* Nt, uint64_t Address, uint64_t EndAddress) {
ULONG Size;
const auto* LoadConfig =
reinterpret_cast<_IMAGE_LOAD_CONFIG_DIRECTORY64*>(RtlImageDirectoryEntryToData(Module, true, IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG, &Size));
if (!LoadConfig || LoadConfig->Size <= offsetof(_IMAGE_LOAD_CONFIG_DIRECTORY64, VolatileMetadataPointer)) {
@@ -288,7 +289,6 @@ void LoadImageVolatileMetadata(uint64_t Address) {
return;
}
fextl::set<uint64_t> VolatileInstructions;
const auto* VolatileAccessTableBegin = reinterpret_cast<IMAGE_VOLATILE_RVA_METADATA*>(Address + VolatileMetadata->VolatileAccessTable);
const auto* VolatileAccessTableEnd =
VolatileAccessTableBegin + (VolatileMetadata->VolatileAccessTableSize / sizeof(IMAGE_VOLATILE_RVA_METADATA));
@@ -296,13 +296,31 @@ void LoadImageVolatileMetadata(uint64_t Address) {
VolatileInstructions.emplace(Address + It->Rva);
}
FEXCore::IntervalList<uint64_t> VolatileValidRanges;
const auto* VolatileInfoRangeTableBegin = reinterpret_cast<IMAGE_VOLATILE_RANGE_METADATA*>(Address + VolatileMetadata->VolatileInfoRangeTable);
const auto* VolatileInfoRangeTableEnd =
VolatileInfoRangeTableBegin + (VolatileMetadata->VolatileInfoRangeTableSize / sizeof(IMAGE_VOLATILE_RANGE_METADATA));
for (auto It = VolatileInfoRangeTableBegin; It != VolatileInfoRangeTableEnd; It++) {
VolatileValidRanges.Insert({Address + It->Rva, Address + It->Rva + It->Size});
}
}
void LoadImageVolatileMetadata(const fextl::string& ModuleName, uint64_t Address) {
const auto Module = reinterpret_cast<HMODULE>(Address);
IMAGE_NT_HEADERS* Nt = RtlImageNtHeader(Module);
uint64_t EndAddress = Address + Nt->OptionalHeader.SizeOfImage;
fextl::set<uint64_t> VolatileInstructions {};
FEXCore::IntervalList<uint64_t> VolatileValidRanges {};
LoadImageVolatileMetadata(VolatileInstructions, VolatileValidRanges, Module, Nt, Address, EndAddress);
auto it = ExtendedMetaData.find(ModuleName);
if (it != ExtendedMetaData.end()) {
FEX::Windows::ApplyFEXExtendedVolatileMetadata(it->second, VolatileInstructions, VolatileValidRanges, Address, EndAddress);
}
if (VolatileInstructions.empty() && VolatileValidRanges.Empty()) {
return;
}
LogMan::Msg::DFmt("Loaded volatile metadata for {:X}: {} entries", Address, VolatileInstructions.size());
std::scoped_lock Lock(CTX->GetCodeInvalidationMutex());
@@ -314,7 +332,7 @@ void HandleImageMap(uint64_t Address) {
LogMan::Msg::DFmt("Load module {}: {:X}", ModuleName, Address);
FEX_CONFIG_OPT(VolatileMetadata, VOLATILEMETADATA);
if (VolatileMetadata) {
LoadImageVolatileMetadata(Address);
LoadImageVolatileMetadata(ModuleName, Address);
}
InvalidationTracker->HandleImageMap(ModuleName, Address);
}
@@ -630,6 +648,9 @@ NTSTATUS ProcessInit() {
FEXCore::Context::InitializeStaticTables(FEXCore::Context::MODE_64BIT);
FEX_CONFIG_OPT(ExtendedVolatileMetadataConfig, EXTENDEDVOLATILEMETADATA);
ExtendedMetaData = FEX::VolatileMetadata::ParseExtendedVolatileMetadata(ExtendedVolatileMetadataConfig());
SignalDelegator = fextl::make_unique<FEX::DummyHandlers::DummySignalDelegator>();
SyscallHandler = fextl::make_unique<Exception::ECSyscallHandler>();
Exception::HandlerConfig.emplace();
@@ -941,7 +962,7 @@ NTSTATUS ThreadInit() {
auto NewSegments = new FEXCore::Core::CPUState::gdt_segment[32];
// Setup initial code-segment GDT
auto &GDT = NewSegments[FEXCore::Core::CPUState::DEFAULT_USER_CS];
auto& GDT = NewSegments[FEXCore::Core::CPUState::DEFAULT_USER_CS];
FEXCore::Core::CPUState::SetGDTBase(&GDT, 0);
FEXCore::Core::CPUState::SetGDTLimit(&GDT, 0xF'FFFFU);
GDT.L = 1; // L = Long Mode = 64-bit
@@ -1033,7 +1054,7 @@ NTSTATUS ThreadTerm(HANDLE Thread, LONG ExitCode) {
auto ThreadState = CPUArea.ThreadState();
// GDT and LDT are mirrored, only free one.
delete [] ThreadState->CurrentFrame->State.segment_arrays[FEXCore::Core::CPUState::SEGMENT_ARRAY_INDEX_GDT];
delete[] ThreadState->CurrentFrame->State.segment_arrays[FEXCore::Core::CPUState::SEGMENT_ARRAY_INDEX_GDT];
FEX::Windows::CallRetStack::DestroyThread(ThreadState);
CTX->DestroyThread(ThreadState);
+42
View File
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: MIT
#pragma once
#include "Common/VolatileMetadata.h"
#include <FEXCore/fextl/unordered_map.h>
#include <FEXCore/fextl/string.h>
#include <FEXCore/fextl/set.h>
#include <FEXCore/Utils/IntervalList.h>
namespace FEX::Windows {
inline void ApplyFEXExtendedVolatileMetadata(FEX::VolatileMetadata::ExtendedVolatileMetadata& ExtendedMetaData,
fextl::set<uint64_t>& VolatileInstructions,
FEXCore::IntervalList<uint64_t>& VolatileValidRanges, uint64_t Address, uint64_t EndAddress) {
// Load FEX extended volatile metadata.
// Walk the volatile instructions first if they exist.
for (const auto it_inst : ExtendedMetaData.VolatileInstructions) {
const auto inst_address = it_inst + Address;
if (inst_address < EndAddress) {
VolatileInstructions.emplace(Address + it_inst);
} else {
LogMan::Msg::DFmt("Volatile instruction 0x{:x} couldn't fit in to module range [0x{:x}, 0x{:x}). Not adding anymore volatile "
"instructions. Inspect your config!",
inst_address, Address, EndAddress);
return;
}
}
// Walk the volatile list
for (const auto it_ranges : ExtendedMetaData.VolatileValidRanges) {
VolatileValidRanges.Insert({Address + it_ranges.Offset, Address + it_ranges.End});
}
// If it is fully disabled, then set the entire module range
if (ExtendedMetaData.ModuleTSODisabled) {
VolatileValidRanges.Clear();
VolatileValidRanges.Insert({Address, EndAddress});
}
}
} // namespace FEX::Windows
+37 -3
View File
@@ -26,6 +26,7 @@ $end_info$
#include <FEXCore/Utils/FPState.h>
#include <FEXCore/Utils/ArchHelpers/Arm64.h>
#include <FEXCore/Utils/TypeDefines.h>
#include <FEXCore/Utils/SignalScopeGuards.h>
#include "Common/ArgumentLoader.h"
#include "Common/CallRetStack.h"
@@ -43,6 +44,7 @@ $end_info$
#include "DummyHandlers.h"
#include "BTInterface.h"
#include "Windows/Common/SHMStats.h"
#include "Windows/Common/VolatileMetadata.h"
#include <cstdint>
#include <type_traits>
@@ -127,6 +129,8 @@ std::mutex ThreadCreationMutex;
// Map of TIDs to their FEX thread state, `ThreadCreationMutex` must be locked when accessing
std::unordered_map<DWORD, FEXCore::Core::InternalThreadState*> Threads;
static fextl::unordered_map<fextl::string, FEX::VolatileMetadata::ExtendedVolatileMetadata> ExtendedMetaData {};
decltype(__wine_unix_call_dispatcher) WineUnixCall;
std::pair<NTSTATUS, TLS> GetThreadTLS(HANDLE Thread) {
@@ -159,9 +163,36 @@ bool IsAddressInJit(uint64_t Address) {
return Thread->CTX->IsAddressInCodeBuffer(Thread, Address);
}
void LoadImageVolatileMetadata(const fextl::string& ModuleName, uint64_t Address) {
const auto Module = reinterpret_cast<HMODULE>(Address);
IMAGE_NT_HEADERS* Nt = RtlImageNtHeader(Module);
uint64_t EndAddress = Address + Nt->OptionalHeader.SizeOfImage;
fextl::set<uint64_t> VolatileInstructions;
FEXCore::IntervalList<uint64_t> VolatileValidRanges;
// Load FEX extended volatile metadata.
auto it = ExtendedMetaData.find(ModuleName);
if (it != ExtendedMetaData.end()) {
FEX::Windows::ApplyFEXExtendedVolatileMetadata(it->second, VolatileInstructions, VolatileValidRanges, Address, EndAddress);
}
if (VolatileInstructions.empty() && VolatileValidRanges.Empty()) {
return;
}
LogMan::Msg::DFmt("Loaded volatile metadata for {:X}: {} entries", Address, VolatileInstructions.size());
std::scoped_lock Lock(CTX->GetCodeInvalidationMutex());
CTX->AddForceTSOInformation(VolatileValidRanges, std::move(VolatileInstructions));
}
void HandleImageMap(uint64_t Address) {
fextl::string ModuleName = FEX::Windows::GetSectionFilePath(Address);
LogMan::Msg::DFmt("Load module {}: {:X}", ModuleName, Address);
FEX_CONFIG_OPT(VolatileMetadata, VOLATILEMETADATA);
if (VolatileMetadata) {
LoadImageVolatileMetadata(ModuleName, Address);
}
InvalidationTracker->HandleImageMap(ModuleName, Address);
}
} // namespace
@@ -494,6 +525,9 @@ void BTCpuProcessInit() {
FEXCore::Context::InitializeStaticTables(FEXCore::Context::MODE_32BIT);
FEX_CONFIG_OPT(ExtendedVolatileMetadataConfig, EXTENDEDVOLATILEMETADATA);
ExtendedMetaData = FEX::VolatileMetadata::ParseExtendedVolatileMetadata(ExtendedVolatileMetadataConfig());
SignalDelegator = fextl::make_unique<FEX::DummyHandlers::DummySignalDelegator>();
SyscallHandler = fextl::make_unique<WowSyscallHandler>();
Context::HandlerConfig.emplace();
@@ -572,7 +606,7 @@ void BTCpuThreadInit() {
auto NewSegments = new FEXCore::Core::CPUState::gdt_segment[32];
// Setup initial code-segment GDT
auto &GDT = NewSegments[FEXCore::Core::CPUState::DEFAULT_USER_CS];
auto& GDT = NewSegments[FEXCore::Core::CPUState::DEFAULT_USER_CS];
FEXCore::Core::CPUState::SetGDTBase(&GDT, 0);
FEXCore::Core::CPUState::SetGDTLimit(&GDT, 0xF'FFFFU);
GDT.L = 0; // L = Long Mode = 32-bit
@@ -638,7 +672,7 @@ void BTCpuThreadTerm(HANDLE Thread, LONG ExitCode) {
auto ThreadState = TLS.ThreadState();
// GDT and LDT are mirrored, only free one.
delete [] ThreadState->CurrentFrame->State.segment_arrays[FEXCore::Core::CPUState::SEGMENT_ARRAY_INDEX_GDT];
delete[] ThreadState->CurrentFrame->State.segment_arrays[FEXCore::Core::CPUState::SEGMENT_ARRAY_INDEX_GDT];
FEX::Windows::CallRetStack::DestroyThread(ThreadState);
CTX->DestroyThread(ThreadState);
@@ -734,7 +768,7 @@ __attribute__((naked)) void BTCpuSimulate() {
".seh_endproc;");
}
extern "C" void BTCpuSimulateImpl(CONTEXT *entry_context) {
extern "C" void BTCpuSimulateImpl(CONTEXT* entry_context) {
auto TLS = GetTLS();
TLS.EntryContext() = entry_context;
TLS.CachedCallRetSp() = TLS.ThreadState()->CurrentFrame->State.callret_sp;
+1
View File
@@ -5,6 +5,7 @@ set (TESTS
Filesystem
StringUtils
fextl_function
ExtendedVolatileMetadata
)
list(APPEND LIBS Common FEXCore JemallocLibs)
@@ -0,0 +1,158 @@
// SPDX-License-Identifier: MIT
#include <catch2/catch_all.hpp>
#include "Common/VolatileMetadata.h"
TEST_CASE("Basic - Empty") {
const auto String = "";
const auto Result = FEX::VolatileMetadata::ParseExtendedVolatileMetadata(String);
REQUIRE(Result.empty());
}
TEST_CASE("Basic - Empty - modules") {
const auto String = ":::::";
const auto Result = FEX::VolatileMetadata::ParseExtendedVolatileMetadata(String);
REQUIRE(Result.empty());
}
TEST_CASE("Basic - Single") {
const auto String = "hl2_linux";
const auto Result = FEX::VolatileMetadata::ParseExtendedVolatileMetadata(String);
REQUIRE(Result.size() == 1);
REQUIRE(Result.contains("hl2_linux"));
CHECK(Result.at("hl2_linux").ModuleTSODisabled == true);
CHECK(Result.at("hl2_linux").VolatileInstructions.empty());
CHECK(Result.at("hl2_linux").VolatileValidRanges.Empty());
}
TEST_CASE("Basic - Multiple") {
const auto String = "hl2_linux:DeckJob";
const auto Result = FEX::VolatileMetadata::ParseExtendedVolatileMetadata(String);
REQUIRE(Result.size() == 2);
REQUIRE(Result.contains("hl2_linux"));
CHECK(Result.at("hl2_linux").ModuleTSODisabled == true);
CHECK(Result.at("hl2_linux").VolatileInstructions.empty());
CHECK(Result.at("hl2_linux").VolatileValidRanges.Empty());
REQUIRE(Result.contains("DeckJob"));
CHECK(Result.at("DeckJob").ModuleTSODisabled == true);
CHECK(Result.at("DeckJob").VolatileInstructions.empty());
CHECK(Result.at("DeckJob").VolatileValidRanges.Empty());
}
TEST_CASE("Basic - Single plus empty") {
const auto String = "hl2_linux:::::";
const auto Result = FEX::VolatileMetadata::ParseExtendedVolatileMetadata(String);
REQUIRE(Result.size() == 1);
REQUIRE(Result.contains("hl2_linux"));
CHECK(Result.at("hl2_linux").ModuleTSODisabled == true);
CHECK(Result.at("hl2_linux").VolatileInstructions.empty());
CHECK(Result.at("hl2_linux").VolatileValidRanges.Empty());
}
static inline bool ContainsRange(std::pair<uint64_t, uint64_t> Range, const std::vector<std::pair<uint64_t, uint64_t>>& ValidRanges) {
return std::ranges::find(ValidRanges, Range) != ValidRanges.end();
}
TEST_CASE("Basic - Single - offset") {
const auto String = "hl2_linux;0x0-0x1000";
const auto Result = FEX::VolatileMetadata::ParseExtendedVolatileMetadata(String);
REQUIRE(Result.size() == 1);
REQUIRE(Result.contains("hl2_linux"));
CHECK(Result.at("hl2_linux").ModuleTSODisabled == false);
CHECK(Result.at("hl2_linux").VolatileInstructions.empty());
CHECK(Result.at("hl2_linux").VolatileValidRanges.Empty() == false);
const std::vector<std::pair<uint64_t, uint64_t>> ValidRanges = {
{0, 0x1000},
};
for (auto it : Result.at("hl2_linux").VolatileValidRanges) {
CHECK(ContainsRange(std::make_pair(it.Offset, it.End), ValidRanges));
}
}
TEST_CASE("Basic - Single - offset x2") {
const auto String = "hl2_linux;0x0-0x1000,0x2000-0x3000";
const auto Result = FEX::VolatileMetadata::ParseExtendedVolatileMetadata(String);
REQUIRE(Result.size() == 1);
REQUIRE(Result.contains("hl2_linux"));
CHECK(Result.at("hl2_linux").ModuleTSODisabled == false);
CHECK(Result.at("hl2_linux").VolatileInstructions.empty());
CHECK(Result.at("hl2_linux").VolatileValidRanges.Empty() == false);
const std::vector<std::pair<uint64_t, uint64_t>> ValidRanges = {
{0, 0x1000},
{0x2000, 0x3000},
};
for (auto it : Result.at("hl2_linux").VolatileValidRanges) {
CHECK(ContainsRange(std::make_pair(it.Offset, it.End), ValidRanges));
}
}
TEST_CASE("Basic - Single - offset plus instruction") {
const auto String = "hl2_linux;0x0-0x1000;0x1,0x2,0x3";
const auto Result = FEX::VolatileMetadata::ParseExtendedVolatileMetadata(String);
REQUIRE(Result.size() == 1);
REQUIRE(Result.contains("hl2_linux"));
CHECK(Result.at("hl2_linux").ModuleTSODisabled == false);
CHECK(Result.at("hl2_linux").VolatileInstructions.empty() == false);
CHECK(Result.at("hl2_linux").VolatileValidRanges.Empty() == false);
const std::vector<std::pair<uint64_t, uint64_t>> ValidRanges = {
{0, 0x1000},
};
const std::vector<uint64_t> ValidInsts = {
1,
2,
3,
};
for (auto it : Result.at("hl2_linux").VolatileValidRanges) {
CHECK(ContainsRange(std::make_pair(it.Offset, it.End), ValidRanges));
}
for (auto it : Result.at("hl2_linux").VolatileInstructions) {
CHECK_THAT(ValidInsts, Catch::Matchers::Contains(it));
}
}
TEST_CASE("Basic - Double - offset") {
const auto String = "hl2_linux;0x0-0x1000:DeckJob;0x2000-0x3000";
const auto Result = FEX::VolatileMetadata::ParseExtendedVolatileMetadata(String);
REQUIRE(Result.size() == 2);
{
REQUIRE(Result.contains("hl2_linux"));
CHECK(Result.at("hl2_linux").ModuleTSODisabled == false);
CHECK(Result.at("hl2_linux").VolatileInstructions.empty());
CHECK(Result.at("hl2_linux").VolatileValidRanges.Empty() == false);
const std::vector<std::pair<uint64_t, uint64_t>> ValidRanges = {
{0, 0x1000},
};
for (auto it : Result.at("hl2_linux").VolatileValidRanges) {
CHECK(ContainsRange(std::make_pair(it.Offset, it.End), ValidRanges));
}
}
{
REQUIRE(Result.contains("DeckJob"));
CHECK(Result.at("DeckJob").ModuleTSODisabled == false);
CHECK(Result.at("DeckJob").VolatileInstructions.empty());
CHECK(Result.at("DeckJob").VolatileValidRanges.Empty() == false);
const std::vector<std::pair<uint64_t, uint64_t>> ValidRanges = {
{0x2000, 0x3000},
};
for (auto it : Result.at("DeckJob").VolatileValidRanges) {
CHECK(ContainsRange(std::make_pair(it.Offset, it.End), ValidRanges));
}
}
}