检索特定 CPU 特征

Retrieve specific CPU feature

有很多关于如何清点计算机硬件的信息,但我对检索有关特定 CPU 寄存器的信息的想法很感兴趣。可以使用 PowerShell 吗?我不是在谈论再次发明 CPUID,我需要专业人士的意见是否可能。如果可能的话,有人可以建议如何实现这个想法吗?

下面的脚本是我以前的获取cpuid的实现。我不记得我停止开发它的原因。不管怎样,似乎根本就没有空闲时间。此脚本应在 PowerShell v5 中正常工作。您可以将其用作研究的起点,并根据需要进行修改。希望这有帮助。

using namespace System.Reflection
using namespace System.Reflection.Emit
using namespace System.Runtime.InteropServices

# Brief : delegates "creator"
function Set-Delegate {
  [OutputType([Type])]
  param(
    [Parameter(Mandatory, Position=0)]
    [ValidateScript({$_ -ne [IntPtr]::Zero})]
    [IntPtr]$ProcAddress,

    [Parameter(Mandatory, Position=1)]
    [ValidateNotNull()]
    [Type]$Prototype,

    [Parameter(Position=2)]
    [ValidateNotNullOrEmpty()]
    [CallingConvention]$CallingConvention = 'StdCall'
  )

  $method = $Prototype.GetMethod('Invoke')
  $returntype, $paramtypes = $method.ReturnType, $method.GetParameters().ParameterType
  $holder = New-Object Reflection.Emit.DynamicMethod(
    'Invoke', $returntype, $(if (!$paramtypes) { $null } else { $paramtypes }), $Prototype
  )
  $il = $holder.GetILGenerator()

  if ($paramtypes) {
    (0..($paramtypes.Length - 1)).ForEach{$il.Emit([OpCodes]::Ldarg, $_)}
  }

  switch ([IntPtr]::Size) {
    4 { $il.Emit([OpCodes]::Ldc_I4, $ProcAddress.ToInt32()) }
    8 { $il.Emit([OpCodes]::Ldc_I8, $ProcAddress.ToInt64()) }
  }
  $il.EmitCalli(
    [OpCodes]::Calli, $CallingConvention, $returntype,
    $(if (!$paramtypes) { $null } else { $paramtypes })
  )
  $il.Emit([OpCodes]::Ret)

  $holder.CreateDelegate($Prototype)
}

# Brief : wrapper for reflected GetModuleHandle and GetProcAddress functions
# This is required to establish and invoke VirtualAlloc and VirtuallFree functions
# without creating dynamic assembly into current AppDomain. Be warned, this technique
# can be used in malware.
function Get-ProcAddress {
  [OutputType([Hashtable])]
  param(
    [Parameter(Mandatory, Position=0)]
    [ValidateNotNullOrEmpty()]
    [String]$Module,

    [Parameter(Mandatory, Position=1)]
    [ValidateNotNull()]
    [String[]]$Function
  )

  begin {
    [Object].Assembly.GetType('Microsoft.Win32.Win32Native').GetMethods(
      [BindingFlags]'Static, NonPublic'
    ).Where{$_.Name -cmatch '\AGet(ProcA|ModuleH)'}.ForEach{Set-Variable $_.Name $_}

    if (($mod = $GetModuleHandle.Invoke($null, @($Module))) -eq [IntPtr]::Zero) {
      throw (New-Object ComponentModel.Win32Exception(0x7E)).Message
    }
  }
  process {}
  end {
    $table = @{}
    $Function.ForEach{
      if (($$ = $GetProcAddress.Invoke($null, @($mod, $_))) -ne [IntPtr]::Zero) {$table.$_ = $$}
    }
    $table
  }
}

# Brief : sets functons addresses into delegates
function New-Delegate {
  [OutputType([Hashtable])]
  param(
    [Parameter(Mandatory, Position=0)]
    [ValidateNotNullOrEmpty()]
    [String]$Module,

    [Parameter(Mandatory, Position=1)]
    [ValidateNotNull()]
    [Hashtable]$Signature
  )

  $scope, $fname = @{}, (Get-ProcAddress -Module $Module -Function $Signature.Keys)
  $fname.Keys.ForEach{$scope.$_ = Set-Delegate $fname.$_ $Signature.$_}
  $scope
}

# Brief : helper function for pasrsing bytes
function Get-Blocks {
  [OutputType([Hashtable])]
  param(
    [Parameter(Mandatory, Position=0)]
    [ValidateNotNull()]
    [Byte[]]$Bytes,

    [Parameter()][Switch]$AsInteger,
    [Parameter()][switch]$AsString
  )

  $tmp, $reg = @{}, @{eax = $Bytes[0..3];ebx = $Bytes[4..7];ecx = $Bytes[8..11];edx = $Bytes[12..15]}
  if ($AsInteger) {
    $reg.Keys.ForEach{$tmp.$_ = [BitConverter]::ToInt32($reg.$_, 0)}
  }

  if ($AsString) {
    $reg.Keys.ForEach{$tmp.$_ = -join [Char[]]$reg.$_}
  }

  $tmp
}

# Brief : helper function for dumping features
function Set-MapFeatures {
  begin {
    function private:New-Hashtable([String[]]$Regs, [Int32[]]$Bits) {
      $out = @{}
      for ($i = 0; $i -lt $Regs.Length; $i++) {
        $Out.Add($Regs[$i], $Bits[$i])
      }
      $out
    }

    $chk = for ($i = 0; $i -le 31; $i++) {1 -shl $i}
    # excluding
    $edx_low, $ecx_low, $ecx_high = (0x00000400, 0x00100000), 0x00010000, (
      0x00004000, 0x00040000, 0x00100000, 0x02000000, 0x20000000, 0x40000000, 0x80000000
    )
    # common
    $edx_high = (
      0x00000800, 0x00080000, 0x00100000, 0x00400000, 0x02000000,
      0x04000000, 0x08000000, 0x20000000, 0x40000000, 0x80000000
    )
    # registers
    $edx_low_reg = ('fpu;vme;de;pse;tsc;msr;pae;mce;cx8;apic;sep;mtrr;pge;mca;cmov;pat;' +
      'pse36;psn;clfsh;ds;acpi;mmx;fxsr;sse;sse2;ss;htt;tm;ia64;pbe').Split(';')
    $ecx_low_reg = ('sse3;pclmulqdq;dtes64;monitor;ds_cpl;vmx;smx;est;tm2;ssse3;cnxt_id;' +
       'sdbg;fma;cx16;xtpr;pdcm;pcid;dca;sse4_1;sse4_2;x2apic;movbe;popcnt;tsc_deadline;' +
       'aes;xsave;osxsave;avx;f16c;rdrnd;hypervisor').Split(';')
    $edx_high_reg = 'syscall;mp;nx;mmxext;fxsr_opt;pdpe1gb;rdtscp;lm;3dnowext;3dnow'.Split(';')
    $ecx_high_reg = ('lahf_lm;cmp_legacy;svm;extapic;cr8_legacy;abm;sse4a;misalignsse;' +
           '3dnowprefetch;osvw;ibs;xop;skinit;wdt;lwp;fma4;tce;nodeid_msr;tbm;topoext;' +
           'perfctr_core;perfctr_nb;dbx;perftsc;pcx_l2i').Split(';')
    $set = @()
  }
  process {}
  end {
    # checkers
    $edx_low  = $chk.Where{$edx_low  -notcontains $_}
    $ecx_low  = $chk.Where{$ecx_low  -notcontains $_}
    $ecx_high = $chk.Where{$ecx_high -notcontains $_}

    $set += New-Hashtable $edx_low_reg $edx_low
    $set += New-Hashtable $ecx_low_reg $ecx_low
    $set += New-Hashtable $edx_high_reg $edx_high
    $set += New-Hashtable $ecx_high_reg $ecx_high

    $set
  }
}

# Brief : gets CPUID (CPU name and registers)
function Get-CpuId {
  begin {
    $kernel32 = New-Delegate kernel32 -Signature @{
      VirtualAlloc = [Func[IntPtr, UIntPtr, UInt32, UInt32, IntPtr]]
      VirtualFree  = [Func[IntPtr, UIntPtr, UInt32, Boolean]]
    }

    [Byte[]]$bytes = switch ([IntPtr]::Size) {
      4 {
        0x55,                   # push ebp
        0x8B, 0xEC,             # mov  ebp,  esp
        0x53,                   # push ebx
        0x57,                   # push edi
        0x8B, 0x45, 0x08,       # mov  eax, dword ptr[ebp+8]
        0x0F, 0xA2,             # cpuid
        0x8B, 0x7D, 0x0C,       # mov  edi, dword ptr[ebp+12]
        0x89, 0x07,             # mov  dword ptr[edi+0],  eax
        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
        0x57,                   # pop  edi
        0x5B,                   # pop  ebx
        0x8B, 0xE5,             # mov  esp, ebp
        0x5D,                   # pop  ebp
        0xC3                    # ret
      }
      8 {
        0x53,                   # push rbx
        0x49, 0x89, 0xD0,       # mov  r8,  rdx
        0x89, 0xC8,             # mov  eax, ecx
        0x0F, 0xA2,             # cpuid
        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
      }
    } # cpuid
  }
  process {
    try {
      $ptr = $kernel32.VirtualAlloc.Invoke(
        [IntPtr]::Zero, (New-Object UIntPtr($bytes.Length)), (0x1000 -bor 0x2000), 0x40
      )
      # __cpuid via generic delegate
      $cpuid = Set-Delegate $ptr -Prototype ([Action[Int32, [Byte[]]]]) -CallingConvention 'Cdecl'
      [Marshal]::Copy($bytes, 0, $ptr, $bytes.Length) # copy required bytes
      # shake it, baby! extracting data
      $map = Set-MapFeatures # map of features
      [Byte[]]$buf = New-Object Byte[] 16
      $cpuid.Invoke(0, $buf)
      $features, $vendor = @{}, "$(($str = Get-Blocks $buf -AsString).ebx)$($str.edx)$($str.ecx)"
      # low leaves
      $ids = (Get-Blocks $buf -AsInteger).eax
      for ($i = 0; $i -le $ids; $i++) {
        $cpuid.Invoke($i, $buf)

        if ($i -eq 1) {
          $reg = Get-Blocks $buf -AsInteger

          $map[0].Keys.ForEach{$features.$_ = $reg.edx -band $map[0].$_}
          $map[1].Keys.ForEach{$features.$_ = $reg.ecx -band $map[1].$_}
        }
      }
      # top leaves
      $cpuid.Invoke(0x80000000, $buf)
      $ids, $name = (Get-Blocks $buf -AsInteger).eax, ''
      for ($i = 0x80000000; $i -le $ids; $i++) {
        $cpuid.Invoke($i, $buf)

        if ($i -eq 0x80000001) {
          $reg = Get-Blocks $buf -AsInteger

          $map[2].Keys.ForEach{$features.$_ = $reg.edx -band $map[2].$_}
          $map[3].Keys.ForEach{$features.$_ = $reg.ecx -band $map[3].$_}
        }

        if ($i -eq 0x80000002 -or $i -eq 0x80000003 -or $i -eq 0x80000004) {
          $name += "$(($reg = Get-Blocks $buf -AsString).eax)$($reg.ebx)$($reg.ecx)$($reg.edx)"
        }
      }
      # wrap data into PSObject
      New-Object PSObject -Property @{
        Vendor      = $vendor
        Name        = $name
        Features    = $features.Keys.ForEach{if ($features.$_) {$_}}
      }
    }
    catch { $_ }
    finally {
      if ($ptr) { [void]$kernel32.VirtualFree.Invoke($ptr, [UIntPtr]::Zero, 0x8000) }
    }
  }
  end {}
}