Files
ReLibreHardwareMonitor/LibreHardwareMonitorLib/Hardware/OpCode.cs
T
MadLongTomandPhyxionNL aa7c8be02f feat: Add Native AOT compilation support for LibreHardwareMonitorLib (#2280)
* feat: Add Native AOT compilation support for LibreHardwareMonitorLib

- Replace non-generic Marshal.GetDelegateForFunctionPointer with generic versions
  (NvApi.cs, NvidiaML.cs, OpCode.cs)
- Replace non-generic Marshal.PtrToStructure/SizeOf with generic versions
  (AtiAdlxx.cs, AmdGpu.cs, Ipmi.cs)
- Replace reflection-based Mono.Posix calls with direct Syscall for non-NETFRAMEWORK
  (OpCode.cs)
- Add DynamicallyAccessedMembers annotations for trim-safe generic marshaling
  (MsiCoreLiquidController.cs, WireViewPro2.cs)
- Add IsAotCompatible, IsTrimmable, and AOT/trim analyzers to csproj for net8.0+
- All changes maintain full backward compatibility with net472/netstandard2.0

Tested: dotnet publish with PublishAot=true on net10.0 win-x64 succeeds.
AOT binary correctly enumerates all hardware and sensors without errors.

* feat: Add conditional compilation for .NET 5.0 or greater in various classes

* feat: Add AOT test project and update workflows for AOT compilation

* Drop test project

* NET5_0_OR_GREATER > NET

---------

Co-authored-by: PhyxionNL <7643972+PhyxionNL@users.noreply.github.com>
2026-04-07 13:38:41 +02:00

300 lines
8.4 KiB
C#

// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
#if !NETFRAMEWORK
using Mono.Unix.Native;
#else
using System.Reflection;
#endif
using Windows.Win32;
using Windows.Win32.System.Memory;
namespace LibreHardwareMonitor.Hardware;
internal static class OpCode
{
public static CpuidDelegate CpuId;
public static RdtscDelegate Rdtsc;
private static IntPtr _codeBuffer;
private static ulong _size;
// void __stdcall cpuidex(unsigned int index, unsigned int ecxValue,
// unsigned int* eax, unsigned int* ebx, unsigned int* ecx,
// unsigned int* edx)
// {
// int info[4];
// __cpuidex(info, index, ecxValue);
// *eax = info[0];
// *ebx = info[1];
// *ecx = info[2];
// *edx = info[3];
// }
private static readonly byte[] CpuId32 =
[
0x55, // push ebp
0x8B,
0xEC, // mov ebp, esp
0x83,
0xEC,
0x10, // sub esp, 10h
0x8B,
0x45,
0x08, // mov eax, dword ptr [ebp+8]
0x8B,
0x4D,
0x0C, // mov ecx, dword ptr [ebp+0Ch]
0x53, // push ebx
0x0F,
0xA2, // cpuid
0x56, // push esi
0x8D,
0x75,
0xF0, // lea esi, [info]
0x89,
0x06, // mov dword ptr [esi],eax
0x8B,
0x45,
0x10, // mov eax, dword ptr [eax]
0x89,
0x5E,
0x04, // mov dword ptr [esi+4], ebx
0x89,
0x4E,
0x08, // mov dword ptr [esi+8], ecx
0x89,
0x56,
0x0C, // mov dword ptr [esi+0Ch], edx
0x8B,
0x4D,
0xF0, // mov ecx, dword ptr [info]
0x89,
0x08, // mov dword ptr [eax], ecx
0x8B,
0x45,
0x14, // mov eax, dword ptr [ebx]
0x8B,
0x4D,
0xF4, // mov ecx, dword ptr [ebp-0Ch]
0x89,
0x08, // mov dword ptr [eax], ecx
0x8B,
0x45,
0x18, // mov eax, dword ptr [ecx]
0x8B,
0x4D,
0xF8, // mov ecx, dword ptr [ebp-8]
0x89,
0x08, // mov dword ptr [eax], ecx
0x8B,
0x45,
0x1C, // mov eax, dword ptr [edx]
0x8B,
0x4D,
0xFC, // mov ecx, dword ptr [ebp-4]
0x5E, // pop esi
0x89,
0x08, // mov dword ptr [eax], ecx
0x5B, // pop ebx
0xC9, // leave
0xC2,
0x18,
0x00 // ret 18h
];
private static readonly byte[] CpuId64Linux =
[
0x49,
0x89,
0xD2, // mov r10, rdx
0x49,
0x89,
0xCB, // mov r11, rcx
0x53, // push rbx
0x89,
0xF8, // mov eax, edi
0x89,
0xF1, // mov ecx, esi
0x0F,
0xA2, // cpuid
0x41,
0x89,
0x02, // mov dword ptr [r10], eax
0x41,
0x89,
0x1B, // mov dword ptr [r11], ebx
0x41,
0x89,
0x08, // mov dword ptr [r8], ecx
0x41,
0x89,
0x11, // mov dword ptr [r9], edx
0x5B, // pop rbx
0xC3 // ret
];
private static readonly byte[] CpuId64Windows =
[
0x48,
0x89,
0x5C,
0x24,
0x08, // mov qword ptr [rsp+8], rbx
0x8B,
0xC1, // mov eax, ecx
0x8B,
0xCA, // mov ecx, edx
0x0F,
0xA2, // cpuid
0x41,
0x89,
0x00, // mov dword ptr [r8], eax
0x48,
0x8B,
0x44,
0x24,
0x28, // mov rax, qword ptr [rsp+28h]
0x41,
0x89,
0x19, // mov dword ptr [r9], ebx
0x48,
0x8B,
0x5C,
0x24,
0x08, // mov rbx, qword ptr [rsp+8]
0x89,
0x08, // mov dword ptr [rax], ecx
0x48,
0x8B,
0x44,
0x24,
0x30, // mov rax, qword ptr [rsp+30h]
0x89,
0x10, // mov dword ptr [rax], edx
0xC3 // ret
];
// unsigned __int64 __stdcall rdtsc() {
// return __rdtsc();
// }
private static readonly byte[] Rdtsc32 =
[
0x0F,
0x31, // rdtsc
0xC3 // ret
];
private static readonly byte[] Rdtsc64 =
[
0x0F,
0x31, // rdtsc
0x48,
0xC1,
0xE2,
0x20, // shl rdx, 20h
0x48,
0x0B,
0xC2, // or rax, rdx
0xC3 // ret
];
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate bool CpuidDelegate(uint index, uint ecxValue, out uint eax, out uint ebx, out uint ecx, out uint edx);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate ulong RdtscDelegate();
#if NET
[RequiresUnreferencedCode("Dynamic code generation for OpCode delegates")]
#endif
public static unsafe void Open()
{
byte[] rdTscCode;
byte[] cpuidCode;
if (IntPtr.Size == 4)
{
rdTscCode = Rdtsc32;
cpuidCode = CpuId32;
}
else
{
rdTscCode = Rdtsc64;
cpuidCode = Software.OperatingSystem.IsUnix ? CpuId64Linux : CpuId64Windows;
}
_size = (ulong)(rdTscCode.Length + cpuidCode.Length);
if (Software.OperatingSystem.IsUnix)
{
#if NETFRAMEWORK
Assembly assembly = Assembly.Load("Mono.Posix, Version=2.0.0.0, Culture=neutral, " + "PublicKeyToken=0738eb9f132ed756");
Type sysCall = assembly.GetType("Mono.Unix.Native.Syscall");
MethodInfo mmap = sysCall.GetMethod("mmap");
Type mmapProts = assembly.GetType("Mono.Unix.Native.MmapProts");
object mmapProtsParam = Enum.ToObject(mmapProts,
(int)mmapProts.GetField("PROT_READ").GetValue(null) |
(int)mmapProts.GetField("PROT_WRITE").GetValue(null) |
(int)mmapProts.GetField("PROT_EXEC").GetValue(null));
Type mmapFlags = assembly.GetType("Mono.Unix.Native.MmapFlags");
object mmapFlagsParam = Enum.ToObject(mmapFlags,
(int)mmapFlags.GetField("MAP_ANONYMOUS").GetValue(null) |
(int)mmapFlags.GetField("MAP_PRIVATE").GetValue(null));
if (mmap != null)
_codeBuffer = (IntPtr)mmap.Invoke(null, [IntPtr.Zero, _size, mmapProtsParam, mmapFlagsParam, -1, 0]);
#else
_codeBuffer = Syscall.mmap(IntPtr.Zero, _size,
MmapProts.PROT_READ | MmapProts.PROT_WRITE | MmapProts.PROT_EXEC,
MmapFlags.MAP_ANONYMOUS | MmapFlags.MAP_PRIVATE, -1, 0);
#endif
}
else
{
_codeBuffer = (IntPtr)PInvoke.VirtualAlloc(null,
(UIntPtr)_size,
VIRTUAL_ALLOCATION_TYPE.MEM_COMMIT | VIRTUAL_ALLOCATION_TYPE.MEM_RESERVE,
PAGE_PROTECTION_FLAGS.PAGE_EXECUTE_READWRITE);
}
Marshal.Copy(rdTscCode, 0, _codeBuffer, rdTscCode.Length);
Rdtsc = Marshal.GetDelegateForFunctionPointer<RdtscDelegate>(_codeBuffer);
IntPtr cpuidAddress = (IntPtr)((long)_codeBuffer + rdTscCode.Length);
Marshal.Copy(cpuidCode, 0, cpuidAddress, cpuidCode.Length);
CpuId = Marshal.GetDelegateForFunctionPointer<CpuidDelegate>(cpuidAddress);
}
public static unsafe void Close()
{
Rdtsc = null;
CpuId = null;
if (Software.OperatingSystem.IsUnix)
{
#if NETFRAMEWORK
Assembly assembly = Assembly.Load("Mono.Posix, Version=2.0.0.0, Culture=neutral, " + "PublicKeyToken=0738eb9f132ed756");
Type sysCall = assembly.GetType("Mono.Unix.Native.Syscall");
MethodInfo method = sysCall.GetMethod("munmap");
method?.Invoke(null, [_codeBuffer, _size]);
#else
Syscall.munmap(_codeBuffer, _size);
#endif
}
else
{
PInvoke.VirtualFree((void*)_codeBuffer, UIntPtr.Zero, VIRTUAL_FREE_TYPE.MEM_RELEASE);
}
}
}