Reputation: 6986
Given that WMI is Windows only and the absence of a registry in operating systems such as Linux and Mac, how could one obtain processor name in .NET Core?
I intend to make the following method (which uses registry) cross platform:
private static string GetProcessorName()
{
var key = Registry.LocalMachine.OpenSubKey(@"HARDWARE\DESCRIPTION\System\CentralProcessor\0\");
return key?.GetValue("ProcessorNameString").ToString() ?? "Not Found";
}
You can assume that I can tell at runtime what OS I am running under.
Upvotes: 6
Views: 9180
Reputation: 1235
It cannot be done in C# without 'hacks'. You must use cpuid instruction (or OS functions that calls cpuid). Also you can allocate memory and set it as executable and copy to it code which is calls cpuid.
First you must detect CPU architecture (for Windows this can be done via environment variables PROCESSOR_ARCHITECTURE
and PROCESSOR_ARCHITEW6432
) - what processor is using (x86/x86_64/ARM/ARM64)
Then you need get processor name via cpuid instruction or ARM equivalent.
You can modify following Windows code for Linux (you need to change Virtual Alloc function)
[StructLayout(LayoutKind.Sequential)]
internal ref struct CpuIdInfo
{
public uint Eax;
public uint Ebx;
public uint Ecx;
public uint Edx;
public static void AppendAsString(StringBuilder builder,uint value)
{
var val = value;
while (val != 0)
{
builder.Append((char) (val & 0xFF));
val >>= 8;
}
}
public string GetString()
{
StringBuilder ret = new StringBuilder(16);
AppendAsString(ret,Ebx);
AppendAsString(ret,Edx);
AppendAsString(ret,Ecx);
return ret.ToString();
}
}
internal sealed class CpuIdAssemblyCode
: IDisposable
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void CpuIDDelegate(int level, ref CpuIdInfo cpuId);
private IntPtr _codePointer;
private uint _size;
private CpuIDDelegate _delegate;
public CpuIdAssemblyCode()
{
byte[] codeBytes = (IntPtr.Size == 4) ? x86CodeBytes : x64CodeBytes;
_size = (uint) codeBytes.Length;
_codePointer = NativeMethods.Kernel32.VirtualAlloc(
IntPtr.Zero,
new UIntPtr(_size),
AllocationType.COMMIT | AllocationType.RESERVE,
MemoryProtection.EXECUTE_READWRITE
);
Marshal.Copy(codeBytes, 0, _codePointer, codeBytes.Length);
#if NET40
_delegate = (CpuIDDelegate) Marshal.GetDelegateForFunctionPointer(_codePointer, typeof(CpuIDDelegate));
#else
_delegate = Marshal.GetDelegateForFunctionPointer<CpuIDDelegate>(_codePointer);
#endif
}
~CpuIdAssemblyCode()
{
Dispose(false);
}
public void Call(int level, ref CpuIdInfo cpuInfo)
{
_delegate(level, ref cpuInfo);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
NativeMethods.Kernel32.VirtualFree(_codePointer, _size, 0x8000);
}
// Basic ASM strategy --
// void x86CpuId(int level, byte* buffer)
// {
// eax = level
// cpuid
// buffer[0] = eax
// buffer[4] = ebx
// buffer[8] = ecx
// buffer[12] = edx
// }
private readonly static byte[] x86CodeBytes = {
0x55, // push ebp
0x8B, 0xEC, // mov ebp,esp
0x53, // push ebx
0x57, // push edi
0x8B, 0x45, 0x08, // mov eax, dword ptr [ebp+8] (move level into eax)
0x0F, 0xA2, // cpuid
0x8B, 0x7D, 0x0C, // mov edi, dword ptr [ebp+12] (move address of buffer into edi)
0x89, 0x07, // mov dword ptr [edi+0], eax (write eax, ... to buffer)
0x89, 0x5F, 0x04, // mov dword ptr [edi+4], ebx
0x89, 0x4F, 0x08, // mov dword ptr [edi+8], ecx
0x89, 0x57, 0x0C, // mov dword ptr [edi+12],edx
0x5F, // pop edi
0x5B, // pop ebx
0x8B, 0xE5, // mov esp,ebp
0x5D, // pop ebp
0xc3 // ret
};
private readonly static byte[] x64CodeBytes = {
0x53, // push rbx this gets clobbered by cpuid
// rcx is level
// rdx is buffer.
// Need to save buffer elsewhere, cpuid overwrites rdx
// Put buffer in r8, use r8 to reference buffer later.
// Save rdx (buffer addy) to r8
0x49, 0x89, 0xd0, // mov r8, rdx
// Move ecx (level) to eax to call cpuid, call cpuid
0x89, 0xc8, // mov eax, ecx
0x0F, 0xA2, // cpuid
// Write eax et al to buffer
0x41, 0x89, 0x40, 0x00, // mov dword ptr [r8+0], eax
0x41, 0x89, 0x58, 0x04, // mov dword ptr [r8+4], ebx
0x41, 0x89, 0x48, 0x08, // mov dword ptr [r8+8], ecx
0x41, 0x89, 0x50, 0x0c, // mov dword ptr [r8+12], edx
0x5b, // pop rbx
0xc3 // ret
};
}
Usage on Windows x86/x86_64
var asmCode = new CpuIdAssemblyCode();
CpuIdInfo info = new CpuIdInfo();
asmCode.Call(0, ref info);
asmCode.Dispose();
string ret= info.GetString();
Also you can modify my library (it do same thing under Windows)
Upvotes: 0
Reputation: 396
On Linux I used the FreeCSharp class from the example in this link How to get available virtual and physical memory size under Mono? to create a class that can read cpuinfo. For .net core you will most likely have to grab System.Text.RegularExpressions from nuget. I have not tested under .net core only mono, but I'm pretty sure it should work.
using System;
using System.Text.RegularExpressions;
using System.IO;
namespace Example.Lib.Common
{
/// <summary>
/// Reads /proc/cpuinfo to obtain common values
/// </summary>
public class LinuxCpuInfo
{
public string VendorId { get; private set; }
public int CpuFamily { get; private set; }
public int Model { get; private set; }
public string ModelName { get; private set; }
public int Stepping { get; private set; }
public double MHz { get; private set; }
public string CacheSize { get; private set; }
public void GetValues()
{
string[] cpuInfoLines = File.ReadAllLines(@"/proc/cpuinfo");
CpuInfoMatch[] cpuInfoMatches =
{
new CpuInfoMatch(@"^vendor_id\s+:\s+(.+)", value => VendorId = Conversion.ObjectToString(value)),
new CpuInfoMatch(@"^cpu family\s+:\s+(.+)", value => CpuFamily = Conversion.ObjectToInt(value)),
new CpuInfoMatch(@"^model\s+:\s+(.+)", value => Model = Conversion.ObjectToInt(value)),
new CpuInfoMatch(@"^model name\s+:\s+(.+)", value => ModelName = Conversion.ObjectToString(value)),
new CpuInfoMatch(@"^stepping\s+:\s+(.+)", value => Stepping = Conversion.ObjectToInt(value)),
new CpuInfoMatch(@"^cpu MHz\s+:\s+(.+)", value => MHz = Conversion.ObjectToDouble(value)),
new CpuInfoMatch(@"^cache size\s+:\s+(.+)", value => CacheSize = Conversion.ObjectToString(value))
};
foreach (string cpuInfoLine in cpuInfoLines)
{
foreach (CpuInfoMatch cpuInfoMatch in cpuInfoMatches)
{
Match match = cpuInfoMatch.regex.Match(cpuInfoLine);
if (match.Groups[0].Success)
{
string value = match.Groups[1].Value;
cpuInfoMatch.updateValue(value);
}
}
}
}
public class CpuInfoMatch
{
public Regex regex;
public Action<string> updateValue;
public CpuInfoMatch(string pattern, Action<string> update)
{
this.regex = new Regex(pattern, RegexOptions.Compiled);
this.updateValue = update;
}
}
}
}
Upvotes: 2