diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.IsQemuDetected.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.IsQemuDetected.cs new file mode 100644 index 0000000000000..08a717736f735 --- /dev/null +++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.IsQemuDetected.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; +using System.Threading; + +internal static partial class Interop +{ + internal static partial class Sys + { + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_IsQemuDetected")] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool IsQemuDetectedImpl(); + + private static int s_isQemuDetected; + + internal static bool IsQemuDetected() + { + int isQemuDetected = Interlocked.CompareExchange(ref s_isQemuDetected, 0, 0); + if (isQemuDetected == 0) + { + isQemuDetected = IsQemuDetectedImpl() ? 1 : 2; + int oldValue = Interlocked.CompareExchange(ref s_isQemuDetected, isQemuDetected, 0); + if (oldValue != 0) // a different thread has managed to update the value + { + isQemuDetected = oldValue; + } + } + + return isQemuDetected == 1; + } + } +} diff --git a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.Unix.cs b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.Unix.cs index 1b4e6e3e0d184..b391cc88b51c9 100644 --- a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.Unix.cs +++ b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.Unix.cs @@ -31,6 +31,13 @@ public static partial class PlatformDetection public static bool IsMonoLinuxArm64 => IsMonoRuntime && IsLinux && IsArm64Process; public static bool IsNotMonoLinuxArm64 => !IsMonoLinuxArm64; + public static bool IsQemuDetected => +#if TARGET_WINDOWS || TARGET_BROWSER || TARGET_WASI + false; +#else + Interop.Sys.IsQemuDetected(); +#endif + // OSX family public static bool IsApplePlatform => IsOSX || IsiOS || IstvOS || IsMacCatalyst; public static bool IsOSX => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); diff --git a/src/libraries/Common/tests/TestUtilities/TestUtilities.csproj b/src/libraries/Common/tests/TestUtilities/TestUtilities.csproj index 1566f163697bc..58e97718c79ba 100644 --- a/src/libraries/Common/tests/TestUtilities/TestUtilities.csproj +++ b/src/libraries/Common/tests/TestUtilities/TestUtilities.csproj @@ -95,6 +95,8 @@ Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.OpenSslGetProtocolSupport.cs" /> + diff --git a/src/native/libs/System.Native/CMakeLists.txt b/src/native/libs/System.Native/CMakeLists.txt index 4cac1051f6739..fd716df561991 100644 --- a/src/native/libs/System.Native/CMakeLists.txt +++ b/src/native/libs/System.Native/CMakeLists.txt @@ -47,6 +47,7 @@ if (NOT CLR_CMAKE_TARGET_WASI) pal_signal.c pal_threading.c pal_uid.c + "${CLR_SRC_NATIVE_DIR}/minipal/cpufeatures.c" ) else() list (APPEND NATIVE_SOURCES diff --git a/src/native/libs/System.Native/entrypoints.c b/src/native/libs/System.Native/entrypoints.c index 51c761109159b..0a5b9437082e3 100644 --- a/src/native/libs/System.Native/entrypoints.c +++ b/src/native/libs/System.Native/entrypoints.c @@ -224,6 +224,7 @@ static const Entry s_sysNative[] = DllImportEntry(SystemNative_GetUnixRelease) DllImportEntry(SystemNative_GetUnixVersion) DllImportEntry(SystemNative_GetOSArchitecture) + DllImportEntry(SystemNative_IsQemuDetected) DllImportEntry(SystemNative_SearchPath) DllImportEntry(SystemNative_SearchPath_TempDirectory) DllImportEntry(SystemNative_RegisterForSigChld) diff --git a/src/native/libs/System.Native/pal_runtimeinformation.c b/src/native/libs/System.Native/pal_runtimeinformation.c index aa18e7000d148..e5b4d35638141 100644 --- a/src/native/libs/System.Native/pal_runtimeinformation.c +++ b/src/native/libs/System.Native/pal_runtimeinformation.c @@ -8,6 +8,7 @@ #include #include #include +#include #if defined(TARGET_ANDROID) #include #elif defined(TARGET_OSX) @@ -151,3 +152,12 @@ int32_t SystemNative_GetOSArchitecture(void) return result; #endif } + +int32_t SystemNative_IsQemuDetected(void) +{ +#ifdef TARGET_WASI + return 0; +#else + return minipal_detect_qemu() ? 1 : 0; +#endif +} diff --git a/src/native/libs/System.Native/pal_runtimeinformation.h b/src/native/libs/System.Native/pal_runtimeinformation.h index 5f6dc7ab8e2d9..e0105046d701a 100644 --- a/src/native/libs/System.Native/pal_runtimeinformation.h +++ b/src/native/libs/System.Native/pal_runtimeinformation.h @@ -11,3 +11,5 @@ PALEXPORT char* SystemNative_GetUnixRelease(void); PALEXPORT int32_t SystemNative_GetUnixVersion(char* version, int* capacity); PALEXPORT int32_t SystemNative_GetOSArchitecture(void); + +PALEXPORT int32_t SystemNative_IsQemuDetected(void); diff --git a/src/native/minipal/cpufeatures.c b/src/native/minipal/cpufeatures.c index 47327255e40e2..e55e3f8a51da6 100644 --- a/src/native/minipal/cpufeatures.c +++ b/src/native/minipal/cpufeatures.c @@ -11,7 +11,7 @@ #include "cpufeatures.h" #include "cpuid.h" -#if HOST_WINDOWS +#ifdef HOST_WINDOWS #include @@ -28,6 +28,11 @@ #include #include +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-macros" +#endif + // Light-up for hardware capabilities that are not present in older headers used by the portable build. #ifndef HWCAP_ASIMDRDM #define HWCAP_ASIMDRDM (1 << 12) @@ -45,6 +50,14 @@ #define HWCAP_SVE (1 << 22) #endif +#ifndef XSTATE_MASK_AVX512 +#define XSTATE_MASK_AVX512 (0xE0) /* 0b1110_0000 */ +#endif // XSTATE_MASK_AVX512 + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + #endif #if HAVE_SYSCTLBYNAME @@ -56,7 +69,7 @@ #if defined(HOST_UNIX) #if defined(HOST_X86) || defined(HOST_AMD64) -static uint32_t xmmYmmStateSupport() +static uint32_t xmmYmmStateSupport(void) { uint32_t eax; __asm(" xgetbv\n" \ @@ -68,11 +81,7 @@ static uint32_t xmmYmmStateSupport() return ((eax & 0x06) == 0x06) ? 1 : 0; } -#ifndef XSTATE_MASK_AVX512 -#define XSTATE_MASK_AVX512 (0xE0) /* 0b1110_0000 */ -#endif // XSTATE_MASK_AVX512 - -static uint32_t avx512StateSupport() +static uint32_t avx512StateSupport(void) { #if defined(HOST_APPLE) // MacOS has specialized behavior where it reports AVX512 support but doesnt @@ -99,12 +108,12 @@ static uint32_t avx512StateSupport() #endif } -static bool IsAvxEnabled() +static bool IsAvxEnabled(void) { return true; } -static bool IsAvx512Enabled() +static bool IsAvx512Enabled(void) { return true; } @@ -222,11 +231,11 @@ int minipal_getcpufeatures(void) if (IsAvx512Enabled() && (avx512StateSupport() == 1)) // XGETBV XRC0[7:5] == 111 { - if (((cpuidInfo[CPUID_EBX] & (1 << 16)) != 0) && // AVX512F - ((cpuidInfo[CPUID_EBX] & (1 << 30)) != 0) && // AVX512BW - ((cpuidInfo[CPUID_EBX] & (1 << 28)) != 0) && // AVX512CD - ((cpuidInfo[CPUID_EBX] & (1 << 17)) != 0) && // AVX512DQ - ((cpuidInfo[CPUID_EBX] & (1 << 31)) != 0)) // AVX512VL + if ((((uint32_t)cpuidInfo[CPUID_EBX] & ((uint32_t)1 << 16)) != 0) && // AVX512F + (((uint32_t)cpuidInfo[CPUID_EBX] & ((uint32_t)1 << 30)) != 0) && // AVX512BW + (((uint32_t)cpuidInfo[CPUID_EBX] & ((uint32_t)1 << 28)) != 0) && // AVX512CD + (((uint32_t)cpuidInfo[CPUID_EBX] & ((uint32_t)1 << 17)) != 0) && // AVX512DQ + (((uint32_t)cpuidInfo[CPUID_EBX] & ((uint32_t)1 << 31)) != 0)) // AVX512VL { // While the AVX-512 ISAs can be individually lit-up, they really // need F, BW, CD, DQ, and VL to be fully functional without adding @@ -301,12 +310,12 @@ int minipal_getcpufeatures(void) } } - __cpuid(cpuidInfo, 0x80000000); + __cpuid(cpuidInfo, (int)0x80000000); uint32_t maxCpuIdEx = (uint32_t)cpuidInfo[CPUID_EAX]; if (maxCpuIdEx >= 0x80000001) { - __cpuid(cpuidInfo, 0x80000001); + __cpuid(cpuidInfo, (int)0x80000001); if ((cpuidInfo[CPUID_ECX] & (1 << 5)) != 0) // LZCNT { @@ -441,35 +450,90 @@ int minipal_getcpufeatures(void) return result; } -// Detect if the current process is running under the Apple Rosetta x64 emulator -bool minipal_detect_rosetta(void) +static bool GetCpuBrand(char* brand, size_t bufferSize) { #if defined(HOST_AMD64) || defined(HOST_X86) // Check for CPU brand indicating emulation int regs[4]; - char brand[49]; // Get the maximum value for extended function CPUID info __cpuid(regs, (int)0x80000000); if ((unsigned int)regs[0] < 0x80000004) { - return false; // Extended CPUID not supported + brand[0] = '\0'; // Extended CPUID not supported, return empty string or handle error + return false; } - // Retrieve the CPU brand string + // Retrieve the CPU brand string directly into the caller-provided buffer for (unsigned int i = 0x80000002; i <= 0x80000004; ++i) { __cpuid(regs, (int)i); memcpy(brand + (i - 0x80000002) * sizeof(regs), regs, sizeof(regs)); } - brand[sizeof(brand) - 1] = '\0'; + + brand[bufferSize - 1] = '\0'; + + return true; +#else + (void)brand; + (void)bufferSize; + return false; +#endif // HOST_AMD64 || HOST_X86 +} + +// Detect if the current process is running under the Apple Rosetta x64 emulator +bool minipal_detect_rosetta(void) +{ + char brand[49]; + + // Check if CPU brand indicates emulation + return GetCpuBrand(brand, sizeof(brand)) && (strstr(brand, "VirtualApple") != NULL); +} + +#if !defined(HOST_WINDOWS) + +// Detect if the current process is running under QEMU +bool minipal_detect_qemu(void) +{ + char brand[49]; // Check if CPU brand indicates emulation - if (strstr(brand, "VirtualApple") != NULL) + if (GetCpuBrand(brand, sizeof(brand)) && strstr(brand, "QEMU") != NULL) { return true; } -#endif // HOST_AMD64 || HOST_X86 + + // Check for process name of PID 1 indicating emulation + char cmdline[256]; + FILE *cmdline_file = fopen("/proc/1/cmdline", "r"); + if (cmdline_file != NULL) + { + fgets(cmdline, sizeof(cmdline), cmdline_file); + fclose(cmdline_file); + + if (strstr(cmdline, "qemu-") != NULL) + { + return true; + } + } + + // Check for process-level emulation using ps command + FILE *ps_output = popen("ps -e -o comm=", "r"); + if (ps_output != NULL) + { + char process_name[256]; + while (fgets(process_name, sizeof(process_name), ps_output) != NULL) + { + if (strstr(process_name, "qemu") != NULL) + { + pclose(ps_output); + return true; + } + } + pclose(ps_output); + } return false; } + +#endif // !HOST_WINDOWS diff --git a/src/native/minipal/cpufeatures.h b/src/native/minipal/cpufeatures.h index 52527a4565036..f0222e4a98fd0 100644 --- a/src/native/minipal/cpufeatures.h +++ b/src/native/minipal/cpufeatures.h @@ -66,6 +66,7 @@ extern "C" int minipal_getcpufeatures(void); bool minipal_detect_rosetta(void); +bool minipal_detect_qemu(void); #ifdef __cplusplus }