diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.cpp b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.cpp index 8112de09b6e6..b0d5a8b2266b 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.cpp @@ -133,6 +133,23 @@ std::wstring Environment::GetDllDirectoryValue() return expandedStr; } +ProcessorArchitecture Environment::GetCurrentProcessArchitecture() +{ + // Use compile-time detection - we know which architectures we support + // and this is the most reliable and efficient approach. IsWow64Process2 + // doesn't show the correct architecture when running under x64 emulation + // on ARM64. +#if defined(_M_ARM64) + return ProcessorArchitecture::ARM64; +#elif defined(_M_AMD64) + return ProcessorArchitecture::AMD64; +#elif defined(_M_IX86) + return ProcessorArchitecture::x86; +#else + static_assert(false, "Unknown target architecture"); +#endif +} + bool Environment::IsRunning64BitProcess() { // Check the bitness of the currently running process diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.h index 9e3e1b1bf772..a9e6e85d9ecc 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.h @@ -5,6 +5,7 @@ #include #include +#include "ProcessorArchitecture.h" class Environment { @@ -23,6 +24,8 @@ class Environment static bool IsRunning64BitProcess(); static + ProcessorArchitecture GetCurrentProcessArchitecture(); + static HRESULT CopyToDirectory(const std::wstring& source, const std::filesystem::path& destination, bool cleanDest, const std::filesystem::path& directoryToIgnore, int& copiedFileCount); static bool CheckUpToDate(const std::wstring& source, const std::filesystem::path& destination, const std::wstring& extension, const std::filesystem::path& directoryToIgnore); diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.cpp b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.cpp index 8233d68a115e..8fc74b47c993 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.cpp @@ -411,7 +411,6 @@ HostFxrResolver::InvokeWhereToFindDotnet() HandleWrapper hThread; CComBSTR pwzDotnetName = nullptr; DWORD dwFilePointer = 0; - BOOL fIsCurrentProcess64Bit = FALSE; DWORD dwExitCode = 0; STRU struDotnetSubstring; STRU struDotnetLocationsString; @@ -426,6 +425,7 @@ HostFxrResolver::InvokeWhereToFindDotnet() securityAttributes.bInheritHandle = TRUE; LOG_INFO(L"Invoking where.exe to find dotnet.exe"); + auto currentProcessArch = Environment::GetCurrentProcessArchitecture(); // Create a read/write pipe that will be used for reading the result of where.exe FINISHED_LAST_ERROR_IF(!CreatePipe(&hStdOutReadPipe, &hStdOutWritePipe, &securityAttributes, 0)); @@ -499,13 +499,9 @@ HostFxrResolver::InvokeWhereToFindDotnet() } FINISHED_IF_FAILED(struDotnetLocationsString.CopyA(pzFileContents, dwNumBytesRead)); - LOG_INFOF(L"where.exe invocation returned: '%ls'", struDotnetLocationsString.QueryStr()); - fIsCurrentProcess64Bit = Environment::IsRunning64BitProcess(); - - LOG_INFOF(L"Current process bitness type detected as isX64=%d", fIsCurrentProcess64Bit); - + // Look for a dotnet.exe that matches the current process architecture while (TRUE) { index = struDotnetLocationsString.IndexOf(L"\r\n", prevIndex); @@ -518,28 +514,38 @@ HostFxrResolver::InvokeWhereToFindDotnet() // \r\n is two wchars, so add 2 here. prevIndex = index + 2; - LOG_INFOF(L"Processing entry '%ls'", struDotnetSubstring.QueryStr()); - - if (fIsCurrentProcess64Bit == IsX64(struDotnetSubstring.QueryStr())) + ProcessorArchitecture dotnetArch = GetFileProcessorArchitecture(struDotnetSubstring.QueryStr()); + if (dotnetArch == currentProcessArch) { - // The bitness of dotnet matched with the current worker process bitness. + LOG_INFOF(L"Found dotnet.exe matching current process architecture (%ls) '%ls'", + ProcessorArchitectureToString(dotnetArch), + struDotnetSubstring.QueryStr()); + return std::make_optional(struDotnetSubstring.QueryStr()); } + else + { + LOG_INFOF(L"Skipping dotnet.exe with non-matching architecture %ls (need %ls). '%ls'", + ProcessorArchitectureToString(dotnetArch), + ProcessorArchitectureToString(currentProcessArch), + struDotnetSubstring.QueryStr()); + } } Finished: return result; } -BOOL HostFxrResolver::IsX64(const WCHAR* dotnetPath) +// Reads the PE header of the binary to determine its architecture. +ProcessorArchitecture HostFxrResolver::GetFileProcessorArchitecture(const WCHAR* binaryPath) { // Errors while reading from the file shouldn't throw unless // file.exception(bits) is set - std::ifstream file(dotnetPath, std::ios::binary); + std::ifstream file(binaryPath, std::ios::binary); if (!file.is_open()) { - LOG_TRACEF(L"Failed to open file %ls", dotnetPath); - return false; + LOG_TRACEF(L"Failed to open file %ls", binaryPath); + return ProcessorArchitecture::Unknown; } // Read the DOS header @@ -547,8 +553,8 @@ BOOL HostFxrResolver::IsX64(const WCHAR* dotnetPath) file.read(reinterpret_cast(&dosHeader), sizeof(dosHeader)); if (dosHeader.e_magic != IMAGE_DOS_SIGNATURE) // 'MZ' { - LOG_TRACEF(L"%ls is not a valid executable file (missing MZ header).", dotnetPath); - return false; + LOG_TRACEF(L"%ls is not a valid executable file (missing MZ header).", binaryPath); + return ProcessorArchitecture::Unknown; } // Seek to the PE header @@ -559,32 +565,30 @@ BOOL HostFxrResolver::IsX64(const WCHAR* dotnetPath) file.read(reinterpret_cast(&peSignature), sizeof(peSignature)); if (peSignature != IMAGE_NT_SIGNATURE) // 'PE\0\0' { - LOG_TRACEF(L"%ls is not a valid PE file (missing PE header).", dotnetPath); - return false; + LOG_TRACEF(L"%ls is not a valid PE file (missing PE header).", binaryPath); + return ProcessorArchitecture::Unknown; } // Read the file header IMAGE_FILE_HEADER fileHeader{}; file.read(reinterpret_cast(&fileHeader), sizeof(fileHeader)); - // Read the optional header magic field - WORD magic{}; - file.read(reinterpret_cast(&magic), sizeof(magic)); - - // Determine the architecture based on the magic value - if (magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) + // Determine the architecture based on the machine type + switch (fileHeader.Machine) { - LOG_INFOF(L"%ls is 32-bit", dotnetPath); - return false; - } - else if (magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) - { - LOG_INFOF(L"%ls is 64-bit", dotnetPath); - return true; + case IMAGE_FILE_MACHINE_I386: + LOG_INFOF(L"%ls is x86 (32-bit)", binaryPath); + return ProcessorArchitecture::x86; + case IMAGE_FILE_MACHINE_AMD64: + LOG_INFOF(L"%ls is AMD64 (x64)", binaryPath); + return ProcessorArchitecture::AMD64; + case IMAGE_FILE_MACHINE_ARM64: + LOG_INFOF(L"%ls is ARM64", binaryPath); + return ProcessorArchitecture::ARM64; + default: + LOG_INFOF(L"%ls has unknown architecture (machine type: 0x%X)", binaryPath, fileHeader.Machine); + return ProcessorArchitecture::Unknown; } - - LOG_INFOF(L"%ls is unknown architecture %i", dotnetPath, fileHeader.Machine); - return false; } std::optional diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.h index 08ec650aec54..9065e2aecd2b 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.h @@ -8,8 +8,8 @@ #include #include #include - #include "ErrorContext.h" +#include "ProcessorArchitecture.h" #define READ_BUFFER_SIZE 4096 @@ -74,7 +74,7 @@ class HostFxrResolver const std::filesystem::path & requestedPath ); - static BOOL IsX64(const WCHAR* dotnetPath); + static ProcessorArchitecture GetFileProcessorArchitecture(const WCHAR* binaryPath); struct LocalFreeDeleter { diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/ProcessorArchitecture.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/ProcessorArchitecture.h new file mode 100644 index 000000000000..195feddcae7b --- /dev/null +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/ProcessorArchitecture.h @@ -0,0 +1,28 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +enum class ProcessorArchitecture +{ + Unknown, + x86, + AMD64, + ARM64 +}; + +inline const wchar_t* ProcessorArchitectureToString(ProcessorArchitecture arch) +{ + switch (arch) + { + case ProcessorArchitecture::x86: + return L"x86"; + case ProcessorArchitecture::AMD64: + return L"AMD64"; + case ProcessorArchitecture::ARM64: + return L"ARM64"; + case ProcessorArchitecture::Unknown: + default: + return L"Unknown"; + } +} \ No newline at end of file