MaYaN
MaYaN

Reputation: 6986

How can I get CPU name in .NET Core?

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

Answers (2)

Alexei Shcherbakov
Alexei Shcherbakov

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

Nick Hanshaw
Nick Hanshaw

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

Related Questions