Skip to content

Commit

Permalink
Add support for buggy VMMs and older CPUs
Browse files Browse the repository at this point in the history
  • Loading branch information
VisualEhrmanntraut committed Jul 30, 2024
1 parent f5ee51f commit bd7f644
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 30 deletions.
112 changes: 83 additions & 29 deletions ForgedInvariant/ForgedInvariant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,19 @@ _Atomic(int) ForgedInvariantMain::threadsEngaged = ATOMIC_VAR_INIT(0);
_Atomic(UInt64) ForgedInvariantMain::targetTSC = ATOMIC_VAR_INIT(0);

constexpr UInt32 CPUID_LEAF7_TSC_ADJUST = getBit<UInt32>(1);
constexpr UInt32 CPUID_AMD_FAMILY_17H = 0x17;
constexpr UInt32 CPUID_INTEL_FAMILY_6H = 6;
constexpr UInt32 CPUID_INTEL_MODEL_PENRYN = 23;
constexpr UInt64 CPUID_FEATURE_HTT = getBit<UInt64>(28);

constexpr UInt32 MSR_TSC = 0x10;
constexpr UInt32 MSR_TSC_ADJUST = 0x3B;
constexpr UInt32 MSR_HWCR = 0xC0010015;
constexpr UInt64 MSR_HWCR_LOCK_TSC_TO_CURR_P0 = getBit<UInt64>(21);

constexpr UInt8 kIOPMTracePointSleepCPUs = 0x18;
constexpr UInt8 kIOPMTracePointWakePlatformActions = 0x22;

void ForgedInvariantMain::resetTscAdjust(void *) { wrmsr64(MSR_TSC_ADJUST, 0); }

void ForgedInvariantMain::lockTscFreqIfPossible() {
Expand Down Expand Up @@ -68,26 +76,26 @@ void ForgedInvariantMain::syncTsc() {
}

void ForgedInvariantMain::wrapXcpmUrgency(int urgency, UInt64 rtPeriod, UInt64 rtDeadline) {
// What are you so urgent for, if the clock is not working, huh??
// Maybe you should've used an actually reliable clock source
// instead of the number of cycles the CPU has done.
if (!atomic_load_explicit(&synchronised, memory_order_relaxed)) { return; }
FunctionCast(wrapXcpmUrgency, callback->orgXcpmUrgency)(urgency, rtPeriod, rtDeadline);
}

constexpr UInt8 kIOPMTracePointSleepCPUs = 0x18;
constexpr UInt8 kIOPMTracePointWakePlatformActions = 0x22;

void ForgedInvariantMain::wrapTracePoint(void *that, UInt8 point) {
switch (point) {
case kIOPMTracePointSleepCPUs: {
case kIOPMTracePointSleepCPUs: { // Those CPUs sure like to sleep.
atomic_store_explicit(&systemAwake, false, memory_order_relaxed);
atomic_store_explicit(&synchronised, false, memory_order_relaxed);
break;
}
case kIOPMTracePointWakePlatformActions: {
case kIOPMTracePointWakePlatformActions: { // Oh, now you want to wake up, you lazy turd?
atomic_store_explicit(&systemAwake, true, memory_order_relaxed);
callback->syncTsc();
break;
}
default: {
default: { // Don't care. Lol!
break;
}
}
Expand All @@ -114,56 +122,102 @@ void ForgedInvariantMain::processPatcher(KernelPatcher &patcher) {
}

void ForgedInvariantMain::init() {
UInt32 eax;
UInt32 ebx;
UInt32 ecx;
UInt32 edx;

SYSLOG("Main", "Copyright (c) 2024 ChefKiss. If you've paid for this, you've been scammed.");
callback = this;

// Monterey got a change in the task scheduling requiring the target TSC value
// to be in synchronise with the first thread and not the last one.
this->constants.newSyncMethod = getKernelVersion() >= KernelVersion::Monterey;
UInt32 reg = 0;
// CPUID Leaf 7 Count 0 Bit 1 defines whether a CPU supports TSC_ADJUST, according to the Intel SDM.
this->constants.resetTscAdjust =
this->constants.newSyncMethod && CPUInfo::getCpuid(7, 0, nullptr, &reg) && (reg & CPUID_LEAF7_TSC_ADJUST) != 0;
this->constants.newSyncMethod && CPUInfo::getCpuid(7, 0, nullptr, &ebx) && (ebx & CPUID_LEAF7_TSC_ADJUST) != 0;

switch (BaseDeviceInfo::get().cpuVendor) {
const BaseDeviceInfo &info = BaseDeviceInfo::get();
switch (info.cpuVendor) {
case CPUInfo::CpuVendor::Unknown: {
PANIC("Main", "CPU Vendor is unknown.");
PANIC("Main", "Who made your CPU? Black Mesa?");
break;
}
case CPUInfo::CpuVendor::AMD: {
// For AMD, we need to determine the thread count using an AMD-specific CPUID extension
reg = 0;
if (CPUInfo::getCpuid(0x80000008, 0, nullptr, nullptr, &reg)) {
// For AMD, we try to determine the thread count using an AMD-specific CPUID extension.
if (CPUInfo::getCpuid(0x80000008, 0, nullptr, nullptr, &ecx)) {
// Amount of threads that the system has are stored in bits 0..8
this->constants.threadCount = (reg & 0xFF) + 1;
this->constants.threadCount = (ecx & 0xFF) + 1;
} else {
SYSLOG("Main", "AMD-specific extension not supported, assuming the CPU is so old that it only has "
"1 thread...");
this->constants.threadCount = 1;
SYSLOG("Main", "AMD-specific extension not supported...");
}
reg = 0;
if (CPUInfo::getCpuid(1, 0, &reg)) {
CPUInfo::CpuVersion *ver = reinterpret_cast<CPUInfo::CpuVersion *>(&reg);
UInt32 family = ver->family == 0xF ? ver->family + ver->extendedFamily : ver->family;
this->constants.lockTSCFreqUsingHWCR = family >= 0x17;

// We must get the family manually on AMD because Acidanthera designs
// their software with the intent to make the life of AMD CPU users
// absolute hell and will therefore not fill in the extended CPUID
// information if the CPU is an AMD one. And yes, the logic is the same.
if (CPUInfo::getCpuid(1, 0, &eax)) {
const CPUInfo::CpuVersion *ver = reinterpret_cast<const CPUInfo::CpuVersion *>(&eax);
const UInt32 family = ver->family == 0xF ? (ver->family + ver->extendedFamily) : ver->family;
// The specific bit in the HWCR MSR is only available since 17h.
this->constants.lockTSCFreqUsingHWCR = family >= CPUID_AMD_FAMILY_17H;
} else {
SYSLOG("Main", "No CPUID leaf 1? [insert related megamind picture here]");
this->constants.lockTSCFreqUsingHWCR = false;
if (this->constants.threadCount == 0) {
SYSLOG("Main", "Setting thread count to 1 as both the CPUID leaf 1 and the AMD-specific extension "
"are not present!");
this->constants.threadCount = 1;
}
}
break;
}
case CPUInfo::CpuVendor::Intel: {
// bits 0..16 of this MSR contain the thread count, according to the Intel SDM.
this->constants.threadCount = rdmsr64(MSR_CORE_THREAD_COUNT) & 0xFFFF;
// On Intel, we try to determine the thread count using MSR_CORE_THREAD_COUNT
// Which is only available after Penryn, according to the XNU source code.
// The Intel SDM seems to disagree (?) and says it's available since Haswell-E.
// Thanks, very cool!
if (info.cpuFamily > CPUID_INTEL_FAMILY_6H ||
(info.cpuFamily == CPUID_INTEL_FAMILY_6H && info.cpuModel > CPUID_INTEL_MODEL_PENRYN)) {
// bits 0..16 of this MSR contain the thread count, according to the Intel SDM.
this->constants.threadCount = rdmsr64(MSR_CORE_THREAD_COUNT) & 0xFFFF;
} else {
SYSLOG("Main", "MSR_CORE_THREAD_COUNT not supported!");
}
break;
}
}

// Failed to get the thread count using modern methods,
// we must get it through CPUID.
if (this->constants.threadCount == 0) {
SYSLOG("Main", "Failed to get thread count via modern methods, using CPUID!");

if (CPUInfo::getCpuid(1, 0, nullptr, &ebx, &ecx, &edx)) {
const UInt64 features = (static_cast<UInt64>(ecx) << 32) | edx;
if (features & CPUID_FEATURE_HTT) {
// If the HTT feature is supported then ebc will contain the
// maximum APIC ID that's usable at 16..23
this->constants.threadCount = (ebx >> 16) & 0xFF;
} else {
// Damn... that's one ancient CPU huh?
// Or a buggy VMM
this->constants.threadCount = 1;
}

} else {
SYSLOG("Main", "No CPUID leaf 1? [insert related megamind picture here]");
this->constants.threadCount = 1;
}
}

this->constants.targetThread = this->constants.newSyncMethod ? 0 : (this->constants.threadCount - 1);

DBGLOG("Main", "Will use new synchronisation method? %s.", this->constants.newSyncMethod ? "Yes" : "No");
DBGLOG("Main", "Will reset TSC_ADJUST? %s.", this->constants.resetTscAdjust ? "Yes" : "No");
DBGLOG("Main", "Can use LockTscToCurrentP0? %s.", this->constants.lockTSCFreqUsingHWCR ? "Yes" : "No");
DBGLOG("Main", "System has %d threads.", this->constants.threadCount);
DBGLOG("Main", "Will fetch TSC value from thread %d.", this->constants.targetThread);
DBGLOG("Main", "Synchronisation method: %s.", this->constants.newSyncMethod ? "New" : "Old");
DBGLOG("Main", "TSC_ADJUST: %s.", this->constants.resetTscAdjust ? "Available" : "Unavailable");
DBGLOG("Main", "LockTscToCurrentP0: %s.", this->constants.lockTSCFreqUsingHWCR ? "Available" : "Unavailable");
DBGLOG("Main", "Thread count: %d.", this->constants.threadCount);
DBGLOG("Main", "Target thread: %d.", this->constants.targetThread);

lockTscFreqIfPossible();

Expand Down
1 change: 0 additions & 1 deletion ForgedInvariant/ForgedInvariant.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

#pragma once
#include <Headers/kern_patcher.hpp>
#include <stdatomic.h>

struct ForgedInvariantConstants {
bool newSyncMethod {false};
Expand Down

0 comments on commit bd7f644

Please sign in to comment.