| File: net462\System\ProcessHelper.cs | Web Access |
| Project: src\vstest\src\Microsoft.TestPlatform.PlatformAbstractions\Microsoft.TestPlatform.PlatformAbstractions.csproj (Microsoft.TestPlatform.PlatformAbstractions) |
// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.ComponentModel; using System.Diagnostics; using System.IO; # if NETCOREAPP || NETSTANDARD2_0_OR_GREATER using System.Runtime.InteropServices; #endif using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces; namespace Microsoft.VisualStudio.TestPlatform.PlatformAbstractions; public partial class ProcessHelper : IProcessHelper { #if NETFRAMEWORK || NETSTANDARD2_0_OR_GREATER private PlatformArchitecture? _currentProcessArchitecture; /// <inheritdoc/> public string GetCurrentProcessLocation() => Path.GetDirectoryName(GetCurrentProcessFileName()); /// <inheritdoc/> public nint GetProcessHandle(int processId) => processId == _currentProcess.Id ? _currentProcess.Handle : Process.GetProcessById(processId).Handle; /// <inheritdoc/> public PlatformArchitecture GetCurrentProcessArchitecture() { // If we already cached the current process architecture, no need to figure it out again. if (_currentProcessArchitecture is not null) { return _currentProcessArchitecture.Value; } // When this is current process, we can just check if IntPointer size to get if we are 64-bit or 32-bit. // When it is 32-bit we can just return, if it is 64-bit we need to clarify if x64 or arm64. if (IntPtr.Size == 4) { _currentProcessArchitecture = PlatformArchitecture.X86; return _currentProcessArchitecture.Value; } _currentProcessArchitecture ??= GetProcessArchitecture(_currentProcess.Id); return _currentProcessArchitecture.Value; } #endif public PlatformArchitecture GetProcessArchitecture(int processId) { #if !NETFRAMEWORK if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { // No implementation for this for cross platform, and we cannot move this to platform specific file. // Usages are only from hang dumper. throw new NotImplementedException(); } #endif // If the current process is 64-bit, or this is any remote process, we need to query it via native api. var process = processId == _currentProcess.Id ? _currentProcess : Process.GetProcessById(processId); try { if (!NativeMethods.IsWow64Process2(process.Handle, out ushort processMachine, out ushort nativeMachine)) { throw new Win32Exception(); } if (processMachine != NativeMethods.IMAGE_FILE_MACHINE_UNKNOWN) { // The process is running using WOW64, which suggests it is 32-bit (or any of the other machines, that we cannot // handle, so we just assume x86). return PlatformArchitecture.X86; } // If processMachine is IMAGE_FILE_MACHINE_UNKNOWN mean that we're not running using WOW64 emulation. // If nativeMachine is IMAGE_FILE_MACHINE_ARM64 mean that we're running on ARM64 architecture device. if (processMachine == NativeMethods.IMAGE_FILE_MACHINE_UNKNOWN && nativeMachine == NativeMethods.IMAGE_FILE_MACHINE_ARM64) { // To distinguish between ARM64 and x64 emulated on ARM64 we check the PE header of the current running executable. if (IsArm64Executable(process.MainModule!.FileName)) { return PlatformArchitecture.ARM64; } else { return PlatformArchitecture.X64; } } else { return PlatformArchitecture.X64; } } catch { // At the moment we cannot log messages inside the Microsoft.TestPlatform.PlatformAbstractions. // We did an attempt in https://github.com/microsoft/vstest/pull/3422 - 17.2.0-preview-20220301-01 - but we reverted after // because we broke a scenario where for .NET Framework application inside the test host // we loaded runner version of Microsoft.TestPlatform.PlatformAbstractions but newer version Microsoft.TestPlatform.ObjectModel(the one close // to the test container) and the old PlatformAbstractions doesn't contain the methods expected by the new ObjectModel throwing // a MissedMethodException. if (!Environment.Is64BitOperatingSystem) { // When we know this is not 64-bit operating system, then all processes are running as 32-bit, both // the current process and other processes. return PlatformArchitecture.X86; } try { var isWow64Process = NativeMethods.IsWow64Process(process.Handle, out var isWow64); if (!isWow64Process) { // Do nothing we cannot log errors here. } // The process is running using WOW64, which suggests it is 32-bit (or any of the other machines, that we cannot // handle, so we just assume x86). If it is not wow, we assume x64, because we failed the call to more advanced api // that can tell us if this is arm64, so we are probably on older version of OS which is x64. // We could call PlatformArchitecture.Architecture, but that uses the same api that we just failed to invoke. return isWow64 ? PlatformArchitecture.X86 : PlatformArchitecture.X64; } catch { // We are on 64-bit system, let's assume x64 when we fail to determine the value. return PlatformArchitecture.X64; } } } private static bool IsArm64Executable(string path) { // This document specifies the structure of executable (image) files // https://docs.microsoft.com/windows/win32/debug/pe-format#general-concepts using Stream fs = new FileStream(path, FileMode.Open, FileAccess.Read); using BinaryReader reader = new(fs); // https://docs.microsoft.com/windows/win32/debug/pe-format#ms-dos-stub-image-only // At location 0x3c, the stub has the file offset to the PE signature. fs.Position = 0x3C; var peHeader = reader.ReadUInt32(); // Check if the offset is invalid if (peHeader > fs.Length - 5) { return false; } // https://docs.microsoft.com/windows/win32/debug/pe-format#signature-image-only // Moving to the PE Header start location. fs.Position = peHeader; // After the MS-DOS stub, at the file offset specified at offset 0x3c, is a 4-byte signature that identifies the file as a PE format image file. // This signature is "PE\0\0" (the letters "P" and "E" followed by two null bytes). uint signature = reader.ReadUInt32(); if (signature != 0x00004550) { return false; } // https://docs.microsoft.com/windows/win32/debug/pe-format#coff-file-header-object-and-image // At the beginning of an object file, or immediately after the signature of an image file, is a standard COFF file header. var machine = reader.ReadUInt16(); reader.ReadUInt16(); //NumberOfSections reader.ReadUInt32(); //TimeDateStamp reader.ReadUInt32(); //PointerToSymbolTable reader.ReadUInt32(); //NumberOfSymbols reader.ReadUInt16(); //SizeOfOptionalHeader reader.ReadUInt16(); //Characteristics // https://docs.microsoft.com/windows/win32/debug/pe-format#optional-header-image-only ushort magic = reader.ReadUInt16(); return magic is 0x010B or 0x020B && machine == NativeMethods.IMAGE_FILE_MACHINE_ARM64; } }