diff --git a/.github/workflows/dotnet.yaml b/.github/workflows/dotnet.yaml new file mode 100644 index 0000000..ec4ac6a --- /dev/null +++ b/.github/workflows/dotnet.yaml @@ -0,0 +1,98 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/SchemaStore/schemastore/master/src/schemas/json/github-workflow.json +name: .NET + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +env: + DOTNET_NOLOGO: true + DOTNET_CLI_TELEMETRY_OPTOUT: true + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true + NUGET_XMLDOC_MODE: '' + DOTNET_MULTILEVEL_LOOKUP: 0 + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # so that NerdBank.GitVersioning has access to history + - name: Install Nix + uses: cachix/install-nix-action@v30 + with: + extra_nix_config: | + access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} + - name: Restore dependencies + run: nix develop --command dotnet restore + - name: Build + run: nix develop --command dotnet build --no-restore --configuration Release + - name: Test + run: nix develop --command dotnet test --no-build --verbosity normal --configuration Release + + build-nix: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Nix + uses: cachix/install-nix-action@v30 + with: + extra_nix_config: | + access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} + - name: Build + run: nix build + - name: Reproducibility check + run: nix build --rebuild + + check-dotnet-format: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Nix + uses: cachix/install-nix-action@v30 + with: + extra_nix_config: | + access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} + - name: Run Fantomas + run: nix run .#fantomas -- --check . + + check-nix-format: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Nix + uses: cachix/install-nix-action@v30 + with: + extra_nix_config: | + access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} + - name: Run Alejandra + run: nix develop --command alejandra --check . + + flake-check: + name: Check flake + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Install Nix + uses: cachix/install-nix-action@v30 + with: + extra_nix_config: | + access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} + - name: Flake check + run: nix flake check + + all-required-checks-complete: + if: ${{ always() }} + needs: [check-dotnet-format, check-nix-format, build, build-nix, flake-check] + runs-on: ubuntu-latest + steps: + - uses: G-Research/common-actions/check-required-lite@2b7dc49cb14f3344fbe6019c14a31165e258c059 + with: + needs-context: ${{ toJSON(needs) }} diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..19a68f4 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,19 @@ + + + embedded + true + [UNDEFINED] + true + true + true + embedded + FS3388,FS3559 + + + + + + + true + + diff --git a/WoofWare.PawPrint/AbstractMachine.fs b/WoofWare.PawPrint/AbstractMachine.fs index 9327064..037b187 100644 --- a/WoofWare.PawPrint/AbstractMachine.fs +++ b/WoofWare.PawPrint/AbstractMachine.fs @@ -1,6 +1,8 @@ namespace WoofWare.PawPrint +open System open System.Collections.Immutable +open System.Reflection.Metadata open Microsoft.FSharp.Core type ThreadId = | ThreadId of int @@ -376,11 +378,49 @@ module AbstractMachine = | Rethrow -> failwith "todo" | Throw -> failwith "todo" + let private executeUnaryMetadata + (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 + | k -> failwith $"Unrecognised kind: %O{k}" + + let method = + dumped.Methods.[MethodDefinitionHandle.op_Explicit dumped.MethodSpecs.[handle].Method] + + failwith "TODO: now do this!" + state + | Callvirt -> failwith "todo" + | Castclass -> failwith "todo" + | Newobj -> failwith "todo" + | Newarr -> failwith "todo" + | Box -> failwith "todo" + | Ldelema -> failwith "todo" + | Isinst -> failwith "todo" + | Stfld -> failwith "todo" + | Stsfld -> failwith "todo" + | Ldfld -> failwith "todo" + | Ldflda -> failwith "todo" + | Ldsfld -> failwith "todo" + | Unbox_Any -> failwith "todo" + | Stelem -> failwith "todo" + | Ldelem -> failwith "todo" + let executeOneStep (state : IlMachineState) (dumped : DumpedAssembly) (thread : ThreadId) : IlMachineState = let instruction = state.ThreadState.[thread].MethodState match instruction.ExecutingMethod.Locations.[instruction.IlOpIndex] with | IlOp.Nullary op -> executeNullary state thread dumped op | UnaryConst unaryConstIlOp -> failwith "todo" - | UnaryMetadataToken (unaryMetadataTokenIlOp, bytes) -> failwith "todo" + | UnaryMetadataToken (unaryMetadataTokenIlOp, bytes) -> + executeUnaryMetadata unaryMetadataTokenIlOp bytes state dumped thread | Switch immutableArray -> failwith "todo" + | UnaryStringToken (unaryStringTokenIlOp, stringHandle) -> failwith "todo" diff --git a/WoofWare.PawPrint/Assembly.fs b/WoofWare.PawPrint/Assembly.fs index d7013b3..73dc9b5 100644 --- a/WoofWare.PawPrint/Assembly.fs +++ b/WoofWare.PawPrint/Assembly.fs @@ -14,6 +14,9 @@ type DumpedAssembly = Types : TypeInfo list Methods : IReadOnlyDictionary MainMethod : MethodDefinitionHandle + /// Map of four-byte int token to metadata + MethodDefinitions : Map + MethodSpecs : ImmutableDictionary } [] @@ -38,10 +41,32 @@ module Assembly = |> List.collect (fun ty -> ty.Methods |> List.map (fun mi -> KeyValuePair (mi.Handle, mi))) |> ImmutableDictionary.CreateRange + let methodDefnMetadata = + metadataReader.MethodDefinitions + |> Seq.map (fun mh -> + let def = metadataReader.GetMethodDefinition mh + let eh : EntityHandle = MethodDefinitionHandle.op_Implicit mh + let token = MetadataTokens.GetToken eh + token, def + ) + |> Map.ofSeq + + let methodSpecs = + Seq.init + (metadataReader.GetTableRowCount TableIndex.MethodSpec) + (fun i -> + let i = i + 1 + let handle = MetadataTokens.MethodSpecificationHandle i + KeyValuePair (handle, metadataReader.GetMethodSpecification handle) + ) + |> ImmutableDictionary.CreateRange + { Types = result MainMethod = entryPointMethod Methods = methods + MethodDefinitions = methodDefnMetadata + MethodSpecs = methodSpecs } let print (main : MethodDefinitionHandle) (dumped : DumpedAssembly) : unit = diff --git a/WoofWare.PawPrint/BitTwiddling.fs b/WoofWare.PawPrint/BitTwiddling.fs index 4594815..dcbb2c6 100644 --- a/WoofWare.PawPrint/BitTwiddling.fs +++ b/WoofWare.PawPrint/BitTwiddling.fs @@ -17,3 +17,9 @@ module internal BitTwiddling = let inline toUint64 (bytes : ReadOnlySpan) : uint64 = uint64 (toUint32 (bytes.Slice (0, 4))) + 0x10000UL * uint64 (toUint32 (bytes.Slice (4, 4))) + + let inline toInt32 (bytes : ReadOnlySpan) : int32 = + int32 bytes.[0] + + int32 bytes.[1] * 256 + + int32 bytes.[2] * 256 * 256 + + int32 bytes.[3] * 256 * 256 * 256 diff --git a/WoofWare.PawPrint/IlOp.fs b/WoofWare.PawPrint/IlOp.fs index b8766f4..8bb053e 100644 --- a/WoofWare.PawPrint/IlOp.fs +++ b/WoofWare.PawPrint/IlOp.fs @@ -1,6 +1,7 @@ namespace WoofWare.PawPrint open System.Collections.Immutable +open System.Reflection.Metadata type NullaryIlOp = | Nop @@ -121,18 +122,21 @@ type UnaryMetadataTokenIlOp = | Ldfld | Ldflda | Ldsfld - | Ldstr | Unbox_Any | Stelem | Ldelem +type UnaryStringTokenIlOp = | Ldstr + /// A four-byte metadata token. -type MetadataToken = byte[] +type MetadataToken = EntityHandle +type StringToken = StringHandle type IlOp = | Nullary of NullaryIlOp | UnaryConst of UnaryConstIlOp | UnaryMetadataToken of UnaryMetadataTokenIlOp * MetadataToken + | UnaryStringToken of UnaryStringTokenIlOp * StringToken | Switch of int32 ImmutableArray static member Format (opCode : IlOp) (offset : int) : string = $" IL_%04X{offset}: %-20O{opCode}" diff --git a/WoofWare.PawPrint/TypeInfo.fs b/WoofWare.PawPrint/TypeInfo.fs index 3f7cd52..707cd30 100644 --- a/WoofWare.PawPrint/TypeInfo.fs +++ b/WoofWare.PawPrint/TypeInfo.fs @@ -131,18 +131,16 @@ module TypeInfo = LanguagePrimitives.EnumOfValue (uint16 op) let private readMetadataToken (reader : byref) : MetadataToken = - [| - reader.ReadByte () - reader.ReadByte () - reader.ReadByte () - reader.ReadByte () - |] + reader.ReadUInt32 () |> int |> MetadataTokens.EntityHandle + + let private readStringToken (reader : byref) : StringToken = + reader.ReadUInt32 () |> int |> MetadataTokens.StringHandle let private readMethodBody (peReader : PEReader) (methodDef : MethodDefinition) : (IlOp * int) list = if methodDef.RelativeVirtualAddress = 0 then [] else - let methodBody = peReader.GetMethodBody (methodDef.RelativeVirtualAddress) + let methodBody = peReader.GetMethodBody methodDef.RelativeVirtualAddress let ilBytes = methodBody.GetILBytes () use bytes = fixed ilBytes let mutable reader : BlobReader = BlobReader (bytes, ilBytes.Length) @@ -283,8 +281,7 @@ module TypeInfo = IlOp.UnaryMetadataToken (UnaryMetadataTokenIlOp.Callvirt, readMetadataToken &reader) | ILOpCode.Cpobj -> failwith "todo" | ILOpCode.Ldobj -> failwith "todo" - | ILOpCode.Ldstr -> - IlOp.UnaryMetadataToken (UnaryMetadataTokenIlOp.Ldstr, readMetadataToken &reader) + | ILOpCode.Ldstr -> IlOp.UnaryStringToken (UnaryStringTokenIlOp.Ldstr, readStringToken &reader) | ILOpCode.Newobj -> IlOp.UnaryMetadataToken (UnaryMetadataTokenIlOp.Newobj, readMetadataToken &reader) | ILOpCode.Castclass -> @@ -463,14 +460,17 @@ module TypeInfo = (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, foo : TypeReferenceHandle, rawTypeKind : byte) + (reader : MetadataReader, handle : TypeReferenceHandle, rawTypeKind : byte) : TypeDefn = - let typeKind = reader.ResolveSignatureTypeKind (foo, rawTypeKind) + 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 diff --git a/nix/deps.json b/nix/deps.json index 97254dc..f752ab5 100644 --- a/nix/deps.json +++ b/nix/deps.json @@ -14,6 +14,11 @@ "version": "5.0.2", "hash": "sha256-YOoosLEiszPsOOaNAkWhFGU04JJKDOFVoA/ggrZMN10=" }, + { + "pname": "FSharp.Core", + "version": "9.0.101", + "hash": "sha256-bR4PHanvKrzD43qFQxmOmmhhpz+ZmKZMPlgGnlRNcp4=" + }, { "pname": "FsUnit", "version": "7.0.1", @@ -144,6 +149,11 @@ "version": "17.13.0", "hash": "sha256-L/CJzou7dhmShUgXq3aXL3CaLTJll17Q+JY2DBdUUpo=" }, + { + "pname": "Nerdbank.GitVersioning", + "version": "3.7.115", + "hash": "sha256-sqn+i7vvBgBUtm7j82mH+SpApgI2hsmL5DYfLm1Z7gw=" + }, { "pname": "Newtonsoft.Json", "version": "13.0.1",