-
-
Save SeeminglyScience/b115709c022baaee100f94c910b24a48 to your computer and use it in GitHub Desktop.
Funky UX profile optimization parser prototype
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using namespace System.Collections.Generic | |
| function Import-ProfileData { | |
| [CmdletBinding(DefaultParameterSetName = 'Path')] | |
| param( | |
| [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'Path')] | |
| [ValidateNotNullOrEmpty()] | |
| [SupportsWildcards()] | |
| [string] $Path, | |
| [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'LiteralPath')] | |
| [ValidateNotNullOrEmpty()] | |
| [string] $LiteralPath | |
| ) | |
| begin { | |
| $recordTypeOffset = 24 | |
| $signatureLengthMask = 0xffff | |
| $moduleMask = 0xffff | |
| $moduleLevelOffset = 16 | |
| $maxModuleLevels = 0x100 | |
| enum RecordType { | |
| Header = 1 | |
| Module = 2 | |
| ModuleDependency = 3 | |
| Method = 4 | |
| GenericMethod = 5 | |
| } | |
| function AlignUp { | |
| param([int] $value, [int] $alignment) | |
| end { | |
| if ($value -eq 0) { | |
| return 0 | |
| } | |
| $r = $value % $alignment | |
| if ($r -eq 0) { | |
| return $value | |
| } | |
| return $value + ($alignment - $r) | |
| } | |
| } | |
| function ReadCompressedInt { | |
| param([byte[]] $b, [int] $offset) | |
| end { | |
| $length = $b.Length - $offset | |
| $first = $b[$offset] | |
| if (($first -band 128) -eq 0) { | |
| return [pscustomobject]@{ | |
| Value = [int]$first | |
| BytesRead = 1 | |
| } | |
| } | |
| if (($first -band 64) -eq 0) { | |
| if ($length -ge 2) { | |
| return [pscustomobject]@{ | |
| Value = (($first -band 63) -shl 8) -bor $b[$offset + 1] | |
| BytesRead = 2 | |
| } | |
| } | |
| return [pscustomobject]@{ | |
| Value = [int]::MaxValue | |
| BytesRead = 0 | |
| } | |
| } | |
| if ((($first -band 32) -eq 0) -and $length -ge 4) { | |
| return [pscustomobject]@{ | |
| Value = (($first -band 31) -shl 24) -bor | |
| ($b[$offset + 1] -shl 16) -bor | |
| ($b[$offset + 2] -shl 8) -bor | |
| ($b[$offset + 3]) | |
| BytesRead = 4 | |
| } | |
| } | |
| return [pscustomobject]@{ | |
| Value = [int]::MaxValue | |
| BytesRead = 0 | |
| } | |
| } | |
| } | |
| function ImportGenericSignature { | |
| param([byte[]] $b, [System.Reflection.Assembly] $assembly) | |
| begin { | |
| } | |
| end { | |
| [EncodeMethodSigFlags] $sigFlags = ReadCompressedInt $b 0 | |
| if ($sigFlags -ne 'OwnerType') { | |
| throw "Unexpected SigFlags '$sigFlags' at offset 0" | |
| } | |
| [ElementType] $kind = ReadCompressedInt $b 1 | |
| if ($kind -ne 'GenericInst') { | |
| throw "Unexpected kind '$kind' at offset 1" | |
| } | |
| [ElementType] $kind = ReadCompressedInt $b 2 | |
| if ($kind -ne 'Class') { | |
| throw "Unexpected kind '$kind' at offset 1" | |
| } | |
| $encodedToken = ReadCompressedInt $b 3 | |
| $tokenType = $encodedTokenTypes[$encodedToken -band 0x3] | |
| $token = $tokenType -bor ($encodedToken -shr 2) | |
| } | |
| } | |
| [Flags()] | |
| enum EncodeMethodSigFlags { | |
| UnboxingStub = 0x01 | |
| InstantiatingStub = 0x02 | |
| MethodInstantiation = 0x04 | |
| SlotInsteadOfToken = 0x08 | |
| MemberRefToken = 0x10 | |
| Constrained = 0x20 | |
| OwnerType = 0x40 | |
| UpdateContext = 0x80 | |
| AsyncVariant = 0x100 | |
| } | |
| enum ElementType { | |
| End = 0x00 | |
| Void = 0x01 | |
| Boolean = 0x02 | |
| Char = 0x03 | |
| I1 = 0x04 | |
| U1 = 0x05 | |
| I2 = 0x06 | |
| U2 = 0x07 | |
| I4 = 0x08 | |
| U4 = 0x09 | |
| I8 = 0x0A | |
| U8 = 0x0B | |
| R4 = 0x0C | |
| R8 = 0x0D | |
| String = 0x0E | |
| Ptr = 0x0F | |
| ByRef = 0x10 | |
| ValueType = 0x11 | |
| Class = 0x12 | |
| Var = 0x13 | |
| Array = 0x14 | |
| GenericInst = 0x15 | |
| TypedByRef = 0x16 | |
| I = 0x18 | |
| U = 0x19 | |
| FNPtr = 0x1B | |
| Object = 0x1C | |
| SZArray = 0x1D | |
| MVar = 0x1E | |
| CmodReqd = 0x1F | |
| CmodOpt = 0x20 | |
| Internal = 0x21 | |
| Max = 0x22 | |
| VarZapSig = 0x3b | |
| NativeValueTypeZapSig = 0x3d | |
| CannonZapSig = 0x3e | |
| ModuleZapSig = 0x3f | |
| Modifier = 0x40 | |
| Sentinel = 0x41 | |
| Pinned = 0x45 | |
| } | |
| enum CorTokenType { | |
| Module = 0x00000000 | |
| TypeRef = 0x01000000 | |
| TypeDef = 0x02000000 | |
| FieldDef = 0x04000000 | |
| MethodDef = 0x06000000 | |
| ParamDef = 0x08000000 | |
| InterfaceImpl = 0x09000000 | |
| MemberRef = 0x0a000000 | |
| CustomAttribute = 0x0c000000 | |
| Permission = 0x0e000000 | |
| Signature = 0x11000000 | |
| Event = 0x14000000 | |
| Property = 0x17000000 | |
| MethodImpl = 0x19000000 | |
| ModuleRef = 0x1a000000 | |
| TypeSpec = 0x1b000000 | |
| Assembly = 0x20000000 | |
| AssemblyRef = 0x23000000 | |
| File = 0x26000000 | |
| ExportedType = 0x27000000 | |
| ManifestResource = 0x28000000 | |
| NestedClass = 0x29000000 | |
| GenericParam = 0x2a000000 | |
| MethodSpec = 0x2b000000 | |
| GenericParamConstraint = 0x2c000000 | |
| String = 0x70000000 | |
| Name = 0x71000000 | |
| BaseType = 0x72000000 | |
| } | |
| class GenericSigParser { | |
| static [CorTokenType[]] $EncodedTokenTypes = ( | |
| [CorTokenType]::TypeDef, | |
| [CorTokenType]::TypeRef, | |
| [CorTokenType]::TypeSpec, | |
| [CorTokenType]::BaseType) | |
| [byte[]] $Bytes | |
| [int] $Position | |
| [System.Reflection.Module] $Module | |
| [List[psobject]] $Steps = [List[psobject]]::new() | |
| [ProfileData] $ProfileData | |
| GenericSigParser( | |
| [byte[]] $bytes, | |
| [System.Reflection.Module] $module, | |
| [ProfileData] $profileData) | |
| { | |
| $this.Bytes = $bytes | |
| $this.Module = $module | |
| $this.ProfileData = $profileData | |
| } | |
| [object] Parse() { | |
| [EncodeMethodSigFlags] $flags = $this.ReadCompressedInt() | |
| $this.WriteStep('Flags', $flags) | |
| $ownerType = $null | |
| if ($flags.HasFlag([EncodeMethodSigFlags]::OwnerType)) { | |
| $ownerType = $this.ReadEncodedType() | |
| $this.WriteStep('OwnerType', $ownerType, $ownerType.DisplayString) | |
| } | |
| if ($flags.HasFlag([EncodeMethodSigFlags]::SlotInsteadOfToken)) { | |
| throw "Don't think we can do this one" | |
| } | |
| $method = $null | |
| $rid = $this.ReadCompressedInt() | |
| $this.WriteStep('Rid', $rid, ($rid | hex)) | |
| $methodArgs = [type]::EmptyTypes | |
| if ($flags.HasFlag([EncodeMethodSigFlags]::MethodInstantiation)) { | |
| $argCount = $this.ReadCompressedInt() | |
| $methodArgs = [type[]]::new($argCount) | |
| for ($i = 0; $i -lt $argCount; $i++) { | |
| $methodArgs[$i] = $this.ReadEncodedType() | |
| } | |
| } | |
| $typeArgs = ($ownerType)?.IsGenericType ? $ownerType.GetGenericArguments() : [type]::EmptyTypes | |
| if ($flags.HasFlag([EncodeMethodSigFlags]::MemberRefToken)) { | |
| $method = $this.Module.ResolveMember( | |
| [CorTokenType]::MemberRef -bor $rid, | |
| $typeArgs, | |
| $methodArgs) | |
| } else { | |
| $method = $this.Module.ResolveMethod( | |
| [CorTokenType]::MethodDef -bor $rid, | |
| $typeArgs, | |
| $methodArgs) | |
| } | |
| if ($typeArgs) { | |
| foreach ($m in $ownerType.GetMember($method.Name, 60)) { | |
| if ($m -isnot [System.Reflection.MethodBase]) { | |
| continue | |
| } | |
| if ($m.MetadataToken -eq $method.MetadataToken) { | |
| $method = $m | |
| break | |
| } | |
| } | |
| } | |
| if ($methodArgs) { | |
| $method = $method.MakeGenericMethod($methodArgs) | |
| } | |
| if ($flags.HasFlag([EncodeMethodSigFlags]::Constrained)) { | |
| return [pscustomobject]@{ | |
| ConstrainedTo = $this.ReadEncodedType() | |
| Method = $method | |
| } | |
| } | |
| return $method | |
| } | |
| [void] WriteStep([string] $step, [object] $value) { | |
| $this.WriteStep($step, $value, $value) | |
| } | |
| [void] WriteStep([string] $step, [object] $value, [string] $displayValue) { | |
| $this.Steps.Add( | |
| [pscustomobject]@{ | |
| Step = $step | |
| Value = $displayValue | |
| RawValue = $value | |
| End = $this.Position | |
| }) | |
| } | |
| [type] ReadEncodedType() { | |
| return $this.ReadEncodedType($this.Module) | |
| } | |
| [type] ReadEncodedType([System.Reflection.Module] $module) { | |
| [ElementType] $kind = $this.ReadByte() | |
| $this.WriteStep('ElementType', $kind) | |
| if ($kind -eq [ElementType]::GenericInst) { | |
| $genericTypeDef = $this.ReadEncodedType($module) | |
| $argCount = $this.ReadCompressedInt() | |
| $this.WriteStep('GenericArgCount', $argCount) | |
| $genericArgs = [type[]]::new($argCount) | |
| for ($i = 0; $i -lt $argCount; $i++) { | |
| $genericArgs[$i] = $this.ReadEncodedType($this.Module) | |
| } | |
| return $genericTypeDef.MakeGenericType($genericArgs) | |
| } | |
| if ($kind -eq [ElementType]::SZArray) { | |
| $this.ReadEncodedType($module).MakeArrayType() | |
| } | |
| if ($kind -eq [ElementType]::Pointer) { | |
| $this.ReadEncodedType($module).MakePointerType() | |
| } | |
| if ($kind -eq [ElementType]::Void) { | |
| return [void] | |
| } | |
| if ($kind -eq [ElementType]::Boolean) { | |
| return [bool] | |
| } | |
| if ($kind -eq [ElementType]::Char) { | |
| return [char] | |
| } | |
| if ($kind -eq [ElementType]::I1) { | |
| return [sbyte] | |
| } | |
| if ($kind -eq [ElementType]::U1) { | |
| return [byte] | |
| } | |
| if ($kind -eq [ElementType]::I2) { | |
| return [short] | |
| } | |
| if ($kind -eq [ElementType]::U2) { | |
| return [ushort] | |
| } | |
| if ($kind -eq [ElementType]::I4) { | |
| return [int] | |
| } | |
| if ($kind -eq [ElementType]::U4) { | |
| return [uint] | |
| } | |
| if ($kind -eq [ElementType]::I8) { | |
| return [long] | |
| } | |
| if ($kind -eq [ElementType]::U8) { | |
| return [ulong] | |
| } | |
| if ($kind -eq [ElementType]::R4) { | |
| return [float] | |
| } | |
| if ($kind -eq [ElementType]::R8) { | |
| return [double] | |
| } | |
| if ($kind -eq [ElementType]::String) { | |
| return [string] | |
| } | |
| if ($kind -eq [ElementType]::I) { | |
| return [intptr] | |
| } | |
| if ($kind -eq [ElementType]::U) { | |
| return [uintptr] | |
| } | |
| if ($kind -eq [ElementType]::Object) { | |
| return [object] | |
| } | |
| if ($kind -eq [ElementType]::CannonZapSig) { | |
| return [int].Assembly.GetType('System.__Canon') | |
| } | |
| if ($kind -eq [ElementType]::ModuleZapSig) { | |
| $index = $this.ReadCompressedInt() | |
| $newModule = $this.ProfileData.ResolveModule($index) | |
| $this.WriteStep('Module', $newModule, $index) | |
| return $this.ReadEncodedType($newModule) | |
| } | |
| if ($kind -eq [ElementType]::ValueType -or $kind -eq [ElementType]::Class) { | |
| $token = $this.ReadEncodedTypeToken() | |
| return $module.ResolveType($token) | |
| } | |
| throw "Unknown element type '$kind'." | |
| } | |
| [int] ReadEncodedTypeToken() { | |
| $encodedToken = $this.ReadCompressedInt() | |
| $tokenType = $this::EncodedTokenTypes[$encodedToken -band 0x3] | |
| $token = $tokenType -bor ($encodedToken -shr 2) | |
| $this.WriteStep('EncodedToken', $token, ($token | hex)) | |
| return $token | |
| } | |
| [byte] ReadByte() { | |
| if ($this.Position -ge $this.Bytes.Length) { | |
| throw 'Out of range' | |
| } | |
| return $this.Bytes[$this.Position++] | |
| } | |
| [int] ReadCompressedInt() { | |
| $value = $this.ReadCompressedIntNoThrow() | |
| if ($value -eq [int]::MaxValue) { | |
| throw "Bad int at position '$($this.Position)'!" | |
| } | |
| return $value | |
| } | |
| [int] ReadCompressedIntNoThrow() { | |
| $b = $this.Bytes | |
| $offset = $this.Position | |
| $length = $b.Length - $offset | |
| $first = $this.ReadByte() | |
| if (($first -band 0x80) -eq 0) { | |
| return $first | |
| } | |
| if (($first -band 0xC0) -eq 0x80) { | |
| if ($length -lt 2) { | |
| $this.Position-- | |
| return [int]::MaxValue | |
| } | |
| return (($first -band 0x3f) -shl 8) -bor $this.ReadByte() | |
| } | |
| if (($first -band 0xE0) -eq 0xC0) { | |
| if ($length -lt 4) { | |
| $this.Position-- | |
| return [int]::MaxValue | |
| } | |
| return (($first -band 0x1f) -shl 24) -bor | |
| ([int]$this.ReadByte() -shl 16) -bor | |
| ([int]$this.ReadByte() -shl 8) -bor | |
| [int]$this.ReadByte() | |
| } | |
| $this.Position-- | |
| return [int]::MaxValue | |
| # if (($first -band 0x40) -eq 0) { | |
| # if ($length -ge 2) { | |
| # return (($first -band 63) -shl 8) -bor $this.ReadByte() | |
| # } | |
| # $this.Position-- | |
| # return [int]::MaxValue | |
| # } | |
| # if ((($first -band 0x20) -eq 0) -and $length -ge 4) { | |
| # return (($first -band 31) -shl 24) -bor | |
| # ($this.ReadByte() -shl 16) -bor | |
| # ($this.ReadByte() -shl 8) -bor | |
| # $this.ReadByte() | |
| # } | |
| # $this.Position-- | |
| # return [int]::MaxValue | |
| } | |
| } | |
| class ProfileHeader { | |
| [RecordType] $Type | |
| [uint] $RecordId | |
| [uint] $Version | |
| [uint] $TimeStamp | |
| [uint] $ModuleCount | |
| [uint] $MethodCount | |
| [uint] $ModuleDepCount | |
| [ushort[]] $ShortCounters | |
| [uint[]] $LongCounters | |
| } | |
| class ProfileModule { | |
| [RecordType] $Type | |
| [uint] $RecordID | |
| [version] $ModuleVersion | |
| [uint] $ModuleVersionFlags | |
| [guid] $ModuleVersionGuid | |
| [ushort] $JitMethodCount | |
| [ushort] $Flags | |
| [ushort] $LoadLevel | |
| [ushort] $ModuleNameLength | |
| [ushort] $AssemblyNameLength | |
| [string] $ModuleName | |
| [string] $AssemblyName | |
| } | |
| class ProfileModuleDep { | |
| [RecordType] $Type | |
| [uint] $ModuleIndex | |
| [uint] $Level | |
| } | |
| class ProfileMethod { | |
| [RecordType] $Type | |
| [uint] $RecordId | |
| [uint] $ModuleIndex | |
| [int] $Token | |
| } | |
| class ProfileGenericMethod { | |
| [RecordType] $Type | |
| [uint] $RecordId | |
| [uint] $ModuleIndex | |
| [byte[]] $Data | |
| [ProfileData] $Parent | |
| [System.Reflection.MethodBase] ParseSignature() { | |
| $module = $this.Parent.ResolveModule($this.ModuleIndex) | |
| if (-not $module) { | |
| $assembly = [System.Reflection.Assembly]::Load($this.Parent.Modules[$this.ModuleIndex].AssemblyName) | |
| $module = $assembly.Modules[0] | |
| } | |
| return [GenericSigParser]::new($this.Data, $module, $this.Parent).Parse() | |
| } | |
| } | |
| class ProfileData { | |
| [ProfileHeader] $Header | |
| [List[ProfileModule]] $Modules = [List[ProfileModule]]::new() | |
| [List[ProfileModuleDep]] $ModuleDeps = [List[ProfileModuleDep]]::new() | |
| [List[ProfileMethod]] $Methods = [List[ProfileMethod]]::new() | |
| [List[ProfileGenericMethod]] $GenericMethods = [List[ProfileGenericMethod]]::new() | |
| [System.Reflection.Module] ResolveModule([int] $index) { | |
| $toResolve = $this.Modules[$index] | |
| return (Get-Assembly $toResolve.ModuleName).Modules[0] | |
| } | |
| } | |
| $intSize = [System.Runtime.CompilerServices.Unsafe]::SizeOf[int]() | |
| $shortSize = [System.Runtime.CompilerServices.Unsafe]::SizeOf[short]() | |
| } | |
| process { | |
| $result = [ProfileData]::new() | |
| $bytes = Get-Content -Raw -AsByteStream @PSBoundParameters | |
| $stream = [System.IO.MemoryStream]::new($bytes) | |
| $br = [System.IO.BinaryReader]::new($stream) | |
| $result.Header = @{ | |
| Type = [RecordType]::Header | |
| RecordId = $br.ReadUInt32() | |
| Version = $br.ReadUInt32() | |
| TimeStamp = $br.ReadUInt32() | |
| ModuleCount = $br.ReadUInt32() | |
| MethodCount = $br.ReadUInt32() | |
| ModuleDepCount = $br.ReadUInt32() | |
| ShortCounters = for ($i = 0; $i -lt 14; $i++) { | |
| $br.ReadUInt16() | |
| } | |
| LongCounters = for ($i = 0; $i -lt 3; $i++) { | |
| $br.ReadUInt32() | |
| } | |
| } | |
| $length = 0 | |
| $recordStartIndex = $br.BaseStream.Position | |
| while ($true) { | |
| $br.BaseStream.Position = $recordStartIndex + $length | |
| $recordStartIndex = $br.BaseStream.Position | |
| if ($br.BaseStream.Length -eq $br.BaseStream.Position) { | |
| break | |
| } | |
| $data = $br.ReadUInt32() | |
| $recordType = [RecordType]::new() | |
| $recordType.value__ = ($data -shr $recordTypeOffset) | |
| $length = 0 | |
| if ($recordType -eq [RecordType]::Module) { | |
| $length = $data -band $signatureLengthMask | |
| } elseif ($recordType -eq [RecordType]::ModuleDependency) { | |
| $length = $intSize | |
| } elseif ($recordType -eq [RecordType]::Method) { | |
| $length = 2 * $intSize | |
| } elseif ($recordType -eq [RecordType]::GenericMethod) { | |
| $sigLength = $br.ReadUInt16() | |
| $dataSize = $sigLength + $intSize + $shortSize | |
| $length = AlignUp $dataSize $intSize | |
| } else { | |
| throw "Don't know what '$recordType' record type is." | |
| } | |
| if ($recordType -eq 'Module') { | |
| $moduleRecord = [ProfileModule]@{ | |
| Type = $recordType | |
| RecordID = $data | |
| ModuleVersion = [version]::new( | |
| $br.ReadUInt16(), | |
| $br.ReadUInt16(), | |
| $br.ReadUInt16(), | |
| $br.ReadUInt16()) | |
| ModuleVersionFlags = $br.ReadUInt32() | |
| ModuleVersionGuid = [guid]::new($br.ReadBytes(16)) | |
| JitMethodCount = $br.ReadUInt16() | |
| Flags = $br.ReadUInt16() | |
| LoadLevel = $br.ReadUInt16() | |
| ModuleNameLength = $br.ReadUInt16() | |
| AssemblyNameLength = $br.ReadUInt16() | |
| } | |
| # Padding | |
| $null = $br.ReadUInt16() | |
| $moduleName = [System.Text.Encoding]::UTF8.GetString($br.ReadBytes($moduleRecord.ModuleNameLength)) | |
| # Ignore null char seperator | |
| $null = $br.ReadByte() | |
| $assemblyName = [System.Text.Encoding]::UTF8.GetString($br.ReadBytes($moduleRecord.AssemblyNameLength)) | |
| $moduleRecord.ModuleName = $moduleName | |
| $moduleRecord.AssemblyName = $assemblyName | |
| $result.Modules.Add($moduleRecord) | |
| continue | |
| } | |
| if ($recordType -eq 'ModuleDependency') { | |
| $result.ModuleDeps.Add( | |
| @{ | |
| Type = $recordType | |
| ModuleIndex = $data -band $moduleMask | |
| Level = ($data -shr $moduleLevelOffset) -band ($maxModuleLevels - 1) | |
| }) | |
| continue | |
| } | |
| if ($recordType -eq 'Method') { | |
| $result.Methods.Add( | |
| @{ | |
| Type = $recordType | |
| RecordId = $data | |
| ModuleIndex = $data -band $moduleMask | |
| Token = $br.ReadInt32() | |
| }) | |
| continue | |
| } | |
| if ($recordType -eq 'GenericMethod') { | |
| $br.BaseStream.Position -= 2 | |
| $sigLength = $br.ReadUInt16() | |
| $genericMethod = [ProfileGenericMethod]@{ | |
| Type = $recordType | |
| RecordId = $data | |
| ModuleIndex = $data -band $moduleMask | |
| Data = $br.ReadBytes($sigLength) | |
| Parent = $result | |
| } | |
| $result.GenericMethods.Add($genericMethod) | |
| continue | |
| } | |
| } | |
| return $result | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment