lzybkr / PSILAsm

Inline MSIL in PowerShell
14 stars 2 forks source link

Alternative point of view #1

Open gregzakh opened 4 years ago

gregzakh commented 4 years ago
#requires -version 6
using namespace System.Reflection.Emit
using namespace System.Management.Automation

function New-ILMethod {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory, Position=0)]
    [ValidateNotNullOrEmpty()]
    [String]$Code,

    [Parameter()][Type]$ReturnType = [void],
    [Parameter()][Type[]]$Parameters = @(),
    [Parameter()][ScriptBlock]$Variables
  )

  process {
    $dm = [DynamicMethod]::new((New-Guid).Guid, $ReturnType, $Parameters)
    $il = $dm.GetILGenerator()
    # declare variables
    if ($Variables) {
      $group = [PSParser]::Tokenize($variables, [ref]$null).Where{
        $_.Type -in ('Command', 'Variable')
      } | Group-Object Type

      for ($_x = 0; $_x -lt $group[0].Count; $_x++) {
        Set-Variable -Name $($group[1].Group[$_x].Content) -Value $il.DeclareLocal(
          [Type]::GetType("System.$($group[0].Group[$_x].Content)")
        )
      }
    }
    # define labels
    $lnum = ($Code.Split("`n") | Select-String -Pattern '^(\s+)?:').Length
    if ($lnum) {
      $labels = (0..($lnum - 1)).ForEach{
        $Code = $Code -replace ":L_$_.*", "`$labels[$_]"
        $il.DefineLabel()
      }
    }
    # construct body
    [ScriptBlock]::Create((Out-String -InputObject ($Code.Split("`n").Trim().ForEach{
      '$il.' + ("Emit([OpCodes]::$_)","MarkLabel($_)")[$_.StartsWith('$')]
    }))).Invoke()
    # establish method
    $farg, $fret = ($Parameters.Name.ForEach{"[$_]"} -join ', '), "[$($ReturnType.Name)]"
    $dm.CreateDelegate(
      ("Action[$farg]", "Func[$(
        (($farg + ', ' + $fret), $fret)[!$farg]
      )]")[[void] -ne $ReturnType] -as [Type]
    )
  }
}

<# ======================================================================================
# Example 1
# =======================================================================================
$hello = New-ILMethod -Code @'
  ldstr, 'Hello, world!'
  call, [Console].GetMethod('WriteLine', [Type[]]([String]))
  ret
'@ -Parameters ([String])
$hello.Invoke($null)
# =======================================================================================
# Example 2
# =======================================================================================
$poem = New-ILMethod -Parameters ([String]) -Code @'
  ldarg_0
  call, [Console].GetMethod('WriteLine', [Type[]]([String]))
  ret
'@
$poem.Invoke(@'
  Mary had a little lamb whose fleece was white as snow
  And everywhere that Mary went the lamb was sure to go
'@)
#========================================================================================
# Example 3
# =======================================================================================
$addition = New-ILMethod -Code @'
  ldarg_0
  ldarg_1
  add
  ret
'@ -ReturnType ([Int32]) -Parameters ([Int32], [Int32])
$addition.Invoke(3, 9)
# =======================================================================================
# Example 4
# =======================================================================================
$rshift = New-ILMethod -Code @'
  ldarg_0
  ldarg_1
  ldc_i4_s, 31
  and
  shr
  ret
'@ -ReturnType ([Int32]) -Parameters ([Int32], [Int32])
$rshift.Invoke(7, 1)
# =======================================================================================
# Example 5
$entropy = New-ILMethod -Variables {
  Int32  $i
  Int32* $rng
  Int32* $pi
  Double $ent
  Double $src
} -ReturnType ([Double]) -Parameters ([Byte[]]) -Code @'
  ldc_i4, 0x100
  conv_u
  ldc_i4_4
  mul_ovf_un
  localloc
  stloc_1
  ldloc_1
  ldc_i4, 0x400
  conv_i
  add
  stloc_2
  ldc_r8, 0.0
  stloc_3
  ldarg_0
  ldlen
  conv_i4
  dup
  stloc_0
  conv_r8
  stloc_s, $src
  br_s, :L_0
  :L_1 # 0x28
  ldloc_1
  ldarg_0
  ldloc_0
  ldelem_u1
  conv_i
  ldc_i4_4
  mul
  add
  dup
  ldind_i4
  ldc_i4_1
  add
  stind_i4
  :L_0 # 0x35
  ldloc_0
  ldc_i4_1
  sub
  dup
  stloc_0
  ldc_i4_0
  bge_s, :L_1
  br_s, :L_2
  :L_3 # 0x3f
  ldloc_2
  ldind_i4
  ldc_i4_0
  ble_s, :L_2
  ldloc_3
  ldloc_2
  ldind_i4
  conv_r8
  ldloc_2
  ldind_i4
  conv_r8
  ldloc_s, $src
  div
  ldc_r8, 2.
  call, [Math].GetMethod('Log', [Type[]]([Double], [Double]))
  mul
  add
  stloc_3
  :L_2 # 0x5f
  ldloc_2
  ldc_i4_4
  conv_i
  sub
  dup
  stloc_2
  ldloc_1
  bge_un_s, :L_3
  ldloc_3
  neg
  ldloc_s, $src
  div
  ret
'@
'{0:F3}' -f $entropy.Invoke([IO.File]::ReadAllBytes($MyInvocation.MyCommand.Path))
====================================================================================== #>

Of course, it's better to use AST instead PSParser but it's just a concept. What is your opinion?

lzybkr commented 4 years ago

It feels like the difference between inline asm in VC++ (x86) and gcc.

The VC++ syntax is very similar to what you'd write in a .asm file whereas the gcc syntax is less so, but maybe a bit more powerful.

At any rate, my sample code was never meant to be useful, it was more of a joke because who would ever want to write inline IL in PowerShell. I did go slightly overboard once I realized that it was almost elegant though, e.g. you can get Intellisense which I discovered by accident when the ISE would suggest completions after typing . that is in some instructions.