Skip to content

Commit 5fb10e1

Browse files
author
Requiem
committed
Addressed critical performance bottlenecks
1 parent 54b2c39 commit 5fb10e1

2 files changed

Lines changed: 228 additions & 149 deletions

File tree

src/vmaware.hpp

Lines changed: 111 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@
3131
* - struct for internal cpu operations => line 742
3232
* - struct for internal memoization => line 1213
3333
* - struct for internal utility functions => line 1337
34-
* - struct for internal core components => line 10026
35-
* - start of VM detection technique list => line 2338
36-
* - start of public VM detection functions => line 10690
37-
* - start of externally defined variables => line 11638
34+
* - struct for internal core components => line 10065
35+
* - start of VM detection technique list => line 2361
36+
* - start of public VM detection functions => line 10729
37+
* - start of externally defined variables => line 11675
3838
*
3939
*
4040
* ============================== EXAMPLE ===================================
@@ -1408,7 +1408,7 @@ struct VM {
14081408
#endif
14091409
}
14101410

1411-
#if defined(WINDOWS) && (defined(UNICODE) || defined(_UNICODE))
1411+
#if (WINDOWS) && (defined(UNICODE) || defined(_UNICODE))
14121412
// handle TCHAR conversion
14131413
[[nodiscard]] static bool exists(const TCHAR* path) {
14141414
char c_szText[_MAX_PATH]{};
@@ -1743,7 +1743,7 @@ struct VM {
17431743
#endif
17441744
}
17451745

1746-
// et available memory space
1746+
// get available memory space
17471747
[[nodiscard]] static u64 get_memory_space() {
17481748
#if (WINDOWS)
17491749
MEMORYSTATUSEX statex = { 0 };
@@ -1872,6 +1872,30 @@ struct VM {
18721872
#endif
18731873
}
18741874

1875+
// Returns a list of running process names
1876+
[[nodiscard]] static std::unordered_set<std::string> get_running_process_names() {
1877+
std::unordered_set<std::string> processNames;
1878+
DWORD processes[1024], bytesReturned;
1879+
1880+
if (!K32EnumProcesses(processes, sizeof(processes), &bytesReturned)) {
1881+
return processNames;
1882+
}
1883+
1884+
DWORD numProcesses = bytesReturned / sizeof(DWORD);
1885+
char processName[MAX_PATH];
1886+
1887+
for (DWORD i = 0; i < numProcesses; ++i) {
1888+
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processes[i]);
1889+
if (hProcess != nullptr) {
1890+
if (K32GetModuleBaseNameA(hProcess, nullptr, processName, sizeof(processName))) {
1891+
processNames.insert(processName);
1892+
}
1893+
CloseHandle(hProcess);
1894+
}
1895+
}
1896+
return processNames;
1897+
}
1898+
18751899
// Retrieves the computer name
18761900
[[nodiscard]] static std::string get_hostname() {
18771901
#if (WINDOWS)
@@ -1892,13 +1916,12 @@ struct VM {
18921916
return std::string();
18931917
}
18941918

1895-
18961919
/**
18971920
* @brief Checks whether the system is running in a Hyper-V virtual machine or if the host system has Hyper-V enabled
18981921
* @note Hyper-V's presence on a host system can set certain hypervisor-related CPU flags that may appear similar to those in a virtualized environment, which can make it challenging to differentiate between an actual Hyper-V virtual machine (VM) and a host system with Hyper-V enabled.
18991922
* This can lead to false conclusions, where the system might mistakenly be identified as running in a Hyper-V VM, when in reality, it's simply the host system with Hyper-V features active.
19001923
* This check aims to distinguish between these two cases by identifying specific CPU flags and hypervisor-related artifacts that are indicative of a Hyper-V VM rather than a host system with Hyper-V enabled.
1901-
* @author idea by Requiem (https://github.com/NotRequiem)
1924+
* @author Requiem (https://github.com/NotRequiem)
19021925
* @returns hyperx_state enum indicating the detected state:
19031926
* - HYPERV_ARTIFACT_VM for host with Hyper-V enabled
19041927
* - HYPERV_REAL_VM for real Hyper-V VM
@@ -2482,7 +2505,7 @@ struct VM {
24822505

24832506
/**
24842507
* @brief Check if mac address starts with certain VM designated values
2485-
* @category All systems (I think)
2508+
* @category Linux and Windows
24862509
* @implements VM::MAC
24872510
*/
24882511
[[nodiscard]] static bool mac_address_check() {
@@ -2532,30 +2555,19 @@ struct VM {
25322555
debug("MAC: ", "not successful");
25332556
}
25342557
#elif (WINDOWS)
2535-
PIP_ADAPTER_INFO AdapterInfo;
2536-
DWORD dwBufLen = sizeof(IP_ADAPTER_INFO);
2537-
2538-
AdapterInfo = (IP_ADAPTER_INFO*)std::malloc(sizeof(IP_ADAPTER_INFO));
2539-
2540-
if (AdapterInfo == NULL) {
2558+
DWORD dwBufLen = 0;
2559+
if (GetAdaptersInfo(nullptr, &dwBufLen) != ERROR_BUFFER_OVERFLOW) {
25412560
return false;
25422561
}
25432562

2544-
if (GetAdaptersInfo(AdapterInfo, &dwBufLen) == ERROR_BUFFER_OVERFLOW) {
2545-
std::free(AdapterInfo);
2546-
AdapterInfo = (IP_ADAPTER_INFO*)std::malloc(dwBufLen);
2547-
if (AdapterInfo == NULL) {
2548-
return false;
2549-
}
2563+
PIP_ADAPTER_INFO AdapterInfo = (PIP_ADAPTER_INFO)std::malloc(dwBufLen);
2564+
if (AdapterInfo == nullptr) {
2565+
return false;
25502566
}
25512567

25522568
if (GetAdaptersInfo(AdapterInfo, &dwBufLen) == NO_ERROR) {
2553-
PIP_ADAPTER_INFO pAdapterInfo = AdapterInfo;
2554-
for (std::size_t i = 0; i < 6; i++) {
2555-
mac[i] = pAdapterInfo->Address[i];
2556-
}
2569+
std::memcpy(mac, AdapterInfo->Address, sizeof(mac));
25572570
}
2558-
25592571
std::free(AdapterInfo);
25602572
#else
25612573
return false;
@@ -2574,32 +2586,28 @@ struct VM {
25742586
*/
25752587
debug("MAC: ", ss.str());
25762588
#endif
2577-
25782589
// better expression to fix code duplication
2579-
auto compare = [=](const u8 mac1, const u8 mac2, const u8 mac3) noexcept -> bool {
2590+
auto compare = [mac](u8 mac1, u8 mac2, u8 mac3) noexcept -> bool {
25802591
return (mac[0] == mac1 && mac[1] == mac2 && mac[2] == mac3);
2581-
};
2592+
};
25822593

2583-
if (compare(0x08, 0x00, 0x27)) {
2594+
// Check for known virtualization MAC address prefixes
2595+
if (compare(0x08, 0x00, 0x27))
25842596
return core::add(brands::VBOX);
2585-
}
25862597

2587-
if (
2588-
(compare(0x00, 0x0C, 0x29)) ||
2589-
(compare(0x00, 0x1C, 0x14)) ||
2590-
(compare(0x00, 0x50, 0x56)) ||
2591-
(compare(0x00, 0x05, 0x69))
2592-
) {
2598+
if (compare(0x00, 0x0C, 0x29) ||
2599+
compare(0x00, 0x1C, 0x14) ||
2600+
compare(0x00, 0x50, 0x56) ||
2601+
compare(0x00, 0x05, 0x69))
2602+
{
25932603
return core::add(brands::VMWARE);
25942604
}
25952605

2596-
if (compare(0x00, 0x16, 0xE3)) {
2606+
if (compare(0x00, 0x16, 0xE3))
25972607
return core::add(brands::XEN);
2598-
}
25992608

2600-
if (compare(0x00, 0x1C, 0x42)) {
2609+
if (compare(0x00, 0x1C, 0x42))
26012610
return core::add(brands::PARALLELS);
2602-
}
26032611

26042612
/*
26052613
see https://github.com/kernelwernel/VMAware/issues/105
@@ -3396,17 +3404,14 @@ struct VM {
33963404
#if (!WINDOWS)
33973405
return false;
33983406
#else
3399-
DWORD pnsize = 0x1000;
3400-
char provider[0x1000];
3407+
char provider[256];
3408+
DWORD pnsize = sizeof(provider);
3409+
const DWORD retv = WNetGetProviderNameA(WNNC_NET_RDR2SAMPLE, provider, &pnsize);
34013410

3402-
DWORD retv = WNetGetProviderNameA(WNNC_NET_RDR2SAMPLE, provider, &pnsize);
3403-
bool result = false;
3404-
3405-
if (retv == NO_ERROR) {
3406-
result = (strcmp(provider, "VirtualBox Shared Folders") == 0);
3407-
}
3411+
if (retv != NO_ERROR)
3412+
return false;
34083413

3409-
return result;
3414+
return (strncmp(provider, "VirtualBox Shared Folders", 26) == 0);
34103415
#endif
34113416
}
34123417

@@ -3651,41 +3656,43 @@ struct VM {
36513656
#if (!WINDOWS)
36523657
return false;
36533658
#else
3654-
if (util::is_proc_running("joeboxserver.exe") || util::is_proc_running("joeboxcontrol.exe")) {
3659+
const auto runningProcesses = util::get_running_process_names();
3660+
3661+
if (runningProcesses.count("joeboxserver.exe") || runningProcesses.count("joeboxcontrol.exe")) {
36553662
debug("VM_PROCESSES: Detected JoeBox process.");
36563663
return core::add(brands::JOEBOX);
36573664
}
36583665

3659-
if (util::is_proc_running("prl_cc.exe") || util::is_proc_running("prl_tools.exe")) {
3666+
if (runningProcesses.count("prl_cc.exe") || runningProcesses.count("prl_tools.exe")) {
36603667
debug("VM_PROCESSES: Detected Parallels process.");
36613668
return core::add(brands::PARALLELS);
36623669
}
36633670

3664-
if (util::is_proc_running("vboxservice.exe") || util::is_proc_running("vboxtray.exe")) {
3671+
if (runningProcesses.count("vboxservice.exe") || runningProcesses.count("vboxtray.exe")) {
36653672
debug("VM_PROCESSES: Detected VBox process.");
36663673
return core::add(brands::VBOX);
36673674
}
36683675

3669-
if (util::is_proc_running("vmsrvc.exe") || util::is_proc_running("vmusrvc.exe")) {
3676+
if (runningProcesses.count("vmsrvc.exe") || runningProcesses.count("vmusrvc.exe")) {
36703677
debug("VM_PROCESSES: Detected VPC process.");
36713678
return core::add(brands::VPC);
36723679
}
36733680

3674-
if (util::is_proc_running("xenservice.exe") || util::is_proc_running("xsvc_depriv.exe")) {
3681+
if (runningProcesses.count("xenservice.exe") || runningProcesses.count("xsvc_depriv.exe")) {
36753682
debug("VM_PROCESSES: Detected Xen process.");
36763683
return core::add(brands::XEN);
36773684
}
36783685

3679-
if (util::is_proc_running("vm3dservice.exe") ||
3680-
util::is_proc_running("VGAuthService.exe") ||
3681-
util::is_proc_running("vmtoolsd.exe")) {
3686+
if (runningProcesses.count("vm3dservice.exe") ||
3687+
runningProcesses.count("VGAuthService.exe") ||
3688+
runningProcesses.count("vmtoolsd.exe")) {
36823689
debug("VM_PROCESSES: Detected VMware process.");
36833690
return core::add(brands::VMWARE);
36843691
}
36853692

3686-
if (util::is_proc_running("vdagent.exe") ||
3687-
util::is_proc_running("vdservice.exe") ||
3688-
util::is_proc_running("qemuwmi.exe")) {
3693+
if (runningProcesses.count("vdagent.exe") ||
3694+
runningProcesses.count("vdservice.exe") ||
3695+
runningProcesses.count("qemuwmi.exe")) {
36893696
debug("VM_PROCESSES: Detected QEMU process.");
36903697
return core::add(brands::QEMU);
36913698
}
@@ -7893,6 +7900,33 @@ struct VM {
78937900
#ifdef __VMAWARE_DEBUG__
78947901
u64 totalCycles = 0;
78957902
#endif
7903+
char* flushBuffer = nullptr; // avoiding volatile on purpose
7904+
constexpr size_t kAlignment = 64;
7905+
constexpr size_t kBufferSize = static_cast<size_t>(64 * 1024) * 1024;
7906+
7907+
#if (WINDOWS)
7908+
#define COMPILER_BARRIER() _ReadWriteBarrier()
7909+
#else
7910+
#define COMPILER_BARRIER() __asm__ __volatile__("" ::: "memory")
7911+
#endif
7912+
7913+
#if (WINDOWS)
7914+
flushBuffer = (char*)_aligned_malloc(kBufferSize, kAlignment);
7915+
if (!flushBuffer) {
7916+
flushBuffer = new (std::nothrow) char[kBufferSize];
7917+
}
7918+
#elif (LINUX || APPLE)
7919+
int err = posix_memalign((void**)&flushBuffer, kAlignment, kBufferSize);
7920+
if (err != 0 || !flushBuffer) {
7921+
flushBuffer = new (std::nothrow) char[kBufferSize];
7922+
}
7923+
#else
7924+
// volatile char* flushBuffer = new volatile char[kBufferSize];
7925+
flushBuffer = new (std::nothrow) char[kBufferSize];
7926+
#endif
7927+
// Define a rotation scheme over segments. Here, we split the buffer into a number of segments
7928+
constexpr size_t segmentsCount = 8; // basically 1/8 of the buffer per iteration
7929+
constexpr size_t segmentSize = kBufferSize / segmentsCount;
78967930
int spikeCount = 0;
78977931
for (int i = 0; i < classicIterations; i++) {
78987932
u64 start = __rdtsc();
@@ -7913,21 +7947,27 @@ struct VM {
79137947
if (cycles >= classicThreshold) {
79147948
spikeCount++;
79157949
}
7916-
// to induce cache flushing
7917-
constexpr size_t bufferSize = static_cast<size_t>(64 * 1024) * 1024;
7918-
volatile char* flushBuffer = new volatile char[bufferSize];
7950+
// Instead of flushing the entire buffer every iteration (which would decrease performance a lot),
7951+
// flush only one segment per iteration
7952+
size_t segmentIndex = i % segmentsCount;
7953+
size_t offsetStart = segmentIndex * segmentSize;
7954+
size_t offsetEnd = offsetStart + segmentSize;
79197955

7920-
// better than thread sleeps
7921-
for (size_t j = 0; j < bufferSize; j += 64) {
7956+
// this detection works better when inducing cache flushing without thread sleeps
7957+
for (size_t j = offsetStart; j < offsetEnd; j += 64) {
79227958
flushBuffer[j] = static_cast<char>(j);
7923-
#if (x86 && (GCC || CLANG || MSVC))
7924-
_mm_clflush(const_cast<const void*>(
7925-
reinterpret_cast<const volatile void*>(&flushBuffer[j])));
7959+
#if defined(x86) && (defined(GCC) || defined(CLANG) || defined(MSVC))
7960+
COMPILER_BARRIER();
7961+
// _mm_clflushopt not available on some systems
7962+
_mm_clflush(reinterpret_cast<const void*>(&flushBuffer[j]));
79267963
#endif
79277964
}
7928-
7929-
delete[] flushBuffer;
79307965
}
7966+
#if (WINDOWS)
7967+
_aligned_free((void*)flushBuffer);
7968+
#else
7969+
free((void*)flushBuffer);
7970+
#endif
79317971

79327972
#ifdef __VMAWARE_DEBUG__
79337973
const double averageCycles = static_cast<double>(totalCycles) / classicIterations;

0 commit comments

Comments
 (0)