diff --git a/WoofWare.PawPrint.App/Program.fs b/WoofWare.PawPrint.App/Program.fs index cf02c7e..773c47d 100644 --- a/WoofWare.PawPrint.App/Program.fs +++ b/WoofWare.PawPrint.App/Program.fs @@ -3,6 +3,7 @@ namespace WoofWare.PawPrint open System open System.Collections.Immutable open System.IO +open WoofWare.DotnetRuntimeLocator module Program = /// Returns the pointer to the resulting array on the heap. @@ -34,14 +35,26 @@ module Program = let reallyMain (argv : string[]) : int = match argv |> Array.toList with | dllPath :: args -> + let dotnetRuntimes = + // TODO: work out which runtime it expects to use. For now we just use the first one we find. + DotnetEnvironmentInfo.Get().Frameworks + |> Seq.map (fun fi -> Path.Combine (fi.Path, fi.Version.ToString ())) + |> Seq.toArray + use fileStream = new FileStream (dllPath, FileMode.Open, FileAccess.Read) let dumped = Assembly.read fileStream - let mainMethod = dumped.Methods.[dumped.MainMethod] + + let entryPoint = + match dumped.MainMethod with + | None -> failwith $"No entry point in {dllPath}" + | Some d -> d + + let mainMethod = dumped.Methods.[entryPoint] if mainMethod.Signature.GenericParameterCount > 0 then failwith "Refusing to execute generic main method" - let state = IlMachineState.Initial + let state = IlMachineState.Initial dumped let arrayAllocation, state = match mainMethod.Signature.ParameterTypes |> Seq.toList with @@ -56,20 +69,14 @@ module Program = let state, mainThread = state |> IlMachineState.AddThread - { - LocalVariables = ImmutableArray.Empty - IlOpIndex = 0 - EvaluationStack = EvalStack.Empty + { MethodState.Empty mainMethod None with Arguments = ImmutableArray.Create (CliObject.OfManagedObject arrayAllocation) - ExecutingMethod = dumped.Methods.[dumped.MainMethod] - LocalMemoryPool = () - ReturnState = None } let mutable state = state while true do - state <- AbstractMachine.executeOneStep state dumped mainThread + state <- AbstractMachine.executeOneStep dotnetRuntimes state mainThread 0 | _ -> diff --git a/WoofWare.PawPrint.App/WoofWare.PawPrint.App.fsproj b/WoofWare.PawPrint.App/WoofWare.PawPrint.App.fsproj index ebce71d..a786f7b 100644 --- a/WoofWare.PawPrint.App/WoofWare.PawPrint.App.fsproj +++ b/WoofWare.PawPrint.App/WoofWare.PawPrint.App.fsproj @@ -13,6 +13,10 @@ + + + + diff --git a/WoofWare.PawPrint/AbstractMachine.fs b/WoofWare.PawPrint/AbstractMachine.fs index 037b187..b0d03e1 100644 --- a/WoofWare.PawPrint/AbstractMachine.fs +++ b/WoofWare.PawPrint/AbstractMachine.fs @@ -1,7 +1,9 @@ namespace WoofWare.PawPrint open System +open System.Collections.Generic open System.Collections.Immutable +open System.IO open System.Reflection.Metadata open Microsoft.FSharp.Core @@ -131,6 +133,19 @@ type MethodState = EvaluationStack = state.EvaluationStack |> EvalStack.Push state.Arguments.[index] } + static member Empty (method : MethodInfo) (returnState : MethodState option) = + { + EvaluationStack = EvalStack.Empty + LocalVariables = + // TODO: use method.LocalsInit + ImmutableArray.Empty + IlOpIndex = 0 + Arguments = Array.zeroCreate method.Parameters.Length |> ImmutableArray.ToImmutableArray + ExecutingMethod = method + LocalMemoryPool = () + ReturnState = returnState + } + type ThreadState = { // TODO: thread-local storage, synchronisation state, exception handling context @@ -208,15 +223,25 @@ type IlMachineState = /// Multiple managed heaps are allowed, but we hopefully only need one. ManagedHeap : ManagedHeap ThreadState : Map + InternedStrings : ImmutableDictionary + ActiveAssemblyName : string + LoadedAssemblies : Map } - static member Initial : IlMachineState = + member this.ActiveAssembly = this.LoadedAssemblies.[this.ActiveAssemblyName] + + static member Initial (entryAssembly : DumpedAssembly) : IlMachineState = + let assyName = entryAssembly.ThisAssemblyDefinition.Name + { NextThreadId = 0 EvalStacks = Map.empty // CallStack = [] ManagedHeap = ManagedHeap.Empty ThreadState = Map.empty + InternedStrings = ImmutableDictionary.Empty + ActiveAssemblyName = assyName + LoadedAssemblies = Map.ofList [ assyName, entryAssembly ] } static member AddThread (newThreadState : MethodState) (state : IlMachineState) : IlMachineState * ThreadId = @@ -229,6 +254,9 @@ type IlMachineState = // CallStack = state.CallStack ManagedHeap = state.ManagedHeap ThreadState = state.ThreadState |> Map.add thread (ThreadState.New newThreadState) + InternedStrings = state.InternedStrings + ActiveAssemblyName = state.ActiveAssemblyName + LoadedAssemblies = state.LoadedAssemblies } newState, thread @@ -241,6 +269,19 @@ type IlMachineState = ManagedHeap = heap } + static member PushToStack (o : CliObject) (thread : ThreadId) (state : IlMachineState) = + { state with + EvalStacks = + state.EvalStacks + |> Map.change + thread + (fun s -> + match s with + | None -> failwith "tried to push to stack of nonexistent thread" + | Some stack -> EvalStack.Push o stack |> Some + ) + } + static member SetArrayValue (arrayAllocation : ManagedHeapAddress) (v : CliObject) @@ -294,7 +335,6 @@ module AbstractMachine = let internal executeNullary (state : IlMachineState) (currentThread : ThreadId) - (dumped : DumpedAssembly) (op : NullaryIlOp) : IlMachineState = @@ -377,27 +417,179 @@ module AbstractMachine = | Endfinally -> failwith "todo" | Rethrow -> failwith "todo" | Throw -> failwith "todo" + | Localloc -> failwith "todo" + | Stind_I -> failwith "todo" + | Stind_I1 -> failwith "todo" + | Stind_I2 -> failwith "todo" + | Stind_I4 -> failwith "todo" + | Stind_I8 -> failwith "todo" + | Stind_R4 -> failwith "todo" + | Stind_R8 -> failwith "todo" + | Ldind_i -> failwith "todo" + | Ldind_i1 -> failwith "todo" + | Ldind_i2 -> failwith "todo" + | Ldind_i4 -> failwith "todo" + | Ldind_i8 -> failwith "todo" + | Ldind_u1 -> failwith "todo" + | Ldind_u2 -> failwith "todo" + | Ldind_u4 -> failwith "todo" + | Ldind_u8 -> failwith "todo" + | Ldind_r4 -> failwith "todo" + | Ldind_r8 -> failwith "todo" + | Rem -> failwith "todo" + | Rem_un -> failwith "todo" + | Volatile -> failwith "todo" + | Tail -> failwith "todo" + | Conv_ovf_i_un -> failwith "todo" + | Conv_ovf_u_un -> failwith "todo" + | Conv_ovf_i1_un -> failwith "todo" + | Conv_ovf_u1_un -> failwith "todo" + | Conv_ovf_i2_un -> failwith "todo" + | Conv_ovf_u2_un -> failwith "todo" + | Conv_ovf_i4_un -> failwith "todo" + | Conv_ovf_u4_un -> failwith "todo" + | Conv_ovf_i8_un -> failwith "todo" + | Conv_ovf_u8_un -> failwith "todo" + | Conv_ovf_i -> failwith "todo" + | Conv_ovf_u -> failwith "todo" + | Neg -> failwith "todo" + | Not -> failwith "todo" + | Ldind_ref -> failwith "todo" + | Stind_ref -> failwith "todo" + | Ldelem_i -> failwith "todo" + | Ldelem_i1 -> failwith "todo" + | Ldelem_u1 -> failwith "todo" + | Ldelem_i2 -> failwith "todo" + | Ldelem_u2 -> failwith "todo" + | Ldelem_i4 -> failwith "todo" + | Ldelem_u4 -> failwith "todo" + | Ldelem_i8 -> failwith "todo" + | Ldelem_u8 -> failwith "todo" + | Ldelem_r4 -> failwith "todo" + | Ldelem_r8 -> failwith "todo" + | Ldelem_ref -> failwith "todo" + | Stelem_i -> failwith "todo" + | Stelem_i1 -> failwith "todo" + | Stelem_u1 -> failwith "todo" + | Stelem_i2 -> failwith "todo" + | Stelem_u2 -> failwith "todo" + | Stelem_i4 -> failwith "todo" + | Stelem_u4 -> failwith "todo" + | Stelem_i8 -> failwith "todo" + | Stelem_u8 -> failwith "todo" + | Stelem_r4 -> failwith "todo" + | Stelem_r8 -> failwith "todo" + | Stelem_ref -> failwith "todo" let private executeUnaryMetadata + (dotnetRuntimeDirs : string[]) (op : UnaryMetadataTokenIlOp) (metadataToken : MetadataToken) (state : IlMachineState) - (dumped : DumpedAssembly) (thread : ThreadId) : IlMachineState = match op with | Call -> - let handle = - match metadataToken.Kind with - | HandleKind.MethodSpecification -> MethodSpecificationHandle.op_Explicit metadataToken + let state, methodToCall = + match metadataToken with + | MetadataToken.MethodSpecification h -> + let spec = state.ActiveAssembly.MethodSpecs.[h] + + match spec.Method with + | MetadataToken.MethodDef token -> state, state.ActiveAssembly.Methods.[token] + | k -> failwith $"Unrecognised kind: %O{k}" + | MetadataToken.MemberReference h -> + let mem = state.ActiveAssembly.Members.[h] + + let memberSig = + match mem.Signature with + | MemberSignature.Field _ -> failwith "Trying to CALL a field?!" + | MemberSignature.Method method -> method + + let memberName : string = state.ActiveAssembly.Strings mem.Name + + let parent = + match mem.Parent with + | MetadataToken.TypeReference typeRef -> state.ActiveAssembly.TypeRefs.[typeRef] + | parent -> failwith $"Unexpected: {parent}" + + match parent.ResolutionScope with + | AssemblyReference r -> + let state, assy, newAssyName = + let assemblyRef = state.ActiveAssembly.AssemblyReferences.[r] + let assemblyName = state.ActiveAssembly.Strings assemblyRef.Name + + match state.LoadedAssemblies.TryGetValue assemblyName with + | true, v -> state, v, assemblyName + | false, _ -> + let assy = + dotnetRuntimeDirs + |> Seq.choose (fun dir -> + let file = Path.Combine (dir, assemblyName + ".dll") + + try + use f = File.OpenRead file + Console.Error.WriteLine $"Loading {file}" + Assembly.read f |> Some + with :? FileNotFoundException -> + None + ) + |> Seq.exactlyOne + + { state with + LoadedAssemblies = state.LoadedAssemblies |> Map.add assemblyName assy + }, + assy, + assemblyName + + let nsPath = + state.ActiveAssembly.Strings(parent.Namespace).Split '.' |> Array.toList + + let targetNs = assy.NonRootNamespaces.[nsPath] + + let targetType = + targetNs.TypeDefinitions + |> Seq.choose (fun td -> + let ty = assy.TypeDefs.[td] + + if ty.Name = state.ActiveAssembly.Strings parent.Name then + Some ty + else + None + ) + |> Seq.exactlyOne + + let availableMethods = + targetType.Methods + |> List.filter (fun mi -> mi.Name = memberName) + |> List.filter (fun mi -> mi.Signature = memberSig) + + let method = + match availableMethods with + | [] -> failwith $"Could not find member {memberName} with the right signature in CALL" + | [ x ] -> x + | _ -> failwith $"Multiple overloads matching signature for call to {memberName}!" + + { state with + ActiveAssemblyName = newAssyName + }, + method + | k -> failwith $"Unexpected: {k}" + | MetadataToken.MethodDef defn -> state, state.ActiveAssembly.Methods.[defn] | k -> failwith $"Unrecognised kind: %O{k}" - let method = - dumped.Methods.[MethodDefinitionHandle.op_Explicit dumped.MethodSpecs.[handle].Method] + let threadState = + let threadState = state.ThreadState.[thread] + + { threadState with + MethodState = MethodState.Empty methodToCall (Some threadState.MethodState) + } + + { state with + ThreadState = state.ThreadState |> Map.add thread threadState + } - failwith "TODO: now do this!" - state | Callvirt -> failwith "todo" | Castclass -> failwith "todo" | Newobj -> failwith "todo" @@ -413,14 +605,61 @@ module AbstractMachine = | Unbox_Any -> failwith "todo" | Stelem -> failwith "todo" | Ldelem -> failwith "todo" + | Initobj -> failwith "todo" + | Ldsflda -> failwith "todo" + | Ldftn -> failwith "todo" + | Stobj -> failwith "todo" + | Constrained -> failwith "todo" + | Ldtoken -> failwith "todo" + | Cpobj -> failwith "todo" + | Ldobj -> failwith "todo" - let executeOneStep (state : IlMachineState) (dumped : DumpedAssembly) (thread : ThreadId) : IlMachineState = + let private executeUnaryStringToken + (op : UnaryStringTokenIlOp) + (sh : StringToken) + (state : IlMachineState) + (thread : ThreadId) + : IlMachineState + = + match op with + | UnaryStringTokenIlOp.Ldstr -> + let addressToLoad, state = + match state.InternedStrings.TryGetValue sh with + | false, _ -> + let toAllocate = state.ActiveAssembly.Strings sh + let addr, state = IlMachineState.Allocate (ReferenceType.String toAllocate) state + + addr, + { state with + InternedStrings = state.InternedStrings.Add (sh, addr) + } + | true, v -> v, state + + let state = + IlMachineState.PushToStack + (CliObject.Basic (BasicCliObject.ObjectReference (Some addressToLoad))) + thread + state + // +1 for the opcode, +4 for the bytes of the handle. + // TODO: some opcodes are multiple bytes! Should deal with that. + let mutable state = state + + for i = 0 to 4 do + state <- IlMachineState.AdvanceProgramCounter thread state + + state + + let executeOneStep (dotnetRuntimePath : string[]) (state : IlMachineState) (thread : ThreadId) : IlMachineState = let instruction = state.ThreadState.[thread].MethodState + Console.Error.WriteLine + $"[DBG] Executing one step! Now executing: {instruction.IlOpIndex} in {instruction.ExecutingMethod.Name}" + match instruction.ExecutingMethod.Locations.[instruction.IlOpIndex] with - | IlOp.Nullary op -> executeNullary state thread dumped op + | IlOp.Nullary op -> executeNullary state thread op | UnaryConst unaryConstIlOp -> failwith "todo" | UnaryMetadataToken (unaryMetadataTokenIlOp, bytes) -> - executeUnaryMetadata unaryMetadataTokenIlOp bytes state dumped thread + executeUnaryMetadata dotnetRuntimePath unaryMetadataTokenIlOp bytes state thread | Switch immutableArray -> failwith "todo" - | UnaryStringToken (unaryStringTokenIlOp, stringHandle) -> failwith "todo" + | UnaryStringToken (unaryStringTokenIlOp, stringHandle) -> + executeUnaryStringToken unaryStringTokenIlOp stringHandle state thread diff --git a/WoofWare.PawPrint/Assembly.fs b/WoofWare.PawPrint/Assembly.fs index 73dc9b5..7999274 100644 --- a/WoofWare.PawPrint/Assembly.fs +++ b/WoofWare.PawPrint/Assembly.fs @@ -9,36 +9,121 @@ open System.Reflection.Metadata.Ecma335 open System.Reflection.PortableExecutable open Microsoft.FSharp.Core +type AssemblyDefinition = + { + Name : string + } + +type Namespace = + { + Name : StringToken + Parent : NamespaceDefinitionHandle + TypeDefinitions : ImmutableArray + ExportedTypes : ImmutableArray + } + +[] +module Namespace = + /// Returns also the children. + let make + (getString : StringHandle -> string) + (getNamespace : NamespaceDefinitionHandle -> NamespaceDefinition) + (ns : NamespaceDefinition) + : Namespace * ImmutableDictionary + = + let children = ImmutableDictionary.CreateBuilder () + + let rec inner (path : string list) (ns : NamespaceDefinition) : Namespace = + for child in ns.NamespaceDefinitions do + let rendered = getNamespace child + let location = getString rendered.Name :: path + children.Add (List.rev location, inner location rendered) + + { + Name = StringToken.String ns.Name + Parent = ns.Parent + TypeDefinitions = ns.TypeDefinitions + ExportedTypes = ns.ExportedTypes + } + + let result = inner [] ns + result, children.ToImmutable () + +[] +module AssemblyDefinition = + let make + (strings : StringToken -> string) + (assy : System.Reflection.Metadata.AssemblyDefinition) + : AssemblyDefinition + = + { + Name = strings (StringToken.String assy.Name) + } + type DumpedAssembly = { - Types : TypeInfo list + TypeDefs : IReadOnlyDictionary + TypeRefs : IReadOnlyDictionary Methods : IReadOnlyDictionary - MainMethod : MethodDefinitionHandle + Members : IReadOnlyDictionary> + MainMethod : MethodDefinitionHandle option /// Map of four-byte int token to metadata MethodDefinitions : Map - MethodSpecs : ImmutableDictionary + MethodSpecs : ImmutableDictionary + Strings : StringToken -> string + AssemblyReferences : ImmutableDictionary + ThisAssemblyDefinition : AssemblyDefinition + RootNamespace : Namespace + NonRootNamespaces : ImmutableDictionary + // TODO: work out how to render all the strings up front, then drop this + PeReader : PEReader } + interface IDisposable with + member this.Dispose () = this.PeReader.Dispose () + [] module Assembly = let read (dllBytes : Stream) : DumpedAssembly = - use peReader = new PEReader (dllBytes) + let peReader = new PEReader (dllBytes) let metadataReader = peReader.GetMetadataReader () let entryPoint = peReader.PEHeaders.CorHeader.EntryPointTokenOrRelativeVirtualAddress - |> fun x -> if x = 0 then failwith "No entry point" else x + |> fun x -> if x = 0 then None else Some x - let entryPointMethod = MetadataTokens.MethodDefinitionHandle entryPoint + let entryPointMethod = + entryPoint |> Option.map MetadataTokens.MethodDefinitionHandle - let result = - metadataReader.TypeDefinitions - |> Seq.map (TypeInfo.read peReader metadataReader) - |> Seq.toList + let typeRefs = + let builder = ImmutableDictionary.CreateBuilder () + for ty in metadataReader.TypeReferences do + let typeRef = metadataReader.GetTypeReference ty + + let result = + { + Name = StringToken.String typeRef.Name + Namespace = StringToken.String typeRef.Namespace + ResolutionScope = MetadataToken.ofEntityHandle typeRef.ResolutionScope + } + + builder.Add (ty, result) + + builder.ToImmutable () + + let typeDefs = + let builder = ImmutableDictionary.CreateBuilder () + + for ty in metadataReader.TypeDefinitions do + builder.Add (ty, TypeInfo.read peReader metadataReader ty) + + builder.ToImmutable () + + // TODO: this probably misses any methods out which aren't associated with a type definition? let methods = - result - |> List.collect (fun ty -> ty.Methods |> List.map (fun mi -> KeyValuePair (mi.Handle, mi))) + typeDefs + |> Seq.collect (fun (KeyValue (_, ty)) -> ty.Methods |> List.map (fun mi -> KeyValuePair (mi.Handle, mi))) |> ImmutableDictionary.CreateRange let methodDefnMetadata = @@ -57,20 +142,62 @@ module Assembly = (fun i -> let i = i + 1 let handle = MetadataTokens.MethodSpecificationHandle i - KeyValuePair (handle, metadataReader.GetMethodSpecification handle) + KeyValuePair (handle, MethodSpec.make (metadataReader.GetMethodSpecification handle)) ) |> ImmutableDictionary.CreateRange + let memberReferences = + let builder = ImmutableDictionary.CreateBuilder () + + for c in metadataReader.MemberReferences do + builder.Add ( + c, + MemberReference.make + MetadataToken.ofEntityHandle + (metadataReader.GetMemberReference c) + ) + + builder.ToImmutable () + + // TODO: render all this up front + let strings (token : StringToken) = + match token with + | StringToken.String s -> metadataReader.GetString s + | StringToken.UserString s -> metadataReader.GetUserString s + + let assemblyRefs = + let builder = ImmutableDictionary.CreateBuilder () + + for ref in metadataReader.AssemblyReferences do + builder.Add (ref, AssemblyReference.make (metadataReader.GetAssemblyReference ref)) + + builder.ToImmutable () + + let assy = + metadataReader.GetAssemblyDefinition () |> AssemblyDefinition.make strings + + let rootNamespace, nonRootNamespaces = + metadataReader.GetNamespaceDefinitionRoot () + |> Namespace.make metadataReader.GetString metadataReader.GetNamespaceDefinition + { - Types = result + TypeDefs = typeDefs + TypeRefs = typeRefs MainMethod = entryPointMethod Methods = methods MethodDefinitions = methodDefnMetadata MethodSpecs = methodSpecs + Members = memberReferences + Strings = strings + AssemblyReferences = assemblyRefs + ThisAssemblyDefinition = assy + RootNamespace = rootNamespace + NonRootNamespaces = nonRootNamespaces + PeReader = peReader } let print (main : MethodDefinitionHandle) (dumped : DumpedAssembly) : unit = - for typ in dumped.Types do + for KeyValue (_, typ) in dumped.TypeDefs do printfn "\nType: %s.%s" typ.Namespace typ.Name for method in typ.Methods do diff --git a/WoofWare.PawPrint/IlOp.fs b/WoofWare.PawPrint/IlOp.fs index 8bb053e..ecf11e5 100644 --- a/WoofWare.PawPrint/IlOp.fs +++ b/WoofWare.PawPrint/IlOp.fs @@ -1,7 +1,157 @@ namespace WoofWare.PawPrint +open System open System.Collections.Immutable +open System.Reflection open System.Reflection.Metadata +open System.Reflection.Metadata.Ecma335 + +type StringToken = + | UserString of UserStringHandle + | String of StringHandle + +[] +module StringToken = + let ofInt (value : int) : StringToken = + match LanguagePrimitives.EnumOfValue (byte (value &&& 0xFF000000 >>> 24)) with + | HandleKind.UserString -> StringToken.UserString (MetadataTokens.UserStringHandle value) + | HandleKind.String -> StringToken.String (MetadataTokens.StringHandle value) + | v -> failwith $"Unrecognised string handle kind: {v}" + +type MetadataToken = + | MethodDef of MethodDefinitionHandle + | MethodSpecification of MethodSpecificationHandle + | MemberReference of MemberReferenceHandle + | TypeReference of TypeReferenceHandle + | ModuleDefinition of ModuleDefinitionHandle + | AssemblyReference of AssemblyReferenceHandle + | TypeSpecification of TypeSpecificationHandle + | TypeDefinition of TypeDefinitionHandle + | FieldDefinition of FieldDefinitionHandle + | Parameter of ParameterHandle + | InterfaceImplementation of InterfaceImplementationHandle + +[] +module MetadataToken = + let ofInt (value : int32) : MetadataToken = + let asRowNum = value &&& 0x00FFFFFF + + match LanguagePrimitives.EnumOfValue (byte (value &&& 0xFF000000 >>> 24)) with + | HandleKind.ModuleDefinition -> MetadataToken.ModuleDefinition (failwith "TODO") + | HandleKind.TypeReference -> MetadataToken.TypeReference (MetadataTokens.TypeReferenceHandle asRowNum) + | HandleKind.TypeDefinition -> MetadataToken.TypeDefinition (MetadataTokens.TypeDefinitionHandle asRowNum) + | HandleKind.FieldDefinition -> MetadataToken.FieldDefinition (MetadataTokens.FieldDefinitionHandle asRowNum) + | HandleKind.MethodDefinition -> MetadataToken.MethodDef (MetadataTokens.MethodDefinitionHandle asRowNum) + | HandleKind.Parameter -> MetadataToken.Parameter (MetadataTokens.ParameterHandle asRowNum) + | HandleKind.InterfaceImplementation -> + MetadataToken.InterfaceImplementation (MetadataTokens.InterfaceImplementationHandle asRowNum) + | HandleKind.MemberReference -> MetadataToken.MemberReference (MetadataTokens.MemberReferenceHandle asRowNum) + | HandleKind.Constant -> failwith "todo" + | HandleKind.CustomAttribute -> failwith "todo" + | HandleKind.DeclarativeSecurityAttribute -> failwith "todo" + | HandleKind.StandaloneSignature -> failwith "todo" + | HandleKind.EventDefinition -> failwith "todo" + | HandleKind.PropertyDefinition -> failwith "todo" + | HandleKind.MethodImplementation -> failwith "todo" + | HandleKind.ModuleReference -> failwith "todo" + | HandleKind.TypeSpecification -> + MetadataToken.TypeSpecification (MetadataTokens.TypeSpecificationHandle asRowNum) + | HandleKind.AssemblyDefinition -> failwith "todo" + | HandleKind.AssemblyReference -> + MetadataToken.AssemblyReference (MetadataTokens.AssemblyReferenceHandle asRowNum) + | HandleKind.AssemblyFile -> failwith "todo" + | HandleKind.ExportedType -> failwith "todo" + | HandleKind.ManifestResource -> failwith "todo" + | HandleKind.GenericParameter -> failwith "todo" + | HandleKind.MethodSpecification -> + MetadataToken.MethodSpecification (MetadataTokens.MethodSpecificationHandle asRowNum) + | HandleKind.GenericParameterConstraint -> failwith "todo" + | HandleKind.Document -> failwith "todo" + | HandleKind.MethodDebugInformation -> failwith "todo" + | HandleKind.LocalScope -> failwith "todo" + | HandleKind.LocalVariable -> failwith "todo" + | HandleKind.LocalConstant -> failwith "todo" + | HandleKind.ImportScope -> failwith "todo" + | HandleKind.CustomDebugInformation -> failwith "todo" + | HandleKind.UserString -> failwith "todo" + | HandleKind.Blob -> failwith "todo" + | HandleKind.Guid -> failwith "todo" + | HandleKind.String -> failwith "todo" + | HandleKind.NamespaceDefinition -> failwith "todo" + | h -> failwith $"Unrecognised kind: {h}" + + let ofEntityHandle (eh : EntityHandle) : MetadataToken = ofInt (eh.GetHashCode ()) + +type MemberSignature = + | Field of TypeDefn + | Method of TypeMethodSignature + +type MemberReference<'parent> = + { + Name : StringToken + Parent : 'parent + Signature : MemberSignature + } + +type MemberRefSigSwitch = + | Default + | Field + | VarArg + | Generic + + static member Identify (b : byte) = + match b &&& 0xFuy with + | 0uy -> MemberRefSigSwitch.Default + | 5uy -> MemberRefSigSwitch.VarArg + | 6uy -> MemberRefSigSwitch.Field + | 0x10uy -> MemberRefSigSwitch.Generic + | n -> failwith $"Bad member ref sig: %i{n}" + +[] +module MemberReference = + let make<'parent> + (makeParent : EntityHandle -> 'parent) + (mr : System.Reflection.Metadata.MemberReference) + : MemberReference<'parent> + = + let name = StringToken.String mr.Name + + let signature = + try + mr.DecodeMethodSignature (TypeDefn.typeProvider, ()) |> Choice1Of2 + with :? BadImageFormatException -> + mr.DecodeFieldSignature (TypeDefn.typeProvider, ()) |> Choice2Of2 + + let signature = + match signature with + | Choice1Of2 methodSignature -> TypeMethodSignature.make methodSignature |> MemberSignature.Method + | Choice2Of2 typeDefn -> MemberSignature.Field typeDefn + + { + Name = name + // Horrible abuse to get this as an int + Parent = makeParent mr.Parent + Signature = signature + } + +type AssemblyReference = + { + Culture : StringToken + Flags : AssemblyFlags + Name : StringToken + Version : Version + } + +[] +module AssemblyReference = + let make (ref : System.Reflection.Metadata.AssemblyReference) : AssemblyReference = + { + Culture = StringToken.String ref.Culture + Flags = ref.Flags + Name = StringToken.String ref.Name + Version = ref.Version + } + type NullaryIlOp = | Nop @@ -47,9 +197,15 @@ type NullaryIlOp = | Mul_ovf_un | Div | Div_un + | Rem + | Rem_un + | Neg + | Not | Shr | Shr_un | Shl + | Conv_ovf_i + | Conv_ovf_u | And | Or | Xor @@ -70,6 +226,63 @@ type NullaryIlOp = | Endfinally | Rethrow | Throw + | Localloc + | Ldind_ref + | Stind_ref + | Stind_I + | Stind_I1 + | Stind_I2 + | Stind_I4 + | Stind_I8 + | Stind_R4 + | Stind_R8 + | Ldind_i + | Ldind_i1 + | Ldind_i2 + | Ldind_i4 + | Ldind_i8 + | Ldind_u1 + | Ldind_u2 + | Ldind_u4 + | Ldind_u8 + | Ldind_r4 + | Ldind_r8 + | Volatile + | Tail + | Conv_ovf_i_un + | Conv_ovf_u_un + | Conv_ovf_i1_un + | Conv_ovf_u1_un + | Conv_ovf_i2_un + | Conv_ovf_u2_un + | Conv_ovf_i4_un + | Conv_ovf_u4_un + | Conv_ovf_i8_un + | Conv_ovf_u8_un + | Ldelem_i + | Ldelem_i1 + | Ldelem_u1 + | Ldelem_i2 + | Ldelem_u2 + | Ldelem_i4 + | Ldelem_u4 + | Ldelem_i8 + | Ldelem_u8 + | Ldelem_r4 + | Ldelem_r8 + | Ldelem_ref + | Stelem_i + | Stelem_i1 + | Stelem_u1 + | Stelem_i2 + | Stelem_u2 + | Stelem_i4 + | Stelem_u4 + | Stelem_i8 + | Stelem_u8 + | Stelem_r4 + | Stelem_r8 + | Stelem_ref type UnaryConstIlOp = | Stloc of uint16 @@ -88,6 +301,11 @@ type UnaryConstIlOp = | Ble_s of int8 | Bgt_s of int8 | Bge_s of int8 + | Beq of int32 + | Blt of int32 + | Ble of int32 + | Bgt of int32 + | Bge of int32 | Bne_un_s of int8 | Bge_un_s of int8 | Bgt_un_s of int8 @@ -122,16 +340,20 @@ type UnaryMetadataTokenIlOp = | Ldfld | Ldflda | Ldsfld + | Ldsflda | Unbox_Any | Stelem | Ldelem + | Initobj + | Ldftn + | Stobj + | Constrained + | Ldtoken + | Cpobj + | Ldobj type UnaryStringTokenIlOp = | Ldstr -/// A four-byte metadata token. -type MetadataToken = EntityHandle -type StringToken = StringHandle - type IlOp = | Nullary of NullaryIlOp | UnaryConst of UnaryConstIlOp diff --git a/WoofWare.PawPrint/TypeDefn.fs b/WoofWare.PawPrint/TypeDefn.fs new file mode 100644 index 0000000..0acf843 --- /dev/null +++ b/WoofWare.PawPrint/TypeDefn.fs @@ -0,0 +1,169 @@ +namespace WoofWare.PawPrint + +open System.Collections.Immutable +open System.Reflection.Metadata +open System.Reflection.Metadata.Ecma335 + +type TypeMethodSignature<'Types> = + { + Header : SignatureHeader + ParameterTypes : 'Types list + GenericParameterCount : int + RequiredParameterCount : int + ReturnType : 'Types + } + +[] +module TypeMethodSignature = + let make<'T> (p : MethodSignature<'T>) : TypeMethodSignature<'T> = + { + Header = p.Header + ReturnType = p.ReturnType + ParameterTypes = List.ofSeq p.ParameterTypes + GenericParameterCount = p.GenericParameterCount + RequiredParameterCount = p.RequiredParameterCount + } + + +type PrimitiveType = + | Void + | Boolean + | Char + | SByte + | Byte + | Int16 + | UInt16 + | Int32 + | UInt32 + | Int64 + | UInt64 + | Single + | Double + | String + | TypedReference + | IntPtr + | UIntPtr + | Object + + static member OfEnum (ptc : PrimitiveTypeCode) : PrimitiveType = + match ptc with + | PrimitiveTypeCode.Void -> PrimitiveType.Void + | PrimitiveTypeCode.Boolean -> PrimitiveType.Boolean + | PrimitiveTypeCode.Char -> PrimitiveType.Char + | PrimitiveTypeCode.SByte -> PrimitiveType.SByte + | PrimitiveTypeCode.Byte -> PrimitiveType.Byte + | PrimitiveTypeCode.Int16 -> PrimitiveType.Int16 + | PrimitiveTypeCode.UInt16 -> PrimitiveType.UInt16 + | PrimitiveTypeCode.Int32 -> PrimitiveType.Int32 + | PrimitiveTypeCode.UInt32 -> PrimitiveType.UInt32 + | PrimitiveTypeCode.Int64 -> PrimitiveType.Int64 + | PrimitiveTypeCode.UInt64 -> PrimitiveType.UInt64 + | PrimitiveTypeCode.Single -> PrimitiveType.Single + | PrimitiveTypeCode.Double -> PrimitiveType.Double + | PrimitiveTypeCode.String -> PrimitiveType.String + | PrimitiveTypeCode.TypedReference -> PrimitiveType.TypedReference + | PrimitiveTypeCode.IntPtr -> PrimitiveType.IntPtr + | PrimitiveTypeCode.UIntPtr -> PrimitiveType.UIntPtr + | PrimitiveTypeCode.Object -> PrimitiveType.Object + | x -> failwithf $"Unrecognised primitive type code: %O{x}" + +type TypeDefn = + | PrimitiveType of PrimitiveType + | Pinned of TypeDefn + | Pointer of TypeDefn + | Byref of TypeDefn + | OneDimensionalArrayLowerBoundZero of elements : TypeDefn + | Modified of original : TypeDefn * afterMod : TypeDefn * modificationRequired : bool + | FromReference of SignatureTypeKind + | FromDefinition of SignatureTypeKind + | GenericInstantiation of generic : TypeDefn * args : ImmutableArray + | FunctionPointer of TypeMethodSignature + | GenericTypeParameter of index : int + | GenericMethodParameter of index : int + +[] +module TypeDefn = + let fromTypeCode (s : SignatureTypeCode) : TypeDefn = + match s with + | SignatureTypeCode.Invalid -> failwith "todo" + | SignatureTypeCode.Void -> TypeDefn.PrimitiveType PrimitiveType.Void + | SignatureTypeCode.Boolean -> TypeDefn.PrimitiveType PrimitiveType.Boolean + | SignatureTypeCode.Char -> TypeDefn.PrimitiveType PrimitiveType.Char + | SignatureTypeCode.SByte -> TypeDefn.PrimitiveType PrimitiveType.SByte + | SignatureTypeCode.Byte -> TypeDefn.PrimitiveType PrimitiveType.Byte + | SignatureTypeCode.Int16 -> TypeDefn.PrimitiveType PrimitiveType.Int16 + | SignatureTypeCode.UInt16 -> TypeDefn.PrimitiveType PrimitiveType.UInt16 + | SignatureTypeCode.Int32 -> TypeDefn.PrimitiveType PrimitiveType.Int32 + | SignatureTypeCode.UInt32 -> TypeDefn.PrimitiveType PrimitiveType.UInt32 + | SignatureTypeCode.Int64 -> TypeDefn.PrimitiveType PrimitiveType.Int64 + | SignatureTypeCode.UInt64 -> TypeDefn.PrimitiveType PrimitiveType.UInt64 + | SignatureTypeCode.Single -> TypeDefn.PrimitiveType PrimitiveType.Single + | SignatureTypeCode.Double -> TypeDefn.PrimitiveType PrimitiveType.Double + | SignatureTypeCode.String -> TypeDefn.PrimitiveType PrimitiveType.String + | SignatureTypeCode.Pointer -> failwith "todo" + | SignatureTypeCode.ByReference -> failwith "TODO" + | SignatureTypeCode.GenericTypeParameter -> failwith "todo" + | SignatureTypeCode.Array -> failwith "todo" + | SignatureTypeCode.GenericTypeInstance -> failwith "todo" + | SignatureTypeCode.TypedReference -> TypeDefn.PrimitiveType PrimitiveType.TypedReference + | SignatureTypeCode.IntPtr -> TypeDefn.PrimitiveType PrimitiveType.IntPtr + | SignatureTypeCode.UIntPtr -> failwith "todo" + | SignatureTypeCode.FunctionPointer -> failwith "todo" + | SignatureTypeCode.Object -> failwith "todo" + | SignatureTypeCode.SZArray -> failwith "todo" + | SignatureTypeCode.GenericMethodParameter -> failwith "todo" + | SignatureTypeCode.RequiredModifier -> failwith "todo" + | SignatureTypeCode.OptionalModifier -> failwith "todo" + | SignatureTypeCode.TypeHandle -> failwith "todo" + | SignatureTypeCode.Sentinel -> failwith "todo" + | SignatureTypeCode.Pinned -> failwith "todo" + | x -> failwith $"Unrecognised type code: {x}" + + let typeProvider = + { new ISignatureTypeProvider with + member this.GetArrayType (elementType : TypeDefn, shape : ArrayShape) : TypeDefn = failwith "TODO" + member this.GetByReferenceType (elementType : TypeDefn) : TypeDefn = TypeDefn.Byref elementType + + member this.GetSZArrayType (elementType : TypeDefn) : TypeDefn = + TypeDefn.OneDimensionalArrayLowerBoundZero elementType + + member this.GetPrimitiveType (elementType : PrimitiveTypeCode) : TypeDefn = + PrimitiveType.OfEnum elementType |> TypeDefn.PrimitiveType + + member this.GetGenericInstantiation + (generic : TypeDefn, typeArguments : ImmutableArray) + : TypeDefn + = + TypeDefn.GenericInstantiation (generic, typeArguments) + + member this.GetTypeFromDefinition + (reader : MetadataReader, handle : TypeDefinitionHandle, rawTypeKind : byte) + : TypeDefn + = + let handle : EntityHandle = TypeDefinitionHandle.op_Implicit handle + let typeKind = reader.ResolveSignatureTypeKind (handle, rawTypeKind) + + TypeDefn.FromDefinition typeKind + + member this.GetTypeFromReference + (reader : MetadataReader, handle : TypeReferenceHandle, rawTypeKind : byte) + : TypeDefn + = + let handle : EntityHandle = TypeReferenceHandle.op_Implicit handle + let typeKind = reader.ResolveSignatureTypeKind (handle, rawTypeKind) + TypeDefn.FromReference typeKind + + member this.GetPointerType (typeCode : TypeDefn) : TypeDefn = TypeDefn.Pointer typeCode + + member this.GetFunctionPointerType (signature) = + TypeDefn.FunctionPointer (TypeMethodSignature.make signature) + + member this.GetGenericMethodParameter (genericContext, index) = TypeDefn.GenericMethodParameter index + member this.GetGenericTypeParameter (genericContext, index) = TypeDefn.GenericTypeParameter index + + member this.GetModifiedType (modifier, unmodifiedType, isRequired) = + TypeDefn.Modified (unmodifiedType, modifier, isRequired) + + member this.GetPinnedType (elementType) = TypeDefn.Pinned elementType + member this.GetTypeFromSpecification (reader, genericContext, handle, rawTypeKind) = failwith "todo" + } diff --git a/WoofWare.PawPrint/TypeInfo.fs b/WoofWare.PawPrint/TypeInfo.fs index 707cd30..47bee8b 100644 --- a/WoofWare.PawPrint/TypeInfo.fs +++ b/WoofWare.PawPrint/TypeInfo.fs @@ -3,7 +3,9 @@ namespace WoofWare.PawPrint #nowarn "9" open System +open System.Collections.Generic open System.Collections.Immutable +open System.Reflection open System.Reflection.Metadata open System.Reflection.Metadata.Ecma335 open System.Reflection.PortableExecutable @@ -22,83 +24,19 @@ type GenericParameter = SequenceNumber : int } -type TypeMethodSignature<'Types> = +type MethodSpec = { - Header : SignatureHeader - ParameterTypes : ImmutableArray<'Types> - GenericParameterCount : int - RequiredParameterCount : int - ReturnType : 'Types + Method : MetadataToken } [] -module TypeMethodSignature = - let make<'T> (p : MethodSignature<'T>) : TypeMethodSignature<'T> = +module MethodSpec = + let make (p : MethodSpecification) : MethodSpec = { - Header = p.Header - ReturnType = p.ReturnType - ParameterTypes = p.ParameterTypes - GenericParameterCount = p.GenericParameterCount - RequiredParameterCount = p.RequiredParameterCount + // Horrible abuse to get this as an int + Method = MetadataToken.ofInt (p.Method.GetHashCode ()) } -type PrimitiveType = - | Void - | Boolean - | Char - | SByte - | Byte - | Int16 - | UInt16 - | Int32 - | UInt32 - | Int64 - | UInt64 - | Single - | Double - | String - | TypedReference - | IntPtr - | UIntPtr - | Object - - static member OfEnum (ptc : PrimitiveTypeCode) : PrimitiveType = - match ptc with - | PrimitiveTypeCode.Void -> PrimitiveType.Void - | PrimitiveTypeCode.Boolean -> PrimitiveType.Boolean - | PrimitiveTypeCode.Char -> PrimitiveType.Char - | PrimitiveTypeCode.SByte -> PrimitiveType.SByte - | PrimitiveTypeCode.Byte -> PrimitiveType.Byte - | PrimitiveTypeCode.Int16 -> PrimitiveType.Int16 - | PrimitiveTypeCode.UInt16 -> PrimitiveType.UInt16 - | PrimitiveTypeCode.Int32 -> PrimitiveType.Int32 - | PrimitiveTypeCode.UInt32 -> PrimitiveType.UInt32 - | PrimitiveTypeCode.Int64 -> PrimitiveType.Int64 - | PrimitiveTypeCode.UInt64 -> PrimitiveType.UInt64 - | PrimitiveTypeCode.Single -> PrimitiveType.Single - | PrimitiveTypeCode.Double -> PrimitiveType.Double - | PrimitiveTypeCode.String -> PrimitiveType.String - | PrimitiveTypeCode.TypedReference -> PrimitiveType.TypedReference - | PrimitiveTypeCode.IntPtr -> PrimitiveType.IntPtr - | PrimitiveTypeCode.UIntPtr -> PrimitiveType.UIntPtr - | PrimitiveTypeCode.Object -> PrimitiveType.Object - | x -> failwithf $"Unrecognised primitive type code: %O{x}" - -type TypeDefn = - | PrimitiveType of PrimitiveType - | Pinned of TypeDefn - | Pointer of TypeDefn - | Byref of TypeDefn - | OneDimensionalArrayLowerBoundZero of elements : TypeDefn - | Modified of original : TypeDefn * afterMod : TypeDefn * modificationRequired : bool - | FromReference of SignatureTypeKind - | FromDefinition of SignatureTypeKind - | GenericInstantiation of generic : TypeDefn * args : ImmutableArray - | FunctionPointer of TypeMethodSignature - | GenericTypeParameter of index : int - | GenericMethodParameter of index : int - - type MethodInfo = { Handle : MethodDefinitionHandle @@ -110,6 +48,8 @@ type MethodInfo = Parameters : Parameter ImmutableArray Generics : GenericParameter ImmutableArray Signature : TypeMethodSignature + IsPinvokeImpl : bool + LocalsInit : bool } type TypeInfo = @@ -117,6 +57,14 @@ type TypeInfo = Namespace : string Name : string Methods : MethodInfo list + MethodImpls : ImmutableDictionary + } + +type TypeRef = + { + Name : StringToken + Namespace : StringToken + ResolutionScope : MetadataToken } [] @@ -131,14 +79,23 @@ module TypeInfo = LanguagePrimitives.EnumOfValue (uint16 op) let private readMetadataToken (reader : byref) : MetadataToken = - reader.ReadUInt32 () |> int |> MetadataTokens.EntityHandle + reader.ReadUInt32 () |> int |> MetadataToken.ofInt let private readStringToken (reader : byref) : StringToken = - reader.ReadUInt32 () |> int |> MetadataTokens.StringHandle + let value = reader.ReadUInt32 () |> int + StringToken.ofInt value - let private readMethodBody (peReader : PEReader) (methodDef : MethodDefinition) : (IlOp * int) list = + type private MethodBody = + { + Instructions : (IlOp * int) list + LocalInit : bool + MaxStackSize : int + ExceptionRegions : ImmutableArray + } + + let private readMethodBody (peReader : PEReader) (methodDef : MethodDefinition) : MethodBody option = if methodDef.RelativeVirtualAddress = 0 then - [] + None else let methodBody = peReader.GetMethodBody methodDef.RelativeVirtualAddress let ilBytes = methodBody.GetILBytes () @@ -213,11 +170,11 @@ module TypeInfo = | ILOpCode.Br -> IlOp.UnaryConst (UnaryConstIlOp.Br (reader.ReadInt32 ())) | ILOpCode.Brfalse -> IlOp.UnaryConst (UnaryConstIlOp.Brfalse (reader.ReadInt32 ())) | ILOpCode.Brtrue -> IlOp.UnaryConst (UnaryConstIlOp.Brtrue (reader.ReadInt32 ())) - | ILOpCode.Beq -> failwith "todo" - | ILOpCode.Bge -> failwith "todo" - | ILOpCode.Bgt -> failwith "todo" - | ILOpCode.Ble -> failwith "todo" - | ILOpCode.Blt -> failwith "todo" + | ILOpCode.Beq -> IlOp.UnaryConst (UnaryConstIlOp.Beq (reader.ReadInt32 ())) + | ILOpCode.Bge -> IlOp.UnaryConst (UnaryConstIlOp.Bge (reader.ReadInt32 ())) + | ILOpCode.Bgt -> IlOp.UnaryConst (UnaryConstIlOp.Bgt (reader.ReadInt32 ())) + | ILOpCode.Ble -> IlOp.UnaryConst (UnaryConstIlOp.Ble (reader.ReadInt32 ())) + | ILOpCode.Blt -> IlOp.UnaryConst (UnaryConstIlOp.Blt (reader.ReadInt32 ())) | ILOpCode.Bne_un -> IlOp.UnaryConst (UnaryConstIlOp.Bne_un (reader.ReadInt32 ())) | ILOpCode.Bge_un -> IlOp.UnaryConst (UnaryConstIlOp.Bge_un (reader.ReadInt32 ())) | ILOpCode.Bgt_un -> IlOp.UnaryConst (UnaryConstIlOp.Bgt_un (reader.ReadInt32 ())) @@ -236,39 +193,39 @@ module TypeInfo = result.Add (reader.ReadInt32 ()) IlOp.Switch (result.ToImmutable ()) - | ILOpCode.Ldind_i1 -> failwith "todo" - | ILOpCode.Ldind_u1 -> failwith "todo" - | ILOpCode.Ldind_i2 -> failwith "todo" - | ILOpCode.Ldind_u2 -> failwith "todo" - | ILOpCode.Ldind_i4 -> failwith "todo" - | ILOpCode.Ldind_u4 -> failwith "todo" - | ILOpCode.Ldind_i8 -> failwith "todo" - | ILOpCode.Ldind_i -> failwith "todo" - | ILOpCode.Ldind_r4 -> failwith "todo" - | ILOpCode.Ldind_r8 -> failwith "todo" - | ILOpCode.Ldind_ref -> failwith "todo" - | ILOpCode.Stind_ref -> failwith "todo" - | ILOpCode.Stind_i1 -> failwith "todo" - | ILOpCode.Stind_i2 -> failwith "todo" - | ILOpCode.Stind_i4 -> failwith "todo" - | ILOpCode.Stind_i8 -> failwith "todo" - | ILOpCode.Stind_r4 -> failwith "todo" - | ILOpCode.Stind_r8 -> failwith "todo" + | ILOpCode.Ldind_i -> IlOp.Nullary NullaryIlOp.Ldind_i + | ILOpCode.Ldind_i1 -> IlOp.Nullary NullaryIlOp.Ldind_i1 + | ILOpCode.Ldind_u1 -> IlOp.Nullary NullaryIlOp.Ldind_u1 + | ILOpCode.Ldind_i2 -> IlOp.Nullary NullaryIlOp.Ldind_i2 + | ILOpCode.Ldind_u2 -> IlOp.Nullary NullaryIlOp.Ldind_u2 + | ILOpCode.Ldind_i4 -> IlOp.Nullary NullaryIlOp.Ldind_i4 + | ILOpCode.Ldind_u4 -> IlOp.Nullary NullaryIlOp.Ldind_u4 + | ILOpCode.Ldind_i8 -> IlOp.Nullary NullaryIlOp.Ldind_i8 + | ILOpCode.Ldind_r4 -> IlOp.Nullary NullaryIlOp.Ldind_r4 + | ILOpCode.Ldind_r8 -> IlOp.Nullary NullaryIlOp.Ldind_r8 + | ILOpCode.Ldind_ref -> IlOp.Nullary NullaryIlOp.Ldind_ref + | ILOpCode.Stind_ref -> IlOp.Nullary NullaryIlOp.Stind_ref + | ILOpCode.Stind_i1 -> IlOp.Nullary NullaryIlOp.Stind_I1 + | ILOpCode.Stind_i2 -> IlOp.Nullary NullaryIlOp.Stind_I2 + | ILOpCode.Stind_i4 -> IlOp.Nullary NullaryIlOp.Stind_I4 + | ILOpCode.Stind_i8 -> IlOp.Nullary NullaryIlOp.Stind_I8 + | ILOpCode.Stind_r4 -> IlOp.Nullary NullaryIlOp.Stind_R4 + | ILOpCode.Stind_r8 -> IlOp.Nullary NullaryIlOp.Stind_R8 | ILOpCode.Add -> IlOp.Nullary NullaryIlOp.Add | ILOpCode.Sub -> IlOp.Nullary NullaryIlOp.Sub | ILOpCode.Mul -> IlOp.Nullary NullaryIlOp.Mul | ILOpCode.Div -> IlOp.Nullary NullaryIlOp.Div | ILOpCode.Div_un -> IlOp.Nullary NullaryIlOp.Div_un - | ILOpCode.Rem -> failwith "todo" - | ILOpCode.Rem_un -> failwith "todo" + | ILOpCode.Rem -> IlOp.Nullary NullaryIlOp.Rem + | ILOpCode.Rem_un -> IlOp.Nullary NullaryIlOp.Rem_un | ILOpCode.And -> IlOp.Nullary NullaryIlOp.And | ILOpCode.Or -> IlOp.Nullary NullaryIlOp.Or | ILOpCode.Xor -> IlOp.Nullary NullaryIlOp.Xor | ILOpCode.Shl -> IlOp.Nullary NullaryIlOp.Shl | ILOpCode.Shr -> IlOp.Nullary NullaryIlOp.Shr | ILOpCode.Shr_un -> IlOp.Nullary NullaryIlOp.Shr_un - | ILOpCode.Neg -> failwith "todo" - | ILOpCode.Not -> failwith "todo" + | ILOpCode.Neg -> IlOp.Nullary NullaryIlOp.Neg + | ILOpCode.Not -> IlOp.Nullary NullaryIlOp.Not | ILOpCode.Conv_i1 -> IlOp.Nullary NullaryIlOp.Conv_I1 | ILOpCode.Conv_i2 -> IlOp.Nullary NullaryIlOp.Conv_I2 | ILOpCode.Conv_i4 -> IlOp.Nullary NullaryIlOp.Conv_I4 @@ -279,8 +236,10 @@ module TypeInfo = | ILOpCode.Conv_u8 -> IlOp.Nullary NullaryIlOp.Conv_U8 | ILOpCode.Callvirt -> IlOp.UnaryMetadataToken (UnaryMetadataTokenIlOp.Callvirt, readMetadataToken &reader) - | ILOpCode.Cpobj -> failwith "todo" - | ILOpCode.Ldobj -> failwith "todo" + | ILOpCode.Cpobj -> + IlOp.UnaryMetadataToken (UnaryMetadataTokenIlOp.Cpobj, readMetadataToken &reader) + | ILOpCode.Ldobj -> + IlOp.UnaryMetadataToken (UnaryMetadataTokenIlOp.Ldobj, readMetadataToken &reader) | ILOpCode.Ldstr -> IlOp.UnaryStringToken (UnaryStringTokenIlOp.Ldstr, readStringToken &reader) | ILOpCode.Newobj -> IlOp.UnaryMetadataToken (UnaryMetadataTokenIlOp.Newobj, readMetadataToken &reader) @@ -299,20 +258,22 @@ module TypeInfo = IlOp.UnaryMetadataToken (UnaryMetadataTokenIlOp.Stfld, readMetadataToken &reader) | ILOpCode.Ldsfld -> IlOp.UnaryMetadataToken (UnaryMetadataTokenIlOp.Ldsfld, readMetadataToken &reader) - | ILOpCode.Ldsflda -> failwith "todo" + | ILOpCode.Ldsflda -> + IlOp.UnaryMetadataToken (UnaryMetadataTokenIlOp.Ldsflda, readMetadataToken &reader) | ILOpCode.Stsfld -> IlOp.UnaryMetadataToken (UnaryMetadataTokenIlOp.Stsfld, readMetadataToken &reader) - | ILOpCode.Stobj -> failwith "todo" - | ILOpCode.Conv_ovf_i1_un -> failwith "todo" - | ILOpCode.Conv_ovf_i2_un -> failwith "todo" - | ILOpCode.Conv_ovf_i4_un -> failwith "todo" - | ILOpCode.Conv_ovf_i8_un -> failwith "todo" - | ILOpCode.Conv_ovf_u1_un -> failwith "todo" - | ILOpCode.Conv_ovf_u2_un -> failwith "todo" - | ILOpCode.Conv_ovf_u4_un -> failwith "todo" - | ILOpCode.Conv_ovf_u8_un -> failwith "todo" - | ILOpCode.Conv_ovf_i_un -> failwith "todo" - | ILOpCode.Conv_ovf_u_un -> failwith "todo" + | ILOpCode.Stobj -> + IlOp.UnaryMetadataToken (UnaryMetadataTokenIlOp.Stobj, readMetadataToken &reader) + | ILOpCode.Conv_ovf_i_un -> IlOp.Nullary NullaryIlOp.Conv_ovf_i_un + | ILOpCode.Conv_ovf_i1_un -> IlOp.Nullary NullaryIlOp.Conv_ovf_i1_un + | ILOpCode.Conv_ovf_i2_un -> IlOp.Nullary NullaryIlOp.Conv_ovf_i2_un + | ILOpCode.Conv_ovf_i4_un -> IlOp.Nullary NullaryIlOp.Conv_ovf_i4_un + | ILOpCode.Conv_ovf_i8_un -> IlOp.Nullary NullaryIlOp.Conv_ovf_i8_un + | ILOpCode.Conv_ovf_u_un -> IlOp.Nullary NullaryIlOp.Conv_ovf_u_un + | ILOpCode.Conv_ovf_u1_un -> IlOp.Nullary NullaryIlOp.Conv_ovf_u1_un + | ILOpCode.Conv_ovf_u2_un -> IlOp.Nullary NullaryIlOp.Conv_ovf_u2_un + | ILOpCode.Conv_ovf_u4_un -> IlOp.Nullary NullaryIlOp.Conv_ovf_u4_un + | ILOpCode.Conv_ovf_u8_un -> IlOp.Nullary NullaryIlOp.Conv_ovf_u8_un | ILOpCode.Box -> IlOp.UnaryMetadataToken (UnaryMetadataTokenIlOp.Box, readMetadataToken &reader) | ILOpCode.Newarr -> @@ -320,25 +281,25 @@ module TypeInfo = | ILOpCode.Ldlen -> IlOp.Nullary NullaryIlOp.LdLen | ILOpCode.Ldelema -> IlOp.UnaryMetadataToken (UnaryMetadataTokenIlOp.Ldelema, readMetadataToken &reader) - | ILOpCode.Ldelem_i1 -> failwith "todo" - | ILOpCode.Ldelem_u1 -> failwith "todo" - | ILOpCode.Ldelem_i2 -> failwith "todo" - | ILOpCode.Ldelem_u2 -> failwith "todo" - | ILOpCode.Ldelem_i4 -> failwith "todo" - | ILOpCode.Ldelem_u4 -> failwith "todo" - | ILOpCode.Ldelem_i8 -> failwith "todo" - | ILOpCode.Ldelem_i -> failwith "todo" - | ILOpCode.Ldelem_r4 -> failwith "todo" - | ILOpCode.Ldelem_r8 -> failwith "todo" - | ILOpCode.Ldelem_ref -> failwith "todo" - | ILOpCode.Stelem_i -> failwith "todo" - | ILOpCode.Stelem_i1 -> failwith "todo" - | ILOpCode.Stelem_i2 -> failwith "todo" - | ILOpCode.Stelem_i4 -> failwith "todo" - | ILOpCode.Stelem_i8 -> failwith "todo" - | ILOpCode.Stelem_r4 -> failwith "todo" - | ILOpCode.Stelem_r8 -> failwith "todo" - | ILOpCode.Stelem_ref -> failwith "todo" + | ILOpCode.Ldelem_i1 -> IlOp.Nullary NullaryIlOp.Ldelem_i1 + | ILOpCode.Ldelem_u1 -> IlOp.Nullary NullaryIlOp.Ldelem_u1 + | ILOpCode.Ldelem_i2 -> IlOp.Nullary NullaryIlOp.Ldelem_i2 + | ILOpCode.Ldelem_u2 -> IlOp.Nullary NullaryIlOp.Ldelem_u2 + | ILOpCode.Ldelem_i4 -> IlOp.Nullary NullaryIlOp.Ldelem_i4 + | ILOpCode.Ldelem_u4 -> IlOp.Nullary NullaryIlOp.Ldelem_u4 + | ILOpCode.Ldelem_i8 -> IlOp.Nullary NullaryIlOp.Ldelem_i8 + | ILOpCode.Ldelem_i -> IlOp.Nullary NullaryIlOp.Ldelem_i + | ILOpCode.Ldelem_r4 -> IlOp.Nullary NullaryIlOp.Ldelem_r4 + | ILOpCode.Ldelem_r8 -> IlOp.Nullary NullaryIlOp.Ldelem_r8 + | ILOpCode.Ldelem_ref -> IlOp.Nullary NullaryIlOp.Ldelem_ref + | ILOpCode.Stelem_i -> IlOp.Nullary NullaryIlOp.Stelem_i + | ILOpCode.Stelem_i1 -> IlOp.Nullary NullaryIlOp.Stelem_i1 + | ILOpCode.Stelem_i2 -> IlOp.Nullary NullaryIlOp.Stelem_i2 + | ILOpCode.Stelem_i4 -> IlOp.Nullary NullaryIlOp.Stelem_i4 + | ILOpCode.Stelem_i8 -> IlOp.Nullary NullaryIlOp.Stelem_i8 + | ILOpCode.Stelem_r4 -> IlOp.Nullary NullaryIlOp.Stelem_r4 + | ILOpCode.Stelem_r8 -> IlOp.Nullary NullaryIlOp.Stelem_r8 + | ILOpCode.Stelem_ref -> IlOp.Nullary NullaryIlOp.Stelem_ref | ILOpCode.Ldelem -> IlOp.UnaryMetadataToken (UnaryMetadataTokenIlOp.Ldelem, readMetadataToken &reader) | ILOpCode.Stelem -> @@ -356,30 +317,32 @@ module TypeInfo = | ILOpCode.Refanyval -> failwith "todo" | ILOpCode.Ckfinite -> failwith "todo" | ILOpCode.Mkrefany -> failwith "todo" - | ILOpCode.Ldtoken -> failwith "todo" + | ILOpCode.Ldtoken -> + IlOp.UnaryMetadataToken (UnaryMetadataTokenIlOp.Ldtoken, readMetadataToken &reader) | ILOpCode.Conv_u2 -> IlOp.Nullary NullaryIlOp.Conv_U2 | ILOpCode.Conv_u1 -> IlOp.Nullary NullaryIlOp.Conv_U1 | ILOpCode.Conv_i -> IlOp.Nullary NullaryIlOp.Conv_I - | ILOpCode.Conv_ovf_i -> failwith "todo" - | ILOpCode.Conv_ovf_u -> failwith "todo" - | ILOpCode.Add_ovf -> failwith "todo" - | ILOpCode.Add_ovf_un -> failwith "todo" - | ILOpCode.Mul_ovf -> failwith "todo" - | ILOpCode.Mul_ovf_un -> failwith "todo" - | ILOpCode.Sub_ovf -> failwith "todo" - | ILOpCode.Sub_ovf_un -> failwith "todo" + | ILOpCode.Conv_ovf_i -> IlOp.Nullary NullaryIlOp.Conv_ovf_i + | ILOpCode.Conv_ovf_u -> IlOp.Nullary NullaryIlOp.Conv_ovf_u + | ILOpCode.Add_ovf -> IlOp.Nullary NullaryIlOp.Add_ovf + | ILOpCode.Add_ovf_un -> IlOp.Nullary NullaryIlOp.Add_ovf_un + | ILOpCode.Mul_ovf -> IlOp.Nullary NullaryIlOp.Mul_ovf + | ILOpCode.Mul_ovf_un -> IlOp.Nullary NullaryIlOp.Mul_ovf_un + | ILOpCode.Sub_ovf -> IlOp.Nullary NullaryIlOp.Sub_ovf + | ILOpCode.Sub_ovf_un -> IlOp.Nullary NullaryIlOp.Sub_ovf_un | ILOpCode.Endfinally -> IlOp.Nullary NullaryIlOp.Endfinally | ILOpCode.Leave -> IlOp.UnaryConst (UnaryConstIlOp.Leave (reader.ReadInt32 ())) | ILOpCode.Leave_s -> IlOp.UnaryConst (UnaryConstIlOp.Leave_s (reader.ReadSByte ())) | ILOpCode.Stind_i -> failwith "todo" - | ILOpCode.Conv_u -> failwith "todo" + | ILOpCode.Conv_u -> IlOp.Nullary NullaryIlOp.Conv_U | ILOpCode.Arglist -> failwith "todo" | ILOpCode.Ceq -> IlOp.Nullary NullaryIlOp.Ceq | ILOpCode.Cgt -> IlOp.Nullary NullaryIlOp.Cgt | ILOpCode.Cgt_un -> IlOp.Nullary NullaryIlOp.Cgt_un | ILOpCode.Clt -> IlOp.Nullary NullaryIlOp.Clt | ILOpCode.Clt_un -> IlOp.Nullary NullaryIlOp.Clt_un - | ILOpCode.Ldftn -> failwith "todo" + | ILOpCode.Ldftn -> + IlOp.UnaryMetadataToken (UnaryMetadataTokenIlOp.Ldftn, readMetadataToken &reader) | ILOpCode.Ldvirtftn -> failwith "todo" | ILOpCode.Ldarg -> failwith "todo" | ILOpCode.Ldarga -> failwith "todo" @@ -387,13 +350,15 @@ module TypeInfo = | ILOpCode.Ldloc -> failwith "todo" | ILOpCode.Ldloca -> failwith "todo" | ILOpCode.Stloc -> IlOp.UnaryConst (UnaryConstIlOp.Stloc (reader.ReadUInt16 ())) - | ILOpCode.Localloc -> failwith "todo" + | ILOpCode.Localloc -> IlOp.Nullary NullaryIlOp.Localloc | ILOpCode.Endfilter -> IlOp.Nullary NullaryIlOp.Endfilter | ILOpCode.Unaligned -> failwith "todo" - | ILOpCode.Volatile -> failwith "todo" - | ILOpCode.Tail -> failwith "todo" - | ILOpCode.Initobj -> failwith "todo" - | ILOpCode.Constrained -> failwith "todo" + | ILOpCode.Volatile -> IlOp.Nullary NullaryIlOp.Volatile + | ILOpCode.Tail -> IlOp.Nullary NullaryIlOp.Tail + | ILOpCode.Initobj -> + IlOp.UnaryMetadataToken (UnaryMetadataTokenIlOp.Initobj, readMetadataToken &reader) + | ILOpCode.Constrained -> + IlOp.UnaryMetadataToken (UnaryMetadataTokenIlOp.Constrained, readMetadataToken &reader) | ILOpCode.Cpblk -> failwith "todo" | ILOpCode.Initblk -> failwith "todo" | ILOpCode.Rethrow -> IlOp.Nullary NullaryIlOp.Rethrow @@ -404,7 +369,15 @@ module TypeInfo = readInstructions ((opCode, offset) :: acc) - readInstructions [] + let instructions = readInstructions [] + + { + Instructions = instructions + LocalInit = methodBody.LocalVariablesInitialized + MaxStackSize = methodBody.MaxStack + ExceptionRegions = methodBody.ExceptionRegions + } + |> Some let private readMethodParams (metadata : MetadataReader) @@ -439,65 +412,23 @@ module TypeInfo = ) |> ImmutableArray.CreateRange - let private typeProvider = - { new ISignatureTypeProvider with - member this.GetArrayType (elementType : TypeDefn, shape : ArrayShape) : TypeDefn = failwith "TODO" - member this.GetByReferenceType (elementType : TypeDefn) : TypeDefn = TypeDefn.Byref elementType - - member this.GetSZArrayType (elementType : TypeDefn) : TypeDefn = - TypeDefn.OneDimensionalArrayLowerBoundZero elementType - - member this.GetPrimitiveType (elementType : PrimitiveTypeCode) : TypeDefn = - PrimitiveType.OfEnum elementType |> TypeDefn.PrimitiveType - - member this.GetGenericInstantiation - (generic : TypeDefn, typeArguments : ImmutableArray) - : TypeDefn - = - TypeDefn.GenericInstantiation (generic, typeArguments) - - member this.GetTypeFromDefinition - (reader : MetadataReader, handle : TypeDefinitionHandle, rawTypeKind : byte) - : TypeDefn - = - let handle : EntityHandle = TypeDefinitionHandle.op_Implicit handle - let typeKind = reader.ResolveSignatureTypeKind (handle, rawTypeKind) - - TypeDefn.FromDefinition typeKind - - member this.GetTypeFromReference - (reader : MetadataReader, handle : TypeReferenceHandle, rawTypeKind : byte) - : TypeDefn - = - let handle : EntityHandle = TypeReferenceHandle.op_Implicit handle - let typeKind = reader.ResolveSignatureTypeKind (handle, rawTypeKind) - TypeDefn.FromReference typeKind - - member this.GetPointerType (typeCode : TypeDefn) : TypeDefn = TypeDefn.Pointer typeCode - - member this.GetFunctionPointerType (signature) = - TypeDefn.FunctionPointer (TypeMethodSignature.make signature) - - member this.GetGenericMethodParameter (genericContext, index) = TypeDefn.GenericMethodParameter index - member this.GetGenericTypeParameter (genericContext, index) = TypeDefn.GenericTypeParameter index - - member this.GetModifiedType (modifier, unmodifiedType, isRequired) = - TypeDefn.Modified (unmodifiedType, modifier, isRequired) - - member this.GetPinnedType (elementType) = TypeDefn.Pinned elementType - member this.GetTypeFromSpecification (reader, genericContext, handle, rawTypeKind) = failwith "todo" - } - let private readMethod (peReader : PEReader) (metadataReader : MetadataReader) (methodHandle : MethodDefinitionHandle) - : MethodInfo + : MethodInfo option = let methodDef = metadataReader.GetMethodDefinition methodHandle let methodName = metadataReader.GetString methodDef.Name - let methodSig = methodDef.DecodeSignature (typeProvider, ()) + let methodSig = methodDef.DecodeSignature (TypeDefn.typeProvider, ()) let methodBody = readMethodBody peReader methodDef + + match methodBody with + | None -> + Console.Error.WriteLine $"[INF] No method body for {metadataReader.GetString methodDef.Name}" + None + | Some methodBody -> + let methodParams = readMethodParams metadataReader (methodDef.GetParameters ()) let methodGenericParams = @@ -506,12 +437,15 @@ module TypeInfo = { Handle = methodHandle Name = methodName - Instructions = methodBody - Locations = methodBody |> List.map (fun (a, b) -> b, a) |> Map.ofList + Instructions = methodBody.Instructions + Locations = methodBody.Instructions |> List.map (fun (a, b) -> b, a) |> Map.ofList Parameters = methodParams Generics = methodGenericParams Signature = TypeMethodSignature.make methodSig + IsPinvokeImpl = methodDef.Attributes.HasFlag MethodAttributes.PinvokeImpl + LocalsInit = methodBody.LocalInit } + |> Some let internal read (peReader : PEReader) @@ -522,8 +456,30 @@ module TypeInfo = let typeDef = metadataReader.GetTypeDefinition (typeHandle) let methods = typeDef.GetMethods () + let methodImpls = + typeDef.GetMethodImplementations () + |> Seq.map (fun handle -> + let m = metadataReader.GetMethodImplementation handle + + if not (m.MethodBody.Kind.HasFlag HandleKind.MethodImplementation) then + failwith "unexpected kind" + + KeyValuePair (handle, m.MethodBody) + ) + |> ImmutableDictionary.CreateRange + { Namespace = metadataReader.GetString (typeDef.Namespace) Name = metadataReader.GetString (typeDef.Name) - Methods = methods |> Seq.map (readMethod peReader metadataReader) |> Seq.toList + Methods = + methods + |> Seq.choose (fun m -> + let result = readMethod peReader metadataReader m + + match result with + | None -> None + | Some x -> Some x + ) + |> Seq.toList + MethodImpls = methodImpls } diff --git a/WoofWare.PawPrint/WoofWare.PawPrint.fsproj b/WoofWare.PawPrint/WoofWare.PawPrint.fsproj index d0670e3..3d54a46 100644 --- a/WoofWare.PawPrint/WoofWare.PawPrint.fsproj +++ b/WoofWare.PawPrint/WoofWare.PawPrint.fsproj @@ -7,6 +7,7 @@ +