diff --git a/AsmResolver.sln b/AsmResolver.sln index c8f471ef6..0cf5601e0 100644 --- a/AsmResolver.sln +++ b/AsmResolver.sln @@ -86,6 +86,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution appveyor.yml = appveyor.yml EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsmResolver.Symbols.Pdb", "src\AsmResolver.Symbols.Pdb\AsmResolver.Symbols.Pdb.csproj", "{9E311832-D0F2-42CA-84DD-9A91B88F0287}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsmResolver.Symbols.Pdb.Tests", "test\AsmResolver.Symbols.Pdb.Tests\AsmResolver.Symbols.Pdb.Tests.csproj", "{AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsmResolver.DotNet.Dynamic.Tests", "test\AsmResolver.DotNet.Dynamic.Tests\AsmResolver.DotNet.Dynamic.Tests.csproj", "{C089D0AB-B428-4136-89CC-7974CB590513}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsmResolver.DotNet.Dynamic", "src\AsmResolver.DotNet.Dynamic\AsmResolver.DotNet.Dynamic.csproj", "{62420213-67AD-40FC-B451-BD05C2437CE3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -424,6 +432,54 @@ Global {2D1DF5DA-7367-4490-B3F0-B996348E150B}.Release|x64.Build.0 = Release|Any CPU {2D1DF5DA-7367-4490-B3F0-B996348E150B}.Release|x86.ActiveCfg = Release|Any CPU {2D1DF5DA-7367-4490-B3F0-B996348E150B}.Release|x86.Build.0 = Release|Any CPU + {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Debug|x64.ActiveCfg = Debug|Any CPU + {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Debug|x64.Build.0 = Debug|Any CPU + {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Debug|x86.ActiveCfg = Debug|Any CPU + {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Debug|x86.Build.0 = Debug|Any CPU + {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Release|Any CPU.Build.0 = Release|Any CPU + {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Release|x64.ActiveCfg = Release|Any CPU + {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Release|x64.Build.0 = Release|Any CPU + {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Release|x86.ActiveCfg = Release|Any CPU + {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Release|x86.Build.0 = Release|Any CPU + {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Debug|x64.ActiveCfg = Debug|Any CPU + {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Debug|x64.Build.0 = Debug|Any CPU + {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Debug|x86.ActiveCfg = Debug|Any CPU + {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Debug|x86.Build.0 = Debug|Any CPU + {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Release|Any CPU.Build.0 = Release|Any CPU + {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Release|x64.ActiveCfg = Release|Any CPU + {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Release|x64.Build.0 = Release|Any CPU + {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Release|x86.ActiveCfg = Release|Any CPU + {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Release|x86.Build.0 = Release|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Debug|x64.ActiveCfg = Debug|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Debug|x64.Build.0 = Debug|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Debug|x86.ActiveCfg = Debug|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Debug|x86.Build.0 = Debug|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Release|Any CPU.Build.0 = Release|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Release|x64.ActiveCfg = Release|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Release|x64.Build.0 = Release|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Release|x86.ActiveCfg = Release|Any CPU + {C089D0AB-B428-4136-89CC-7974CB590513}.Release|x86.Build.0 = Release|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Debug|x64.ActiveCfg = Debug|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Debug|x64.Build.0 = Debug|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Debug|x86.ActiveCfg = Debug|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Debug|x86.Build.0 = Debug|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Release|Any CPU.Build.0 = Release|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Release|x64.ActiveCfg = Release|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Release|x64.Build.0 = Release|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Release|x86.ActiveCfg = Release|Any CPU + {62420213-67AD-40FC-B451-BD05C2437CE3}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -461,6 +517,10 @@ Global {40483E28-C703-4933-BA5B-9512EF6E6A21} = {EA971BB0-94BA-44DB-B16C-212D2DB27E17} {CF6A7E02-37DC-4963-AC14-76D74ADCD87A} = {B3AF102B-ABE1-41B2-AE48-C40702F45AB0} {2D1DF5DA-7367-4490-B3F0-B996348E150B} = {B3AF102B-ABE1-41B2-AE48-C40702F45AB0} + {9E311832-D0F2-42CA-84DD-9A91B88F0287} = {34A95168-A162-4F6A-803B-B6F221FE9EA6} + {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE} = {786C1732-8C96-45DD-97BB-639C9AA7F45B} + {C089D0AB-B428-4136-89CC-7974CB590513} = {786C1732-8C96-45DD-97BB-639C9AA7F45B} + {62420213-67AD-40FC-B451-BD05C2437CE3} = {34A95168-A162-4F6A-803B-B6F221FE9EA6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3302AC79-6D23-4E7D-8C5F-C0C7261044D0} diff --git a/Directory.Build.props b/Directory.Build.props index a2c6826c2..da8160f3a 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,7 +7,7 @@ https://github.com/Washi1337/AsmResolver git 10 - 4.11.2 + 5.0.0-beta.1 diff --git a/appveyor.yml b/appveyor.yml index 64299e25e..7a1ff9bec 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,7 +4,7 @@ - master image: Visual Studio 2022 - version: 4.11.2-master-build.{build} + version: 5.0.0-master-build.{build} configuration: Release skip_commits: @@ -33,7 +33,7 @@ - development image: Visual Studio 2022 - version: 4.11.2-dev-build.{build} + version: 5.0.0-dev-build.{build} configuration: Release skip_commits: diff --git a/docs/dotnet/cloning.rst b/docs/dotnet/cloning.rst index 540d448d9..871f6b499 100644 --- a/docs/dotnet/cloning.rst +++ b/docs/dotnet/cloning.rst @@ -13,22 +13,22 @@ To help developers in injecting existing code into a module, ``AsmResolver.DotNe The MemberCloner class ---------------------- -The ``MemberCloner`` is the root object responsible for cloning members in a .NET module, and importing them into another. +The ``MemberCloner`` is the root object responsible for cloning members in a .NET module, and importing them into another. In the snippet below, we define a new ``MemberCloner`` that is able to clone and import members into the module ``destinationModule:``. .. code-block:: csharp ModuleDefinition destinationModule = ... - MemberCloner cloner = new MemberCloner(destinationModule); + var cloner = new MemberCloner(destinationModule); In the remaining sections of this article, we assume that the ``MemberCloner`` is initialized using the code above. -Include members to clone ------------------------- +Include members +--------------- -The general idea of the ``MemberCloner`` is to first provide all the members to be cloned, and then clone everything all in one go. The reason why it is done like this, is to allow the ``MemberCloner`` to fix up any cross references to members within the to-be-cloned metadata and CIL code. +The general idea of the ``MemberCloner`` is to first provide all the members to be cloned, and then clone everything all in one go. This is to allow the ``MemberCloner`` to fix up any cross references to members within the to-be-cloned metadata and CIL code. For the sake of the example, we assume that the following two classes are to be injected in ``destinationModule``: @@ -36,7 +36,7 @@ For the sake of the example, we assume that the following two classes are to be public class Rectangle { - public Rectangle(Vector2 location, Vector2 size) + public Rectangle(Vector2 location, Vector2 size) { Location = location; Size = size; @@ -50,7 +50,7 @@ For the sake of the example, we assume that the following two classes are to be public class Vector2 { - public Vector2(int x, int y) + public Vector2(int x, int y) { X = x; Y = y; @@ -60,7 +60,7 @@ For the sake of the example, we assume that the following two classes are to be public int Y { get; set; } } -The first thing we then should do, is find the type definitions that correspond to these classes: +The first step in cloning involves loading the source module, and finding the type definitions that correspond to these classes: .. code-block:: csharp @@ -68,6 +68,7 @@ The first thing we then should do, is find the type definitions that correspond var rectangleType = sourceModule.TopLevelTypes.First(t => t.Name == "Rectangle"); var vectorType = sourceModule.TopLevelTypes.First(t => t.Name == "Vector2"); + Alternatively, if the source assembly is loaded by the CLR, we also can look up the members by metadata token. .. code-block:: csharp @@ -84,41 +85,147 @@ We can then use ``MemberCloner.Include`` to include the types in the cloning pro cloner.Include(rectangleType, recursive: true); cloner.Include(vectorType, recursive: true); -The ``recursive`` parameter indicates whether all members and nested types need to be included as well. + +The ``recursive`` parameter indicates whether all members and nested types need to be included as well. This value is ``true`` by default and can also be omitted. + +.. code-block:: csharp + + cloner.Include(rectangleType); + cloner.Include(vectorType); + ``Include`` returns the same ``MemberCloner`` instance. It is therefore also possible to create a long method chain of members to include in the cloning process. .. code-block:: csharp cloner - .Include(rectangleType, recursive: true) - .Include(vectorType, recursive: true); + .Include(rectangleType) + .Include(vectorType); + Cloning individual methods, fields, properties and/or events is also supported. This can be done by including the corresponding ``MethodDefinition``, ``FieldDefinition``, ``PropertyDefinition`` and/or ``EventDefinition`` instead. -Cloning the included members +Cloning the included members ---------------------------- -When all members are included, it is possible to call ``MemberCloner.Clone`` to clone them all in one go. +When all members are included, it is possible to call ``MemberCloner.Clone`` to clone them all in one go. .. code-block:: csharp var result = cloner.Clone(); -The ``MemberCloner`` will automatically resolve any cross references between types, fields and methods that are included in the cloning process. + +The ``MemberCloner`` will automatically resolve any cross references between types, fields and methods that are included in the cloning process. For instance, going with the example in the previous section, if both the ``Rectangle`` as well as the ``Vector2`` classes are included, any reference in ``Rectangle`` to ``Vector2`` will be replaced with a reference to the cloned ``Vector2``. If not all members are included, the ``MemberCloner`` will assume that these are references to external libraries, and will use the ``ReferenceImporter`` to construct references to these members instead. -.. warning:: - The ``MemberCloner`` heavily depends on the ``ReferenceImporter`` class for copying references into the destination module. This class has some limitations, in particular on importing / cloning from modules targeting different framework versions. See :ref:`dotnet-importer-common-caveats` for more information. +Custom reference importers +-------------------------- + +The ``MemberCloner`` heavily depends on the ``CloneContextAwareReferenceImporter`` class for copying references into the destination module. This class is derived from ``ReferenceImporter``, which has some limitations. In particular, limitations arise when cloning from modules targeting different framework versions, or when trying to reference members that may already exist in the target module (e.g., when dealing with ``NullableAttribute`` annotated metadata). +To account for these situations, the cloner allows for specifying custom reference importer instances. By deriving from the ``CloneContextAwareReferenceImporter`` class and overriding methods such as ``ImportMethod``, we can reroute specific member references to the appropriate metadata if needed. Below is an example of a basic implementation of an importer that attempts to map method references from the ``System.Runtime.CompilerServices`` namespace to definitions that are already present in the target module: -Injecting the cloned members +.. code-block:: csharp + + public class MyImporter : CloneContextAwareReferenceImporter + { + private static readonly SignatureComparer Comparer = new(); + + public MyImporter(MemberCloneContext context) + : base(context) + { + } + + public override IMethodDefOrRef ImportMethod(IMethodDefOrRef method) + { + // Check if the method is from a type defined in the System.Runtime.CompilerServices namespace. + if (method.DeclaringType is { Namespace.Value: "System.Runtime.CompilerServices" } type) + { + // We might already have a type and method defined in the target module (e.g., NullableAttribute::.ctor(int32)). + // Try find it in the target module. + + var existingMethod = this.Context.Module + .TopLevelTypes.FirstOrDefault(t => t.IsTypeOf(type.Namespace, type.Name))? + .Methods.FirstOrDefault(m => method.Name == m.Name && Comparer.Equals(m.Signature, method.Signature)); + + // If we found a matching definition, then return it instead of importing the reference. + if (existingMethod is not null) + return existingMethod; + } + + return base.ImportMethod(method); + } + } + + +We can then pass a custom importer factory to our member cloner constructor as follows: + +.. code-block:: csharp + + var cloner = new MemberCloner(destinationModule, context => new MyImporter(context)); + + +All references to methods defined in the ``System.Runtime.CompilerServices`` namespace will then be mapped to the appropriate method definitions if they exist in the target module. + +See :ref:`dotnet-importer-common-caveats` for more information on reference importing and its caveats. + + +Post processing of cloned members +--------------------------------- + +In some cases, cloned members may need to be post-processed before they are injected into the target module. The ``MemberCloner`` class can be initialized with an instance of a ``IMemberClonerListener``, that gets notified by the cloner object every time a definition was cloned. + +Below an example that appends the string ``_Cloned`` to the name for every cloned type. + +.. code-block:: csharp + + public class MyListener : MemberClonerListener + { + public override void OnClonedType(TypeDefinition original, TypeDefinition cloned) + { + cloned.Name = $"{original.Name}_Cloned"; + base.OnClonedType(original, cloned); + } + } + + +We can then initialize our cloner with an instance of our listener class: + +.. code-block:: csharp + + var cloner = new MemberCloner(destinationModule, new MyListener()); + + +Alternatively, we can also override the more generic ``OnClonedMember`` instead, which gets fired for every member definition that was cloned. + +.. code-block:: csharp + + public class MyListener : MemberClonerListener + { + public override void OnClonedMember(IMemberDefinition original, IMemberDefinition cloned) + { + /* ... Do post processing here ... */ + base.OnClonedMember(original, cloned); + } + } + + +As a shortcut, this can also be done by passing in a delegate or lambda instead to the ``MemberCloner`` constructor. + +.. code-block:: csharp + + var cloner = new MemberCloner(destinationModule, (original, cloned) => { + /* ... Do post processing here ... */ + }); + + +Injecting the cloned members ---------------------------- -After cloning, we obtain a ``MemberCloneResult``, which contains a register of all members cloned by the member cloner. +The ``Clone`` method returns a ``MemberCloneResult``, which contains a register of all members cloned by the member cloner. - ``OriginalMembers``: The collection containing all original members. - ``ClonedMembers``: The collection containing all cloned members. @@ -136,9 +243,21 @@ Alternatively, we can get all cloned top-level types. var clonedTypes = result.ClonedTopLevelTypes; -It is important to note that the ``MemberCloner`` class itself does not inject any of the cloned members. To inject the cloned types, we can for instance add them to the ``ModuleDefinition.TopLevelTypes`` collection: +It is important to note that the ``MemberCloner`` class itself does not inject any of the cloned members by itself. To inject the cloned types, we can for instance add them to the ``ModuleDefinition.TopLevelTypes`` collection: .. code-block:: csharp foreach (var clonedType in clonedTypes) - destinationModule.TopLevelTypes.Add(clonedType); \ No newline at end of file + destinationModule.TopLevelTypes.Add(clonedType); + + +However, since injecting the cloned top level types is a very common use-case for the cloner, AsmResolver defines the ``InjectTypeClonerListener`` class that implements a cloner listener that injects all top level types automatically into the destination module. In such a case, the code can be reduced to the following: + +.. code-block:: csharp + + new MemberCloner(destinationModule, new InjectTypeClonerListener(destinationModule)) + .Include(rectangleType) + .Include(vectorType) + .Clone(); + + // `destinationModule` now contains copies of `rectangleType` and `vectorType`. diff --git a/docs/dotnet/dynamic-methods.rst b/docs/dotnet/dynamic-methods.rst new file mode 100644 index 000000000..9b43317e7 --- /dev/null +++ b/docs/dotnet/dynamic-methods.rst @@ -0,0 +1,63 @@ +Dynamic Methods +=============== + +Dynamic methods are methods that are constructed and assembled at run time. They allow for dynamically generating managed code, without having to go through the process of compiling or generating new assemblies. This is used a lot in obfuscators that implement for example reference proxies or virtual machines. + +AsmResolver has support for reading dynamic methods, and transforming them into ``MethodDefinition`` objects that can be processed further. All relevant classes are present in the following namespace: + +.. code-block:: csharp + + using AsmResolver.DotNet.Dynamic; + +.. note:: + + Since AsmResolver 5.0, this namespace exists in a separate ``AsmResolver.DotNet.Dynamic`` nuget package. + + +Reading dynamic methods +----------------------- + +The following example demonstrates how to transform an instance of ``DynamicMethod`` into a ``DynamicMethodDefinition``: + +.. code-block:: csharp + + DynamicMethod dynamicMethod = ... + + var contextModule = ModuleDefinition.FromFile(typeof(Program).Assembly.Location); + var definition = new DynamicMethodDefinition(contextModule, dynamicMethod); + + +Note that the constructor requires a context module. This is the module that will be used to import or resolve any references within the method. + +.. warning:: + + Reading dynamic methods relies on dynamic analysis, and may therefore result in arbitrary code execution. Make sure to only use this in a safe environment if the input module is not trusted. + + +Using dynamic methods +--------------------- + +An instance of ``DynamicMethodDefinition`` is virtually the same as any other ``MethodDefinition``, and thus all its properties can be inspected and modified. Below an example that prints all the instructions that were present in the body of the dynamic method: + +.. code-block:: csharp + + DynamicMethodDefinition definition = ... + foreach (var instr in definition.CilMethodBody.Instructions) + Console.WriteLine(instr); + +``DynamicMethodDefinition`` are fully imported method definitions. This means we can safely add them to the context module: + +.. code-block:: csharp + + // Read dynamic method. + var contextModule = ModuleDefinition.FromFile(typeof(Program).Assembly.Location); + var definition = new DynamicMethodDefinition(contextModule, dynamicMethod); + + // Add to type. + contextModule.GetOrCreateModuleType().Methods.Add(definition); + + // Save + contextModule.Write("Program.patched.dll"); + + +See :ref:`dotnet-obtaining-methods-and-fields` and :ref:`dotnet-cil-method-bodies` for more information on how to use ``MethodDefinition`` objects. diff --git a/docs/dotnet/managed-method-bodies.rst b/docs/dotnet/managed-method-bodies.rst index 281be6f31..3d438d3de 100644 --- a/docs/dotnet/managed-method-bodies.rst +++ b/docs/dotnet/managed-method-bodies.rst @@ -1,3 +1,5 @@ +.. _dotnet-cil-method-bodies: + CIL Method Bodies ================= diff --git a/docs/dotnet/member-tree.rst b/docs/dotnet/member-tree.rst index bffe00935..633d16808 100644 --- a/docs/dotnet/member-tree.rst +++ b/docs/dotnet/member-tree.rst @@ -27,7 +27,7 @@ Below, an example program that iterates through all types recursively and prints .. code-block:: csharp public const int IndentationWidth = 3; - + private static void Main(string[] args) { var module = ModuleDefinition.FromFile(...); @@ -41,14 +41,15 @@ Below, an example program that iterates through all types recursively and prints { // Print the name of the current type. Console.WriteLine("{0}- {1} : {2:X8}", indentation, type.Name, type.MetadataToken.ToInt32()); - + // Dump any nested types. DumpTypes(type.NestedTypes, indentationLevel + 1); } } +.. _dotnet-obtaining-methods-and-fields: -Obtaining methods and fields +Obtaining methods and fields ---------------------------- The ``TypeDefinition`` class exposes collections of methods and fields that the type defines: @@ -76,7 +77,7 @@ Methods and fields have a ``Signature`` property, that contain the return and pa .. code-block:: csharp FieldDefinition field = ... - Console.WriteLine("Return type: " + field.Signature.FieldType); + Console.WriteLine("Field type: " + field.Signature.FieldType); However, for reading parameters from a method definition, it is preferred to use the ``Parameters`` property instead of the ``ParameterTypes`` property stored in the signature. This is because the ``Parameters`` property automatically binds the types to the parameter definitions that are associated to these parameter types. This provides additional information, such as the name of the parameter: @@ -98,7 +99,7 @@ Obtaining properties and events is similar to obtaining methods and fields; ``Ty Console.WriteLine("{0} : {1:X8}", @event.Name, @event.MetadataToken.ToInt32()); .. code-block:: csharp - + foreach (var property in type.Properties) Console.WriteLine("{0} : {1:X8}", property.Name, property.MetadataToken.ToInt32()); @@ -112,4 +113,3 @@ Properties and events have methods associated to them. These are accessible thro Console.WriteLine("{0} {1} : {2:X8}", semantic.Attributes, semantic.Method.Name, semantic.MetadataToken.ToInt32()); } - diff --git a/docs/dotnet/unmanaged-method-bodies.rst b/docs/dotnet/unmanaged-method-bodies.rst index c5edc20d3..2e733ac28 100644 --- a/docs/dotnet/unmanaged-method-bodies.rst +++ b/docs/dotnet/unmanaged-method-bodies.rst @@ -23,23 +23,23 @@ To make the CLR treat the output file as a mixed mode application, the ``ILOnly` .. code-block:: csharp ModuleDefinition module = ... - module.Attributes &= ~DotNetDirectoryFlags.ILOnly; + module.IsILOnly = false; Furthermore, make sure the right architecture is specified. For example, for an x86 64-bit binary, use the following: .. code-block:: csharp - module.PEKind = OptionalHeaderMagic.Pe32Plus; + module.PEKind = OptionalHeaderMagic.PE32Plus; module.MachineType = MachineType.Amd64; - module.Attributes &= ~DotNetDirectoryFlags.Bit32Required; + module.IsBit32Required = false; For 32-bit x86 binaries, use the following: .. code-block:: csharp - module.PEKind = OptionalHeaderMagic.Pe32; + module.PEKind = OptionalHeaderMagic.PE32; module.MachineType = MachineType.I386; - module.Attributes |= DotNetDirectoryFlags.Bit32Required; + module.IsBit32Required = true; Flags for native methods diff --git a/docs/index.rst b/docs/index.rst index 89f3b0aeb..da9a805c7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -58,6 +58,7 @@ Table of Contents: dotnet/methods dotnet/managed-method-bodies dotnet/unmanaged-method-bodies + dotnet/dynamic-methods dotnet/managed-resources dotnet/cloning dotnet/token-allocation diff --git a/docs/pefile/headers.rst b/docs/pefile/headers.rst index 18468f5b6..fc011d58d 100644 --- a/docs/pefile/headers.rst +++ b/docs/pefile/headers.rst @@ -7,6 +7,6 @@ After you obtained an instance of the ``PEFile`` class, it is possible to read a Console.WriteLine("e_flanew: {0:X8}", peFile.DosHeader.NextHeaderOffset); Console.WriteLine("Machine: {0:X8}", peFile.FileHeader.Machine); - Console.WriteLine("Entrypoint: {0:X8}", peFile.OptionalHeader.AddressOfEntrypoint); + Console.WriteLine("EntryPoint: {0:X8}", peFile.OptionalHeader.AddressOfEntryPoint); Every change made to these headers will be reflected in the output executable, however very little verification on these values is done. diff --git a/docs/peimage/dotnet.rst b/docs/peimage/dotnet.rst index 73e9175ac..a4f41da3b 100644 --- a/docs/peimage/dotnet.rst +++ b/docs/peimage/dotnet.rst @@ -12,7 +12,7 @@ The .NET data directory can be accessed by the ``IPEImage.DotNetDirectory`` prop IPEImage peImage = ... - Console.WriteLine("Managed entrypoint: {0:X8}", peImage.DotNetDirectory.Entrypoint); + Console.WriteLine("Managed entry point: {0:X8}", peImage.DotNetDirectory.EntryPoint); Metadata directory diff --git a/docs/peimage/tls.rst b/docs/peimage/tls.rst index 564b25600..ad2f97ac0 100644 --- a/docs/peimage/tls.rst +++ b/docs/peimage/tls.rst @@ -6,7 +6,7 @@ Executables that use multiple threads might require static (non-stack) memory th All code relevant to the TLS data directory of a PE resides in the following namespace: .. code-block:: csharp - + using AsmResolver.PE.Tls; @@ -21,7 +21,7 @@ The PE file format defines a segment of memory within the TLS data directory tha .. code-block:: csharp - + var indexSegment = new DataSegment(new byte[8]); var directory = new TlsDirectory @@ -48,18 +48,14 @@ Next to static initialization data, it is also possible to specify a list of fun Creating new TLS directories ---------------------------- -Since the TLS data directory stores its data using virtual addresses (VA) rather than relative virtual addresses (RVA), AsmResolver requires the image base as well as the pointer size. This is done through the ``ImageBase`` and ``Is32Bit`` properties. By default, the following values are assumed: +Adding a new TLS directory to an image can be done using the parameterless constructor of the ``TlsDirectory`` class: .. code-block:: csharp - var directory = new TlsDirectory(); - directory.ImageBase = 0x00400000; - directory.Is32Bit = true; - - -Typically, you should make sure they are in sync with the values found in the file and optional header of the final PE file. Upon reading from an existing PE file, these two properties are initialized to the values stored in these two headers. + var tlsDirectory = new TlsDirectory(); + image.TlsDirectory = tlsDirectory; -When building a relocatable PE file, you might also need to add base address relocations to the VAs inside the TLS directory. To quickly get all the base relocations required, use the ``GetRequiredBaseRelocations`` method: +A TLS directory references all its sub segments using virtual addresses (VA) rather than relative addresses (RVA). This means that constructing a relocatable PE image with a TLS directory requires base relocation entries to be registered that let the Windows PE loader rebase all virtual addresses used in the directory when necessary. To quickly register all the required base relocations, you can call the ``GetRequiredBaseRelocations`` method and add all returned entries to the base relocation directory of the PE image: .. code-block:: csharp @@ -67,5 +63,5 @@ When building a relocatable PE file, you might also need to add base address rel IPEImage image = ...; - foreach (var relocation in image.TlsDirectory.GetRequiredBaseRelocations()) - image.Relocations.Add(relocation); \ No newline at end of file + foreach (var relocation in tlsDirectory.GetRequiredBaseRelocations()) + image.Relocations.Add(relocation); diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 000000000..bebbea9fa --- /dev/null +++ b/src/.gitignore @@ -0,0 +1,2 @@ +!AsmResolver.Symbols.Pdb/ + diff --git a/src/AsmResolver.DotNet.Dynamic/AsmResolver.DotNet.Dynamic.csproj b/src/AsmResolver.DotNet.Dynamic/AsmResolver.DotNet.Dynamic.csproj new file mode 100644 index 000000000..5519f663e --- /dev/null +++ b/src/AsmResolver.DotNet.Dynamic/AsmResolver.DotNet.Dynamic.csproj @@ -0,0 +1,32 @@ + + + + AsmResolver.DotNet.Dynamic + Dynamic method support for the AsmResolver executable file inspection toolsuite. + exe pe directories imports exports resources dotnet cil inspection manipulation assembly disassembly dynamic + true + 1701;1702;NU5105 + net6.0;netcoreapp3.1;netstandard2.0 + enable + + + + bin\Debug\AsmResolver.DotNet.Dynamic.xml + + + + bin\Release\AsmResolver.DotNet.Dynamic.xml + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/src/AsmResolver.DotNet/Code/Cil/DynamicCilOperandResolver.cs b/src/AsmResolver.DotNet.Dynamic/DynamicCilOperandResolver.cs similarity index 90% rename from src/AsmResolver.DotNet/Code/Cil/DynamicCilOperandResolver.cs rename to src/AsmResolver.DotNet.Dynamic/DynamicCilOperandResolver.cs index d3244f8e2..3efbe83d1 100644 --- a/src/AsmResolver.DotNet/Code/Cil/DynamicCilOperandResolver.cs +++ b/src/AsmResolver.DotNet.Dynamic/DynamicCilOperandResolver.cs @@ -1,13 +1,14 @@ using System; using System.Collections.Generic; using System.Reflection; +using AsmResolver.DotNet.Code.Cil; using AsmResolver.DotNet.Serialized; using AsmResolver.DotNet.Signatures; using AsmResolver.IO; using AsmResolver.PE.DotNet.Cil; using AsmResolver.PE.DotNet.Metadata.Tables; -namespace AsmResolver.DotNet.Code.Cil +namespace AsmResolver.DotNet.Dynamic { /// /// Provides an implementation of that resolves operands based on @@ -25,7 +26,7 @@ public DynamicCilOperandResolver(SerializedModuleDefinition contextModule, CilMe { _tokens = tokens ?? throw new ArgumentNullException(nameof(tokens)); _readerContext = contextModule.ReaderContext; - _importer = new ReferenceImporter(contextModule); + _importer = contextModule.DefaultImporter; } /// @@ -34,13 +35,13 @@ public DynamicCilOperandResolver(SerializedModuleDefinition contextModule, CilMe switch (token.Table) { case TableIndex.TypeDef: - object? type = _tokens[(int) token.Rid]; + object? type = _tokens[(int)token.Rid]; if (type is RuntimeTypeHandle runtimeTypeHandle) return _importer.ImportType(Type.GetTypeFromHandle(runtimeTypeHandle)); break; case TableIndex.Field: - object? field = _tokens[(int) token.Rid]; + object? field = _tokens[(int)token.Rid]; if (field is null) return null; @@ -61,7 +62,7 @@ public DynamicCilOperandResolver(SerializedModuleDefinition contextModule, CilMe case TableIndex.Method: case TableIndex.MemberRef: - object? obj = _tokens[(int) token.Rid]; + object? obj = _tokens[(int)token.Rid]; if (obj is null) return null; @@ -94,7 +95,7 @@ public DynamicCilOperandResolver(SerializedModuleDefinition contextModule, CilMe break; case TableIndex.StandAloneSig: - var reader = ByteArrayDataSource.CreateReader((byte[]) _tokens[(int) token.Rid]!); + var reader = new BinaryStreamReader((byte[])_tokens[(int)token.Rid]!); return CallingConventionSignature.FromReader(new BlobReadContext(_readerContext), ref reader); } @@ -104,7 +105,7 @@ public DynamicCilOperandResolver(SerializedModuleDefinition contextModule, CilMe /// public override object? ResolveString(MetadataToken token) { - return _tokens[(int) token.Rid] as string; + return _tokens[(int)token.Rid] as string; } } } diff --git a/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs b/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs new file mode 100644 index 000000000..e39ebc5f5 --- /dev/null +++ b/src/AsmResolver.DotNet.Dynamic/DynamicMethodDefinition.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using AsmResolver.DotNet.Code.Cil; +using AsmResolver.DotNet.Serialized; +using AsmResolver.DotNet.Signatures; +using AsmResolver.DotNet.Signatures.Types; +using AsmResolver.IO; +using AsmResolver.PE.DotNet.Cil; +using AsmResolver.PE.DotNet.Metadata.Tables; +using MethodAttributes = AsmResolver.PE.DotNet.Metadata.Tables.Rows.MethodAttributes; + +namespace AsmResolver.DotNet.Dynamic +{ + /// + /// Represents a single method in a type definition of a .NET module. + /// + public class DynamicMethodDefinition : MethodDefinition + { + /// + /// Create a Dynamic Method Definition + /// + /// Target Module + /// Dynamic Method / Delegate / DynamicResolver + public DynamicMethodDefinition(ModuleDefinition module, object dynamicMethodObj) : + base(new MetadataToken(TableIndex.Method, 0)) + { + dynamicMethodObj = DynamicMethodHelper.ResolveDynamicResolver(dynamicMethodObj); + var methodBase = FieldReader.ReadField(dynamicMethodObj, "m_method"); + if (methodBase is null) + { + throw new ArgumentException( + "Could not get the underlying method base in the provided dynamic method object."); + } + + Module = module; + Name = methodBase.Name; + Attributes = (MethodAttributes)methodBase.Attributes; + Signature = module.DefaultImporter.ImportMethodSignature(ResolveSig(methodBase, module)); + CilMethodBody = CreateDynamicMethodBody(this, dynamicMethodObj); + } + + private MethodSignature ResolveSig(MethodBase methodBase, ModuleDefinition module) + { + var importer = module.DefaultImporter; + var returnType = methodBase is MethodInfo info + ? importer.ImportTypeSignature(info.ReturnType) + : module.CorLibTypeFactory.Void; + + var parameters = methodBase.GetParameters(); + + var parameterTypes = new TypeSignature[parameters.Length]; + for (int i = 0; i < parameterTypes.Length; i++) + parameterTypes[i] = importer.ImportTypeSignature(parameters[i].ParameterType); + + return new MethodSignature( + methodBase.IsStatic ? 0 : CallingConventionAttributes.HasThis, + returnType, parameterTypes); + } + + /// + public override ModuleDefinition Module { get; } + + /// + /// Creates a CIL method body from a dynamic method. + /// + /// The method that owns the method body. + /// The Dynamic Method/Delegate/DynamicResolver. + /// The method body. + private static CilMethodBody CreateDynamicMethodBody(MethodDefinition method, object dynamicMethodObj) + { + if (method.Module is not SerializedModuleDefinition module) + throw new ArgumentException("Method body should reference a serialized module."); + + var result = new CilMethodBody(method); + dynamicMethodObj = DynamicMethodHelper.ResolveDynamicResolver(dynamicMethodObj); + + // Attempt to get the code field. + byte[]? code = FieldReader.ReadField(dynamicMethodObj, "m_code"); + + // If it is still null, it might still be set using DynamicILInfo::SetCode. + // Find the code stored in the DynamicILInfo if available. + if (code is null + && FieldReader.TryReadField(dynamicMethodObj, "m_method", out var methodBase) + && methodBase is not null + && FieldReader.TryReadField(methodBase, "m_DynamicILInfo", out object? dynamicILInfo) + && dynamicILInfo is not null) + { + code = FieldReader.ReadField(dynamicILInfo, "m_code"); + } + + if (code is null) + throw new InvalidOperationException("Dynamic method does not have a CIL code stream."); + + // Get remaining fields. + object scope = FieldReader.ReadField(dynamicMethodObj, "m_scope")!; + var tokenList = FieldReader.ReadField>(scope, "m_tokens")!; + byte[] localSig = FieldReader.ReadField(dynamicMethodObj, "m_localSignature")!; + byte[] ehHeader = FieldReader.ReadField(dynamicMethodObj, "m_exceptionHeader")!; + var ehInfos = FieldReader.ReadField>(dynamicMethodObj, "m_exceptions")!; + + //Local Variables + DynamicMethodHelper.ReadLocalVariables(result, method, localSig); + + // Read raw instructions. + var reader = new BinaryStreamReader(code); + var disassembler = new CilDisassembler(reader, new DynamicCilOperandResolver(module, result, tokenList)); + result.Instructions.AddRange(disassembler.ReadInstructions()); + + //Exception Handlers + DynamicMethodHelper.ReadReflectionExceptionHandlers(result, ehInfos, ehHeader, module.DefaultImporter); + + return result; + } + } +} diff --git a/src/AsmResolver.DotNet/DynamicMethodHelper.cs b/src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs similarity index 57% rename from src/AsmResolver.DotNet/DynamicMethodHelper.cs rename to src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs index f3cece5db..9deb9ec5e 100644 --- a/src/AsmResolver.DotNet/DynamicMethodHelper.cs +++ b/src/AsmResolver.DotNet.Dynamic/DynamicMethodHelper.cs @@ -6,22 +6,36 @@ using AsmResolver.DotNet.Code.Cil; using AsmResolver.DotNet.Serialized; using AsmResolver.DotNet.Signatures; +using AsmResolver.DotNet.Signatures.Types; using AsmResolver.IO; using AsmResolver.PE.DotNet.Cil; +using AsmResolver.PE.DotNet.Metadata.Tables.Rows; -namespace AsmResolver.DotNet +namespace AsmResolver.DotNet.Dynamic { internal static class DynamicMethodHelper { + private static readonly MethodInfo GetTypeFromHandleUnsafeMethod; + + static DynamicMethodHelper() + { + // We need to use reflection for this to stay compatible with .netstandard 2.0. + GetTypeFromHandleUnsafeMethod = typeof(Type) + .GetMethod("GetTypeFromHandleUnsafe", + (BindingFlags) (-1), + null, + new[] {typeof(IntPtr)}, + null)!; + } + public static void ReadLocalVariables(CilMethodBody methodBody, MethodDefinition method, byte[] localSig) { - if (!(method.Module is SerializedModuleDefinition module)) + if (method.Module is not SerializedModuleDefinition module) throw new ArgumentException("Method body should reference a serialized module."); - var reader = ByteArrayDataSource.CreateReader(localSig); - if (CallingConventionSignature.FromReader( - new BlobReadContext(module.ReaderContext), - ref reader) is not LocalVariablesSignature localsSignature) + var reader = new BinaryStreamReader(localSig); + if (ReadLocalVariableSignature(new BlobReadContext(module.ReaderContext), ref reader) + is not { } localsSignature) { throw new ArgumentException("Invalid local variables signature."); } @@ -30,14 +44,58 @@ public static void ReadLocalVariables(CilMethodBody methodBody, MethodDefinition methodBody.LocalVariables.Add(new CilLocalVariable(localsSignature.VariableTypes[i])); } + private static TypeSignature ReadTypeSignature(in BlobReadContext context, ref BinaryStreamReader reader) + { + return (ElementType) reader.PeekByte() == ElementType.Internal + ? ReadInternalTypeSignature(context, ref reader) + : TypeSignature.FromReader(in context, ref reader); + } + + private static TypeSignature ReadInternalTypeSignature(in BlobReadContext context, ref BinaryStreamReader reader) + { + var address = IntPtr.Size switch + { + 4 => new IntPtr(reader.ReadInt32()), + _ => new IntPtr(reader.ReadInt64()) + }; + + // Let the runtime translate the address to a type and import it. + var clrType = (Type?) GetTypeFromHandleUnsafeMethod.Invoke(null, new object[] { address }); + + var type = clrType is not null + ? new ReferenceImporter(context.ReaderContext.ParentModule).ImportType(clrType) + : InvalidTypeDefOrRef.Get(InvalidTypeSignatureError.IllegalTypeSpec); + + return new TypeDefOrRefSignature(type); + } + + private static LocalVariablesSignature ReadLocalVariableSignature( + in BlobReadContext context, + ref BinaryStreamReader reader) + { + var result = new LocalVariablesSignature(); + result.Attributes = (CallingConventionAttributes) reader.ReadByte(); + + if (!reader.TryReadCompressedUInt32(out uint count)) + { + context.ReaderContext.BadImage("Invalid number of local variables in local variable signature."); + return result; + } + + for (int i = 0; i < count; i++) + result.VariableTypes.Add(ReadTypeSignature(context, ref reader)); + + return result; + } + public static void ReadReflectionExceptionHandlers(CilMethodBody methodBody, IList? ehInfos, byte[] ehHeader, ReferenceImporter importer) { //Sample needed! - if (ehHeader is {Length: > 4}) + if (ehHeader is { Length: > 4 }) throw new NotImplementedException("Exception handlers from ehHeader not supported yet."); - if (ehInfos is {Count: > 0}) + if (ehInfos is { Count: > 0 }) { foreach (var ehInfo in ehInfos) InterpretEHInfo(methodBody, importer, ehInfo); @@ -49,7 +107,7 @@ private static void InterpretEHInfo(CilMethodBody methodBody, ReferenceImporter for (int i = 0; i < FieldReader.ReadField(ehInfo, "m_currentCatch"); i++) { // Get ExceptionHandlerInfo Field Values - var endFinally = FieldReader.ReadField(ehInfo, "m_endFinally"); + int endFinally = FieldReader.ReadField(ehInfo, "m_endFinally"); var instructions = methodBody.Instructions; var endFinallyLabel = endFinally >= 0 @@ -61,7 +119,7 @@ private static void InterpretEHInfo(CilMethodBody methodBody, ReferenceImporter int handlerStart = FieldReader.ReadField(ehInfo, "m_catchAddr")![i]; int handlerEnd = FieldReader.ReadField(ehInfo, "m_catchEndAddr")![i]; var exceptionType = FieldReader.ReadField(ehInfo, "m_catchClass")![i]; - var handlerType = (CilExceptionHandlerType) FieldReader.ReadField(ehInfo, "m_type")![i]; + var handlerType = (CilExceptionHandlerType)FieldReader.ReadField(ehInfo, "m_type")![i]; var endTryLabel = instructions.GetByOffset(tryEnd)?.CreateLabel() ?? new CilOffsetLabel(tryEnd); @@ -81,7 +139,6 @@ private static void InterpretEHInfo(CilMethodBody methodBody, ReferenceImporter } } - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls GetTypes")] public static object ResolveDynamicResolver(object dynamicMethodObj) { //Convert dynamicMethodObj to DynamicResolver @@ -97,21 +154,25 @@ public static object ResolveDynamicResolver(object dynamicMethodObj) if (dynamicMethodObj.GetType().FullName == "System.Reflection.Emit.DynamicMethod") { - var resolver = FieldReader.ReadField(dynamicMethodObj, "m_resolver"); + object? resolver = FieldReader.ReadField(dynamicMethodObj, "m_resolver"); if (resolver != null) dynamicMethodObj = resolver; } //Create Resolver if it does not exist. if (dynamicMethodObj.GetType().FullName == "System.Reflection.Emit.DynamicMethod") { - var dynamicResolver = typeof(OpCode).Module.GetTypes() + var dynamicResolver = typeof(OpCode).Module + .GetTypes() .First(t => t.Name == "DynamicResolver"); - var ilGenerator = dynamicMethodObj.GetType().GetRuntimeMethods().First(q => q.Name == "GetILGenerator") + object? ilGenerator = dynamicMethodObj + .GetType() + .GetRuntimeMethods() + .First(q => q.Name == "GetILGenerator") .Invoke(dynamicMethodObj, null); //Create instance of dynamicResolver - dynamicMethodObj = Activator.CreateInstance(dynamicResolver, (BindingFlags) (-1), null, new[] + dynamicMethodObj = Activator.CreateInstance(dynamicResolver, (BindingFlags)(-1), null, new[] { ilGenerator }, null)!; diff --git a/src/AsmResolver.DotNet/FieldReader.cs b/src/AsmResolver.DotNet.Dynamic/FieldReader.cs similarity index 96% rename from src/AsmResolver.DotNet/FieldReader.cs rename to src/AsmResolver.DotNet.Dynamic/FieldReader.cs index 8133f1e63..40e4a4450 100644 --- a/src/AsmResolver.DotNet/FieldReader.cs +++ b/src/AsmResolver.DotNet.Dynamic/FieldReader.cs @@ -1,6 +1,6 @@ -using System.Reflection; +using System.Reflection; -namespace AsmResolver.DotNet +namespace AsmResolver.DotNet.Dynamic { internal static class FieldReader { diff --git a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj index 456ce3119..edf05784b 100644 --- a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj +++ b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj @@ -8,6 +8,7 @@ 1701;1702;NU5105 enable net6.0;netcoreapp3.1;netstandard2.0 + true @@ -23,11 +24,11 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/AsmResolver.DotNet/AssemblyDescriptor.cs b/src/AsmResolver.DotNet/AssemblyDescriptor.cs index 5d19e5b42..ed22fb622 100644 --- a/src/AsmResolver.DotNet/AssemblyDescriptor.cs +++ b/src/AsmResolver.DotNet/AssemblyDescriptor.cs @@ -240,7 +240,6 @@ protected static byte[] ComputePublicKeyToken(byte[] publicKey, AssemblyHashAlgo AssemblyHashAlgorithm.None => SHA1.Create(), // Default algo is SHA-1. AssemblyHashAlgorithm.Md5 => MD5.Create(), AssemblyHashAlgorithm.Sha1 => SHA1.Create(), - AssemblyHashAlgorithm.Hmac => HMAC.Create(), AssemblyHashAlgorithm.Sha256 => SHA256.Create(), AssemblyHashAlgorithm.Sha384 => SHA384.Create(), AssemblyHashAlgorithm.Sha512 => SHA512.Create(), diff --git a/src/AsmResolver.DotNet/AssemblyReference.cs b/src/AsmResolver.DotNet/AssemblyReference.cs index d81b2b9c9..f699d00fc 100644 --- a/src/AsmResolver.DotNet/AssemblyReference.cs +++ b/src/AsmResolver.DotNet/AssemblyReference.cs @@ -70,8 +70,14 @@ public AssemblyReference(AssemblyDescriptor descriptor) Version = descriptor.Version; Attributes = descriptor.Attributes; HasPublicKey = false; + PublicKeyOrToken = descriptor.GetPublicKeyToken(); + if (PublicKeyOrToken?.Length == 0) + PublicKeyOrToken = null; + Culture = descriptor.Culture; + if (Utf8String.IsNullOrEmpty(Culture)) + Culture = null; } /// diff --git a/src/AsmResolver.DotNet/AssemblyResolverBase.cs b/src/AsmResolver.DotNet/AssemblyResolverBase.cs index bd3fa1ac1..64d057c99 100644 --- a/src/AsmResolver.DotNet/AssemblyResolverBase.cs +++ b/src/AsmResolver.DotNet/AssemblyResolverBase.cs @@ -13,10 +13,7 @@ namespace AsmResolver.DotNet public abstract class AssemblyResolverBase : IAssemblyResolver { private static readonly string[] BinaryFileExtensions = {".dll", ".exe"}; - private static readonly SignatureComparer Comparer = new() - { - AcceptNewerAssemblyVersionNumbers = true - }; + private static readonly SignatureComparer Comparer = new(SignatureComparisonFlags.AcceptNewerVersions); private readonly Dictionary _cache = new(new SignatureComparer()); diff --git a/src/AsmResolver.DotNet/Builder/Discovery/MemberDiscoverer.cs b/src/AsmResolver.DotNet/Builder/Discovery/MemberDiscoverer.cs index d6eeac3c6..ca626518d 100644 --- a/src/AsmResolver.DotNet/Builder/Discovery/MemberDiscoverer.cs +++ b/src/AsmResolver.DotNet/Builder/Discovery/MemberDiscoverer.cs @@ -121,6 +121,9 @@ public static MemberDiscoveryResult DiscoverMembersInModule(ModuleDefinition mod private void CollectExistingMembers() { + if (_module.DotNetDirectory?.Metadata is null) + return; + if ((_flags & MemberDiscoveryFlags.PreserveTypeOrder) != 0) CollectMembersFromTable(TableIndex.TypeDef); if ((_flags & MemberDiscoveryFlags.PreserveFieldOrder) != 0) @@ -139,8 +142,8 @@ private void CollectMembersFromTable(TableIndex tableIndex) where TMember: IMetadataMember, IModuleProvider { // Get original number of elements in the table. - int count = _module.DotNetDirectory!.Metadata - !.GetStream() + int count = _module.DotNetDirectory!.Metadata! + .GetStream() .GetTable(tableIndex) .Count; @@ -185,8 +188,9 @@ private void CollectNewlyAddedFixedMembers() var method = type.Methods[i]; InsertOrAppendIfNew(method, true); - foreach (var parameter in method.ParameterDefinitions) - InsertOrAppendIfNew(parameter, true); + // Try find new parameters. + for (int j = 0; j < method.ParameterDefinitions.Count; j++) + InsertOrAppendIfNew(method.ParameterDefinitions[j], true); } // Try find new properties. diff --git a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.MemberTree.cs b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.MemberTree.cs index 8e2e2ee27..617253eef 100644 --- a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.MemberTree.cs +++ b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.MemberTree.cs @@ -132,6 +132,9 @@ public void DefineTypes(IEnumerable types) var typeDefTable = Metadata.TablesStream.GetTable(TableIndex.TypeDef); var nestedClassTable = Metadata.TablesStream.GetSortedTable(TableIndex.NestedClass); + if (types is ICollection collection) + typeDefTable.EnsureCapacity(typeDefTable.Count + collection.Count); + foreach (var type in types) { // At this point, we might not have added all type defs/refs/specs yet, so we cannot determine @@ -177,6 +180,8 @@ public void DefineTypes(IEnumerable types) public void DefineFields(IEnumerable fields) { var table = Metadata.TablesStream.GetTable(TableIndex.Field); + if (fields is ICollection collection) + table.EnsureCapacity(table.Count + collection.Count); foreach (var field in fields) { @@ -197,6 +202,8 @@ public void DefineFields(IEnumerable fields) public void DefineMethods(IEnumerable methods) { var table = Metadata.TablesStream.GetTable(TableIndex.Method); + if (methods is ICollection collection) + table.EnsureCapacity(table.Count + collection.Count); foreach (var method in methods) { @@ -227,6 +234,8 @@ public void DefineMethods(IEnumerable methods) public void DefineParameters(IEnumerable parameters) { var table = Metadata.TablesStream.GetTable(TableIndex.Param); + if (parameters is ICollection collection) + table.EnsureCapacity(table.Count + collection.Count); foreach (var parameter in parameters) { @@ -247,6 +256,8 @@ public void DefineParameters(IEnumerable parameters) public void DefineProperties(IEnumerable properties) { var table = Metadata.TablesStream.GetTable(TableIndex.Property); + if (properties is ICollection collection) + table.EnsureCapacity(table.Count + collection.Count); foreach (var property in properties) { @@ -267,6 +278,8 @@ public void DefineProperties(IEnumerable properties) public void DefineEvents(IEnumerable events) { var table = Metadata.TablesStream.GetTable(TableIndex.Event); + if (events is ICollection collection) + table.EnsureCapacity(table.Count + collection.Count); foreach (var @event in events) { @@ -299,6 +312,17 @@ public void FinalizeTypes() uint propertyList = 1; uint eventList = 1; + tablesStream.GetTable(TableIndex.FieldPtr) + .EnsureCapacity(tablesStream.GetTable(TableIndex.Field).Count); + tablesStream.GetTable(TableIndex.MethodPtr) + .EnsureCapacity(tablesStream.GetTable(TableIndex.Method).Count); + tablesStream.GetTable(TableIndex.ParamPtr) + .EnsureCapacity(tablesStream.GetTable(TableIndex.Param).Count); + tablesStream.GetTable(TableIndex.PropertyPtr) + .EnsureCapacity(tablesStream.GetTable(TableIndex.Property).Count); + tablesStream.GetTable(TableIndex.EventPtr) + .EnsureCapacity(tablesStream.GetTable(TableIndex.Event).Count); + for (uint rid = 1; rid <= typeDefTable.Count; rid++) { var typeToken = new MetadataToken(TableIndex.TypeDef, rid); diff --git a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.cs b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.cs index 3b0e5ad60..60fe3f5af 100644 --- a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.cs +++ b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.cs @@ -143,7 +143,7 @@ public DotNetDirectoryBuildResult CreateDirectory() { Metadata = Metadata.CreateMetadata(), DotNetResources = Resources.Size > 0 ? Resources.CreateDirectory() : null, - Entrypoint = GetEntrypoint(), + EntryPoint = GetEntryPoint(), Flags = Module.Attributes, StrongName = StrongNameSize > 0 ? new DataSegment(new byte[StrongNameSize]) : null, VTableFixups = VTableFixups.Directory.Count > 0 ? VTableFixups.Directory : null @@ -152,29 +152,29 @@ public DotNetDirectoryBuildResult CreateDirectory() return new DotNetDirectoryBuildResult(directory, _tokenMapping); } - private uint GetEntrypoint() + private uint GetEntryPoint() { - if (Module.ManagedEntrypoint is null) + if (Module.ManagedEntryPoint is null) return 0; - var entrypointToken = MetadataToken.Zero; + var entryPointToken = MetadataToken.Zero; - switch (Module.ManagedEntrypoint.MetadataToken.Table) + switch (Module.ManagedEntryPoint.MetadataToken.Table) { case TableIndex.Method: - entrypointToken = GetMethodDefinitionToken(Module.ManagedEntrypointMethod!); + entryPointToken = GetMethodDefinitionToken(Module.ManagedEntryPointMethod!); break; case TableIndex.File: - entrypointToken = AddFileReference((FileReference) Module.ManagedEntrypoint); + entryPointToken = AddFileReference((FileReference) Module.ManagedEntryPoint); break; default: - ErrorListener.MetadataBuilder($"Invalid managed entrypoint {Module.ManagedEntrypoint.SafeToString()}."); + ErrorListener.MetadataBuilder($"Invalid managed entry point {Module.ManagedEntryPoint.SafeToString()}."); break; } - return entrypointToken.ToUInt32(); + return entryPointToken.ToUInt32(); } private void AddMethodSemantics(MetadataToken ownerToken, IHasSemantics provider) diff --git a/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs b/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs index 7c0619f9f..7750a6146 100644 --- a/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs +++ b/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs @@ -230,6 +230,9 @@ private void ImportBasicTablesIfSpecified(ModuleDefinition module, DotNetDirecto private void ImportTypeSpecsAndMemberRefsIfSpecified(ModuleDefinition module, DotNetDirectoryBuffer buffer) { + if (module.DotNetDirectory is null) + return; + if ((MetadataBuilderFlags & MetadataBuilderFlags.PreserveTypeSpecificationIndices) != 0) { ImportTables(module, TableIndex.TypeSpec, diff --git a/src/AsmResolver.DotNet/Builder/ManagedPEImageBuilder.cs b/src/AsmResolver.DotNet/Builder/ManagedPEImageBuilder.cs index 66ff6f0a8..c67b17956 100644 --- a/src/AsmResolver.DotNet/Builder/ManagedPEImageBuilder.cs +++ b/src/AsmResolver.DotNet/Builder/ManagedPEImageBuilder.cs @@ -70,7 +70,7 @@ public PEImageBuildResult CreateImage(ModuleDefinition module) }; // Construct new .NET directory. - var symbolProvider = new NativeSymbolsProvider(image.ImageBase); + var symbolProvider = new NativeSymbolsProvider(); var result = DotNetDirectoryFactory.CreateDotNetDirectory( module, symbolProvider, diff --git a/src/AsmResolver.DotNet/Builder/Metadata/Blob/BlobStreamBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Blob/BlobStreamBuffer.cs index 691afffa6..4074289d4 100644 --- a/src/AsmResolver.DotNet/Builder/Metadata/Blob/BlobStreamBuffer.cs +++ b/src/AsmResolver.DotNet/Builder/Metadata/Blob/BlobStreamBuffer.cs @@ -17,6 +17,8 @@ public class BlobStreamBuffer : IMetadataStreamBuffer private readonly BinaryStreamWriter _writer; private readonly Dictionary _blobs = new(ByteArrayEqualityComparer.Instance); + private readonly MemoryStreamWriterPool _blobWriterPool = new(); + /// /// Creates a new blob stream buffer with the default blob stream name. /// @@ -116,11 +118,11 @@ public uint GetBlobIndex(ITypeCodedIndexProvider provider, BlobSignature? signat return 0u; // Serialize blob. - using var stream = new MemoryStream(); - var writer = new BinaryStreamWriter(stream); - signature.Write(new BlobSerializationContext(writer, provider, errorListener)); - return GetBlobIndex(stream.ToArray()); + using var rentedWriter = _blobWriterPool.Rent(); + signature.Write(new BlobSerializationContext(rentedWriter.Writer, provider, errorListener)); + + return GetBlobIndex(rentedWriter.GetData()); } /// diff --git a/src/AsmResolver.DotNet/Builder/Metadata/MetadataBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/MetadataBuffer.cs index 05b7fbe95..4085377ee 100644 --- a/src/AsmResolver.DotNet/Builder/Metadata/MetadataBuffer.cs +++ b/src/AsmResolver.DotNet/Builder/Metadata/MetadataBuffer.cs @@ -88,6 +88,7 @@ public IMetadata CreateMetadata() var result = new PE.DotNet.Metadata.Metadata { VersionString = _versionString, + IsEncMetadata = TablesStream.IsEncMetadata }; // Create and add streams. diff --git a/src/AsmResolver.DotNet/Builder/Metadata/Tables/DistinctMetadataTableBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Tables/DistinctMetadataTableBuffer.cs index 32a440059..ac966743d 100644 --- a/src/AsmResolver.DotNet/Builder/Metadata/Tables/DistinctMetadataTableBuffer.cs +++ b/src/AsmResolver.DotNet/Builder/Metadata/Tables/DistinctMetadataTableBuffer.cs @@ -44,6 +44,16 @@ public DistinctMetadataTableBuffer(IMetadataTableBuffer underlyingBuffer) } } + /// + public void EnsureCapacity(int capacity) + { + _underlyingBuffer.EnsureCapacity(capacity); + +#if NETSTANDARD2_1_OR_GREATER + _entries.EnsureCapacity(capacity); +#endif + } + /// public ref TRow GetRowRef(uint rid) => ref _underlyingBuffer.GetRowRef(rid); diff --git a/src/AsmResolver.DotNet/Builder/Metadata/Tables/IMetadataTableBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Tables/IMetadataTableBuffer.cs index 5cecd39db..b73b5061a 100644 --- a/src/AsmResolver.DotNet/Builder/Metadata/Tables/IMetadataTableBuffer.cs +++ b/src/AsmResolver.DotNet/Builder/Metadata/Tables/IMetadataTableBuffer.cs @@ -44,6 +44,12 @@ public interface IMetadataTableBuffer : IMetadataTableBuffer set; } + /// + /// Ensures the capacity of the table buffer is at least the provided amount of elements. + /// + /// The number of elements to store. + void EnsureCapacity(int capacity); + /// /// Gets or sets a reference to a row in the metadata table. /// diff --git a/src/AsmResolver.DotNet/Builder/Metadata/Tables/TablesStreamBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Tables/TablesStreamBuffer.cs index 49651b052..e9786577f 100644 --- a/src/AsmResolver.DotNet/Builder/Metadata/Tables/TablesStreamBuffer.cs +++ b/src/AsmResolver.DotNet/Builder/Metadata/Tables/TablesStreamBuffer.cs @@ -74,7 +74,7 @@ public TablesStreamBuffer() } /// - public string Name => HasEnCData ? TablesStream.EncStreamName : TablesStream.CompressedStreamName; + public string Name => IsEncMetadata ? TablesStream.EncStreamName : TablesStream.CompressedStreamName; /// public bool IsEmpty @@ -92,9 +92,9 @@ public bool IsEmpty } /// - /// Gets a value indicating whether the buffer contains edit-and-continue data. + /// Gets a value indicating whether the buffer contains edit-and-continue metadata tables. /// - public bool HasEnCData + public bool IsEncMetadata { get { diff --git a/src/AsmResolver.DotNet/Builder/Metadata/Tables/UnsortedMetadataTableBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Tables/UnsortedMetadataTableBuffer.cs index fe754d7ac..17717b185 100644 --- a/src/AsmResolver.DotNet/Builder/Metadata/Tables/UnsortedMetadataTableBuffer.cs +++ b/src/AsmResolver.DotNet/Builder/Metadata/Tables/UnsortedMetadataTableBuffer.cs @@ -42,6 +42,13 @@ public UnsortedMetadataTableBuffer(MetadataTable table) } } + /// + public void EnsureCapacity(int capacity) + { + if (_entries.Capacity < capacity) + _entries.Capacity = capacity; + } + /// public ref TRow GetRowRef(uint rid) => ref _entries.GetElementRef((int)(rid - 1)); @@ -92,6 +99,9 @@ private void EnsureRowsAllocated(uint rid) /// public void FlushToTable() { + if (_table.Capacity < _entries.Count) + _table.Capacity = _entries.Count; + foreach (var row in _entries) _table.Add(row); } diff --git a/src/AsmResolver.DotNet/Builder/VTableFixups/VTableFixupsBuffer.cs b/src/AsmResolver.DotNet/Builder/VTableFixups/VTableFixupsBuffer.cs index 44ceec89c..e0f512a63 100644 --- a/src/AsmResolver.DotNet/Builder/VTableFixups/VTableFixupsBuffer.cs +++ b/src/AsmResolver.DotNet/Builder/VTableFixups/VTableFixupsBuffer.cs @@ -61,7 +61,7 @@ public void MapTokenToExport(UnmanagedExportInfo exportInfo, MetadataToken token vtableFixup.Tokens.Add(token); var vtableSymbol = new Symbol(vtableFixup.Tokens.GetReferenceToIndex(vtableFixup.Tokens.Count - 1)); - var thunkStub = _targetPlatform.CreateThunkStub(0x00400000, vtableSymbol); + var thunkStub = _targetPlatform.CreateThunkStub(vtableSymbol); // Register exported symbol. var stubReference = thunkStub.Segment.ToReference(); diff --git a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs index 2acc2a57b..c3a6025cf 100644 --- a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs +++ b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs @@ -445,7 +445,7 @@ private void WriteFileContents(IBinaryStreamWriter writer, uint alignment) if (file.Type == BundleFileType.Assembly) writer.Align(alignment); - file.Contents.UpdateOffsets(writer.Offset, (uint) writer.Offset); + file.Contents.UpdateOffsets(new RelocationParameters(writer.Offset, (uint) writer.Offset)); file.Contents.Write(writer); } } diff --git a/src/AsmResolver.DotNet/Cloning/CallbackClonerListener.cs b/src/AsmResolver.DotNet/Cloning/CallbackClonerListener.cs new file mode 100644 index 000000000..e2a956603 --- /dev/null +++ b/src/AsmResolver.DotNet/Cloning/CallbackClonerListener.cs @@ -0,0 +1,33 @@ +using System; + +namespace AsmResolver.DotNet.Cloning +{ + /// + /// This implementation that calls the to a callback action. + /// + public class CallbackClonerListener : MemberClonerListener + { + private readonly Action _callback; + + /// + /// Creates a new instance of the class. + /// + /// The Callback used. + public CallbackClonerListener(Action callback) => + _callback = callback; + + /// + /// Gets a singleton instance of the class that performs no operation + /// on any of the cloning procedure notifications. + /// + public static CallbackClonerListener EmptyInstance + { + get; + } = new((_, _) => { }); + + /// + public override void OnClonedMember(IMemberDefinition original, IMemberDefinition cloned) => + _callback(original, cloned); + + } +} diff --git a/src/AsmResolver.DotNet/Cloning/IMemberClonerListener.cs b/src/AsmResolver.DotNet/Cloning/IMemberClonerListener.cs new file mode 100644 index 000000000..79bcbaae2 --- /dev/null +++ b/src/AsmResolver.DotNet/Cloning/IMemberClonerListener.cs @@ -0,0 +1,45 @@ +namespace AsmResolver.DotNet.Cloning +{ + /// + /// Callback listener that receives calls after cloning process. + /// + public interface IMemberClonerListener + { + /// + /// This function is called for every member that got cloned. + /// + /// The original member. + /// The cloned member. + public void OnClonedMember(IMemberDefinition original, IMemberDefinition cloned); + /// + /// This function is called for every type that got cloned. + /// + /// The original type. + /// The cloned type. + public void OnClonedType(TypeDefinition original, TypeDefinition cloned); + /// + /// This function is called for every method that got cloned. + /// + /// The original method. + /// The cloned method. + public void OnClonedMethod(MethodDefinition original, MethodDefinition cloned); + /// + /// This function is called for every field that got cloned. + /// + /// The original field. + /// The cloned field. + public void OnClonedField(FieldDefinition original, FieldDefinition cloned); + /// + /// This function is called for every property that got cloned. + /// + /// The original property. + /// The cloned property. + public void OnClonedProperty(PropertyDefinition original, PropertyDefinition cloned); + /// + /// This function is called for every event that got cloned. + /// + /// The original event. + /// The cloned event. + public void OnClonedEvent(EventDefinition original, EventDefinition cloned); + } +} diff --git a/src/AsmResolver.DotNet/Cloning/InjectTypeClonerListener.cs b/src/AsmResolver.DotNet/Cloning/InjectTypeClonerListener.cs new file mode 100644 index 000000000..d69c9e1b9 --- /dev/null +++ b/src/AsmResolver.DotNet/Cloning/InjectTypeClonerListener.cs @@ -0,0 +1,34 @@ +namespace AsmResolver.DotNet.Cloning +{ + /// + /// Implements a that injects all non-nested types into the target module. + /// + public class InjectTypeClonerListener : MemberClonerListener + { + /// + /// Creates a new instance of the type. + /// + /// The target module to inject into. + public InjectTypeClonerListener(ModuleDefinition targetModule) + { + TargetModule = targetModule; + } + + /// + /// Gets the target module to inject the types in. + /// + public ModuleDefinition TargetModule + { + get; + } + + /// + public override void OnClonedType(TypeDefinition original, TypeDefinition cloned) + { + if (!original.IsNested) + TargetModule.TopLevelTypes.Add(cloned); + + base.OnClonedType(original, cloned); + } + } +} diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloneContext.cs b/src/AsmResolver.DotNet/Cloning/MemberCloneContext.cs index 78c52c8c3..9c0a297c1 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloneContext.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloneContext.cs @@ -35,9 +35,9 @@ public ModuleDefinition Module } /// - /// Gets the object responsible for importing references into the target mdoule. + /// Gets the object responsible for importing references into the target module. /// - public ReferenceImporter Importer + public CloneContextAwareReferenceImporter Importer { get; } diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.Fields.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.Fields.cs index b68ab9c81..78df400d3 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloner.Fields.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.Fields.cs @@ -39,7 +39,12 @@ private static FieldDefinition CreateFieldStub(MemberCloneContext context, Field private void DeepCopyFields(MemberCloneContext context) { foreach (var field in _fieldsToClone) + { DeepCopyField(context, field); + var clonedMember = (FieldDefinition)context.ClonedMembers[field]; + _clonerListener.OnClonedMember(field, clonedMember); + _clonerListener.OnClonedField(field, clonedMember); + } } private void DeepCopyField(MemberCloneContext context, FieldDefinition field) diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.Methods.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.Methods.cs index a7c2dc227..af6085a14 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloner.Methods.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.Methods.cs @@ -54,7 +54,12 @@ private static ParameterDefinition CloneParameterDefinition(MemberCloneContext c private void DeepCopyMethods(MemberCloneContext context) { foreach (var method in _methodsToClone) + { DeepCopyMethod(context, method); + var clonedMember = (MethodDefinition)context.ClonedMembers[method]; + _clonerListener.OnClonedMember(method, clonedMember); + _clonerListener.OnClonedMethod(method, clonedMember); + } } private void DeepCopyMethod(MemberCloneContext context, MethodDefinition method) diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs index 10f3f30c4..7d3e5ea6b 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs @@ -17,6 +17,9 @@ private void DeepCopyProperties(MemberCloneContext context) { declaringType.Properties.Add(clonedProperty); } + var clonedMember = clonedProperty; + _clonerListener.OnClonedMember(property, clonedMember); + _clonerListener.OnClonedProperty(property, clonedMember); } } @@ -51,6 +54,9 @@ private void DeepCopyEvents(MemberCloneContext context) { declaringType.Events.Add(clonedEvent); } + var clonedMember = clonedEvent; + _clonerListener.OnClonedMember(@event, clonedMember); + _clonerListener.OnClonedEvent(@event, clonedMember); } } diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.cs index 1580d1efa..64acfe3a5 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloner.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.cs @@ -17,6 +17,7 @@ namespace AsmResolver.DotNet.Cloning /// public partial class MemberCloner { + private readonly IMemberClonerListener _clonerListener; private readonly Func? _importerFactory; private readonly ModuleDefinition _targetModule; @@ -30,7 +31,10 @@ public partial class MemberCloner /// Creates a new instance of the class. /// /// The target module to copy the members into. - public MemberCloner(ModuleDefinition targetModule) : this(targetModule, null) { } + public MemberCloner(ModuleDefinition targetModule) + : this(targetModule, (original, cloned) => { }) + { + } /// /// Creates a new instance of the class. @@ -39,9 +43,43 @@ public partial class MemberCloner /// The factory for creating the reference importer public MemberCloner(ModuleDefinition targetModule, Func? importerFactory) + : this(targetModule, importerFactory, null) + { + } + + /// + /// Creates a new instance of the class. + /// + /// The target module to copy the members into. + /// The callback used in the cloner listener. + public MemberCloner(ModuleDefinition targetModule, Action callback) + : this(targetModule, new CallbackClonerListener(callback)) + { + } + + /// + /// Creates a new instance of the class. + /// + /// The target module to copy the members into. + /// The callback listener used in the cloner. + public MemberCloner(ModuleDefinition targetModule, IMemberClonerListener listener) + : this(targetModule, null, listener) + { + } + + /// + /// Creates a new instance of the class. + /// + /// The target module to copy the members into. + /// The factory for creating the reference importer + /// The listener used in the cloner. + public MemberCloner(ModuleDefinition targetModule, + Func? importerFactory, + IMemberClonerListener? clonerListener) { _targetModule = targetModule ?? throw new ArgumentNullException(nameof(targetModule)); _importerFactory = importerFactory; + _clonerListener = clonerListener ?? CallbackClonerListener.EmptyInstance; } /// @@ -272,7 +310,12 @@ private void DeepCopyMembers(MemberCloneContext context) private void DeepCopyTypes(MemberCloneContext context) { foreach (var type in _typesToClone) + { DeepCopyType(context, type); + var clonedMember = (TypeDefinition)context.ClonedMembers[type]; + _clonerListener.OnClonedMember(type, clonedMember); + _clonerListener.OnClonedType(type, clonedMember); + } } private void DeepCopyType(MemberCloneContext context, TypeDefinition type) diff --git a/src/AsmResolver.DotNet/Cloning/MemberClonerListener.cs b/src/AsmResolver.DotNet/Cloning/MemberClonerListener.cs new file mode 100644 index 000000000..0c527ff40 --- /dev/null +++ b/src/AsmResolver.DotNet/Cloning/MemberClonerListener.cs @@ -0,0 +1,19 @@ +namespace AsmResolver.DotNet.Cloning +{ + /// + public abstract class MemberClonerListener : IMemberClonerListener + { + /// + public virtual void OnClonedMember(IMemberDefinition original, IMemberDefinition cloned) { } + /// + public virtual void OnClonedEvent(EventDefinition original, EventDefinition cloned) { } + /// + public virtual void OnClonedField(FieldDefinition original, FieldDefinition cloned) { } + /// + public virtual void OnClonedMethod(MethodDefinition original, MethodDefinition cloned) { } + /// + public virtual void OnClonedProperty(PropertyDefinition original, PropertyDefinition cloned) { } + /// + public virtual void OnClonedType(TypeDefinition original, TypeDefinition cloned) { } + } +} diff --git a/src/AsmResolver.DotNet/Code/Cil/CilInstructionCollection.cs b/src/AsmResolver.DotNet/Code/Cil/CilInstructionCollection.cs index 8287e1895..970bc7b26 100644 --- a/src/AsmResolver.DotNet/Code/Cil/CilInstructionCollection.cs +++ b/src/AsmResolver.DotNet/Code/Cil/CilInstructionCollection.cs @@ -260,7 +260,7 @@ private void ExpandMacro(CilInstruction instruction) break; case CilCode.Ldarga_S: - instruction.OpCode = CilOpCodes.Ldarga_S; + instruction.OpCode = CilOpCodes.Ldarga; break; case CilCode.Starg_S: @@ -314,7 +314,7 @@ private void ExpandMacro(CilInstruction instruction) instruction.OpCode = CilOpCodes.Brtrue; break; case CilCode.Bge_Un_S: - instruction.OpCode = CilOpCodes.Bge_Un_S; + instruction.OpCode = CilOpCodes.Bge_Un; break; case CilCode.Bgt_Un_S: instruction.OpCode = CilOpCodes.Bgt_Un; diff --git a/src/AsmResolver.DotNet/Code/Cil/CilMaxStackCalculator.cs b/src/AsmResolver.DotNet/Code/Cil/CilMaxStackCalculator.cs index e485fd1e9..3237c0914 100644 --- a/src/AsmResolver.DotNet/Code/Cil/CilMaxStackCalculator.cs +++ b/src/AsmResolver.DotNet/Code/Cil/CilMaxStackCalculator.cs @@ -132,14 +132,31 @@ private void ScheduleSuccessors(in StackState currentState) { case CilFlowControl.Branch: // Schedule branch target. - ScheduleLabel(instruction.Offset, (ICilLabel) instruction.Operand!, nextStackSize); + switch (instruction.Operand) + { + case sbyte delta: + ScheduleDelta(currentState.InstructionIndex, delta, nextStackSize); + break; + + case int delta: + ScheduleDelta(currentState.InstructionIndex, delta, nextStackSize); + break; + + case ICilLabel label: + ScheduleLabel(currentState.InstructionIndex, label, nextStackSize); + break; + + default: + throw new NotSupportedException( + $"Invalid or unsupported operand type at offset IL_{instruction.Offset:X4}."); + } break; case CilFlowControl.ConditionalBranch when instruction.OpCode.Code == CilCode.Switch: // Schedule all switch targets for processing. var targets = (IList) instruction.Operand!; for (int i = 0; i < targets.Count; i++) - ScheduleLabel(instruction.Offset, targets[i], nextStackSize); + ScheduleLabel(currentState.InstructionIndex, targets[i], nextStackSize); // Schedule default case (= fallthrough instruction). ScheduleNext(currentState.InstructionIndex, nextStackSize); @@ -147,7 +164,7 @@ private void ScheduleSuccessors(in StackState currentState) case CilFlowControl.ConditionalBranch: // Schedule branch target. - ScheduleLabel(instruction.Offset, (ICilLabel) instruction.Operand!, nextStackSize); + ScheduleLabel(currentState.InstructionIndex, (ICilLabel) instruction.Operand!, nextStackSize); // Schedule fallthrough instruction. ScheduleNext(currentState.InstructionIndex, nextStackSize); @@ -187,6 +204,14 @@ private void ScheduleLabel(int currentIndex, ICilLabel label, int nextStackSize) ScheduleIndex(currentIndex, nextIndex, label.Offset, nextStackSize); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ScheduleDelta(int currentIndex, int offsetDelta, int nextStackSize) + { + int nextOffset = _body.Instructions[currentIndex].Offset + offsetDelta; + int nextIndex = _body.Instructions.GetIndexByOffset(nextOffset); + ScheduleIndex(currentIndex, nextIndex, nextOffset, nextStackSize); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ScheduleNext(int currentIndex, int nextStackSize) { diff --git a/src/AsmResolver.DotNet/Code/Cil/CilMethodBody.cs b/src/AsmResolver.DotNet/Code/Cil/CilMethodBody.cs index 180e474f8..3e7beb925 100644 --- a/src/AsmResolver.DotNet/Code/Cil/CilMethodBody.cs +++ b/src/AsmResolver.DotNet/Code/Cil/CilMethodBody.cs @@ -130,43 +130,6 @@ public bool VerifyLabelsOnBuild | (value ? CilMethodBodyBuildFlags.VerifyLabels : 0); } - /// - /// Creates a CIL method body from a dynamic method. - /// - /// The method that owns the method body. - /// The Dynamic Method/Delegate/DynamicResolver. - /// The method body. - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls ResolveDynamicResolver")] - public static CilMethodBody FromDynamicMethod(MethodDefinition method, object dynamicMethodObj) - { - if (!(method.Module is SerializedModuleDefinition module)) - throw new ArgumentException("Method body should reference a serialized module."); - - var result = new CilMethodBody(method); - dynamicMethodObj = DynamicMethodHelper.ResolveDynamicResolver(dynamicMethodObj); - - //Get Runtime Fields - byte[] code = FieldReader.ReadField(dynamicMethodObj, "m_code")!; - object scope = FieldReader.ReadField(dynamicMethodObj, "m_scope")!; - var tokenList = FieldReader.ReadField>(scope, "m_tokens")!; - byte[] localSig = FieldReader.ReadField(dynamicMethodObj, "m_localSignature")!; - byte[] ehHeader = FieldReader.ReadField(dynamicMethodObj, "m_exceptionHeader")!; - var ehInfos = FieldReader.ReadField>(dynamicMethodObj, "m_exceptions")!; - - //Local Variables - DynamicMethodHelper.ReadLocalVariables(result, method, localSig); - - // Read raw instructions. - var reader = ByteArrayDataSource.CreateReader(code); - var disassembler = new CilDisassembler(reader, new DynamicCilOperandResolver(module, result, tokenList)); - result.Instructions.AddRange(disassembler.ReadInstructions()); - - //Exception Handlers - DynamicMethodHelper.ReadReflectionExceptionHandlers(result, ehInfos, ehHeader, new ReferenceImporter(module)); - - return result; - } - /// /// Creates a CIL method body from a raw CIL method body. /// @@ -184,15 +147,13 @@ public static CilMethodBody FromDynamicMethod(MethodDefinition method, object dy { var result = new CilMethodBody(method); - operandResolver ??= new PhysicalCilOperandResolver(context.ParentModule, result); - // Interpret body header. var fatBody = rawBody as CilRawFatMethodBody; if (fatBody is not null) { result.MaxStack = fatBody.MaxStack; result.InitializeLocals = fatBody.InitLocals; - ReadLocalVariables(context.ParentModule, result, fatBody); + ReadLocalVariables(context, result, fatBody); } else { @@ -201,56 +162,89 @@ public static CilMethodBody FromDynamicMethod(MethodDefinition method, object dy } // Parse instructions. - ReadInstructions(result, operandResolver, rawBody); + operandResolver ??= new PhysicalCilOperandResolver(context.ParentModule, result); + ReadInstructions(context, result, operandResolver, rawBody); // Read exception handlers. if (fatBody is not null) - ReadExceptionHandlers(fatBody, result); + ReadExceptionHandlers(context, fatBody, result); return result; } private static void ReadLocalVariables( - ModuleDefinition module, + ModuleReaderContext context, CilMethodBody result, CilRawFatMethodBody fatBody) { - if (fatBody.LocalVarSigToken != MetadataToken.Zero - && module.TryLookupMember(fatBody.LocalVarSigToken, out var member) - && member is StandAloneSignature {Signature: LocalVariablesSignature localVariablesSignature}) + // Method bodies can have 0 tokens if there are no locals defined. + if (fatBody.LocalVarSigToken == MetadataToken.Zero) + return; + + // If there is a non-zero token however, it needs to point to a stand-alone signature with a + // local variable signature stored in it. + if (!context.ParentModule.TryLookupMember(fatBody.LocalVarSigToken, out var member) + || member is not StandAloneSignature { Signature: LocalVariablesSignature localVariablesSignature }) { - var variableTypes = localVariablesSignature.VariableTypes; - for (int i = 0; i < variableTypes.Count; i++) - result.LocalVariables.Add(new CilLocalVariable(variableTypes[i])); + context.BadImage($"Method body of {result.Owner.SafeToString()} contains an invalid local variable signature token."); + return; } + + // Copy over the local variable types from the signature into the method body. + var variableTypes = localVariablesSignature.VariableTypes; + for (int i = 0; i < variableTypes.Count; i++) + result.LocalVariables.Add(new CilLocalVariable(variableTypes[i])); } private static void ReadInstructions( + ModuleReaderContext context, CilMethodBody result, ICilOperandResolver operandResolver, CilRawMethodBody rawBody) { - var reader = rawBody.Code.CreateReader(); - var disassembler = new CilDisassembler(reader, operandResolver); - result.Instructions.AddRange(disassembler.ReadInstructions()); + try + { + var reader = rawBody.Code.CreateReader(); + var disassembler = new CilDisassembler(reader, operandResolver); + result.Instructions.AddRange(disassembler.ReadInstructions()); + } + catch (Exception ex) + { + context.RegisterException(new BadImageFormatException( + $"Method body of {result.Owner.SafeToString()} contains an invalid CIL code stream.", ex)); + } } - private static void ReadExceptionHandlers(CilRawFatMethodBody fatBody, CilMethodBody result) + private static void ReadExceptionHandlers( + ModuleReaderContext context, + CilRawFatMethodBody fatBody, + CilMethodBody result) { - for (int i = 0; i < fatBody.ExtraSections.Count; i++) + try { - var section = fatBody.ExtraSections[i]; - if (section.IsEHTable) + for (int i = 0; i < fatBody.ExtraSections.Count; i++) { - var reader = ByteArrayDataSource.CreateReader(section.Data); - uint size = section.IsFat - ? CilExceptionHandler.FatExceptionHandlerSize - : CilExceptionHandler.TinyExceptionHandlerSize; - - while (reader.CanRead(size)) - result.ExceptionHandlers.Add(CilExceptionHandler.FromReader(result, ref reader, section.IsFat)); + var section = fatBody.ExtraSections[i]; + if (section.IsEHTable) + { + var reader = new BinaryStreamReader(section.Data); + uint size = section.IsFat + ? CilExceptionHandler.FatExceptionHandlerSize + : CilExceptionHandler.TinyExceptionHandlerSize; + + while (reader.CanRead(size)) + { + var handler = CilExceptionHandler.FromReader(result, ref reader, section.IsFat); + result.ExceptionHandlers.Add(handler); + } + } } } + catch (Exception ex) + { + context.RegisterException(new BadImageFormatException( + $"Method body of {result.Owner.SafeToString()} contains invalid extra sections.", ex)); + } } /// diff --git a/src/AsmResolver.DotNet/Code/Cil/CilMethodBodySerializer.cs b/src/AsmResolver.DotNet/Code/Cil/CilMethodBodySerializer.cs index 023e04f9e..43c98d62d 100644 --- a/src/AsmResolver.DotNet/Code/Cil/CilMethodBodySerializer.cs +++ b/src/AsmResolver.DotNet/Code/Cil/CilMethodBodySerializer.cs @@ -15,13 +15,15 @@ namespace AsmResolver.DotNet.Code.Cil /// public class CilMethodBodySerializer : IMethodBodySerializer { + private readonly MemoryStreamWriterPool _writerPool = new(); + /// /// Gets or sets the value of an override switch indicating whether the max stack should always be recalculated /// or should always be preserved. /// /// /// - /// When this property is set to true, the maximum stack depth of all method bodies will be recaculated. + /// When this property is set to true, the maximum stack depth of all method bodies will be recalculated. /// /// /// When this property is set to false, the maximum stack depth of all method bodies will be preserved. @@ -35,7 +37,7 @@ public class CilMethodBodySerializer : IMethodBodySerializer { get; set; - } + } = null; /// /// Gets or sets the value of an override switch indicating whether labels should always be verified for @@ -62,7 +64,7 @@ public class CilMethodBodySerializer : IMethodBodySerializer /// public ISegmentReference SerializeMethodBody(MethodBodySerializationContext context, MethodDefinition method) { - if (method.CilMethodBody == null) + if (method.CilMethodBody is null) return SegmentReference.Null; var body = method.CilMethodBody; @@ -88,7 +90,7 @@ public ISegmentReference SerializeMethodBody(MethodBodySerializationContext cont } // Serialize CIL stream. - var code = BuildRawCodeStream(context, body); + byte[] code = BuildRawCodeStream(context, body); // Build method body. var rawBody = body.IsFat @@ -98,8 +100,7 @@ public ISegmentReference SerializeMethodBody(MethodBodySerializationContext cont return rawBody.ToReference(); } - private static CilRawMethodBody BuildTinyMethodBody(byte[] code) => - new CilRawTinyMethodBody(code); + private static CilRawMethodBody BuildTinyMethodBody(byte[] code) => new CilRawTinyMethodBody(code); private CilRawMethodBody BuildFatMethodBody(MethodBodySerializationContext context, CilMethodBody body, byte[] code) { @@ -111,7 +112,10 @@ private CilRawMethodBody BuildFatMethodBody(MethodBodySerializationContext conte } else { - var localVarSig = new LocalVariablesSignature(body.LocalVariables.Select(v => v.VariableType)); + var localVarSig = new LocalVariablesSignature(); + for (int i = 0; i < body.LocalVariables.Count; i++) + localVarSig.VariableTypes.Add(body.LocalVariables[i].VariableType); + var standAloneSig = new StandAloneSignature(localVarSig); token = context.TokenProvider.GetStandAloneSignatureToken(standAloneSig); } @@ -137,35 +141,33 @@ private CilRawMethodBody BuildFatMethodBody(MethodBodySerializationContext conte return fatBody; } - private static byte[] BuildRawCodeStream(MethodBodySerializationContext context, CilMethodBody body) + private byte[] BuildRawCodeStream(MethodBodySerializationContext context, CilMethodBody body) { - using var codeStream = new MemoryStream(); var bag = context.ErrorListener; - var writer = new BinaryStreamWriter(codeStream); + using var rentedWriter = _writerPool.Rent(); var assembler = new CilAssembler( - writer, + rentedWriter.Writer, new CilOperandBuilder(context.TokenProvider, bag), body.Owner.SafeToString, bag); assembler.WriteInstructions(body.Instructions); - return codeStream.ToArray(); + return rentedWriter.GetData(); } private byte[] SerializeExceptionHandlers(MethodBodySerializationContext context, IList exceptionHandlers, bool needsFatFormat) { - using var sectionStream = new MemoryStream(); - var writer = new BinaryStreamWriter(sectionStream); + using var rentedWriter = _writerPool.Rent(); for (int i = 0; i < exceptionHandlers.Count; i++) { var handler = exceptionHandlers[i]; - WriteExceptionHandler(context, writer, handler, needsFatFormat); + WriteExceptionHandler(context, rentedWriter.Writer, handler, needsFatFormat); } - return sectionStream.ToArray(); + return rentedWriter.GetData(); } private void WriteExceptionHandler(MethodBodySerializationContext context, IBinaryStreamWriter writer, CilExceptionHandler handler, bool useFatFormat) diff --git a/src/AsmResolver.DotNet/Code/Cil/CilOperandBuilder.cs b/src/AsmResolver.DotNet/Code/Cil/CilOperandBuilder.cs index c39f58992..92fe90c14 100644 --- a/src/AsmResolver.DotNet/Code/Cil/CilOperandBuilder.cs +++ b/src/AsmResolver.DotNet/Code/Cil/CilOperandBuilder.cs @@ -31,8 +31,7 @@ public int GetVariableIndex(object? operand) CilLocalVariable localVariable => localVariable.Index, byte raw => raw, ushort raw => raw, - _ => _errorListener.RegisterExceptionAndReturnDefault( - new NotSupportedException($"Invalid or unsupported variable operand ({operand.SafeToString()}).")) + _ => _errorListener.NotSupportedAndReturn($"Invalid or unsupported variable operand ({operand.SafeToString()}).") }; } @@ -44,8 +43,7 @@ public int GetArgumentIndex(object? operand) Parameter parameter => parameter.MethodSignatureIndex, byte raw => raw, ushort raw => raw, - _ => _errorListener.RegisterExceptionAndReturnDefault( - new NotSupportedException($"Invalid or unsupported argument operand ({operand.SafeToString()}).")) + _ => _errorListener.NotSupportedAndReturn($"Invalid or unsupported argument operand ({operand.SafeToString()}).") }; } @@ -55,9 +53,9 @@ public uint GetStringToken(object? operand) return operand switch { string value => 0x70000000 | _provider.GetUserStringIndex(value), + MetadataToken token => token.ToUInt32(), uint raw => raw, - _ => _errorListener.RegisterExceptionAndReturnDefault( - new NotSupportedException($"Invalid or unsupported string operand ({operand.SafeToString()}).")) + _ => _errorListener.NotSupportedAndReturn($"Invalid or unsupported string operand ({operand.SafeToString()}).") }; } @@ -69,8 +67,7 @@ public MetadataToken GetMemberToken(object? operand) IMetadataMember member => GetMemberToken(member), MetadataToken token => token, uint raw => raw, - _ => _errorListener.RegisterExceptionAndReturnDefault( - new NotSupportedException($"Invalid or unsupported member operand ({operand.SafeToString()}).")) + _ => _errorListener.NotSupportedAndReturn($"Invalid or unsupported member operand ({operand.SafeToString()}).") }; } diff --git a/src/AsmResolver.DotNet/Code/Cil/OriginalMetadataTokenProvider.cs b/src/AsmResolver.DotNet/Code/Cil/OriginalMetadataTokenProvider.cs new file mode 100644 index 000000000..db8440077 --- /dev/null +++ b/src/AsmResolver.DotNet/Code/Cil/OriginalMetadataTokenProvider.cs @@ -0,0 +1,74 @@ +using AsmResolver.DotNet.Builder.Metadata; +using AsmResolver.PE.DotNet.Metadata.Tables; +using AsmResolver.PE.DotNet.Metadata.UserStrings; + +namespace AsmResolver.DotNet.Code.Cil +{ + /// + /// Provides an implementation for the interface that always returns the + /// original metadata token that was assigned to the provided metadata member or string. + /// + public class OriginalMetadataTokenProvider : IMetadataTokenProvider + { + private readonly ModuleDefinition? _module; + + /// + /// Creates a new token provider. + /// + /// + /// The module to pull the original tokens from, or null if no verification should be done on the + /// declaring module. + /// + public OriginalMetadataTokenProvider(ModuleDefinition? module) + { + _module = module; + } + + private MetadataToken GetToken(IMetadataMember member) + { + if (_module is not null && member is IModuleProvider provider && provider.Module == _module) + throw new MemberNotImportedException(member); + + return member.MetadataToken; + } + + /// + public MetadataToken GetTypeReferenceToken(TypeReference type) => GetToken(type); + + /// + public MetadataToken GetTypeDefinitionToken(TypeDefinition type) => GetToken(type); + + /// + public MetadataToken GetFieldDefinitionToken(FieldDefinition field) => GetToken(field); + + /// + public MetadataToken GetMethodDefinitionToken(MethodDefinition method) => GetToken(method); + + /// + public MetadataToken GetMemberReferenceToken(MemberReference member) => GetToken(member); + + /// + public MetadataToken GetStandAloneSignatureToken(StandAloneSignature signature) => GetToken(signature); + + /// + public MetadataToken GetAssemblyReferenceToken(AssemblyReference assembly) => GetToken(assembly); + + /// + public MetadataToken GetTypeSpecificationToken(TypeSpecification type) => GetToken(type); + + /// + public MetadataToken GetMethodSpecificationToken(MethodSpecification method) => GetToken(method); + + /// + public uint GetUserStringIndex(string value) + { + if (_module?.DotNetDirectory?.Metadata?.TryGetStream(out UserStringsStream? stream) ?? false) + { + if (stream.TryFindStringIndex(value, out uint offset)) + return offset; + } + + return 0; + } + } +} diff --git a/src/AsmResolver.DotNet/Code/Native/INativeSymbolsProvider.cs b/src/AsmResolver.DotNet/Code/Native/INativeSymbolsProvider.cs index 30d0f2181..fa7dfac36 100644 --- a/src/AsmResolver.DotNet/Code/Native/INativeSymbolsProvider.cs +++ b/src/AsmResolver.DotNet/Code/Native/INativeSymbolsProvider.cs @@ -8,14 +8,6 @@ namespace AsmResolver.DotNet.Code.Native /// public interface INativeSymbolsProvider { - /// - /// Gets or sets the image base the final PE image is using. - /// - ulong ImageBase - { - get; - } - /// /// Adds a single symbol to the prototype. /// diff --git a/src/AsmResolver.DotNet/Code/Native/NativeMethodBodySerializer.cs b/src/AsmResolver.DotNet/Code/Native/NativeMethodBodySerializer.cs index e4253b717..b1a8d9f37 100644 --- a/src/AsmResolver.DotNet/Code/Native/NativeMethodBodySerializer.cs +++ b/src/AsmResolver.DotNet/Code/Native/NativeMethodBodySerializer.cs @@ -12,13 +12,19 @@ public class NativeMethodBodySerializer : IMethodBodySerializer /// public ISegmentReference SerializeMethodBody(MethodBodySerializationContext context, MethodDefinition method) { + // We treat any non-conventional bounded method body as a plain native method body that we + // don't need to process further. + if (method.MethodBody is {Address.IsBounded: true} plainBody) + return plainBody.Address; + + // We only support special treatment of native method bodies. if (method.MethodBody is not NativeMethodBody nativeMethodBody) return SegmentReference.Null; var provider = context.SymbolsProvider; // Create new raw code segment containing the native code. - var segment = new CodeSegment(provider.ImageBase, nativeMethodBody.Code); + var segment = new CodeSegment(nativeMethodBody.Code); // Process fixups. for (int i = 0; i < nativeMethodBody.AddressFixups.Count; i++) diff --git a/src/AsmResolver.DotNet/Code/Native/NativeSymbolsProvider.cs b/src/AsmResolver.DotNet/Code/Native/NativeSymbolsProvider.cs index 9a1e97273..b09c1e09f 100644 --- a/src/AsmResolver.DotNet/Code/Native/NativeSymbolsProvider.cs +++ b/src/AsmResolver.DotNet/Code/Native/NativeSymbolsProvider.cs @@ -22,21 +22,6 @@ public class NativeSymbolsProvider : INativeSymbolsProvider private uint _maxExportedOrdinal = 0; private readonly List _floatingExportedSymbols = new(); - /// - /// Creates a new instance of the class. - /// - /// The image base of the final PE image. - public NativeSymbolsProvider(ulong imageBase) - { - ImageBase = imageBase; - } - - /// - public ulong ImageBase - { - get; - } - /// public ISymbol ImportSymbol(ISymbol symbol) { diff --git a/src/AsmResolver.DotNet/Collections/MethodSemanticsCollection.cs b/src/AsmResolver.DotNet/Collections/MethodSemanticsCollection.cs index 0310136af..35cfefda3 100644 --- a/src/AsmResolver.DotNet/Collections/MethodSemanticsCollection.cs +++ b/src/AsmResolver.DotNet/Collections/MethodSemanticsCollection.cs @@ -20,6 +20,16 @@ public MethodSemanticsCollection(IHasSemantics owner) { } + /// + /// Creates a new instance of the class. + /// + /// The owner of the collection. + /// The initial number of elements the collection can store. + public MethodSemanticsCollection(IHasSemantics owner, int capacity) + : base(owner, capacity) + { + } + internal bool ValidateMembership { get; diff --git a/src/AsmResolver.DotNet/CustomAttribute.cs b/src/AsmResolver.DotNet/CustomAttribute.cs index d3d8023f0..ddfc5d11e 100644 --- a/src/AsmResolver.DotNet/CustomAttribute.cs +++ b/src/AsmResolver.DotNet/CustomAttribute.cs @@ -25,6 +25,17 @@ protected CustomAttribute(MetadataToken token) _signature = new LazyVariable(GetSignature); } + /// + /// Creates a new custom attribute. + /// + /// The constructor of the attribute to call. + public CustomAttribute(ICustomAttributeType? constructor) + : this(new MetadataToken(TableIndex.CustomAttribute, 0)) + { + Constructor = constructor; + Signature = new CustomAttributeSignature(); + } + /// /// Creates a new custom attribute. /// diff --git a/src/AsmResolver.DotNet/DefaultMetadataResolver.cs b/src/AsmResolver.DotNet/DefaultMetadataResolver.cs index 18e57b9e5..fcfc69b8b 100644 --- a/src/AsmResolver.DotNet/DefaultMetadataResolver.cs +++ b/src/AsmResolver.DotNet/DefaultMetadataResolver.cs @@ -13,10 +13,7 @@ namespace AsmResolver.DotNet public class DefaultMetadataResolver : IMetadataResolver { private readonly ConcurrentDictionary _typeCache; - private readonly SignatureComparer _comparer = new() - { - IgnoreAssemblyVersionNumbers = true - }; + private readonly SignatureComparer _comparer = new(SignatureComparisonFlags.VersionAgnostic); /// /// Creates a new metadata resolver. diff --git a/src/AsmResolver.DotNet/DotNetRuntimeInfo.cs b/src/AsmResolver.DotNet/DotNetRuntimeInfo.cs index 33813a6ed..d289f45f5 100644 --- a/src/AsmResolver.DotNet/DotNetRuntimeInfo.cs +++ b/src/AsmResolver.DotNet/DotNetRuntimeInfo.cs @@ -53,6 +53,21 @@ public Version Version get; } + /// + /// Gets a value indicating whether the application targets the .NET or .NET Core runtime or not. + /// + public bool IsNetCoreApp => Name == NetCoreApp; + + /// + /// Gets a value indicating whether the application targets the .NET Framework runtime or not. + /// + public bool IsNetFramework => Name == NetFramework; + + /// + /// Gets a value indicating whether the application targets the .NET standard specification or not. + /// + public bool IsNetStandard => Name == NetStandard; + /// /// Attempts to parse the framework name as provided in . /// diff --git a/src/AsmResolver.DotNet/DynamicMethodDefinition.cs b/src/AsmResolver.DotNet/DynamicMethodDefinition.cs deleted file mode 100644 index b95d8f003..000000000 --- a/src/AsmResolver.DotNet/DynamicMethodDefinition.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Reflection; -using AsmResolver.DotNet.Code.Cil; -using AsmResolver.DotNet.Signatures; -using AsmResolver.DotNet.Signatures.Types; -using AsmResolver.PE.DotNet.Metadata.Tables; -using MethodAttributes = AsmResolver.PE.DotNet.Metadata.Tables.Rows.MethodAttributes; - -namespace AsmResolver.DotNet -{ - /// - /// Represents a single method in a type definition of a .NET module. - /// - public class DynamicMethodDefinition : MethodDefinition - { - /// - /// Create a Dynamic Method Definition - /// - /// Target Module - /// Dynamic Method / Delegate / DynamicResolver - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls ResolveDynamicResolver and FromDynamicMethod")] - public DynamicMethodDefinition(ModuleDefinition module,object dynamicMethodObj) : - base(new MetadataToken(TableIndex.Method, 0)) - { - dynamicMethodObj = DynamicMethodHelper.ResolveDynamicResolver(dynamicMethodObj); - var methodBase = FieldReader.ReadField(dynamicMethodObj, "m_method"); - if (methodBase is null) - { - throw new ArgumentException( - "Could not get the underlying method base in the provided dynamic method object."); - } - - Module = module; - Name = methodBase.Name; - Attributes = (MethodAttributes)methodBase.Attributes; - Signature = new ReferenceImporter(module).ImportMethodSignature(ResolveSig(methodBase,module)); - CilMethodBody = CilMethodBody.FromDynamicMethod(this,dynamicMethodObj); - } - - private MethodSignature ResolveSig(MethodBase methodBase,ModuleDefinition module) - { - var imp = new ReferenceImporter(module); - var returnType = methodBase is MethodInfo info - ? imp.ImportTypeSignature(info.ReturnType) - : module.CorLibTypeFactory.Void; - - var parameters = methodBase.GetParameters(); - - var parameterTypes = new TypeSignature[parameters.Length]; - for (int i = 0; i < parameterTypes.Length; i++) - parameterTypes[i] = imp.ImportTypeSignature(parameters[i].ParameterType); - - return new MethodSignature( - methodBase.IsStatic ? 0 : CallingConventionAttributes.HasThis, - returnType, parameterTypes); - } - - /// - public override ModuleDefinition Module { get; } - - } -} diff --git a/src/AsmResolver.DotNet/FileReference.cs b/src/AsmResolver.DotNet/FileReference.cs index 8de6ba74a..8f6078e0e 100644 --- a/src/AsmResolver.DotNet/FileReference.cs +++ b/src/AsmResolver.DotNet/FileReference.cs @@ -12,7 +12,7 @@ namespace AsmResolver.DotNet public class FileReference : MetadataMember, IImplementation, - IManagedEntrypoint, + IManagedEntryPoint, IOwnedCollectionElement { private readonly LazyVariable _name; diff --git a/src/AsmResolver.DotNet/IManagedEntrypoint.cs b/src/AsmResolver.DotNet/IManagedEntryPoint.cs similarity index 62% rename from src/AsmResolver.DotNet/IManagedEntrypoint.cs rename to src/AsmResolver.DotNet/IManagedEntryPoint.cs index 9b0c3fb27..3b23ab1bd 100644 --- a/src/AsmResolver.DotNet/IManagedEntrypoint.cs +++ b/src/AsmResolver.DotNet/IManagedEntryPoint.cs @@ -2,9 +2,9 @@ { /// /// Represents a member that is either a method definition or a reference to an external file, that can be used to - /// indicate the managed entrypoint of a .NET module. + /// indicate the managed entry point of a .NET module. /// - public interface IManagedEntrypoint : IMetadataMember + public interface IManagedEntryPoint : IMetadataMember { } -} \ No newline at end of file +} diff --git a/src/AsmResolver.DotNet/ITypeDefOrRef.cs b/src/AsmResolver.DotNet/ITypeDefOrRef.cs index a81bc335b..61701cde0 100644 --- a/src/AsmResolver.DotNet/ITypeDefOrRef.cs +++ b/src/AsmResolver.DotNet/ITypeDefOrRef.cs @@ -1,3 +1,5 @@ +using AsmResolver.DotNet.Signatures.Types; + namespace AsmResolver.DotNet { /// @@ -35,5 +37,16 @@ public interface ITypeDefOrRef : ITypeDescriptor, IMemberRefParent, IHasCustomAt /// The reference importer to use for importing the type. /// The imported type. new ITypeDefOrRef ImportWith(ReferenceImporter importer); + + /// + /// Transforms the type descriptor to an instance of a , which can be used in + /// blob signatures. + /// + /// true if the type is a value type, false otherwise. + /// The constructed type signature instance. + /// + /// This function can be used to avoid type resolution on type references. + /// + TypeSignature ToTypeSignature(bool isValueType); } } diff --git a/src/AsmResolver.DotNet/InvalidTypeDefOrRef.cs b/src/AsmResolver.DotNet/InvalidTypeDefOrRef.cs index aa0dc667d..6197f4085 100644 --- a/src/AsmResolver.DotNet/InvalidTypeDefOrRef.cs +++ b/src/AsmResolver.DotNet/InvalidTypeDefOrRef.cs @@ -90,6 +90,8 @@ public static InvalidTypeDefOrRef Get(InvalidTypeSignatureError error) TypeSignature ITypeDescriptor.ToTypeSignature() => throw new InvalidOperationException(); + TypeSignature ITypeDefOrRef.ToTypeSignature(bool isValueType) => throw new InvalidOperationException(); + /// public override string ToString() => ((IFullNameProvider) this).Name!; diff --git a/src/AsmResolver.DotNet/MethodDefinition.cs b/src/AsmResolver.DotNet/MethodDefinition.cs index 4a32048be..a65658523 100644 --- a/src/AsmResolver.DotNet/MethodDefinition.cs +++ b/src/AsmResolver.DotNet/MethodDefinition.cs @@ -26,7 +26,7 @@ public class MethodDefinition : IHasGenericParameters, IMemberForwarded, IHasSecurityDeclaration, - IManagedEntrypoint + IManagedEntryPoint { private readonly LazyVariable _name; private readonly LazyVariable _declaringType; diff --git a/src/AsmResolver.DotNet/MethodImplementation.cs b/src/AsmResolver.DotNet/MethodImplementation.cs index bd4a18fc2..13ed8b7ac 100644 --- a/src/AsmResolver.DotNet/MethodImplementation.cs +++ b/src/AsmResolver.DotNet/MethodImplementation.cs @@ -1,9 +1,11 @@ +using System; + namespace AsmResolver.DotNet { /// /// Defines an explicit implementation of a method defined by an interface. /// - public readonly struct MethodImplementation + public readonly struct MethodImplementation : IEquatable { /// /// Creates a new explicit implementation of a method. @@ -33,7 +35,31 @@ public MethodImplementation(IMethodDefOrRef? declaration, IMethodDefOrRef? body) } /// - public override string ToString() => - $".override {Declaration} with {Body}"; + public override string ToString() => $".override {Declaration} with {Body}"; + + /// + /// Determines whether two method implementations record are equal. + /// + /// The other implementation record. + /// true if they are considered equal, false otherwise. + public bool Equals(MethodImplementation other) + { + return Equals(Declaration, other.Declaration) && Equals(Body, other.Body); + } + + /// + public override bool Equals(object? obj) + { + return obj is MethodImplementation other && Equals(other); + } + + /// + public override int GetHashCode() + { + unchecked + { + return ((Declaration != null ? Declaration.GetHashCode() : 0) * 397) ^ (Body != null ? Body.GetHashCode() : 0); + } + } } } diff --git a/src/AsmResolver.DotNet/ModuleDefinition.cs b/src/AsmResolver.DotNet/ModuleDefinition.cs index 3e693c258..c463b7e2a 100644 --- a/src/AsmResolver.DotNet/ModuleDefinition.cs +++ b/src/AsmResolver.DotNet/ModuleDefinition.cs @@ -44,7 +44,7 @@ public class ModuleDefinition : private IList? _assemblyReferences; private IList? _customAttributes; - private readonly LazyVariable _managedEntrypoint; + private readonly LazyVariable _managedEntryPoint; private IList? _moduleReferences; private IList? _fileReferences; private IList? _resources; @@ -172,6 +172,8 @@ public static ModuleDefinition FromModule(Module module, ModuleReaderParameters var handle = (IntPtr) GetHINSTANCEMethod?.Invoke(null, new object[] { module })!; if (handle == IntPtr.Zero) throw new NotSupportedException("The current platform does not support getting the base address of an instance of System.Reflection.Module."); + if (handle == (IntPtr) (-1)) + throw new NotSupportedException("Provided module does not have a module base address."); // Dynamically loaded modules are in their unmapped form, as opposed to modules loaded normally by the // Windows PE loader. They also have a fully qualified name "" or similar. @@ -269,7 +271,7 @@ protected ModuleDefinition(MetadataToken token) _mvid = new LazyVariable(GetMvid); _encId = new LazyVariable(GetEncId); _encBaseId = new LazyVariable(GetEncBaseId); - _managedEntrypoint = new LazyVariable(GetManagedEntrypoint); + _managedEntryPoint = new LazyVariable(GetManagedEntryPoint); _runtimeVersion = new LazyVariable(GetRuntimeVersion); _nativeResources = new LazyVariable(GetNativeResources); Attributes = DotNetDirectoryFlags.ILOnly; @@ -494,13 +496,13 @@ public bool IsStrongNameSigned } /// - /// Gets or sets a value indicating whether the .NET module has a native entrypoint or not. + /// Gets or sets a value indicating whether the .NET module has a native entry point or not. /// - public bool HasNativeEntrypoint + public bool HasNativeEntryPoint { - get => (Attributes & DotNetDirectoryFlags.NativeEntrypoint) != 0; - set => Attributes = (Attributes & ~DotNetDirectoryFlags.NativeEntrypoint) - | (value ? DotNetDirectoryFlags.NativeEntrypoint : 0); + get => (Attributes & DotNetDirectoryFlags.NativeEntryPoint) != 0; + set => Attributes = (Attributes & ~DotNetDirectoryFlags.NativeEntryPoint) + | (value ? DotNetDirectoryFlags.NativeEntryPoint : 0); } /// @@ -574,7 +576,7 @@ public OptionalHeaderMagic PEKind { get; set; - } = OptionalHeaderMagic.Pe32; + } = OptionalHeaderMagic.PE32; /// /// Gets or sets the subsystem to use when running the underlying portable executable (PE) file. @@ -744,22 +746,22 @@ public IMetadataResolver MetadataResolver } /// - /// Gets or sets the managed method that is invoked when the .NET module is initialized. + /// Gets or sets the managed method that is invoked after the .NET module is loaded and initialized. /// - public MethodDefinition? ManagedEntrypointMethod + public MethodDefinition? ManagedEntryPointMethod { - get => ManagedEntrypoint as MethodDefinition; - set => ManagedEntrypoint = value; + get => ManagedEntryPoint as MethodDefinition; + set => ManagedEntryPoint = value; } /// - /// Gets or sets the managed entrypoint that is invoked when the .NET module is initialized. This is either a - /// method, or a reference to a secondary module containing the entrypoint method. + /// Gets or sets the managed entry point that is invoked when the .NET module is initialized. This is either a + /// method, or a reference to a secondary module containing the entry point method. /// - public IManagedEntrypoint? ManagedEntrypoint + public IManagedEntryPoint? ManagedEntryPoint { - get => _managedEntrypoint.Value; - set => _managedEntrypoint.Value = value; + get => _managedEntryPoint.Value; + set => _managedEntryPoint.Value = value; } /// @@ -1036,13 +1038,13 @@ public TypeDefinition GetOrCreateModuleType() protected virtual string GetRuntimeVersion() => KnownRuntimeVersions.Clr40; /// - /// Obtains the managed entrypoint of this module. + /// Obtains the managed entry point of this module. /// - /// The entrypoint. + /// The entry point. /// - /// This method is called upon initialization of the property. + /// This method is called upon initialization of the property. /// - protected virtual IManagedEntrypoint? GetManagedEntrypoint() => null; + protected virtual IManagedEntryPoint? GetManagedEntryPoint() => null; /// /// Obtains the native win32 resources directory of the underlying PE image (if available). diff --git a/src/AsmResolver.DotNet/ReferenceImporter.cs b/src/AsmResolver.DotNet/ReferenceImporter.cs index 33d904554..eb27ec74e 100644 --- a/src/AsmResolver.DotNet/ReferenceImporter.cs +++ b/src/AsmResolver.DotNet/ReferenceImporter.cs @@ -315,10 +315,18 @@ public virtual TypeSignature ImportTypeSignature(Type type) if (corlibType != null) return corlibType; - var reference = new TypeReference(TargetModule, - ImportAssembly(new ReflectionAssemblyDescriptor(TargetModule, type.Assembly.GetName())), - type.Namespace, - type.Name); + TypeReference reference; + + if (type.IsNested) + { + var scope = (IResolutionScope) ImportType(type.DeclaringType!); + reference = new TypeReference(TargetModule, scope, null, type.Name); + } + else + { + var scope = ImportAssembly(new ReflectionAssemblyDescriptor(TargetModule, type.Assembly.GetName())); + reference = new TypeReference(TargetModule, scope, type.Namespace, type.Name); + } return new TypeDefOrRefSignature(reference, type.IsValueType); } @@ -492,6 +500,7 @@ public virtual MethodSpecification ImportMethod(MethodSpecification method) /// /// The method to import. /// The imported method. + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls System.Reflection.Module.ResolveMethod(int)")] public virtual IMethodDescriptor ImportMethod(MethodBase method) { if (method is null) @@ -522,6 +531,7 @@ public virtual IMethodDescriptor ImportMethod(MethodBase method) return new MemberReference(ImportType(method.DeclaringType), method.Name, result); } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls AsmResolver.DotNet.ReferenceImporter.ImportMethod(System.Reflection.MethodBase)")] private IMethodDescriptor ImportGenericMethod(MethodInfo method) { var memberRef = (IMethodDefOrRef) ImportMethod(method.GetGenericMethodDefinition()); @@ -576,6 +586,7 @@ public FieldSignature ImportFieldSignature(FieldSignature signature) /// The field to import. /// The imported field. /// Occurs when a field is not added to a type. + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls System.Reflection.Module.ResolveField(int)")] public MemberReference ImportField(FieldInfo field) { if (field is null) diff --git a/src/AsmResolver.DotNet/Serialized/CachedSerializedMemberFactory.cs b/src/AsmResolver.DotNet/Serialized/CachedSerializedMemberFactory.cs index c21fc9e91..909126c46 100644 --- a/src/AsmResolver.DotNet/Serialized/CachedSerializedMemberFactory.cs +++ b/src/AsmResolver.DotNet/Serialized/CachedSerializedMemberFactory.cs @@ -39,7 +39,7 @@ internal class CachedSerializedMemberFactory internal CachedSerializedMemberFactory(ModuleReaderContext context) { _context = context ?? throw new ArgumentNullException(nameof(context)); - _tablesStream = _context.Image.DotNetDirectory!.Metadata!.GetStream(); + _tablesStream = _context.TablesStream; } internal bool TryLookupMember(MetadataToken token, [NotNullWhen(true)] out IMetadataMember? member) diff --git a/src/AsmResolver.DotNet/Serialized/ModuleReaderContext.cs b/src/AsmResolver.DotNet/Serialized/ModuleReaderContext.cs index 8329d537c..e5dafc8ff 100644 --- a/src/AsmResolver.DotNet/Serialized/ModuleReaderContext.cs +++ b/src/AsmResolver.DotNet/Serialized/ModuleReaderContext.cs @@ -1,6 +1,11 @@ using System; using AsmResolver.PE; using AsmResolver.PE.DotNet.Metadata; +using AsmResolver.PE.DotNet.Metadata.Blob; +using AsmResolver.PE.DotNet.Metadata.Guid; +using AsmResolver.PE.DotNet.Metadata.Strings; +using AsmResolver.PE.DotNet.Metadata.Tables; +using AsmResolver.PE.DotNet.Metadata.UserStrings; namespace AsmResolver.DotNet.Serialized { @@ -21,6 +26,45 @@ public ModuleReaderContext(IPEImage image, SerializedModuleDefinition parentModu Image = image ?? throw new ArgumentNullException(nameof(image)); ParentModule = parentModule ?? throw new ArgumentNullException(nameof(parentModule)); Parameters = parameters ?? throw new ArgumentNullException(nameof(parameters)); + + // Both CLR and CoreCLR implement a slightly different loading procedure for EnC metadata. + // While the difference is very subtle, it has a slight effect on which streams are selected + // when multiple streams with the same name are present in the metadata directory. This only + // really happens in post-processed binaries (e.g., obfuscated binaries). Any normal .NET + // compiler only emits one stream for each stream type. + // + // For normal metadata (i.e., metadata with a #~ stream), every stream is loaded. This means that + // if a stream has the same name as a previously loaded one, it will override the contents of the previous. + // On the other hand, EnC metadata (i.e., metadata with a #- stream) looks up the first occurrence + // of the stream of the provided name. The exception for this is the tables stream itself, for which both + // the CLR and CoreCLR seem to always result in a file corruption error when there are multiple table streams. + bool isEncMetadata = Metadata.IsEncMetadata; + + for (int i = 0; i < Metadata.Streams.Count; i++) + { + switch (Metadata.Streams[i]) + { + case TablesStream tablesStream when TablesStream is null: + TablesStream = tablesStream; + break; + case BlobStream blobStream when BlobStream is null || !isEncMetadata: + BlobStream = blobStream; + break; + case GuidStream guidStream when GuidStream is null || !isEncMetadata: + GuidStream = guidStream; + break; + case StringsStream stringsStream when StringsStream is null || !isEncMetadata: + StringsStream = stringsStream; + break; + case UserStringsStream userStringsStream when UserStringsStream is null || !isEncMetadata: + UserStringsStream = userStringsStream; + break; + } + } + + // There should at least be a tables stream. + if (TablesStream is null) + throw new ArgumentException("Metadata directory does not contain a tables stream."); } /// @@ -44,6 +88,46 @@ public SerializedModuleDefinition ParentModule /// public IMetadata Metadata => Image.DotNetDirectory!.Metadata!; + /// + /// Gets the main tables stream in the metadata directory. + /// + public TablesStream TablesStream + { + get; + } + + /// + /// Gets the main blob stream in the metadata directory. + /// + public BlobStream? BlobStream + { + get; + } + + /// + /// Gets the main GUID stream in the metadata directory. + /// + public GuidStream? GuidStream + { + get; + } + + /// + /// Gets the main strings stream in the metadata directory. + /// + public StringsStream? StringsStream + { + get; + } + + /// + /// Gets the main user-strings stream in the metadata directory. + /// + public UserStringsStream? UserStringsStream + { + get; + } + /// /// Gets the reader parameters. /// diff --git a/src/AsmResolver.DotNet/Serialized/SerializedAssemblyDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedAssemblyDefinition.cs index c5b4e557f..3af420240 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedAssemblyDefinition.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedAssemblyDefinition.cs @@ -47,28 +47,13 @@ public class SerializedAssemblyDefinition : AssemblyDefinition } /// - protected override Utf8String? GetName() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Name) - : null; - } + protected override Utf8String? GetName() => _context.StringsStream?.GetStringByIndex(_row.Name); /// - protected override Utf8String? GetCulture() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Culture) - : null; - } + protected override Utf8String? GetCulture() => _context.StringsStream?.GetStringByIndex(_row.Culture); /// - protected override byte[]? GetPublicKey() - { - return _context.Metadata.TryGetStream(out var blobStream) - ? blobStream.GetBlobByIndex(_row.PublicKey) - : null; - } + protected override byte[]? GetPublicKey() => _context.BlobStream?.GetBlobByIndex(_row.PublicKey); /// protected override IList GetModules() @@ -82,13 +67,15 @@ protected override IList GetModules() var moduleResolver = _context.Parameters.ModuleResolver; if (moduleResolver is not null) { - var metadata = _context.Image.DotNetDirectory!.Metadata!; - var tablesStream = metadata.GetStream(); - var stringsStream = metadata.GetStream(); + var tablesStream = _context.TablesStream; + var stringsStream = _context.StringsStream; + if (stringsStream is null) + return result; var filesTable = tablesStream.GetTable(TableIndex.File); - foreach (var fileRow in filesTable) + for (int i = 0; i < filesTable.Count; i++) { + var fileRow = filesTable[i]; if (fileRow.Attributes == FileAttributes.ContainsMetadata) { string? name = stringsStream.GetStringByIndex(fileRow.Name); @@ -115,9 +102,8 @@ public override bool TryGetTargetFramework(out DotNetRuntimeInfo info) // We need to override this to be able to detect the runtime without lazily resolving all kinds of members. // Get relevant streams. - var metadata = _manifestModule.DotNetDirectory.Metadata!; - var tablesStream = metadata.GetStream(); - if (!metadata.TryGetStream(out var stringsStream)) + var tablesStream = _context.TablesStream; + if (_context.StringsStream is not { } stringsStream) { info = default; return false; diff --git a/src/AsmResolver.DotNet/Serialized/SerializedAssemblyReference.cs b/src/AsmResolver.DotNet/Serialized/SerializedAssemblyReference.cs index 372edc8a9..4ee1756ad 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedAssemblyReference.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedAssemblyReference.cs @@ -34,28 +34,13 @@ public SerializedAssemblyReference(ModuleReaderContext context, MetadataToken to } /// - protected override Utf8String? GetName() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Name) - : null; - } + protected override Utf8String? GetName() => _context.StringsStream?.GetStringByIndex(_row.Name); /// - protected override Utf8String? GetCulture() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Culture) - : null; - } + protected override Utf8String? GetCulture() => _context.StringsStream?.GetStringByIndex(_row.Culture); /// - protected override byte[]? GetPublicKeyOrToken() - { - return _context.Metadata.TryGetStream(out var blobStream) - ? blobStream.GetBlobByIndex(_row.PublicKeyOrToken) - : null; - } + protected override byte[]? GetPublicKeyOrToken() => _context.BlobStream?.GetBlobByIndex(_row.PublicKeyOrToken); /// protected override IList GetCustomAttributes() => diff --git a/src/AsmResolver.DotNet/Serialized/SerializedConstant.cs b/src/AsmResolver.DotNet/Serialized/SerializedConstant.cs index a245d2cd5..6b0b4024a 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedConstant.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedConstant.cs @@ -45,7 +45,7 @@ public class SerializedConstant : Constant /// protected override DataBlobSignature? GetValue() { - if (!_context.Metadata.TryGetStream(out var blobStream) + if (_context.BlobStream is not { } blobStream || !blobStream.TryGetBlobReaderByIndex(_row.Value, out var reader)) { // Don't report error. null constants are allowed (e.g. null strings). diff --git a/src/AsmResolver.DotNet/Serialized/SerializedCustomAttribute.cs b/src/AsmResolver.DotNet/Serialized/SerializedCustomAttribute.cs index 2e903be4c..9809a0516 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedCustomAttribute.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedCustomAttribute.cs @@ -42,8 +42,7 @@ public SerializedCustomAttribute(ModuleReaderContext context, MetadataToken toke /// protected override ICustomAttributeType? GetConstructor() { - var token = _context.Metadata - .GetStream() + var token = _context.TablesStream .GetIndexEncoder(CodedIndex.CustomAttributeType) .DecodeIndex(_row.Type); @@ -58,17 +57,14 @@ public SerializedCustomAttribute(ModuleReaderContext context, MetadataToken toke if (Constructor is null) return null; - if (!_context.Metadata.TryGetStream(out var blobStream) + if (_context.BlobStream is not { } blobStream || !blobStream.TryGetBlobReaderByIndex(_row.Value, out var reader)) { return _context.BadImageAndReturn( $"Invalid signature blob index in custom attribute {MetadataToken}."); } - return CustomAttributeSignature.FromReader( - new BlobReadContext(_context), - Constructor, - ref reader); + return CustomAttributeSignature.FromReader(new BlobReadContext(_context), Constructor, reader); } } } diff --git a/src/AsmResolver.DotNet/Serialized/SerializedEventDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedEventDefinition.cs index 4b4e83c4a..086de5b61 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedEventDefinition.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedEventDefinition.cs @@ -34,18 +34,12 @@ public SerializedEventDefinition(ModuleReaderContext context, MetadataToken toke } /// - protected override Utf8String? GetName() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Name) - : null; - } + protected override Utf8String? GetName() => _context.StringsStream?.GetStringByIndex(_row.Name); /// protected override ITypeDefOrRef? GetEventType() { - var eventTypeToken = _context.Metadata - .GetStream() + var eventTypeToken = _context.TablesStream .GetIndexEncoder(CodedIndex.TypeDefOrRef) .DecodeIndex(_row.EventType); diff --git a/src/AsmResolver.DotNet/Serialized/SerializedExportedType.cs b/src/AsmResolver.DotNet/Serialized/SerializedExportedType.cs index 5a2ff8938..9c5ed48b9 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedExportedType.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedExportedType.cs @@ -34,26 +34,15 @@ public SerializedExportedType(ModuleReaderContext context, MetadataToken token, } /// - protected override Utf8String? GetName() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Name) - : null; - } + protected override Utf8String? GetName() => _context.StringsStream?.GetStringByIndex(_row.Name); /// - protected override Utf8String? GetNamespace() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Namespace) - : null; - } + protected override Utf8String? GetNamespace() => _context.StringsStream?.GetStringByIndex(_row.Namespace); /// protected override IImplementation? GetImplementation() { - var token = _context.Metadata - .GetStream() + var token = _context.TablesStream .GetIndexEncoder(CodedIndex.Implementation) .DecodeIndex(_row.Implementation); diff --git a/src/AsmResolver.DotNet/Serialized/SerializedFieldDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedFieldDefinition.cs index d9012ea83..083dd51e9 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedFieldDefinition.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedFieldDefinition.cs @@ -35,17 +35,12 @@ public SerializedFieldDefinition(ModuleReaderContext context, MetadataToken toke } /// - protected override Utf8String? GetName() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Name) - : null; - } + protected override Utf8String? GetName() => _context.StringsStream?.GetStringByIndex(_row.Name); /// protected override FieldSignature? GetSignature() { - if (!_context.Metadata.TryGetStream(out var blobStream) + if (_context.BlobStream is not { } blobStream || !blobStream.TryGetBlobReaderByIndex(_row.Signature, out var reader)) { return _context.BadImageAndReturn( @@ -90,9 +85,8 @@ public SerializedFieldDefinition(ModuleReaderContext context, MetadataToken toke var module = _context.ParentModule; uint rid = module.GetFieldRvaRid(MetadataToken); - bool result = _context.Metadata - .GetStream() - .GetTable() + bool result = _context.TablesStream + .GetTable(TableIndex.FieldRva) .TryGetByRid(rid, out var fieldRvaRow); return result @@ -104,8 +98,7 @@ public SerializedFieldDefinition(ModuleReaderContext context, MetadataToken toke protected override int? GetFieldOffset() { uint rid = _context.ParentModule.GetFieldLayoutRid(MetadataToken); - bool result = _context.Metadata - .GetStream() + bool result = _context.TablesStream .GetTable() .TryGetByRid(rid, out var fieldLayoutRow); diff --git a/src/AsmResolver.DotNet/Serialized/SerializedFileReference.cs b/src/AsmResolver.DotNet/Serialized/SerializedFileReference.cs index 11e530681..71cb7b69e 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedFileReference.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedFileReference.cs @@ -35,20 +35,10 @@ public class SerializedFileReference : FileReference } /// - protected override Utf8String? GetName() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Name) - : null; - } + protected override Utf8String? GetName() => _context.StringsStream?.GetStringByIndex(_row.Name); /// - protected override byte[]? GetHashValue() - { - return _context.Metadata.TryGetStream(out var blobStream) - ? blobStream.GetBlobByIndex(_row.HashValue) - : null; - } + protected override byte[]? GetHashValue() => _context.BlobStream?.GetBlobByIndex(_row.HashValue); /// protected override IList GetCustomAttributes() => diff --git a/src/AsmResolver.DotNet/Serialized/SerializedGenericParameter.cs b/src/AsmResolver.DotNet/Serialized/SerializedGenericParameter.cs index 841c47adc..592a6ef1a 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedGenericParameter.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedGenericParameter.cs @@ -34,18 +34,12 @@ public SerializedGenericParameter(ModuleReaderContext context, MetadataToken tok } /// - protected override Utf8String? GetName() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Name) - : null; - } + protected override Utf8String? GetName() => _context.StringsStream?.GetStringByIndex(_row.Name); /// protected override IHasGenericParameters? GetOwner() { - var ownerToken = _context.Metadata - .GetStream() + var ownerToken = _context.TablesStream .GetIndexEncoder(CodedIndex.TypeOrMethodDef) .DecodeIndex(_row.Owner); @@ -58,10 +52,11 @@ public SerializedGenericParameter(ModuleReaderContext context, MetadataToken tok /// protected override IList GetConstraints() { - var result = new OwnedCollection(this); - var module = _context.ParentModule; - foreach (uint rid in module.GetGenericParameterConstraints(MetadataToken)) + var rids = module.GetGenericParameterConstraints(MetadataToken); + var result = new OwnedCollection(this, rids.Count); + + foreach (uint rid in rids) { var constraintToken = new MetadataToken(TableIndex.GenericParamConstraint, rid); result.Add((GenericParameterConstraint) module.LookupMember(constraintToken)); diff --git a/src/AsmResolver.DotNet/Serialized/SerializedGenericParameterConstraint.cs b/src/AsmResolver.DotNet/Serialized/SerializedGenericParameterConstraint.cs index 8a1fa8066..465669f09 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedGenericParameterConstraint.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedGenericParameterConstraint.cs @@ -43,8 +43,7 @@ public class SerializedGenericParameterConstraint : GenericParameterConstraint /// protected override ITypeDefOrRef? GetConstraint() { - var token = _context.Metadata - .GetStream() + var token = _context.TablesStream .GetIndexEncoder(CodedIndex.TypeDefOrRef) .DecodeIndex(_row.Constraint); diff --git a/src/AsmResolver.DotNet/Serialized/SerializedImplementationMap.cs b/src/AsmResolver.DotNet/Serialized/SerializedImplementationMap.cs index b0fa9d372..26858a826 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedImplementationMap.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedImplementationMap.cs @@ -43,12 +43,7 @@ public class SerializedImplementationMap : ImplementationMap } /// - protected override Utf8String? GetName() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.ImportName) - : null; - } + protected override Utf8String? GetName() => _context.StringsStream?.GetStringByIndex(_row.ImportName); /// protected override ModuleReference? GetScope() diff --git a/src/AsmResolver.DotNet/Serialized/SerializedInterfaceImplementation.cs b/src/AsmResolver.DotNet/Serialized/SerializedInterfaceImplementation.cs index a49ddd6f8..d2c216f34 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedInterfaceImplementation.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedInterfaceImplementation.cs @@ -45,8 +45,7 @@ public class SerializedInterfaceImplementation : InterfaceImplementation /// protected override ITypeDefOrRef? GetInterface() { - var token = _context.Metadata - .GetStream() + var token = _context.TablesStream .GetIndexEncoder(CodedIndex.TypeDefOrRef) .DecodeIndex(_row.Interface); diff --git a/src/AsmResolver.DotNet/Serialized/SerializedManifestResource.cs b/src/AsmResolver.DotNet/Serialized/SerializedManifestResource.cs index 68a75abc8..2a81b2a1a 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedManifestResource.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedManifestResource.cs @@ -34,12 +34,7 @@ public SerializedManifestResource(ModuleReaderContext context, MetadataToken tok } /// - protected override Utf8String? GetName() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Name) - : null; - } + protected override Utf8String? GetName() => _context.StringsStream?.GetStringByIndex(_row.Name); /// protected override IImplementation? GetImplementation() @@ -47,9 +42,7 @@ public SerializedManifestResource(ModuleReaderContext context, MetadataToken tok if (_row.Implementation == 0) return null; - var encoder = _context.Metadata - .GetStream() - .GetIndexEncoder(CodedIndex.Implementation); + var encoder = _context.TablesStream.GetIndexEncoder(CodedIndex.Implementation); var token = encoder.DecodeIndex(_row.Implementation); return _context.ParentModule.TryLookupMember(token, out var member) diff --git a/src/AsmResolver.DotNet/Serialized/SerializedMemberReference.cs b/src/AsmResolver.DotNet/Serialized/SerializedMemberReference.cs index e8ce03db4..1a867ef11 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedMemberReference.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedMemberReference.cs @@ -37,8 +37,7 @@ public class SerializedMemberReference : MemberReference /// protected override IMemberRefParent? GetParent() { - var encoder = _context.Metadata - .GetStream() + var encoder = _context.TablesStream .GetIndexEncoder(CodedIndex.MemberRefParent); var parentToken = encoder.DecodeIndex(_row.Parent); @@ -49,17 +48,12 @@ public class SerializedMemberReference : MemberReference } /// - protected override Utf8String? GetName() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Name) - : null; - } + protected override Utf8String? GetName() => _context.StringsStream?.GetStringByIndex(_row.Name); /// protected override CallingConventionSignature? GetSignature() { - if (!_context.Metadata.TryGetStream(out var blobStream) + if (_context.BlobStream is not { } blobStream || !blobStream.TryGetBlobReaderByIndex(_row.Signature, out var reader)) { return _context.BadImageAndReturn( diff --git a/src/AsmResolver.DotNet/Serialized/SerializedMethodDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedMethodDefinition.cs index ed4ed367c..0d09b8e0a 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedMethodDefinition.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedMethodDefinition.cs @@ -40,17 +40,12 @@ public SerializedMethodDefinition(ModuleReaderContext context, MetadataToken tok } /// - protected override Utf8String? GetName() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Name) - : null; - } + protected override Utf8String? GetName() => _context.StringsStream?.GetStringByIndex(_row.Name); /// protected override MethodSignature? GetSignature() { - if (!_context.Metadata.TryGetStream(out var blobStream) + if (_context.BlobStream is not { } blobStream || !blobStream.TryGetBlobReaderByIndex(_row.Signature, out var reader)) { return _context.BadImageAndReturn( @@ -81,9 +76,10 @@ public SerializedMethodDefinition(ModuleReaderContext context, MetadataToken tok /// protected override IList GetParameterDefinitions() { - var result = new OwnedCollection(this); + var parameterRange = _context.ParentModule.GetParameterRange(MetadataToken.Rid); + var result = new OwnedCollection(this, parameterRange.Count); - foreach (var token in _context.ParentModule.GetParameterRange(MetadataToken.Rid)) + foreach (var token in parameterRange) { if (_context.ParentModule.TryLookupMember(token, out var member) && member is ParameterDefinition parameter) result.Add(parameter); @@ -108,9 +104,10 @@ protected override IList GetParameterDefinitions() /// protected override IList GetGenericParameters() { - var result = new OwnedCollection(this); + var rids = _context.ParentModule.GetGenericParameters(MetadataToken); + var result = new OwnedCollection(this, rids.Count); - foreach (uint rid in _context.ParentModule.GetGenericParameters(MetadataToken)) + foreach (uint rid in rids) { if (_context.ParentModule.TryLookupMember(new MetadataToken(TableIndex.GenericParam, rid), out var member) && member is GenericParameter genericParameter) diff --git a/src/AsmResolver.DotNet/Serialized/SerializedMethodSemantics.cs b/src/AsmResolver.DotNet/Serialized/SerializedMethodSemantics.cs index fec338689..650190976 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedMethodSemantics.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedMethodSemantics.cs @@ -42,8 +42,7 @@ public SerializedMethodSemantics(ModuleReaderContext context, MetadataToken toke /// protected override IHasSemantics? GetAssociation() { - var token = _context.Metadata - .GetStream() + var token = _context.TablesStream .GetIndexEncoder(CodedIndex.HasSemantics) .DecodeIndex(_row.Association); diff --git a/src/AsmResolver.DotNet/Serialized/SerializedMethodSpecification.cs b/src/AsmResolver.DotNet/Serialized/SerializedMethodSpecification.cs index cff8d92d4..929366651 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedMethodSpecification.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedMethodSpecification.cs @@ -32,8 +32,7 @@ public SerializedMethodSpecification(ModuleReaderContext context, MetadataToken /// protected override IMethodDefOrRef? GetMethod() { - var methodToken = _context.Metadata - .GetStream() + var methodToken = _context.TablesStream .GetIndexEncoder(CodedIndex.MethodDefOrRef) .DecodeIndex(_row.Method); @@ -46,7 +45,7 @@ public SerializedMethodSpecification(ModuleReaderContext context, MetadataToken /// protected override GenericInstanceMethodSignature? GetSignature() { - if (!_context.Metadata.TryGetStream(out var blobStream) + if (_context.BlobStream is not { } blobStream || !blobStream.TryGetBlobReaderByIndex(_row.Instantiation, out var reader)) { return _context.BadImageAndReturn( diff --git a/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.Metadata.cs b/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.Metadata.cs index f7e13d09f..243f31ca5 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.Metadata.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.Metadata.cs @@ -49,7 +49,7 @@ private void EnsureTypeDefinitionTreeInitialized() private OneToManyRelation InitializeTypeDefinitionTree() { - var tablesStream = DotNetDirectory.Metadata!.GetStream(); + var tablesStream = ReaderContext.TablesStream; var nestedClassTable = tablesStream.GetTable(TableIndex.NestedClass); var typeDefTree = new OneToManyRelation(); @@ -59,7 +59,7 @@ private void EnsureTypeDefinitionTreeInitialized() return typeDefTree; } - internal IEnumerable GetNestedTypeRids(uint enclosingTypeRid) + internal OneToManyRelation.ValueSet GetNestedTypeRids(uint enclosingTypeRid) { EnsureTypeDefinitionTreeInitialized(); return _typeDefTree.GetValues(enclosingTypeRid); @@ -103,12 +103,12 @@ private void EnsureMethodSemanticsInitialized() [MemberNotNull(nameof(_semanticMethods))] private void InitializeMethodSemantics() { - var tablesStream = DotNetDirectory.Metadata!.GetStream(); + var tablesStream = ReaderContext.TablesStream; var semanticsTable = tablesStream.GetTable(TableIndex.MethodSemantics); var encoder = tablesStream.GetIndexEncoder(CodedIndex.HasSemantics); - var semantics = new OneToManyRelation(); - var semanticMethods = new Dictionary(); + var semantics = new OneToManyRelation(semanticsTable.Count); + var semanticMethods = new Dictionary(semanticsTable.Count); for (int i = 0; i < semanticsTable.Count; i++) { var methodSemanticsRow = semanticsTable[i]; @@ -123,7 +123,7 @@ private void InitializeMethodSemantics() Interlocked.CompareExchange(ref _semanticMethods, semanticMethods, null); } - internal IEnumerable GetMethodSemantics(MetadataToken owner) + internal OneToManyRelation.ValueSet GetMethodSemantics(MetadataToken owner) { EnsureMethodSemanticsInitialized(); return _semantics.GetValues(owner); @@ -151,11 +151,11 @@ private void EnsureConstantsInitialized() private OneToOneRelation GetConstants() { - var tablesStream = DotNetDirectory.Metadata!.GetStream(); + var tablesStream = ReaderContext.TablesStream; var constantTable = tablesStream.GetTable(TableIndex.Constant); var encoder = tablesStream.GetIndexEncoder(CodedIndex.HasConstant); - var constants = new OneToOneRelation(); + var constants = new OneToOneRelation(constantTable.Count); for (int i = 0; i < constantTable.Count; i++) { var ownerToken = encoder.DecodeIndex(constantTable[i].Parent); @@ -195,11 +195,11 @@ private void EnsureCustomAttributesInitialized() private OneToManyRelation InitializeCustomAttributes() { - var tablesStream = DotNetDirectory.Metadata!.GetStream(); + var tablesStream = ReaderContext.TablesStream; var attributeTable = tablesStream.GetTable(TableIndex.CustomAttribute); var encoder = tablesStream.GetIndexEncoder(CodedIndex.HasCustomAttribute); - var customAttributes = new OneToManyRelation(); + var customAttributes = new OneToManyRelation(attributeTable.Count); for (int i = 0; i < attributeTable.Count; i++) { var ownerToken = encoder.DecodeIndex(attributeTable[i].Parent); @@ -213,7 +213,7 @@ private void EnsureCustomAttributesInitialized() /// protected override IList GetCustomAttributes() => GetCustomAttributeCollection(this); - internal ICollection GetCustomAttributes(MetadataToken ownerToken) + internal OneToManyRelation.ValueSet GetCustomAttributes(MetadataToken ownerToken) { EnsureCustomAttributesInitialized(); return _customAttributes.GetValues(ownerToken); @@ -228,9 +228,10 @@ internal MetadataToken GetCustomAttributeOwner(uint attributeRid) internal IList GetCustomAttributeCollection(IHasCustomAttribute owner) { EnsureCustomAttributesInitialized(); - var result = new OwnedCollection(owner); + var rids = _customAttributes.GetValues(owner.MetadataToken); + var result = new OwnedCollection(owner, rids.Count); - foreach (uint rid in _customAttributes.GetValues(owner.MetadataToken)) + foreach (uint rid in rids) { var attribute = (CustomAttribute) LookupMember(new MetadataToken(TableIndex.CustomAttribute, rid)); result.Add(attribute); @@ -248,11 +249,11 @@ private void EnsureSecurityDeclarationsInitialized() private OneToManyRelation InitializeSecurityDeclarations() { - var tablesStream = DotNetDirectory.Metadata!.GetStream(); + var tablesStream = ReaderContext.TablesStream; var declarationTable = tablesStream.GetTable(TableIndex.DeclSecurity); var encoder = tablesStream.GetIndexEncoder(CodedIndex.HasDeclSecurity); - var securityDeclarations = new OneToManyRelation(); + var securityDeclarations = new OneToManyRelation(declarationTable.Count); for (int i = 0; i < declarationTable.Count; i++) { var ownerToken = encoder.DecodeIndex(declarationTable[i].Parent); @@ -272,9 +273,10 @@ internal MetadataToken GetSecurityDeclarationOwner(uint attributeRid) internal IList GetSecurityDeclarationCollection(IHasSecurityDeclaration owner) { EnsureSecurityDeclarationsInitialized(); - var result = new OwnedCollection(owner); + var rids = _securityDeclarations.GetValues(owner.MetadataToken); + var result = new OwnedCollection(owner, rids.Count); - foreach (uint rid in _securityDeclarations.GetValues(owner.MetadataToken)) + foreach (uint rid in rids) { var attribute = (SecurityDeclaration) LookupMember(new MetadataToken(TableIndex.DeclSecurity, rid)); result.Add(attribute); @@ -292,11 +294,11 @@ private void EnsureGenericParametersInitialized() private OneToManyRelation InitializeGenericParameters() { - var tablesStream = DotNetDirectory.Metadata!.GetStream(); + var tablesStream = ReaderContext.TablesStream; var parameterTable = tablesStream.GetTable(TableIndex.GenericParam); var encoder = tablesStream.GetIndexEncoder(CodedIndex.TypeOrMethodDef); - var genericParameters = new OneToManyRelation(); + var genericParameters = new OneToManyRelation(parameterTable.Count); for (int i = 0; i < parameterTable.Count; i++) { var ownerToken = encoder.DecodeIndex(parameterTable[i].Owner); @@ -313,7 +315,7 @@ internal MetadataToken GetGenericParameterOwner(uint parameterRid) return _genericParameters.GetKey(parameterRid); } - internal ICollection GetGenericParameters(MetadataToken ownerToken) + internal OneToManyRelation.ValueSet GetGenericParameters(MetadataToken ownerToken) { EnsureGenericParametersInitialized(); return _genericParameters.GetValues(ownerToken); @@ -328,10 +330,10 @@ private void EnsureGenericParameterConstrainsInitialized() private OneToManyRelation InitializeGenericParameterConstraints() { - var tablesStream = DotNetDirectory.Metadata!.GetStream(); + var tablesStream = ReaderContext.TablesStream; var constraintTable = tablesStream.GetTable(TableIndex.GenericParamConstraint); - var constraints = new OneToManyRelation(); + var constraints = new OneToManyRelation(constraintTable.Count); for (int i = 0; i < constraintTable.Count; i++) { var ownerToken = new MetadataToken(TableIndex.GenericParam, constraintTable[i].Owner); @@ -348,7 +350,7 @@ internal MetadataToken GetGenericParameterConstraintOwner(uint constraintRid) return _genericParameterConstraints.GetKey(constraintRid); } - internal ICollection GetGenericParameterConstraints(MetadataToken ownerToken) + internal OneToManyRelation.ValueSet GetGenericParameterConstraints(MetadataToken ownerToken) { EnsureGenericParameterConstrainsInitialized(); return _genericParameterConstraints.GetValues(ownerToken); @@ -363,10 +365,10 @@ private void EnsureInterfacesInitialized() private OneToManyRelation InitializeInterfaces() { - var tablesStream = DotNetDirectory.Metadata!.GetStream(); + var tablesStream = ReaderContext.TablesStream; var interfaceImplTable = tablesStream.GetTable(TableIndex.InterfaceImpl); - var interfaces = new OneToManyRelation(); + var interfaces = new OneToManyRelation(interfaceImplTable.Count); for (int i = 0; i < interfaceImplTable.Count; i++) { var ownerToken = new MetadataToken(TableIndex.TypeDef, interfaceImplTable[i].Class); @@ -383,7 +385,7 @@ internal MetadataToken GetInterfaceImplementationOwner(uint implementationRid) return _interfaces.GetKey(implementationRid); } - internal ICollection GetInterfaceImplementationRids(MetadataToken ownerToken) + internal OneToManyRelation.ValueSet GetInterfaceImplementationRids(MetadataToken ownerToken) { EnsureInterfacesInitialized(); return _interfaces.GetValues(ownerToken); @@ -398,10 +400,10 @@ private void EnsureMethodImplementationsInitialized() private OneToManyRelation InitializeMethodImplementations() { - var tablesStream = DotNetDirectory.Metadata!.GetStream(); + var tablesStream = ReaderContext.TablesStream; var methodImplTable = tablesStream.GetTable(TableIndex.MethodImpl); - var methodImplementations = new OneToManyRelation(); + var methodImplementations = new OneToManyRelation(methodImplTable.Count); for (int i = 0; i < methodImplTable.Count; i++) { var ownerToken = new MetadataToken(TableIndex.TypeDef, methodImplTable[i].Class); @@ -412,7 +414,7 @@ private void EnsureMethodImplementationsInitialized() return methodImplementations; } - internal ICollection GetMethodImplementationRids(MetadataToken ownerToken) + internal OneToManyRelation.ValueSet GetMethodImplementationRids(MetadataToken ownerToken) { EnsureMethodImplementationsInitialized(); return _methodImplementations.GetValues(ownerToken); @@ -427,10 +429,10 @@ private void EnsureClassLayoutsInitialized() private OneToOneRelation InitializeClassLayouts() { - var tablesStream = DotNetDirectory.Metadata!.GetStream(); + var tablesStream = ReaderContext.TablesStream; var layoutTable = tablesStream.GetTable(TableIndex.ClassLayout); - var layouts = new OneToOneRelation(); + var layouts = new OneToOneRelation(layoutTable.Count); for (int i = 0; i < layoutTable.Count; i++) { var ownerToken = new MetadataToken(TableIndex.TypeDef, layoutTable[i].Parent); @@ -456,11 +458,11 @@ private void EnsureImplementationMapsInitialized() private OneToOneRelation InitializeImplementationMaps() { - var tablesStream = DotNetDirectory.Metadata!.GetStream(); + var tablesStream = ReaderContext.TablesStream; var mapTable = tablesStream.GetTable(TableIndex.ImplMap); var encoder = tablesStream.GetIndexEncoder(CodedIndex.TypeOrMethodDef); - var maps = new OneToOneRelation(); + var maps = new OneToOneRelation(mapTable.Count); for (int i = 0; i < mapTable.Count; i++) { var ownerToken = encoder.DecodeIndex(mapTable[i].MemberForwarded); @@ -492,10 +494,10 @@ private void EnsureFieldRvasInitialized() private OneToOneRelation InitializeFieldRvas() { - var tablesStream = DotNetDirectory.Metadata!.GetStream(); + var tablesStream = ReaderContext.TablesStream; var rvaTable = tablesStream.GetTable(TableIndex.FieldRva); - var rvas = new OneToOneRelation(); + var rvas = new OneToOneRelation(rvaTable.Count); for (int i = 0; i < rvaTable.Count; i++) { var ownerToken = new MetadataToken(TableIndex.Field, rvaTable[i].Field); @@ -521,11 +523,11 @@ private void EnsureFieldMarshalsInitialized() private OneToOneRelation InitializeFieldMarshals() { - var tablesStream = DotNetDirectory.Metadata!.GetStream(); + var tablesStream = ReaderContext.TablesStream; var marshalTable = tablesStream.GetTable(TableIndex.FieldMarshal); var encoder = tablesStream.GetIndexEncoder(CodedIndex.HasFieldMarshal); - var marshals = new OneToOneRelation(); + var marshals = new OneToOneRelation(marshalTable.Count); for (int i = 0; i < marshalTable.Count; i++) { var ownerToken = encoder.DecodeIndex(marshalTable[i].Parent); @@ -544,12 +546,9 @@ internal uint GetFieldMarshalRid(MetadataToken fieldToken) internal MarshalDescriptor? GetFieldMarshal(MetadataToken ownerToken) { - var metadata = DotNetDirectory.Metadata!; - var table = metadata - .GetStream() - .GetTable(TableIndex.FieldMarshal); + var table = ReaderContext.TablesStream.GetTable(TableIndex.FieldMarshal); - if (!metadata.TryGetStream(out var blobStream) + if (ReaderContext.BlobStream is not { } blobStream || !table.TryGetByRid(GetFieldMarshalRid(ownerToken), out var row) || !blobStream.TryGetBlobReaderByIndex(row.NativeType, out var reader)) { @@ -568,10 +567,10 @@ private void EnsureFieldLayoutsInitialized() private OneToOneRelation InitializeFieldLayouts() { - var tablesStream = DotNetDirectory.Metadata!.GetStream(); + var tablesStream = ReaderContext.TablesStream; var layoutTable = tablesStream.GetTable(TableIndex.FieldLayout); - var fieldLayouts = new OneToOneRelation(); + var fieldLayouts = new OneToOneRelation(layoutTable.Count); for (int i = 0; i < layoutTable.Count; i++) { var ownerToken = new MetadataToken(TableIndex.Field, layoutTable[i].Field); diff --git a/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs index e147de1e8..9b7866ab4 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs @@ -119,7 +119,7 @@ public ModuleReaderContext ReaderContext /// public override bool TryLookupString(MetadataToken token, [NotNullWhen(true)] out string? value) { - if (!ReaderContext.Metadata.TryGetStream(out var userStringsStream)) + if (ReaderContext.UserStringsStream is not { } userStringsStream) { value = null; return false; @@ -131,14 +131,12 @@ public override bool TryLookupString(MetadataToken token, [NotNullWhen(true)] ou /// public override IndexEncoder GetIndexEncoder(CodedIndex codedIndex) => - ReaderContext.Metadata.GetStream().GetIndexEncoder(codedIndex); + ReaderContext.TablesStream.GetIndexEncoder(codedIndex); /// public override IEnumerable GetImportedTypeReferences() { - var table = ReaderContext.Metadata - .GetStream() - .GetTable(TableIndex.TypeRef); + var table = ReaderContext.TablesStream.GetTable(TableIndex.TypeRef); for (uint rid = 1; rid <= table.Count; rid++) { @@ -153,9 +151,7 @@ public override IEnumerable GetImportedTypeReferences() /// public override IEnumerable GetImportedMemberReferences() { - var table = ReaderContext.Metadata - .GetStream() - .GetTable(TableIndex.MemberRef); + var table = ReaderContext.TablesStream.GetTable(TableIndex.MemberRef); for (uint rid = 1; rid <= table.Count; rid++) { @@ -168,47 +164,27 @@ public override IEnumerable GetImportedMemberReferences() } /// - protected override Utf8String? GetName() - { - return ReaderContext.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Name) - : null; - } + protected override Utf8String? GetName() => ReaderContext.StringsStream?.GetStringByIndex(_row.Name); /// - protected override Guid GetMvid() - { - return ReaderContext.Metadata.TryGetStream(out var guidStream) - ? guidStream.GetGuidByIndex(_row.Mvid) - : Guid.Empty; - } + protected override Guid GetMvid() => ReaderContext.GuidStream?.GetGuidByIndex(_row.Mvid) ?? Guid.Empty; /// - protected override Guid GetEncId() - { - return ReaderContext.Metadata.TryGetStream(out var guidStream) - ? guidStream.GetGuidByIndex(_row.EncId) - : Guid.Empty; - } + protected override Guid GetEncId() => ReaderContext.GuidStream?.GetGuidByIndex(_row.EncId) ?? Guid.Empty; /// - protected override Guid GetEncBaseId() - { - return ReaderContext.Metadata.TryGetStream(out var guidStream) - ? guidStream.GetGuidByIndex(_row.EncBaseId) - : Guid.Empty; - } + protected override Guid GetEncBaseId() => ReaderContext.GuidStream?.GetGuidByIndex(_row.EncBaseId) ?? Guid.Empty; /// protected override IList GetTopLevelTypes() { EnsureTypeDefinitionTreeInitialized(); - var types = new OwnedCollection(this); + var typeDefTable = ReaderContext.TablesStream.GetTable(TableIndex.TypeDef); + int nestedTypeCount = ReaderContext.TablesStream.GetTable(TableIndex.NestedClass).Count; - var typeDefTable = ReaderContext.Metadata - .GetStream() - .GetTable(TableIndex.TypeDef); + var types = new OwnedCollection(this, + typeDefTable.Count - nestedTypeCount); for (int i = 0; i < typeDefTable.Count; i++) { @@ -226,11 +202,9 @@ protected override IList GetTopLevelTypes() /// protected override IList GetAssemblyReferences() { - var result = new OwnedCollection(this); + var table = ReaderContext.TablesStream.GetTable(TableIndex.AssemblyRef); - var table = ReaderContext.Metadata - .GetStream() - .GetTable(TableIndex.AssemblyRef); + var result = new OwnedCollection(this, table.Count); // Don't use the member factory here, this method may be called before the member factory is initialized. for (int i = 0; i < table.Count; i++) @@ -245,11 +219,9 @@ protected override IList GetAssemblyReferences() /// protected override IList GetModuleReferences() { - var result = new OwnedCollection(this); + var table = ReaderContext.TablesStream.GetTable(TableIndex.ModuleRef); - var table = ReaderContext.Metadata - .GetStream() - .GetTable(TableIndex.ModuleRef); + var result = new OwnedCollection(this, table.Count); for (int i = 0; i < table.Count; i++) { @@ -264,11 +236,9 @@ protected override IList GetModuleReferences() /// protected override IList GetFileReferences() { - var result = new OwnedCollection(this); + var table = ReaderContext.TablesStream.GetTable(TableIndex.File); - var table = ReaderContext.Metadata - .GetStream() - .GetTable(TableIndex.File); + var result = new OwnedCollection(this, table.Count); for (int i = 0; i < table.Count; i++) { @@ -283,11 +253,9 @@ protected override IList GetFileReferences() /// protected override IList GetResources() { - var result = new OwnedCollection(this); + var table = ReaderContext.TablesStream.GetTable(TableIndex.ManifestResource); - var table = ReaderContext.Metadata - .GetStream() - .GetTable(TableIndex.ManifestResource); + var result = new OwnedCollection(this, table.Count); for (int i = 0; i < table.Count; i++) { @@ -302,11 +270,9 @@ protected override IList GetResources() /// protected override IList GetExportedTypes() { - var result = new OwnedCollection(this); + var table = ReaderContext.TablesStream.GetTable(TableIndex.ExportedType); - var table = ReaderContext.Metadata - .GetStream() - .GetTable(TableIndex.ExportedType); + var result = new OwnedCollection(this, table.Count); for (int i = 0; i < table.Count; i++) { @@ -322,16 +288,16 @@ protected override IList GetExportedTypes() protected override string GetRuntimeVersion() => ReaderContext.Metadata!.VersionString; /// - protected override IManagedEntrypoint? GetManagedEntrypoint() + protected override IManagedEntryPoint? GetManagedEntryPoint() { - if ((DotNetDirectory.Flags & DotNetDirectoryFlags.NativeEntrypoint) != 0) + if ((DotNetDirectory.Flags & DotNetDirectoryFlags.NativeEntryPoint) != 0) { - // TODO: native entrypoints. + // TODO: native entry points. return null; } - if (DotNetDirectory.Entrypoint != 0) - return LookupMember(DotNetDirectory.Entrypoint) as IManagedEntrypoint; + if (DotNetDirectory.EntryPoint != 0) + return LookupMember(DotNetDirectory.EntryPoint) as IManagedEntryPoint; return null; } @@ -344,18 +310,15 @@ protected override IList GetExportedTypes() private AssemblyDefinition? FindParentAssembly() { - var assemblyTable = ReaderContext.Metadata - .GetStream() - .GetTable(); + var assemblyTable = ReaderContext.TablesStream.GetTable(); if (assemblyTable.Count > 0) { - var assembly = new SerializedAssemblyDefinition( + return new SerializedAssemblyDefinition( ReaderContext, new MetadataToken(TableIndex.Assembly, 1), assemblyTable[0], this); - return assembly; } return null; diff --git a/src/AsmResolver.DotNet/Serialized/SerializedModuleReference.cs b/src/AsmResolver.DotNet/Serialized/SerializedModuleReference.cs index 99f824eb8..64cf93396 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedModuleReference.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedModuleReference.cs @@ -33,12 +33,7 @@ public SerializedModuleReference(ModuleReaderContext context, MetadataToken toke } /// - protected override Utf8String? GetName() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Name) - : null; - } + protected override Utf8String? GetName() => _context.StringsStream?.GetStringByIndex(_row.Name); /// protected override IList GetCustomAttributes() => diff --git a/src/AsmResolver.DotNet/Serialized/SerializedParameterDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedParameterDefinition.cs index f87ad733c..f20a2dff3 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedParameterDefinition.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedParameterDefinition.cs @@ -36,12 +36,7 @@ public SerializedParameterDefinition(ModuleReaderContext context, MetadataToken } /// - protected override Utf8String? GetName() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Name) - : null; - } + protected override Utf8String? GetName() => _context.StringsStream?.GetStringByIndex(_row.Name); /// protected override MethodDefinition? GetMethod() diff --git a/src/AsmResolver.DotNet/Serialized/SerializedPropertyDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedPropertyDefinition.cs index 9739939fd..d2dd3ae74 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedPropertyDefinition.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedPropertyDefinition.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.Collections; using AsmResolver.PE.DotNet.Metadata; @@ -35,17 +36,12 @@ public SerializedPropertyDefinition(ModuleReaderContext context, MetadataToken t } /// - protected override Utf8String? GetName() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Name) - : null; - } + protected override Utf8String? GetName() => _context.StringsStream?.GetStringByIndex(_row.Name); /// protected override PropertySignature? GetSignature() { - if (!_context.Metadata.TryGetStream(out var blobStream) + if (_context.BlobStream is not { } blobStream || !blobStream.TryGetBlobReaderByIndex(_row.Type, out var reader)) { return _context.BadImageAndReturn( @@ -70,11 +66,13 @@ public SerializedPropertyDefinition(ModuleReaderContext context, MetadataToken t /// protected override IList GetSemantics() { - var result = new MethodSemanticsCollection(this); + var module = _context.ParentModule; + var rids = module.GetMethodSemantics(MetadataToken); + + var result = new MethodSemanticsCollection(this, rids.Count); result.ValidateMembership = false; - var module = _context.ParentModule; - foreach (uint rid in module.GetMethodSemantics(MetadataToken)) + foreach (uint rid in rids) { var semanticsToken = new MetadataToken(TableIndex.MethodSemantics, rid); result.Add((MethodSemantics) module.LookupMember(semanticsToken)); diff --git a/src/AsmResolver.DotNet/Serialized/SerializedSecurityDeclaration.cs b/src/AsmResolver.DotNet/Serialized/SerializedSecurityDeclaration.cs index b6ced6c94..c39273a75 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedSecurityDeclaration.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedSecurityDeclaration.cs @@ -49,7 +49,7 @@ public class SerializedSecurityDeclaration : SecurityDeclaration /// protected override PermissionSetSignature? GetPermissionSet() { - if (!_context.Metadata.TryGetStream(out var blobStream) + if (_context.BlobStream is not { } blobStream || !blobStream.TryGetBlobReaderByIndex(_row.PermissionSet, out var reader)) { return _context.BadImageAndReturn( diff --git a/src/AsmResolver.DotNet/Serialized/SerializedStandAloneSignature.cs b/src/AsmResolver.DotNet/Serialized/SerializedStandAloneSignature.cs index 981a8d6d0..49ab50915 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedStandAloneSignature.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedStandAloneSignature.cs @@ -36,7 +36,7 @@ public class SerializedStandAloneSignature : StandAloneSignature /// protected override CallingConventionSignature? GetSignature() { - if (!_context.Metadata.TryGetStream(out var blobStream) + if (_context.BlobStream is not { } blobStream || !blobStream.TryGetBlobReaderByIndex(_row.Signature, out var reader)) { return _context.BadImageAndReturn( diff --git a/src/AsmResolver.DotNet/Serialized/SerializedTypeDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedTypeDefinition.cs index 9b87e76e3..ba495ba5c 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedTypeDefinition.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedTypeDefinition.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using AsmResolver.Collections; using AsmResolver.DotNet.Collections; using AsmResolver.PE.DotNet.Metadata; @@ -35,20 +36,10 @@ public SerializedTypeDefinition(ModuleReaderContext context, MetadataToken token } /// - protected override Utf8String? GetNamespace() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Namespace) - : null; - } + protected override Utf8String? GetNamespace() => _context.StringsStream?.GetStringByIndex(_row.Namespace); /// - protected override Utf8String? GetName() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Name) - : null; - } + protected override Utf8String? GetName() => _context.StringsStream?.GetStringByIndex(_row.Name); /// protected override ITypeDefOrRef? GetBaseType() @@ -56,8 +47,7 @@ public SerializedTypeDefinition(ModuleReaderContext context, MetadataToken token if (_row.Extends == 0) return null; - var token = _context.Metadata - .GetStream() + var token = _context.TablesStream .GetIndexEncoder(CodedIndex.TypeDefOrRef) .DecodeIndex(_row.Extends); @@ -67,9 +57,9 @@ public SerializedTypeDefinition(ModuleReaderContext context, MetadataToken token /// protected override IList GetNestedTypes() { - var result = new OwnedCollection(this); - var rids = _context.ParentModule.GetNestedTypeRids(MetadataToken.Rid); + var result = new OwnedCollection(this, rids.Count); + foreach (uint rid in rids) { var nestedType = (TypeDefinition) _context.ParentModule.LookupMember(new MetadataToken(TableIndex.TypeDef, rid)); @@ -107,7 +97,7 @@ protected override IList GetNestedTypes() private IList CreateMemberCollection(MetadataRange range) where TMember : class, IMetadataMember, IOwnedCollectionElement { - var result = new OwnedCollection(this); + var result = new OwnedCollection(this, range.Count); foreach (var token in range) result.Add((TMember) _context.ParentModule.LookupMember(token)); @@ -126,9 +116,10 @@ private IList CreateMemberCollection(MetadataRange range) /// protected override IList GetGenericParameters() { - var result = new OwnedCollection(this); + var rids = _context.ParentModule.GetGenericParameters(MetadataToken); + var result = new OwnedCollection(this, rids.Count); - foreach (uint rid in _context.ParentModule.GetGenericParameters(MetadataToken)) + foreach (uint rid in rids) { if (_context.ParentModule.TryLookupMember(new MetadataToken(TableIndex.GenericParam, rid), out var member) && member is GenericParameter genericParameter) @@ -143,9 +134,9 @@ protected override IList GetGenericParameters() /// protected override IList GetInterfaces() { - var result = new OwnedCollection(this); - var rids = _context.ParentModule.GetInterfaceImplementationRids(MetadataToken); + var result = new OwnedCollection(this, rids.Count); + foreach (uint rid in rids) { if (_context.ParentModule.TryLookupMember(new MetadataToken(TableIndex.InterfaceImpl, rid), out var member) @@ -161,13 +152,13 @@ protected override IList GetInterfaces() /// protected override IList GetMethodImplementations() { - var result = new List(); - - var tablesStream = _context.Metadata.GetStream(); + var tablesStream = _context.TablesStream; var table = tablesStream.GetTable(TableIndex.MethodImpl); var encoder = tablesStream.GetIndexEncoder(CodedIndex.MethodDefOrRef); var rids = _context.ParentModule.GetMethodImplementationRids(MetadataToken); + var result = new List(rids.Count); + foreach (uint rid in rids) { var row = table.GetByRid(rid); diff --git a/src/AsmResolver.DotNet/Serialized/SerializedTypeReference.cs b/src/AsmResolver.DotNet/Serialized/SerializedTypeReference.cs index 02ca6986e..4e0cb839b 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedTypeReference.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedTypeReference.cs @@ -32,20 +32,10 @@ public SerializedTypeReference(ModuleReaderContext context, MetadataToken token, } /// - protected override Utf8String? GetNamespace() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Namespace) - : null; - } + protected override Utf8String? GetNamespace() => _context.StringsStream?.GetStringByIndex(_row.Namespace); /// - protected override Utf8String? GetName() - { - return _context.Metadata.TryGetStream(out var stringsStream) - ? stringsStream.GetStringByIndex(_row.Name) - : null; - } + protected override Utf8String? GetName() => _context.StringsStream?.GetStringByIndex(_row.Name); /// protected override IResolutionScope? GetScope() @@ -53,7 +43,7 @@ public SerializedTypeReference(ModuleReaderContext context, MetadataToken token, if (_row.ResolutionScope == 0) return _context.ParentModule; - var tablesStream = _context.Metadata.GetStream(); + var tablesStream = _context.TablesStream; var decoder = tablesStream.GetIndexEncoder(CodedIndex.ResolutionScope); var token = decoder.DecodeIndex(_row.ResolutionScope); diff --git a/src/AsmResolver.DotNet/Serialized/SerializedTypeSpecification.cs b/src/AsmResolver.DotNet/Serialized/SerializedTypeSpecification.cs index f8f8c7aad..47f29d5ac 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedTypeSpecification.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedTypeSpecification.cs @@ -34,7 +34,7 @@ public SerializedTypeSpecification(ModuleReaderContext context, MetadataToken to /// protected override TypeSignature? GetSignature() { - if (!_context.Metadata.TryGetStream(out var blobStream) + if (_context.BlobStream is not { } blobStream || !blobStream.TryGetBlobReaderByIndex(_row.Signature, out var reader)) { return _context.BadImageAndReturn( diff --git a/src/AsmResolver.DotNet/Signatures/BlobSerializationContext.cs b/src/AsmResolver.DotNet/Signatures/BlobSerializationContext.cs index bbe68c8ee..d53819ff3 100644 --- a/src/AsmResolver.DotNet/Signatures/BlobSerializationContext.cs +++ b/src/AsmResolver.DotNet/Signatures/BlobSerializationContext.cs @@ -6,7 +6,7 @@ namespace AsmResolver.DotNet.Signatures /// /// Describes a context in which a blob signature is to be serialized in. /// - public class BlobSerializationContext + public readonly struct BlobSerializationContext { /// /// Creates a new instance of the class. diff --git a/src/AsmResolver.DotNet/Signatures/BlobSignature.cs b/src/AsmResolver.DotNet/Signatures/BlobSignature.cs index 17ac56302..d42c16bcd 100644 --- a/src/AsmResolver.DotNet/Signatures/BlobSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/BlobSignature.cs @@ -8,7 +8,7 @@ public abstract class BlobSignature /// /// Serializes the blob to an output stream. /// - public abstract void Write(BlobSerializationContext context); + public abstract void Write(in BlobSerializationContext context); /// /// Wraps the blob signature into a new stand-alone signature that can be referenced by a metadata token. diff --git a/src/AsmResolver.DotNet/Signatures/CustomAttributeArgumentWriter.cs b/src/AsmResolver.DotNet/Signatures/CustomAttributeArgumentWriter.cs index 71049ab4b..a0319b23d 100644 --- a/src/AsmResolver.DotNet/Signatures/CustomAttributeArgumentWriter.cs +++ b/src/AsmResolver.DotNet/Signatures/CustomAttributeArgumentWriter.cs @@ -7,13 +7,13 @@ namespace AsmResolver.DotNet.Signatures { - internal sealed class CustomAttributeArgumentWriter + internal readonly struct CustomAttributeArgumentWriter { private readonly BlobSerializationContext _context; public CustomAttributeArgumentWriter(BlobSerializationContext context) { - _context = context ?? throw new ArgumentNullException(nameof(context)); + _context = context; } public void WriteArgument(CustomAttributeArgument argument) diff --git a/src/AsmResolver.DotNet/Signatures/CustomAttributeSignature.cs b/src/AsmResolver.DotNet/Signatures/CustomAttributeSignature.cs index 1676016d9..8e645704a 100644 --- a/src/AsmResolver.DotNet/Signatures/CustomAttributeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/CustomAttributeSignature.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using AsmResolver.DotNet.Signatures.Types; using AsmResolver.IO; @@ -12,51 +13,26 @@ namespace AsmResolver.DotNet.Signatures /// public class CustomAttributeSignature : ExtendableBlobSignature { - private const ushort CustomAttributeSignaturePrologue = 0x0001; - /// - /// Reads a single custom attribute signature from the input stream. + /// The header value of every custom attribute signature. /// - /// The blob reader context. - /// The constructor that was called. - /// The input stream. - /// The signature. - /// Occurs when the input stream does not point to a valid signature. - public static CustomAttributeSignature? FromReader(in BlobReadContext context, ICustomAttributeType ctor, ref BinaryStreamReader reader) - { - ushort prologue = reader.ReadUInt16(); - if (prologue != CustomAttributeSignaturePrologue) - { - context.ReaderContext.BadImage("Input stream does not point to a valid custom attribute signature."); - return null; - } - - var result = new CustomAttributeSignature(); - - // Read fixed arguments. - var parameterTypes = ctor.Signature?.ParameterTypes ?? Array.Empty(); - for (int i = 0; i < parameterTypes.Count; i++) - { - var argument = CustomAttributeArgument.FromReader(context, parameterTypes[i], ref reader); - result.FixedArguments.Add(argument); - } - - // Read named arguments. - ushort namedArgumentCount = reader.ReadUInt16(); - for (int i = 0; i < namedArgumentCount; i++) - { - var argument = CustomAttributeNamedArgument.FromReader(context, ref reader); - result.NamedArguments.Add(argument); - } + protected const ushort CustomAttributeSignaturePrologue = 0x0001; - return result; - } + private List? _fixedArguments; + private List? _namedArguments; /// /// Creates a new empty custom attribute signature. /// public CustomAttributeSignature() - : this(Enumerable.Empty(), Enumerable.Empty()) + { + } + + /// + /// Creates a new custom attribute signature with the provided fixed arguments. + /// + public CustomAttributeSignature(params CustomAttributeArgument[] fixedArguments) + : this(fixedArguments, Enumerable.Empty()) { } @@ -73,8 +49,8 @@ public CustomAttributeSignature(IEnumerable fixedArgume /// public CustomAttributeSignature(IEnumerable fixedArguments, IEnumerable namedArguments) { - FixedArguments = new List(fixedArguments); - NamedArguments = new List(namedArguments); + _fixedArguments = new List(fixedArguments); + _namedArguments = new List(namedArguments); } /// @@ -82,7 +58,11 @@ public CustomAttributeSignature(IEnumerable fixedArgume /// public IList FixedArguments { - get; + get + { + EnsureIsInitialized(); + return _fixedArguments; + } } /// @@ -90,7 +70,71 @@ public IList FixedArguments /// public IList NamedArguments { - get; + get + { + EnsureIsInitialized(); + return _namedArguments; + } + } + + /// + /// Reads a single custom attribute signature from the input stream. + /// + /// The blob reader context. + /// The constructor that was called. + /// The input stream. + /// The signature. + /// Occurs when the input stream does not point to a valid signature. + public static CustomAttributeSignature FromReader( + in BlobReadContext context, + ICustomAttributeType ctor, + in BinaryStreamReader reader) + { + var argumentTypes = ctor.Signature?.ParameterTypes ?? Array.Empty(); + return new SerializedCustomAttributeSignature(context, argumentTypes, reader); + } + + /// + /// Gets a value indicating whether the and collections + /// are initialized or not. + /// + [MemberNotNullWhen(true, nameof(_fixedArguments))] + [MemberNotNullWhen(true, nameof(_namedArguments))] + protected bool IsInitialized => _fixedArguments is not null && _namedArguments is not null; + + /// + /// Ensures that the and are initialized. + /// + [MemberNotNull(nameof(_fixedArguments))] + [MemberNotNull(nameof(_namedArguments))] + protected void EnsureIsInitialized() + { + if (IsInitialized) + return; + + var fixedArguments = new List(); + var namedArguments = new List(); + + Initialize(fixedArguments, namedArguments); + + lock (this) + { + if (IsInitialized) + return; + _fixedArguments = fixedArguments; + _namedArguments = namedArguments; + } + } + + /// + /// Initializes the argument collections of the signature. + /// + /// The collection that will receive the fixed arguments. + /// The collection that will receive the named arguments. + protected virtual void Initialize( + IList fixedArguments, + IList namedArguments) + { } /// @@ -100,7 +144,7 @@ public override string ToString() } /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { context.Writer.WriteUInt16(CustomAttributeSignaturePrologue); @@ -112,4 +156,5 @@ protected override void WriteContents(BlobSerializationContext context) NamedArguments[i].Write(context); } } + } diff --git a/src/AsmResolver.DotNet/Signatures/DataBlobSignature.cs b/src/AsmResolver.DotNet/Signatures/DataBlobSignature.cs index 062d07a0d..d0c7d498d 100644 --- a/src/AsmResolver.DotNet/Signatures/DataBlobSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/DataBlobSignature.cs @@ -43,7 +43,7 @@ public byte[] Data /// The deserialized literal. public object InterpretData(ElementType elementType) { - var reader = ByteArrayDataSource.CreateReader(Data); + var reader = new BinaryStreamReader(Data); return elementType switch { @@ -65,7 +65,7 @@ public object InterpretData(ElementType elementType) } /// - public override void Write(BlobSerializationContext context) => context.Writer.WriteBytes(Data); + public override void Write(in BlobSerializationContext context) => context.Writer.WriteBytes(Data); /// /// Create a from a value diff --git a/src/AsmResolver.DotNet/Signatures/ExtendableBlobSignature.cs b/src/AsmResolver.DotNet/Signatures/ExtendableBlobSignature.cs index d19e4f9ae..10256d7a2 100644 --- a/src/AsmResolver.DotNet/Signatures/ExtendableBlobSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/ExtendableBlobSignature.cs @@ -17,7 +17,7 @@ public abstract class ExtendableBlobSignature : BlobSignature } /// - public sealed override void Write(BlobSerializationContext context) + public sealed override void Write(in BlobSerializationContext context) { WriteContents(context); if (ExtraData is not null) @@ -27,6 +27,6 @@ public sealed override void Write(BlobSerializationContext context) /// /// Serializes the blob (without extra data) to an output stream. /// - protected abstract void WriteContents(BlobSerializationContext context); + protected abstract void WriteContents(in BlobSerializationContext context); } } diff --git a/src/AsmResolver.DotNet/Signatures/FieldSignature.cs b/src/AsmResolver.DotNet/Signatures/FieldSignature.cs index 02b5495f4..f1d69e005 100644 --- a/src/AsmResolver.DotNet/Signatures/FieldSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/FieldSignature.cs @@ -89,7 +89,7 @@ public TypeSignature FieldType GenericTypeActivator.Instance.InstantiateFieldSignature(this, context); /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { context.Writer.WriteByte((byte) Attributes); FieldType.Write(context); diff --git a/src/AsmResolver.DotNet/Signatures/GenericInstanceMethodSignature.cs b/src/AsmResolver.DotNet/Signatures/GenericInstanceMethodSignature.cs index 2e6286418..33848a34d 100644 --- a/src/AsmResolver.DotNet/Signatures/GenericInstanceMethodSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/GenericInstanceMethodSignature.cs @@ -21,14 +21,14 @@ public class GenericInstanceMethodSignature : CallingConventionSignature, IGener } var attributes = (CallingConventionAttributes) reader.ReadByte(); - var result = new GenericInstanceMethodSignature(attributes); if (!reader.TryReadCompressedUInt32(out uint count)) { context.ReaderContext.BadImage("Invalid number of type arguments in generic method signature."); - return result; + return new GenericInstanceMethodSignature(attributes); } + var result = new GenericInstanceMethodSignature(attributes, (int) count); for (int i = 0; i < count; i++) result.TypeArguments.Add(TypeSignature.FromReader(context, ref reader)); @@ -44,6 +44,17 @@ public GenericInstanceMethodSignature(CallingConventionAttributes attributes) { } + /// + /// Creates a new instantiation signature for a generic method. + /// + /// The attributes. + /// The initial number of elements that the property can store. + public GenericInstanceMethodSignature(CallingConventionAttributes attributes, int capacity) + : base(attributes) + { + TypeArguments = new List(capacity); + } + /// /// Creates a new instantiation signature for a generic method with the provided type arguments. /// @@ -82,7 +93,7 @@ public IList TypeArguments } /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { var writer = context.Writer; writer.WriteByte((byte) Attributes); diff --git a/src/AsmResolver.DotNet/Signatures/LocalVariablesSignature.cs b/src/AsmResolver.DotNet/Signatures/LocalVariablesSignature.cs index 2376fd95c..068dfc4a9 100644 --- a/src/AsmResolver.DotNet/Signatures/LocalVariablesSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/LocalVariablesSignature.cs @@ -93,15 +93,15 @@ public override bool IsImportedInModule(ModuleDefinition module) ImportWith(importer); /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { var writer = context.Writer; writer.WriteByte((byte) Attributes); writer.WriteCompressedUInt32((uint) VariableTypes.Count); - foreach (var type in VariableTypes) - type.Write(context); + for (int i = 0; i < VariableTypes.Count; i++) + VariableTypes[i].Write(context); } /// diff --git a/src/AsmResolver.DotNet/Signatures/Marshal/ComInterfaceMarshalDescriptor.cs b/src/AsmResolver.DotNet/Signatures/Marshal/ComInterfaceMarshalDescriptor.cs index c053be78c..96e60cbc5 100644 --- a/src/AsmResolver.DotNet/Signatures/Marshal/ComInterfaceMarshalDescriptor.cs +++ b/src/AsmResolver.DotNet/Signatures/Marshal/ComInterfaceMarshalDescriptor.cs @@ -58,7 +58,7 @@ public override NativeType NativeType } /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { var writer = context.Writer; diff --git a/src/AsmResolver.DotNet/Signatures/Marshal/CustomMarshalDescriptor.cs b/src/AsmResolver.DotNet/Signatures/Marshal/CustomMarshalDescriptor.cs index 4376c82de..cdea0bda6 100644 --- a/src/AsmResolver.DotNet/Signatures/Marshal/CustomMarshalDescriptor.cs +++ b/src/AsmResolver.DotNet/Signatures/Marshal/CustomMarshalDescriptor.cs @@ -87,7 +87,7 @@ public CustomMarshalDescriptor(string? guid, Utf8String? nativeTypeName, TypeSig } /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { var writer = context.Writer; diff --git a/src/AsmResolver.DotNet/Signatures/Marshal/FixedArrayMarshalDescriptor.cs b/src/AsmResolver.DotNet/Signatures/Marshal/FixedArrayMarshalDescriptor.cs index 6f9401a32..0e0e8cd98 100644 --- a/src/AsmResolver.DotNet/Signatures/Marshal/FixedArrayMarshalDescriptor.cs +++ b/src/AsmResolver.DotNet/Signatures/Marshal/FixedArrayMarshalDescriptor.cs @@ -76,7 +76,7 @@ public FixedArrayMarshalDescriptor(int size, NativeType arrayElementType) } /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { var writer = context.Writer; diff --git a/src/AsmResolver.DotNet/Signatures/Marshal/FixedSysStringMarshalDescriptor.cs b/src/AsmResolver.DotNet/Signatures/Marshal/FixedSysStringMarshalDescriptor.cs index b41cd42cf..cbdf77aec 100644 --- a/src/AsmResolver.DotNet/Signatures/Marshal/FixedSysStringMarshalDescriptor.cs +++ b/src/AsmResolver.DotNet/Signatures/Marshal/FixedSysStringMarshalDescriptor.cs @@ -41,7 +41,7 @@ public int Size } /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { var writer = context.Writer; diff --git a/src/AsmResolver.DotNet/Signatures/Marshal/LPArrayMarshalDescriptor.cs b/src/AsmResolver.DotNet/Signatures/Marshal/LPArrayMarshalDescriptor.cs index 58f98f65a..7346b3616 100644 --- a/src/AsmResolver.DotNet/Signatures/Marshal/LPArrayMarshalDescriptor.cs +++ b/src/AsmResolver.DotNet/Signatures/Marshal/LPArrayMarshalDescriptor.cs @@ -83,7 +83,7 @@ public NativeType ArrayElementType } /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { var writer = context.Writer; diff --git a/src/AsmResolver.DotNet/Signatures/Marshal/SafeArrayMarshalDescriptor.cs b/src/AsmResolver.DotNet/Signatures/Marshal/SafeArrayMarshalDescriptor.cs index 0b4323254..85e2e2a8f 100644 --- a/src/AsmResolver.DotNet/Signatures/Marshal/SafeArrayMarshalDescriptor.cs +++ b/src/AsmResolver.DotNet/Signatures/Marshal/SafeArrayMarshalDescriptor.cs @@ -135,7 +135,7 @@ public bool IsByRef } /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { var writer = context.Writer; diff --git a/src/AsmResolver.DotNet/Signatures/Marshal/SimpleMarshalDescriptor.cs b/src/AsmResolver.DotNet/Signatures/Marshal/SimpleMarshalDescriptor.cs index 81b5a4d34..e4f282186 100644 --- a/src/AsmResolver.DotNet/Signatures/Marshal/SimpleMarshalDescriptor.cs +++ b/src/AsmResolver.DotNet/Signatures/Marshal/SimpleMarshalDescriptor.cs @@ -21,7 +21,7 @@ public override NativeType NativeType } /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { context.Writer.WriteByte((byte) NativeType); } diff --git a/src/AsmResolver.DotNet/Signatures/MethodSignature.cs b/src/AsmResolver.DotNet/Signatures/MethodSignature.cs index 67d530c14..3600c6e32 100644 --- a/src/AsmResolver.DotNet/Signatures/MethodSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/MethodSignature.cs @@ -20,8 +20,10 @@ public class MethodSignature : MethodSignatureBase /// The method signature. public static MethodSignature FromReader(in BlobReadContext context, ref BinaryStreamReader reader) { - var result = new MethodSignature((CallingConventionAttributes) reader.ReadByte(), - context.ReaderContext.ParentModule.CorLibTypeFactory.Void, Enumerable.Empty()); + var result = new MethodSignature( + (CallingConventionAttributes) reader.ReadByte(), + context.ReaderContext.ParentModule.CorLibTypeFactory.Void, + Enumerable.Empty()); // Generic parameter count. if (result.IsGeneric) @@ -199,7 +201,7 @@ public MethodSignature InstantiateGenericTypes(GenericContext context) public FunctionPointerTypeSignature MakeFunctionPointerType() => new(this); /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { var writer = context.Writer; diff --git a/src/AsmResolver.DotNet/Signatures/MethodSignatureBase.cs b/src/AsmResolver.DotNet/Signatures/MethodSignatureBase.cs index 4e58f029a..191bc2710 100644 --- a/src/AsmResolver.DotNet/Signatures/MethodSignatureBase.cs +++ b/src/AsmResolver.DotNet/Signatures/MethodSignatureBase.cs @@ -10,28 +10,27 @@ namespace AsmResolver.DotNet.Signatures /// public abstract class MethodSignatureBase : MemberSignature { + private readonly List _parameterTypes; + /// /// Initializes the base of a method signature. /// - /// - /// - /// + /// The attributes associated to the signature. + /// The return type of the member. + /// The types of all (non-sentinel) parameters. protected MethodSignatureBase( CallingConventionAttributes attributes, TypeSignature memberReturnType, IEnumerable parameterTypes) : base(attributes, memberReturnType) { - ParameterTypes = new List(parameterTypes); + _parameterTypes = new List(parameterTypes); } /// /// Gets an ordered list of types indicating the types of the parameters that this member defines. /// - public IList ParameterTypes - { - get; - } + public IList ParameterTypes => _parameterTypes; /// /// Gets or sets the type of the value that this member returns. @@ -120,6 +119,7 @@ protected void ReadParametersAndReturnType(in BlobReadContext context, ref Binar ReturnType = TypeSignature.FromReader(context, ref reader); // Parameter types. + _parameterTypes.Capacity = (int) parameterCount; bool sentinel = false; for (int i = 0; i < parameterCount; i++) { diff --git a/src/AsmResolver.DotNet/Signatures/PropertySignature.cs b/src/AsmResolver.DotNet/Signatures/PropertySignature.cs index 5883cad30..15b01e0d1 100644 --- a/src/AsmResolver.DotNet/Signatures/PropertySignature.cs +++ b/src/AsmResolver.DotNet/Signatures/PropertySignature.cs @@ -112,7 +112,7 @@ public PropertySignature InstantiateGenericTypes(GenericContext context) => GenericTypeActivator.Instance.InstantiatePropertySignature(this, context); /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { context.Writer.WriteByte((byte) Attributes); WriteParametersAndReturnType(context); diff --git a/src/AsmResolver.DotNet/Signatures/Security/PermissionSetSignature.cs b/src/AsmResolver.DotNet/Signatures/Security/PermissionSetSignature.cs index 8ecf7e4f2..347365357 100644 --- a/src/AsmResolver.DotNet/Signatures/Security/PermissionSetSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Security/PermissionSetSignature.cs @@ -38,7 +38,7 @@ public IList Attributes } = new List(); /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { var writer = context.Writer; diff --git a/src/AsmResolver.DotNet/Signatures/SerializedCustomAttributeSignature.cs b/src/AsmResolver.DotNet/Signatures/SerializedCustomAttributeSignature.cs new file mode 100644 index 000000000..3a841170a --- /dev/null +++ b/src/AsmResolver.DotNet/Signatures/SerializedCustomAttributeSignature.cs @@ -0,0 +1,73 @@ +using System.Linq; +using System.Collections.Generic; +using AsmResolver.DotNet.Signatures.Types; +using AsmResolver.IO; + +namespace AsmResolver.DotNet.Signatures +{ + /// + /// Provides a lazy initialized implementation of the class. + /// + public class SerializedCustomAttributeSignature : CustomAttributeSignature + { + private readonly BlobReadContext _context; + private readonly TypeSignature[] _fixedArgTypes; + private readonly BinaryStreamReader _reader; + + /// + /// Initializes a new lazy custom attribute signature from an input blob stream reader. + /// + /// The blob reading context the signature is situated in. + /// The types of all fixed arguments. + /// The input blob reader. + public SerializedCustomAttributeSignature( + in BlobReadContext context, + IEnumerable fixedArgTypes, + in BinaryStreamReader reader) + { + _context = context; + _fixedArgTypes = fixedArgTypes.ToArray(); + _reader = reader; + } + + /// + protected override void Initialize( + IList fixedArguments, + IList namedArguments) + { + var reader = _reader.Fork(); + + // Verify magic header. + ushort prologue = reader.ReadUInt16(); + if (prologue != CustomAttributeSignaturePrologue) + _context.ReaderContext.BadImage("Input stream does not point to a valid custom attribute signature."); + + // Read fixed arguments. + for (int i = 0; i < _fixedArgTypes.Length; i++) + fixedArguments.Add(CustomAttributeArgument.FromReader(_context, _fixedArgTypes[i], ref reader)); + + // Read named arguments. + ushort namedArgumentCount = reader.ReadUInt16(); + for (int i = 0; i < namedArgumentCount; i++) + namedArguments.Add(CustomAttributeNamedArgument.FromReader(_context, ref reader)); + } + + /// + protected override void WriteContents(in BlobSerializationContext context) + { + // If the arguments are not initialized yet, it means nobody accessed the fixed or named arguments of the + // signature. In such a case, we can safely assume nothing has changed to the signature. + // + // Since custom attribute signatures reference types by their fully qualified name, they do not contain + // any metadata tokens or type indices. Thus, regardless of whether the assembly changed or not, we can + // always just use the raw blob signature without risking breaking any references into the assembly. + // This can save a lot of processing time, given the fact that many custom attribute arguments are Enum + // typed arguments, which would require an expensive type resolution to determine the size of an element. + + if (IsInitialized) + base.WriteContents(context); + else + _reader.Fork().WriteToOutput(context.Writer); + } + } +} diff --git a/src/AsmResolver.DotNet/Signatures/SignatureComparer.ResolutionScope.cs b/src/AsmResolver.DotNet/Signatures/SignatureComparer.ResolutionScope.cs index 5eedef741..31fdb7eac 100644 --- a/src/AsmResolver.DotNet/Signatures/SignatureComparer.ResolutionScope.cs +++ b/src/AsmResolver.DotNet/Signatures/SignatureComparer.ResolutionScope.cs @@ -8,35 +8,28 @@ public partial class SignatureComparer : IEqualityComparer, IEqualityComparer { - /// - /// Gets or sets a value indicating whether version numbers should be excluded in the comparison of two - /// assembly descriptors. - /// - public bool IgnoreAssemblyVersionNumbers + private bool IgnoreAssemblyVersionNumbers { - get; - set; + get + { + return (Flags & SignatureComparisonFlags.VersionAgnostic) == SignatureComparisonFlags.VersionAgnostic; + } } - /// - /// Gets or sets a value indicating whether the containing assembly of the second member to compare is - /// allowed to be a newer version than the containing assembly of the first member. - /// - /// - /// - /// If this property is set to true, then any member reference that is contained in a certain - /// assembly (e.g. with version 1.0.0.0), will be considered equal to a member reference with the - /// same name or signature contained in an assembly with a newer version (e.g. 1.1.0.0). When this - /// property is set to false, the exact version number must match instead. - /// - /// - /// This property is ignored if is true. - /// - /// - public bool AcceptNewerAssemblyVersionNumbers + private bool AcceptNewerAssemblyVersionNumbers { - get; - set; + get + { + return (Flags & SignatureComparisonFlags.AcceptNewerVersions) == SignatureComparisonFlags.AcceptNewerVersions; + } + } + + private bool AcceptOlderAssemblyVersionNumbers + { + get + { + return (Flags & SignatureComparisonFlags.AcceptOlderVersions) == SignatureComparisonFlags.AcceptOlderVersions; + } } /// @@ -80,6 +73,8 @@ public bool Equals(AssemblyDescriptor? x, AssemblyDescriptor? y) versionMatch = true; else if (AcceptNewerAssemblyVersionNumbers) versionMatch = x.Version <= y.Version; + else if (AcceptOlderAssemblyVersionNumbers) + versionMatch = x.Version >= y.Version; else versionMatch = x.Version == y.Version; diff --git a/src/AsmResolver.DotNet/Signatures/SignatureComparer.cs b/src/AsmResolver.DotNet/Signatures/SignatureComparer.cs index 09b9843ff..c4a67b84d 100644 --- a/src/AsmResolver.DotNet/Signatures/SignatureComparer.cs +++ b/src/AsmResolver.DotNet/Signatures/SignatureComparer.cs @@ -9,6 +9,35 @@ public partial class SignatureComparer : IEqualityComparer { private const int ElementTypeOffset = 24; + private const SignatureComparisonFlags DefaultFlags = SignatureComparisonFlags.ExactVersion; + + /// + /// An immutable default instance of . + /// + public static SignatureComparer Default { get; } = new(); + + /// + /// Flags for controlling comparison behavior. + /// + public SignatureComparisonFlags Flags { get; } + + /// + /// The default constructor. + /// + public SignatureComparer() + { + Flags = DefaultFlags; + } + + /// + /// A constructor with a parameter for specifying the + /// used in comparisons. + /// + /// The used in comparisons. + public SignatureComparer(SignatureComparisonFlags flags) + { + Flags = flags; + } /// public bool Equals(byte[]? x, byte[]? y) => ByteArrayEqualityComparer.Instance.Equals(x, y); diff --git a/src/AsmResolver.DotNet/Signatures/SignatureComparisonFlags.cs b/src/AsmResolver.DotNet/Signatures/SignatureComparisonFlags.cs new file mode 100644 index 000000000..698e835cd --- /dev/null +++ b/src/AsmResolver.DotNet/Signatures/SignatureComparisonFlags.cs @@ -0,0 +1,44 @@ +using System; + +namespace AsmResolver.DotNet.Signatures +{ + /// + /// Flags for controlling the behavior of . + /// + [Flags] + public enum SignatureComparisonFlags + { + /// + /// When neither nor are specified, + /// the exact version number must match in the comparison of two assembly descriptors. + /// + ExactVersion = 0, + /// + /// If this flag is used, the containing assembly of the second member to compare is + /// allowed to be an older version than the containing assembly of the first member. + /// + /// + /// If this flag is used, then any member reference that is contained in a certain + /// assembly (e.g. with version 1.1.0.0), will be considered equal to a member reference with the + /// same name or signature contained in an assembly with a older version (e.g. 1.0.0.0). + /// Otherwise, they will be treated as inequal. + /// + AcceptOlderVersions = 1, + /// + /// If this flag is used, the containing assembly of the second member to compare is + /// allowed to be a newer version than the containing assembly of the first member. + /// + /// + /// If this flag is used, then any member reference that is contained in a certain + /// assembly (e.g. with version 1.0.0.0), will be considered equal to a member reference with the + /// same name or signature contained in an assembly with a newer version (e.g. 1.1.0.0). + /// Otherwise, they will be treated as inequal. + /// + AcceptNewerVersions = 2, + /// + /// If this flag is used, version numbers will be excluded in the comparison of two + /// assembly descriptors. + /// + VersionAgnostic = AcceptOlderVersions | AcceptNewerVersions, + } +} diff --git a/src/AsmResolver.DotNet/Signatures/Types/ArrayTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/ArrayTypeSignature.cs index 193142038..081ec88e2 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/ArrayTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/ArrayTypeSignature.cs @@ -87,7 +87,7 @@ internal new static ArrayTypeSignature FromReader(in BlobReadContext context, re return signature; } - var sizes = new List(); + var sizes = new List((int) numSizes); for (int i = 0; i < numSizes; i++) { if (!reader.TryReadCompressedUInt32(out uint size)) @@ -106,7 +106,7 @@ internal new static ArrayTypeSignature FromReader(in BlobReadContext context, re return signature; } - var loBounds = new List(); + var loBounds = new List((int) numLoBounds); for (int i = 0; i < numLoBounds; i++) { if (!reader.TryReadCompressedUInt32(out uint bound)) @@ -201,7 +201,7 @@ public bool Validate() visitor.VisitArrayType(this, state); /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { if (!Validate()) throw new InvalidOperationException(); diff --git a/src/AsmResolver.DotNet/Signatures/Types/CorLibTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/CorLibTypeSignature.cs index ae2a6974c..9f77eaba4 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/CorLibTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/CorLibTypeSignature.cs @@ -103,7 +103,7 @@ public override ElementType ElementType public override ITypeDefOrRef ToTypeDefOrRef() => Type; /// - protected override void WriteContents(BlobSerializationContext context) => + protected override void WriteContents(in BlobSerializationContext context) => context.Writer.WriteByte((byte) ElementType); /// diff --git a/src/AsmResolver.DotNet/Signatures/Types/CustomModifierTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/CustomModifierTypeSignature.cs index 64ec085e1..12b4554ad 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/CustomModifierTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/CustomModifierTypeSignature.cs @@ -80,7 +80,7 @@ public override string Name ModifierType.IsImportedInModule(module) && base.IsImportedInModule(module); /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { context.Writer.WriteByte((byte) ElementType); WriteTypeDefOrRef(context, ModifierType, "Modifier type"); diff --git a/src/AsmResolver.DotNet/Signatures/Types/FunctionPointerTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/FunctionPointerTypeSignature.cs index ff2acc2c8..b5ebe0c15 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/FunctionPointerTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/FunctionPointerTypeSignature.cs @@ -47,13 +47,13 @@ public MethodSignature Signature /// public override ITypeDefOrRef? GetUnderlyingTypeDefOrRef() => - Signature?.ReturnType?.Module?.CorLibTypeFactory.IntPtr.Type; + Signature.ReturnType.Module?.CorLibTypeFactory.IntPtr.Type; /// public override bool IsImportedInModule(ModuleDefinition module) => Signature.IsImportedInModule(module); /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { context.Writer.WriteByte((byte) ElementType); Signature.Write(context); diff --git a/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs index 7ed057d8b..33d588a03 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs @@ -10,6 +10,10 @@ namespace AsmResolver.DotNet.Signatures.Types /// public class GenericInstanceTypeSignature : TypeSignature, IGenericArgumentsProvider { + private readonly List _typeArguments; + private ITypeDefOrRef _genericType; + private bool _isValueType; + internal new static GenericInstanceTypeSignature FromReader(in BlobReadContext context, ref BinaryStreamReader reader) { var genericType = TypeSignature.FromReader(context, ref reader); @@ -21,8 +25,9 @@ internal new static GenericInstanceTypeSignature FromReader(in BlobReadContext c return signature; } + signature._typeArguments.Capacity = (int) count; for (int i = 0; i < count; i++) - signature.TypeArguments.Add(TypeSignature.FromReader(context, ref reader)); + signature._typeArguments.Add(TypeSignature.FromReader(context, ref reader)); return signature; } @@ -52,9 +57,9 @@ public GenericInstanceTypeSignature(ITypeDefOrRef genericType, bool isValueType) private GenericInstanceTypeSignature(ITypeDefOrRef genericType, bool isValueType, IEnumerable typeArguments) { - GenericType = genericType; - TypeArguments = new List(typeArguments); - IsValueType = isValueType; + _genericType = genericType; + _typeArguments = new List(typeArguments); + _isValueType = isValueType; } /// @@ -65,17 +70,18 @@ public GenericInstanceTypeSignature(ITypeDefOrRef genericType, bool isValueType) /// public ITypeDefOrRef GenericType { - get; - set; + get => _genericType; + set + { + _genericType = value; + _isValueType = _genericType.IsValueType; + } } /// /// Gets a collection of type arguments used to instantiate the generic type. /// - public IList TypeArguments - { - get; - } + public IList TypeArguments => _typeArguments; /// public override string? Name @@ -97,10 +103,7 @@ public IList TypeArguments public override ModuleDefinition? Module => GenericType.Module; /// - public override bool IsValueType - { - get; - } + public override bool IsValueType => _isValueType; /// public override TypeDefinition? Resolve() => GenericType.Resolve(); @@ -124,7 +127,7 @@ public override bool IsImportedInModule(ModuleDefinition module) } /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { var writer = context.Writer; diff --git a/src/AsmResolver.DotNet/Signatures/Types/GenericParameterSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/GenericParameterSignature.cs index 24f487016..f29083c29 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/GenericParameterSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/GenericParameterSignature.cs @@ -90,7 +90,7 @@ public int Index public override ITypeDefOrRef? GetUnderlyingTypeDefOrRef() => null; /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { var writer = context.Writer; writer.WriteByte((byte) ElementType); diff --git a/src/AsmResolver.DotNet/Signatures/Types/SentinelTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/SentinelTypeSignature.cs index 49e7faa18..cf0e8e976 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/SentinelTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/SentinelTypeSignature.cs @@ -37,7 +37,7 @@ public class SentinelTypeSignature : TypeSignature public override bool IsImportedInModule(ModuleDefinition module) => true; /// - protected override void WriteContents(BlobSerializationContext context) => + protected override void WriteContents(in BlobSerializationContext context) => context.Writer.WriteByte((byte) ElementType); /// diff --git a/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs index cab577dc4..3fed04061 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs @@ -7,6 +7,9 @@ namespace AsmResolver.DotNet.Signatures.Types /// public class TypeDefOrRefSignature : TypeSignature { + private ITypeDefOrRef _type; + private bool _isValueType; + /// /// Creates a new type signature referencing a type in a type metadata table. /// @@ -23,9 +26,8 @@ public TypeDefOrRefSignature(ITypeDefOrRef type) /// Indicates whether the referenced type is a value type or not. public TypeDefOrRefSignature(ITypeDefOrRef type, bool isValueType) { - Type = type; - IsValueType = isValueType; - + _type = type; + _isValueType = isValueType; } /// @@ -33,7 +35,12 @@ public TypeDefOrRefSignature(ITypeDefOrRef type, bool isValueType) /// public ITypeDefOrRef Type { - get; + get => _type; + set + { + _type = value; + _isValueType = value.IsValueType; + } } /// @@ -52,10 +59,7 @@ public ITypeDefOrRef Type public override ModuleDefinition? Module => Type.Module; /// - public override bool IsValueType - { - get; - } + public override bool IsValueType => _isValueType; /// public override TypeDefinition? Resolve() => Type.Resolve(); @@ -79,7 +83,7 @@ public override bool IsValueType visitor.VisitTypeDefOrRef(this, state); /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { var writer = context.Writer; writer.WriteByte((byte) ElementType); diff --git a/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs index 389411e30..7eb81bfbd 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs @@ -14,18 +14,6 @@ public abstract class TypeSignature : ExtendableBlobSignature, ITypeDescriptor { internal const string NullTypeToString = "<>"; - private static readonly MethodInfo GetTypeFromHandleUnsafeMethod; - - static TypeSignature() - { - GetTypeFromHandleUnsafeMethod = typeof(Type) - .GetMethod("GetTypeFromHandleUnsafe", - (BindingFlags) (-1), - null, - new[] {typeof(IntPtr)}, - null)!; - } - /// public abstract string? Name { @@ -156,19 +144,9 @@ public static TypeSignature FromReader(in BlobReadContext context, ref BinaryStr return new BoxedTypeSignature(FromReader(context, ref reader)); case ElementType.Internal: - var address = IntPtr.Size switch - { - 4 => new IntPtr(reader.ReadInt32()), - _ => new IntPtr(reader.ReadInt64()) - }; - - // Let the runtime translate the address to a type and import it. - var clrType = (Type?) GetTypeFromHandleUnsafeMethod.Invoke(null, new object[] { address }); - var asmResType = clrType is not null - ? new ReferenceImporter(context.ReaderContext.ParentModule).ImportType(clrType) - : InvalidTypeDefOrRef.Get(InvalidTypeSignatureError.IllegalTypeSpec); - - return new TypeDefOrRefSignature(asmResType); + throw new NotSupportedException( + "Encountered an COR_ELEMENT_TYPE_INTERNAL type signature which is not supported by this " + + " type signature reader. Use the AsmResolver.DotNet.Dynamic extension package instead."); default: throw new ArgumentOutOfRangeException($"Invalid or unsupported element type {elementType}."); diff --git a/src/AsmResolver.DotNet/Signatures/Types/TypeSpecificationSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/TypeSpecificationSignature.cs index 412964357..81ced7a33 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/TypeSpecificationSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/TypeSpecificationSignature.cs @@ -44,7 +44,7 @@ public TypeSignature BaseType public override bool IsImportedInModule(ModuleDefinition module) => BaseType.IsImportedInModule(module); /// - protected override void WriteContents(BlobSerializationContext context) + protected override void WriteContents(in BlobSerializationContext context) { context.Writer.WriteByte((byte) ElementType); WriteBaseType(context); diff --git a/src/AsmResolver.DotNet/TokenAllocator.cs b/src/AsmResolver.DotNet/TokenAllocator.cs index f02d606df..9f1cff05a 100644 --- a/src/AsmResolver.DotNet/TokenAllocator.cs +++ b/src/AsmResolver.DotNet/TokenAllocator.cs @@ -38,6 +38,9 @@ private void InitializeTable(IDotNetDirectory netDirectory) var tableStream = netDirectory.Metadata!.GetStream(); for (TableIndex index = 0; index < TableIndex.Max; index++) { + if (!index.IsValidTableIndex()) + continue; + var table = tableStream.GetTable(index); _buckets[(int) index] = new TokenBucket(new MetadataToken(index, (uint) table.Count + 1)); } diff --git a/src/AsmResolver.DotNet/TypeDefinition.cs b/src/AsmResolver.DotNet/TypeDefinition.cs index ce14eb497..b9f658544 100644 --- a/src/AsmResolver.DotNet/TypeDefinition.cs +++ b/src/AsmResolver.DotNet/TypeDefinition.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; using AsmResolver.Collections; using AsmResolver.DotNet.Code.Cil; @@ -498,6 +499,13 @@ public bool IsDelegate IsValueType && this.HasCustomAttribute("System.Runtime.CompilerServices", nameof(ReadOnlyAttribute)); + /// + /// Determines whether the type is marked with the IsByRefLike attribute, indicating a ref struct definition. + /// + public bool IsByRefLike => + IsValueType + && this.HasCustomAttribute("System.Runtime.CompilerServices", "IsByRefLikeAttribute"); + /// /// Gets a collection of fields defined in the type. /// @@ -674,10 +682,13 @@ public bool Implements(string fullName) ITypeDefOrRef ITypeDescriptor.ToTypeDefOrRef() => this; /// - public TypeSignature ToTypeSignature() + public TypeSignature ToTypeSignature() => ToTypeSignature(IsValueType); + + /// + public TypeSignature ToTypeSignature(bool isValueType) { return Module?.CorLibTypeFactory.FromType(this) as TypeSignature - ?? new TypeDefOrRefSignature(this, IsValueType); + ?? new TypeDefOrRefSignature(this, isValueType); } /// diff --git a/src/AsmResolver.DotNet/TypeDescriptorExtensions.cs b/src/AsmResolver.DotNet/TypeDescriptorExtensions.cs index a108f6376..360ad9f54 100644 --- a/src/AsmResolver.DotNet/TypeDescriptorExtensions.cs +++ b/src/AsmResolver.DotNet/TypeDescriptorExtensions.cs @@ -100,7 +100,24 @@ public static class TypeDescriptorExtensions public static GenericInstanceTypeSignature MakeGenericInstanceType( this ITypeDescriptor type, params TypeSignature[] typeArguments) { - return new GenericInstanceTypeSignature(type.ToTypeDefOrRef(), type.IsValueType, typeArguments); + return type.MakeGenericInstanceType(type.IsValueType, typeArguments); + } + + /// + /// Constructs a new pointer type signature with the provided type descriptor as element type. + /// as element type. + /// + /// The element type. + /// true if the type is a value type, false otherwise. + /// The arguments to instantiate the type with. + /// The constructed by-reference type signature. + /// + /// This function can be used to avoid type resolution on type references. + /// + public static GenericInstanceTypeSignature MakeGenericInstanceType( + this ITypeDescriptor type, bool isValueType, params TypeSignature[] typeArguments) + { + return new GenericInstanceTypeSignature(type.ToTypeDefOrRef(), isValueType, typeArguments); } /// diff --git a/src/AsmResolver.DotNet/TypeReference.cs b/src/AsmResolver.DotNet/TypeReference.cs index 0b1332fe9..fef21335e 100644 --- a/src/AsmResolver.DotNet/TypeReference.cs +++ b/src/AsmResolver.DotNet/TypeReference.cs @@ -130,10 +130,13 @@ public IList CustomAttributes ITypeDefOrRef ITypeDescriptor.ToTypeDefOrRef() => this; /// - public TypeSignature ToTypeSignature() + public TypeSignature ToTypeSignature() => ToTypeSignature(IsValueType); + + /// + public TypeSignature ToTypeSignature(bool isValueType) { return Module?.CorLibTypeFactory.FromType(this) as TypeSignature - ?? new TypeDefOrRefSignature(this, IsValueType); + ?? new TypeDefOrRefSignature(this, isValueType); } /// diff --git a/src/AsmResolver.DotNet/TypeSpecification.cs b/src/AsmResolver.DotNet/TypeSpecification.cs index e8d805b4d..aa4420351 100644 --- a/src/AsmResolver.DotNet/TypeSpecification.cs +++ b/src/AsmResolver.DotNet/TypeSpecification.cs @@ -95,6 +95,9 @@ public IList CustomAttributes public TypeSignature ToTypeSignature() => Signature ?? throw new ArgumentException("Signature embedded into the type specification is null."); + /// + public TypeSignature ToTypeSignature(bool isValueType) => ToTypeSignature(); + /// public bool IsImportedInModule(ModuleDefinition module) => Signature?.IsImportedInModule(module) ?? false; diff --git a/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj b/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj index 5c0bfda0f..98b14d222 100644 --- a/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj +++ b/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj @@ -25,7 +25,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/AsmResolver.PE.File/Headers/OptionalHeader.cs b/src/AsmResolver.PE.File/Headers/OptionalHeader.cs index 917865b82..a72535fa3 100644 --- a/src/AsmResolver.PE.File/Headers/OptionalHeader.cs +++ b/src/AsmResolver.PE.File/Headers/OptionalHeader.cs @@ -52,17 +52,17 @@ public static OptionalHeader FromReader(ref BinaryStreamReader reader, bool igno SizeOfCode = reader.ReadUInt32(), SizeOfInitializedData = reader.ReadUInt32(), SizeOfUninitializedData = reader.ReadUInt32(), - AddressOfEntrypoint = reader.ReadUInt32(), + AddressOfEntryPoint = reader.ReadUInt32(), BaseOfCode = reader.ReadUInt32() }; switch (header.Magic) { - case OptionalHeaderMagic.Pe32: + case OptionalHeaderMagic.PE32: header.BaseOfData = reader.ReadUInt32(); header.ImageBase = reader.ReadUInt32(); break; - case OptionalHeaderMagic.Pe32Plus: + case OptionalHeaderMagic.PE32Plus: header.ImageBase = reader.ReadUInt64(); break; default: @@ -84,7 +84,7 @@ public static OptionalHeader FromReader(ref BinaryStreamReader reader, bool igno header.SubSystem = (SubSystem)reader.ReadUInt16(); header.DllCharacteristics = (DllCharacteristics)reader.ReadUInt16(); - if (header.Magic == OptionalHeaderMagic.Pe32) + if (header.Magic == OptionalHeaderMagic.PE32) { header.SizeOfStackReserve = reader.ReadUInt32(); header.SizeOfStackCommit = reader.ReadUInt32(); @@ -120,7 +120,7 @@ public OptionalHeaderMagic Magic { get; set; - } = OptionalHeaderMagic.Pe32; + } = OptionalHeaderMagic.PE32; /// /// Gets or sets the major linker version used to link the portable executable (PE) file. @@ -168,9 +168,9 @@ public uint SizeOfUninitializedData } /// - /// Gets or sets the relative virtual address to the entrypoint of the portable executable (PE) file. + /// Gets or sets the relative virtual address to the entry point of the portable executable (PE) file. /// - public uint AddressOfEntrypoint + public uint AddressOfEntryPoint { get; set; @@ -403,7 +403,7 @@ public IList DataDirectories public override uint GetPhysicalSize() { // TODO: make configurable? - return Magic == OptionalHeaderMagic.Pe32 ? 0xE0u : 0xF0u; + return Magic == OptionalHeaderMagic.PE32 ? 0xE0u : 0xF0u; } /// @@ -417,16 +417,16 @@ public override void Write(IBinaryStreamWriter writer) writer.WriteUInt32(SizeOfCode); writer.WriteUInt32(SizeOfInitializedData); writer.WriteUInt32(SizeOfUninitializedData); - writer.WriteUInt32(AddressOfEntrypoint); + writer.WriteUInt32(AddressOfEntryPoint); writer.WriteUInt32(BaseOfCode); switch (Magic) { - case OptionalHeaderMagic.Pe32: + case OptionalHeaderMagic.PE32: writer.WriteUInt32(BaseOfData); writer.WriteUInt32((uint)ImageBase); break; - case OptionalHeaderMagic.Pe32Plus: + case OptionalHeaderMagic.PE32Plus: writer.WriteUInt64(ImageBase); break; default: @@ -448,7 +448,7 @@ public override void Write(IBinaryStreamWriter writer) writer.WriteUInt16((ushort)SubSystem); writer.WriteUInt16((ushort)DllCharacteristics); - if (Magic == OptionalHeaderMagic.Pe32) + if (Magic == OptionalHeaderMagic.PE32) { writer.WriteUInt32((uint)SizeOfStackReserve); writer.WriteUInt32((uint)SizeOfStackCommit); diff --git a/src/AsmResolver.PE.File/Headers/OptionalHeaderMagic.cs b/src/AsmResolver.PE.File/Headers/OptionalHeaderMagic.cs index abc64a88c..842656ff2 100644 --- a/src/AsmResolver.PE.File/Headers/OptionalHeaderMagic.cs +++ b/src/AsmResolver.PE.File/Headers/OptionalHeaderMagic.cs @@ -8,11 +8,16 @@ public enum OptionalHeaderMagic : ushort /// /// Indicates the binary contains a 32-bit portable executable file. /// - Pe32 = 0x010B, - + PE32 = 0x010B, + /// /// Indicates the binary contains a 64-bit portable executable file. /// - Pe32Plus = 0x020B, + PE32Plus = 0x020B, + + /// + /// Indicates the binary contains a 64-bit portable executable file. + /// + PE64 = PE32Plus } -} \ No newline at end of file +} diff --git a/src/AsmResolver.PE.File/IPEFile.cs b/src/AsmResolver.PE.File/IPEFile.cs index 57e4e50a9..abc25a417 100644 --- a/src/AsmResolver.PE.File/IPEFile.cs +++ b/src/AsmResolver.PE.File/IPEFile.cs @@ -9,7 +9,7 @@ namespace AsmResolver.PE.File /// /// Provides a writable implementation of the interface. /// - public interface IPEFile : ISegmentReferenceResolver, IOffsetConverter + public interface IPEFile : ISegmentReferenceFactory, IOffsetConverter { /// /// When this PE file was read from the disk, gets the file path to the PE file. diff --git a/src/AsmResolver.PE.File/PEFile.cs b/src/AsmResolver.PE.File/PEFile.cs index d1dc0f2d7..d1483258b 100644 --- a/src/AsmResolver.PE.File/PEFile.cs +++ b/src/AsmResolver.PE.File/PEFile.cs @@ -135,7 +135,7 @@ public static PEFile FromFile(IInputFile file) /// The raw bytes representing the contents of the PE file to read. /// The PE file that was read. /// Occurs when the file does not follow the PE file format. - public static PEFile FromBytes(byte[] raw) => FromReader(ByteArrayDataSource.CreateReader(raw)); + public static PEFile FromBytes(byte[] raw) => FromReader(new BinaryStreamReader(raw)); /// /// Reads a mapped PE file starting at the provided module base address (HINSTANCE). @@ -216,8 +216,19 @@ public PESection GetSectionContainingOffset(ulong fileOffset) /// true if the section was found, false otherwise. public bool TryGetSectionContainingOffset(ulong fileOffset, [NotNullWhen(true)] out PESection? section) { - section = Sections.FirstOrDefault(s => s.ContainsFileOffset(fileOffset)); - return section != null; + var sections = Sections; + + for (int i = 0; i < sections.Count; i++) + { + if (sections[i].ContainsFileOffset(fileOffset)) + { + section = sections[i]; + return true; + } + } + + section = null; + return false; } /// @@ -231,8 +242,19 @@ public PESection GetSectionContainingRva(uint rva) /// public bool TryGetSectionContainingRva(uint rva, [NotNullWhen(true)] out PESection? section) { - section = Sections.FirstOrDefault(s => s.ContainsRva(rva)); - return section != null; + var sections = Sections; + + for (int i = 0; i < sections.Count; i++) + { + if (sections[i].ContainsRva(rva)) + { + section = sections[i]; + return true; + } + } + + section = null; + return false; } /// @@ -371,12 +393,15 @@ public void UpdateHeaders() FileHeader.NumberOfSections = (ushort) Sections.Count; - FileHeader.UpdateOffsets( + var relocation = new RelocationParameters(OptionalHeader.ImageBase, 0, 0, + OptionalHeader.Magic == OptionalHeaderMagic.PE32); + + FileHeader.UpdateOffsets(relocation.WithOffsetRva( DosHeader.NextHeaderOffset + 4, - DosHeader.NextHeaderOffset + 4); - OptionalHeader.UpdateOffsets( + DosHeader.NextHeaderOffset + 4)); + OptionalHeader.UpdateOffsets(relocation.WithOffsetRva( FileHeader.Offset + FileHeader.GetPhysicalSize(), - FileHeader.Rva + FileHeader.GetVirtualSize()); + FileHeader.Rva + FileHeader.GetVirtualSize())); FileHeader.SizeOfOptionalHeader = (ushort) OptionalHeader.GetPhysicalSize(); OptionalHeader.SizeOfHeaders = (uint) (OptionalHeader.Offset @@ -391,7 +416,9 @@ public void UpdateHeaders() OptionalHeader.SizeOfImage = lastSection.Rva + lastSection.GetVirtualSize().Align(OptionalHeader.SectionAlignment); - EofData?.UpdateOffsets(lastSection.Offset + lastSection.GetPhysicalSize(), OptionalHeader.SizeOfImage); + EofData?.UpdateOffsets(relocation.WithOffsetRva( + lastSection.Offset + lastSection.GetPhysicalSize(), + OptionalHeader.SizeOfImage)); } /// @@ -399,19 +426,20 @@ public void UpdateHeaders() /// public void AlignSections() { - uint currentFileOffset = OptionalHeader.SizeOfHeaders; + var relocation = new RelocationParameters( + OptionalHeader.ImageBase, + OptionalHeader.SizeOfHeaders.Align(OptionalHeader.FileAlignment), + OptionalHeader.SizeOfHeaders.Align(OptionalHeader.SectionAlignment), + OptionalHeader.Magic == OptionalHeaderMagic.PE32); for (int i = 0; i < Sections.Count; i++) { var section = Sections[i]; - uint rva = i > 0 - ? Sections[i - 1].Rva + Sections[i - 1].GetVirtualSize() - : OptionalHeader.SizeOfHeaders.Align(OptionalHeader.SectionAlignment); - - currentFileOffset = currentFileOffset.Align(OptionalHeader.FileAlignment); - section.UpdateOffsets(currentFileOffset, rva.Align(OptionalHeader.SectionAlignment)); - currentFileOffset += section.GetPhysicalSize(); + section.UpdateOffsets(relocation); + relocation.Advance( + section.GetPhysicalSize().Align(OptionalHeader.FileAlignment), + section.GetVirtualSize().Align(OptionalHeader.SectionAlignment)); } } diff --git a/src/AsmResolver.PE.File/PESection.cs b/src/AsmResolver.PE.File/PESection.cs index 8338dd51f..b091780ae 100644 --- a/src/AsmResolver.PE.File/PESection.cs +++ b/src/AsmResolver.PE.File/PESection.cs @@ -216,7 +216,7 @@ public bool IsMemoryWrite public bool CanUpdateOffsets => true; /// - public void UpdateOffsets(ulong newOffset, uint newRva) => Contents?.UpdateOffsets(newOffset, newRva); + public void UpdateOffsets(in RelocationParameters parameters) => Contents?.UpdateOffsets(parameters); /// public uint GetPhysicalSize() => Contents?.GetPhysicalSize() ?? 0; diff --git a/src/AsmResolver.PE.File/PESegmentReference.cs b/src/AsmResolver.PE.File/PESegmentReference.cs index 5c7326cbc..9eeeaea0a 100644 --- a/src/AsmResolver.PE.File/PESegmentReference.cs +++ b/src/AsmResolver.PE.File/PESegmentReference.cs @@ -32,18 +32,12 @@ public uint Rva get; } - /// - bool IOffsetProvider.CanUpdateOffsets => false; - /// public bool CanRead => _peFile.TryGetSectionContainingRva(Rva, out _); /// public bool IsBounded => false; - /// - void IOffsetProvider.UpdateOffsets(ulong newOffset, uint newRva) => throw new InvalidOperationException(); - /// public BinaryStreamReader CreateReader() => _peFile.CreateReaderAtRva(Rva); diff --git a/src/AsmResolver.PE.File/SerializedPEFile.cs b/src/AsmResolver.PE.File/SerializedPEFile.cs index c9bff4dab..52710fc18 100644 --- a/src/AsmResolver.PE.File/SerializedPEFile.cs +++ b/src/AsmResolver.PE.File/SerializedPEFile.cs @@ -12,6 +12,8 @@ public class SerializedPEFile : PEFile { private readonly List _sectionHeaders; private readonly BinaryStreamReader _reader; + private readonly ulong _originalImageBase; + private readonly bool _is32Bit; /// /// Reads a PE file from an input stream. @@ -36,6 +38,8 @@ public SerializedPEFile(in BinaryStreamReader reader, PEMappingMode mode) // Read NT headers. FileHeader = FileHeader.FromReader(ref _reader); OptionalHeader = OptionalHeader.FromReader(ref _reader); + _originalImageBase = OptionalHeader.ImageBase; + _is32Bit = OptionalHeader.Magic == OptionalHeaderMagic.PE32; // Read section headers. _reader.Offset = OptionalHeader.Offset + FileHeader.SizeOfOptionalHeader; @@ -70,7 +74,12 @@ protected override IList GetSections() physicalContents = new DataSourceSegment(_reader.DataSource, offset, header.VirtualAddress, size); var virtualSegment = new VirtualSegment(physicalContents, header.VirtualSize); - virtualSegment.UpdateOffsets(offset, header.VirtualAddress); + virtualSegment.UpdateOffsets(new RelocationParameters( + _originalImageBase, + offset, + header.VirtualAddress, + _is32Bit)); + result.Add(new PESection(header, virtualSegment)); } diff --git a/src/AsmResolver.PE.Win32Resources/Version/FixedVersionInfo.cs b/src/AsmResolver.PE.Win32Resources/Version/FixedVersionInfo.cs index 03e8f4979..d698bca04 100644 --- a/src/AsmResolver.PE.Win32Resources/Version/FixedVersionInfo.cs +++ b/src/AsmResolver.PE.Win32Resources/Version/FixedVersionInfo.cs @@ -24,7 +24,7 @@ public class FixedVersionInfo : SegmentBase public static FixedVersionInfo FromReader(ref BinaryStreamReader reader) { var result = new FixedVersionInfo(); - result.UpdateOffsets(reader.Offset, reader.Rva); + result.UpdateOffsets(new RelocationParameters(reader.Offset, reader.Rva)); uint signature = reader.ReadUInt32(); if (signature != Signature) diff --git a/src/AsmResolver.PE/AsmResolver.PE.csproj b/src/AsmResolver.PE/AsmResolver.PE.csproj index 058547040..a008308e1 100644 --- a/src/AsmResolver.PE/AsmResolver.PE.csproj +++ b/src/AsmResolver.PE/AsmResolver.PE.csproj @@ -24,7 +24,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/AsmResolver.PE/Builder/PEFileBuilderBase.cs b/src/AsmResolver.PE/Builder/PEFileBuilderBase.cs index 7ce12829a..4f6730ac2 100644 --- a/src/AsmResolver.PE/Builder/PEFileBuilderBase.cs +++ b/src/AsmResolver.PE/Builder/PEFileBuilderBase.cs @@ -51,13 +51,13 @@ public virtual PEFile CreateFile(IPEImage image) protected abstract IEnumerable CreateDataDirectories(PEFile peFile, IPEImage image, TContext context); /// - /// Gets the relative virtual address (RVA) to the entrypoint of the PE file. + /// Gets the relative virtual address (RVA) to the entry point of the PE file. /// - /// The (incomplete) PE file containing the entrypoint. + /// The (incomplete) PE file containing the entry point. /// The image that the PE file was based on. /// The object containing the intermediate values used during the PE file construction. - /// The relative virtual address to the entrypoin. - protected abstract uint GetEntrypointAddress(PEFile peFile, IPEImage image, TContext context); + /// The relative virtual address to the entry point. + protected abstract uint GetEntryPointAddress(PEFile peFile, IPEImage image, TContext context); /// /// Gets the file alignment for the new PE file. @@ -144,7 +144,7 @@ protected virtual void ComputeOptionalHeaderFields(PEFile peFile, IPEImage image header.SizeOfImage += section.GetVirtualSize(); } - header.AddressOfEntrypoint = GetEntrypointAddress(peFile, image, context); + header.AddressOfEntryPoint = GetEntryPointAddress(peFile, image, context); header.BaseOfCode = peFile.Sections.FirstOrDefault(s => s.IsContentCode)?.Rva ?? 0; header.BaseOfData = peFile.Sections.FirstOrDefault(s => s.IsContentInitializedData)?.Rva ?? 0; diff --git a/src/AsmResolver.PE/Code/CodeSegment.cs b/src/AsmResolver.PE/Code/CodeSegment.cs index eac4e00ef..b392e146e 100644 --- a/src/AsmResolver.PE/Code/CodeSegment.cs +++ b/src/AsmResolver.PE/Code/CodeSegment.cs @@ -56,6 +56,13 @@ public IList AddressFixups get; } = new List(); + /// + public override void UpdateOffsets(in RelocationParameters parameters) + { + ImageBase = parameters.ImageBase; + base.UpdateOffsets(in parameters); + } + /// public override uint GetPhysicalSize() => (uint) Code.Length; diff --git a/src/AsmResolver.PE/Debug/Builder/DebugDirectoryBuffer.cs b/src/AsmResolver.PE/Debug/Builder/DebugDirectoryBuffer.cs index bf5ce6ac0..e5afa4e88 100644 --- a/src/AsmResolver.PE/Debug/Builder/DebugDirectoryBuffer.cs +++ b/src/AsmResolver.PE/Debug/Builder/DebugDirectoryBuffer.cs @@ -41,7 +41,7 @@ public void AddEntry(DebugDataEntry entry) public bool CanUpdateOffsets => true; /// - public void UpdateOffsets(ulong newOffset, uint newRva) => _headers.UpdateOffsets(newOffset, newRva); + public void UpdateOffsets(in RelocationParameters parameters) => _headers.UpdateOffsets(parameters); /// public uint GetPhysicalSize() => _headers.GetPhysicalSize(); diff --git a/src/AsmResolver.PE/Debug/CustomDebugDataSegment.cs b/src/AsmResolver.PE/Debug/CustomDebugDataSegment.cs index ec05a4438..98af4faf0 100644 --- a/src/AsmResolver.PE/Debug/CustomDebugDataSegment.cs +++ b/src/AsmResolver.PE/Debug/CustomDebugDataSegment.cs @@ -45,10 +45,10 @@ public DebugDataType Type public bool CanUpdateOffsets => Contents?.CanUpdateOffsets ?? false; /// - public void UpdateOffsets(ulong newOffset, uint newRva) + public void UpdateOffsets(in RelocationParameters parameters) { if (Contents != null) - Contents.UpdateOffsets(newOffset, newRva); + Contents.UpdateOffsets(parameters); else throw new ArgumentNullException(nameof(Contents)); } diff --git a/src/AsmResolver.PE/DotNet/Builder/DotNetSegmentBuffer.cs b/src/AsmResolver.PE/DotNet/Builder/DotNetSegmentBuffer.cs index 44cfd21ef..fe345de97 100644 --- a/src/AsmResolver.PE/DotNet/Builder/DotNetSegmentBuffer.cs +++ b/src/AsmResolver.PE/DotNet/Builder/DotNetSegmentBuffer.cs @@ -73,7 +73,7 @@ private void AddIfPresent(ISegment? segment) } /// - public void UpdateOffsets(ulong newOffset, uint newRva) => _segments.UpdateOffsets(newOffset, newRva); + public void UpdateOffsets(in RelocationParameters parameters) => _segments.UpdateOffsets(parameters); /// public uint GetPhysicalSize() => _segments.GetPhysicalSize(); diff --git a/src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs b/src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs index 9c19fbc71..458daad75 100644 --- a/src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs +++ b/src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs @@ -66,7 +66,7 @@ public ManagedPEBuilderContext(IPEImage image) if (image.DotNetDirectory is null) throw new ArgumentException("Image does not contain a .NET directory."); - ImportDirectory = new ImportDirectoryBuffer(image.PEKind == OptionalHeaderMagic.Pe32); + ImportDirectory = new ImportDirectoryBuffer(image.PEKind == OptionalHeaderMagic.PE32); ExportDirectory = new ExportDirectoryBuffer(); DotNetSegment = new DotNetSegmentBuffer(image.DotNetDirectory); ResourceDirectory = new ResourceDirectoryBuffer(); @@ -134,7 +134,7 @@ public Platform Platform } /// - /// Gets the code segment used as a native entrypoint of the resulting PE file. + /// Gets the code segment used as a native entry point of the resulting PE file. /// /// /// This property might be null if no bootstrapper is to be emitted. For example, since the @@ -246,23 +246,23 @@ protected virtual PESection CreateTextSection(IPEImage image, ManagedPEBuilderCo private static void CreateImportDirectory(IPEImage image, ManagedPEBuilderContext context) { - bool importEntrypointRequired = context.Platform.IsClrBootstrapperRequired + bool importEntryPointRequired = context.Platform.IsClrBootstrapperRequired || (image.DotNetDirectory!.Flags & DotNetDirectoryFlags.ILOnly) == 0; - string entrypointName = (image.Characteristics & Characteristics.Dll) != 0 + string entryPointName = (image.Characteristics & Characteristics.Dll) != 0 ? "_CorDllMain" : "_CorExeMain"; - var modules = CollectImportedModules(image, importEntrypointRequired, entrypointName, out var entrypointSymbol); + var modules = CollectImportedModules(image, importEntryPointRequired, entryPointName, out var entryPointSymbol); foreach (var module in modules) context.ImportDirectory.AddModule(module); - if (importEntrypointRequired) + if (importEntryPointRequired) { - if (entrypointSymbol is null) - throw new InvalidOperationException("Entrypoint symbol was required but not imported."); + if (entryPointSymbol is null) + throw new InvalidOperationException("Entry point symbol was required but not imported."); - context.Bootstrapper = context.Platform.CreateThunkStub(image.ImageBase, entrypointSymbol); + context.Bootstrapper = context.Platform.CreateThunkStub(entryPointSymbol); } } @@ -270,23 +270,23 @@ private static void CreateImportDirectory(IPEImage image, ManagedPEBuilderContex IPEImage image, bool entryRequired, string mscoreeEntryName, - out ImportedSymbol? entrypointSymbol) + out ImportedSymbol? entryPointSymbol) { var modules = new List(); IImportedModule? mscoreeModule = null; - entrypointSymbol = null; + entryPointSymbol = null; foreach (var module in image.Imports) { - // Check if the CLR entrypoint is already imported. + // Check if the CLR entry point is already imported. if (module.Name == "mscoree.dll") { mscoreeModule = module; - // Find entrypoint in this imported module. + // Find entry point in this imported module. if (entryRequired) - entrypointSymbol = mscoreeModule.Symbols.FirstOrDefault(s => s.Name == mscoreeEntryName); + entryPointSymbol = mscoreeModule.Symbols.FirstOrDefault(s => s.Name == mscoreeEntryName); // Only include mscoree.dll if necessary. if (entryRequired || module.Symbols.Count > 1) @@ -308,11 +308,11 @@ private static void CreateImportDirectory(IPEImage image, ManagedPEBuilderContex modules.Add(mscoreeModule); } - // Add entrypoint sumbol if it wasn't imported yet. - if (entrypointSymbol is null) + // Add entry point sumbol if it wasn't imported yet. + if (entryPointSymbol is null) { - entrypointSymbol = new ImportedSymbol(0, mscoreeEntryName); - mscoreeModule.Symbols.Add(entrypointSymbol); + entryPointSymbol = new ImportedSymbol(0, mscoreeEntryName); + mscoreeModule.Symbols.Add(entryPointSymbol); } } @@ -423,7 +423,7 @@ protected override IEnumerable CreateDataDirectories(PEFile peFil } /// - protected override uint GetEntrypointAddress(PEFile peFile, IPEImage image, ManagedPEBuilderContext context) + protected override uint GetEntryPointAddress(PEFile peFile, IPEImage image, ManagedPEBuilderContext context) => context.Bootstrapper?.Segment.Rva ?? 0; /// diff --git a/src/AsmResolver.PE/DotNet/Builder/MethodBodyTableBuffer.cs b/src/AsmResolver.PE/DotNet/Builder/MethodBodyTableBuffer.cs index 71d971514..76b0a93f5 100644 --- a/src/AsmResolver.PE/DotNet/Builder/MethodBodyTableBuffer.cs +++ b/src/AsmResolver.PE/DotNet/Builder/MethodBodyTableBuffer.cs @@ -53,7 +53,7 @@ public void AddCilBody(CilRawMethodBody body) public void AddNativeBody(ISegment body, uint alignment) => _nativeBodies.Add(body, alignment); /// - public void UpdateOffsets(ulong newOffset, uint newRva) => _segments.UpdateOffsets(newOffset, newRva); + public void UpdateOffsets(in RelocationParameters parameters) => _segments.UpdateOffsets(parameters); /// public uint GetPhysicalSize() diff --git a/src/AsmResolver.PE/DotNet/Cil/CilDisassembler.cs b/src/AsmResolver.PE/DotNet/Cil/CilDisassembler.cs index eab86fb08..92aab4cb6 100644 --- a/src/AsmResolver.PE/DotNet/Cil/CilDisassembler.cs +++ b/src/AsmResolver.PE/DotNet/Cil/CilDisassembler.cs @@ -162,7 +162,7 @@ private CilOpCode ReadOpCode() return _reader.ReadInt32(); case CilOperandType.InlineBrTarget: - return new CilOffsetLabel(_reader.ReadInt32() + (int) (_reader.Offset - _reader.StartOffset)); + return new CilOffsetLabel(_reader.ReadInt32() + (int) _reader.RelativeOffset); case CilOperandType.ShortInlineR: return _reader.ReadSingle(); diff --git a/src/AsmResolver.PE/DotNet/Cil/CilRawFatMethodBody.cs b/src/AsmResolver.PE/DotNet/Cil/CilRawFatMethodBody.cs index 69a4a9582..bc70ed255 100644 --- a/src/AsmResolver.PE/DotNet/Cil/CilRawFatMethodBody.cs +++ b/src/AsmResolver.PE/DotNet/Cil/CilRawFatMethodBody.cs @@ -153,7 +153,7 @@ public IList ExtraSections // Create body. var body = new CilRawFatMethodBody(flags, maxStack, localVarSigToken, code); - body.UpdateOffsets(fileOffset, rva); + body.UpdateOffsets(new RelocationParameters(fileOffset, rva)); // Read any extra sections. if (body.HasSections) @@ -172,18 +172,24 @@ public IList ExtraSections } /// - public override void UpdateOffsets(ulong newOffset, uint newRva) + public override void UpdateOffsets(in RelocationParameters parameters) { - base.UpdateOffsets(newOffset, newRva); - Code.UpdateOffsets(newOffset + 12, newRva + 12); + base.UpdateOffsets(parameters); + + var current = parameters.WithAdvance(12); + + Code.UpdateOffsets(current); if (HasSections) { uint codeSize = Code.GetPhysicalSize(); - newOffset = (Code.Offset + codeSize).Align(4); - newRva = (Code.Rva + codeSize).Align(4); + current.Advance(codeSize); + current.Align(4); for (int i = 0; i < ExtraSections.Count; i++) - ExtraSections[i].UpdateOffsets(newOffset, newRva); + { + ExtraSections[i].UpdateOffsets(current); + current.Advance(ExtraSections[i].GetPhysicalSize()); + } } } @@ -197,7 +203,9 @@ public override uint GetPhysicalSize() { ulong sectionsOffset = endOffset.Align(4); length += (uint) (sectionsOffset - endOffset); - length += (uint) ExtraSections.Sum(x => x.GetPhysicalSize()); + + for (int i = 0; i < ExtraSections.Count; i++) + length += ExtraSections[i].GetPhysicalSize(); } return length; diff --git a/src/AsmResolver.PE/DotNet/Cil/CilRawTinyMethodBody.cs b/src/AsmResolver.PE/DotNet/Cil/CilRawTinyMethodBody.cs index 5c35165ad..852af652b 100644 --- a/src/AsmResolver.PE/DotNet/Cil/CilRawTinyMethodBody.cs +++ b/src/AsmResolver.PE/DotNet/Cil/CilRawTinyMethodBody.cs @@ -58,15 +58,15 @@ public CilRawTinyMethodBody(IReadableSegment code) uint codeSize = (uint) flag >> 2; var methodBody = new CilRawTinyMethodBody(reader.ReadSegment(codeSize)); - methodBody.UpdateOffsets(fileOffset, rva); + methodBody.UpdateOffsets(new RelocationParameters(fileOffset, rva)); return methodBody; } /// - public override void UpdateOffsets(ulong newOffset, uint newRva) + public override void UpdateOffsets(in RelocationParameters parameters) { - base.UpdateOffsets(newOffset, newRva); - Code.UpdateOffsets(newOffset + 1, newRva + 1); + base.UpdateOffsets(parameters); + Code.UpdateOffsets(parameters.WithAdvance(1)); } /// diff --git a/src/AsmResolver.PE/DotNet/DotNetDirectory.cs b/src/AsmResolver.PE/DotNet/DotNetDirectory.cs index 30e6b4c58..b6f7e21ca 100644 --- a/src/AsmResolver.PE/DotNet/DotNetDirectory.cs +++ b/src/AsmResolver.PE/DotNet/DotNetDirectory.cs @@ -62,7 +62,7 @@ public DotNetDirectoryFlags Flags } /// - public uint Entrypoint + public uint EntryPoint { get; set; @@ -116,7 +116,7 @@ public uint Entrypoint + 2 * sizeof(ushort) // version + DataDirectory.DataDirectorySize // metadata + sizeof(uint) // flags - + sizeof(uint) // entrypoint + + sizeof(uint) // entry point + 6 * DataDirectory.DataDirectorySize; // data directories. /// @@ -127,7 +127,7 @@ public override void Write(IBinaryStreamWriter writer) writer.WriteUInt16(MinorRuntimeVersion); CreateDataDirectoryHeader(Metadata).Write(writer); writer.WriteUInt32((uint) Flags); - writer.WriteUInt32(Entrypoint); + writer.WriteUInt32(EntryPoint); CreateDataDirectoryHeader(DotNetResources).Write(writer); CreateDataDirectoryHeader(StrongName).Write(writer); CreateDataDirectoryHeader(CodeManagerTable).Write(writer); diff --git a/src/AsmResolver.PE/DotNet/DotNetDirectoryFlags.cs b/src/AsmResolver.PE/DotNet/DotNetDirectoryFlags.cs index 9e5b3074b..9d3bbc045 100644 --- a/src/AsmResolver.PE/DotNet/DotNetDirectoryFlags.cs +++ b/src/AsmResolver.PE/DotNet/DotNetDirectoryFlags.cs @@ -15,36 +15,36 @@ public enum DotNetDirectoryFlags /// Mixed-mode applications should set this flag to zero. /// ILOnly = 0x00000001, - + /// /// Indicates the .NET image requires a 32-bit architecture to run on. /// Bit32Required = 0x00000002, - + /// /// Indicates the .NET image is a .NET library. /// ILLibrary = 0x00000004, - + /// /// Indicates the .NET image is signed with a strong name. /// StrongNameSigned = 0x00000008, - + /// - /// Indicates the entrypoint defined in is a relative virtual address + /// Indicates the entry point defined in is a relative virtual address /// to a native function. /// - NativeEntrypoint = 0x00000010, - + NativeEntryPoint = 0x00000010, + /// /// Indicates the debug data is tracked. /// TrackDebugData = 0x00010000, - + /// /// Indicates the application will run in an 32-bit environment if it is possible. /// Bit32Preferred = 0x00020000 } -} \ No newline at end of file +} diff --git a/src/AsmResolver.PE/DotNet/IDotNetDirectory.cs b/src/AsmResolver.PE/DotNet/IDotNetDirectory.cs index b832628d1..0e808191e 100644 --- a/src/AsmResolver.PE/DotNet/IDotNetDirectory.cs +++ b/src/AsmResolver.PE/DotNet/IDotNetDirectory.cs @@ -52,10 +52,10 @@ DotNetDirectoryFlags Flags } /// - /// Gets or sets the metadata token or entrypoint virtual address, depending on whether - /// is set in . + /// Gets or sets the metadata token or entry point virtual address, depending on whether + /// is set in . /// - uint Entrypoint + uint EntryPoint { get; set; diff --git a/src/AsmResolver.PE/DotNet/Metadata/Blob/SerializedBlobStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Blob/SerializedBlobStream.cs index fbb895be8..03a74685d 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Blob/SerializedBlobStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Blob/SerializedBlobStream.cs @@ -15,7 +15,7 @@ public class SerializedBlobStream : BlobStream /// /// The raw contents of the stream. public SerializedBlobStream(byte[] rawData) - : this(DefaultName, ByteArrayDataSource.CreateReader(rawData)) + : this(DefaultName, new BinaryStreamReader(rawData)) { } @@ -25,7 +25,7 @@ public SerializedBlobStream(byte[] rawData) /// The name of the stream. /// The raw contents of the stream. public SerializedBlobStream(string name, byte[] rawData) - : this(name, ByteArrayDataSource.CreateReader(rawData)) + : this(name, new BinaryStreamReader(rawData)) { } diff --git a/src/AsmResolver.PE/DotNet/Metadata/CustomMetadataStream.cs b/src/AsmResolver.PE/DotNet/Metadata/CustomMetadataStream.cs index b39a0ba3f..056f2b274 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/CustomMetadataStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/CustomMetadataStream.cs @@ -68,7 +68,7 @@ public BinaryStreamReader CreateReader() } /// - public void UpdateOffsets(ulong newOffset, uint newRva) => Contents.UpdateOffsets(newOffset, newRva); + public void UpdateOffsets(in RelocationParameters parameters) => Contents.UpdateOffsets(parameters); /// public uint GetPhysicalSize() => Contents.GetPhysicalSize(); diff --git a/src/AsmResolver.PE/DotNet/Metadata/DefaultMetadataStreamReader.cs b/src/AsmResolver.PE/DotNet/Metadata/DefaultMetadataStreamReader.cs index 5927c54b5..2c4d3364c 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/DefaultMetadataStreamReader.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/DefaultMetadataStreamReader.cs @@ -1,7 +1,7 @@ -using System; using AsmResolver.IO; using AsmResolver.PE.DotNet.Metadata.Blob; using AsmResolver.PE.DotNet.Metadata.Guid; +using AsmResolver.PE.DotNet.Metadata.Pdb; using AsmResolver.PE.DotNet.Metadata.Strings; using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.DotNet.Metadata.UserStrings; @@ -15,8 +15,17 @@ namespace AsmResolver.PE.DotNet.Metadata /// public class DefaultMetadataStreamReader : IMetadataStreamReader { + /// + /// Gets a default instance + /// + public static DefaultMetadataStreamReader Instance + { + get; + } = new(); + /// - public IMetadataStream ReadStream(PEReaderContext context, MetadataStreamHeader header, + public IMetadataStream ReadStream(MetadataReaderContext context, + MetadataStreamHeader header, ref BinaryStreamReader reader) { switch (header.Name) @@ -37,6 +46,9 @@ public class DefaultMetadataStreamReader : IMetadataStreamReader case GuidStream.DefaultName: return new SerializedGuidStream(header.Name, reader); + case PdbStream.DefaultName: + return new SerializedPdbStream(header.Name, reader); + default: return new CustomMetadataStream(header.Name, DataSegment.FromReader(ref reader)); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Guid/SerializedGuidStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Guid/SerializedGuidStream.cs index d03887e62..6634ecd83 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Guid/SerializedGuidStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Guid/SerializedGuidStream.cs @@ -18,7 +18,7 @@ public class SerializedGuidStream : GuidStream /// /// The raw contents of the stream. public SerializedGuidStream(byte[] rawData) - : this(DefaultName, ByteArrayDataSource.CreateReader(rawData)) + : this(DefaultName, new BinaryStreamReader(rawData)) { } @@ -28,7 +28,7 @@ public SerializedGuidStream(byte[] rawData) /// The name of the stream. /// The raw contents of the stream. public SerializedGuidStream(string name, byte[] rawData) - : this(name, ByteArrayDataSource.CreateReader(rawData)) + : this(name, new BinaryStreamReader(rawData)) { } diff --git a/src/AsmResolver.PE/DotNet/Metadata/ILazyMetadataStream.cs b/src/AsmResolver.PE/DotNet/Metadata/ILazyMetadataStream.cs new file mode 100644 index 000000000..0877d6e7d --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/ILazyMetadataStream.cs @@ -0,0 +1,14 @@ +namespace AsmResolver.PE.DotNet.Metadata +{ + /// + /// Represents a metadata stream that is initialized lazily. + /// + public interface ILazyMetadataStream : IMetadataStream + { + /// + /// Finalizes the initialization process of the metadata stream. + /// + /// The metadata directory that defines the stream. + void Initialize(IMetadata parentMetadata); + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/IMetadata.cs b/src/AsmResolver.PE/DotNet/Metadata/IMetadata.cs index 5711bf1a3..b49a1927c 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/IMetadata.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/IMetadata.cs @@ -60,6 +60,14 @@ ushort Flags set; } + /// + /// Gets a value indicating whether the metadata directory is loaded as Edit-and-Continue metadata. + /// + public bool IsEncMetadata + { + get; + } + /// /// Gets a collection of metadata streams that are defined in the metadata header. /// diff --git a/src/AsmResolver.PE/DotNet/Metadata/IMetadataStreamReader.cs b/src/AsmResolver.PE/DotNet/Metadata/IMetadataStreamReader.cs index 319459f43..01a4ed0c1 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/IMetadataStreamReader.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/IMetadataStreamReader.cs @@ -14,6 +14,6 @@ public interface IMetadataStreamReader /// The header of the metadata stream. /// The input stream to read from. /// The read metadata stream. - IMetadataStream ReadStream(PEReaderContext context, MetadataStreamHeader header, ref BinaryStreamReader reader); + IMetadataStream ReadStream(MetadataReaderContext context, MetadataStreamHeader header, ref BinaryStreamReader reader); } } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Metadata.cs b/src/AsmResolver.PE/DotNet/Metadata/Metadata.cs index cc1b88b5c..0066a4ce3 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Metadata.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Metadata.cs @@ -1,9 +1,11 @@ +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using System.Threading; using AsmResolver.IO; +using AsmResolver.PE.DotNet.Metadata.Tables; namespace AsmResolver.PE.DotNet.Metadata { @@ -49,6 +51,13 @@ public ushort Flags set; } + /// + public bool IsEncMetadata + { + get; + set; + } + /// public IList Streams { @@ -60,6 +69,48 @@ public IList Streams } } + /// + /// Reads a .NET metadata directory from a file. + /// + /// The path to the file. + /// The read metadata. + public static Metadata FromFile(string path) => FromBytes(System.IO.File.ReadAllBytes(path)); + + /// + /// Interprets the provided binary data as a .NET metadata directory. + /// + /// The raw data. + /// The read metadata. + public static Metadata FromBytes(byte[] data) => FromReader(new BinaryStreamReader(data)); + + /// + /// Reads a .NET metadata directory from a file. + /// + /// The file to read. + /// The read metadata. + public static Metadata FromFile(IInputFile file) => FromReader(file.CreateReader()); + + /// + /// Interprets the provided binary stream as a .NET metadata directory. + /// + /// The input stream. + /// The read metadata. + public static Metadata FromReader(BinaryStreamReader reader) + { + return FromReader(reader, new MetadataReaderContext(VirtualAddressFactory.Instance)); + } + + /// + /// Interprets the provided binary stream as a .NET metadata directory. + /// + /// The input stream. + /// The context in which the reader is situated in. + /// The read metadata. + public static Metadata FromReader(BinaryStreamReader reader, MetadataReaderContext context) + { + return new SerializedMetadata(context, ref reader); + } + /// public override uint GetPhysicalSize() { @@ -174,33 +225,57 @@ public TStream GetStream() /// public bool TryGetStream(string name, [NotNullWhen(true)] out IMetadataStream? stream) { - var streams = Streams; + bool heapRequested = name is not (TablesStream.CompressedStreamName + or TablesStream.EncStreamName + or TablesStream.UncompressedStreamName); + + return TryFindStream((c, s) => c.Name == s as string, name, heapRequested, out stream); + } + + /// + public bool TryGetStream([NotNullWhen(true)] out TStream? stream) + where TStream : class, IMetadataStream + { + bool heapRequested = !typeof(TablesStream).IsAssignableFrom(typeof(TStream)); - for (int i = 0; i < streams.Count; i++) + if (TryFindStream((c, _) => c is TStream, null, heapRequested, out var candidate)) { - if (streams[i].Name == name) - { - stream = streams[i]; - return true; - } + stream = (TStream) candidate; + return true; } stream = null; return false; } - /// - public bool TryGetStream([NotNullWhen(true)] out TStream? stream) - where TStream : class, IMetadataStream + private bool TryFindStream( + Func condition, + object? state, + bool heapRequested, + [NotNullWhen(true)] out IMetadataStream? stream) { var streams = Streams; - - for (int i = 0; i < streams.Count; i++) + bool reverseOrder = heapRequested && !IsEncMetadata; + if (reverseOrder) + { + for (int i = streams.Count - 1; i >= 0; i--) + { + if (condition(streams[i], state)) + { + stream = streams[i]; + return true; + } + } + } + else { - if (streams[i] is TStream s) + for (int i = 0; i < streams.Count; i++) { - stream = s; - return true; + if (condition(streams[i], state)) + { + stream = streams[i]; + return true; + } } } diff --git a/src/AsmResolver.PE/DotNet/Metadata/MetadataReaderContext.cs b/src/AsmResolver.PE/DotNet/Metadata/MetadataReaderContext.cs new file mode 100644 index 000000000..e16c9100b --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/MetadataReaderContext.cs @@ -0,0 +1,75 @@ +using System; + +namespace AsmResolver.PE.DotNet.Metadata +{ + /// + /// Provides a context for a .NET metadata directory reader. + /// + public class MetadataReaderContext : IErrorListener + { + /// + /// Constructs a new metadata reader context. + /// + /// The factory object responsible for translating RVAs to references. + public MetadataReaderContext(ISegmentReferenceFactory factory) + : this(factory, ThrowErrorListener.Instance, DefaultMetadataStreamReader.Instance) + { + } + + /// + /// Constructs a new metadata reader context. + /// + /// The factory object responsible for translating RVAs to references. + /// The object responsible for collecting any errors during the parsing. + /// The object responsible for reading metadata streams in the .NET data directory. + public MetadataReaderContext( + ISegmentReferenceFactory referenceFactory, + IErrorListener errorListener, + IMetadataStreamReader metadataStreamReader) + { + ReferenceFactory = referenceFactory; + ErrorListener = errorListener; + MetadataStreamReader = metadataStreamReader; + } + + /// + /// Gets the factory responsible for translating RVAs to references. + /// + public ISegmentReferenceFactory ReferenceFactory + { + get; + } + + /// + /// Gets the object responsible for collecting any errors during the parsing. + /// + public IErrorListener ErrorListener + { + get; + } + + /// + /// Gets the object responsible for reading metadata streams in the .NET data directory. + /// + public IMetadataStreamReader MetadataStreamReader + { + get; + } + + /// + /// Constructs a metadata reader context from a PE reader context. + /// + /// The context to transform. + /// The constructed context. + public static MetadataReaderContext FromReaderContext(PEReaderContext context) => new( + context.File, + context.Parameters.ErrorListener, + context.Parameters.MetadataStreamReader); + + /// + public void MarkAsFatal() => ErrorListener.MarkAsFatal(); + + /// + public void RegisterException(Exception exception) => ErrorListener.RegisterException(exception); + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/MetadataSignature.cs b/src/AsmResolver.PE/DotNet/Metadata/MetadataSignature.cs index c94db9875..1966758df 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/MetadataSignature.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/MetadataSignature.cs @@ -9,10 +9,10 @@ public enum MetadataSignature /// Indicates the BSJB metadata directory format is used. /// Bsjb = 0x424A5342, - + /// - /// Indicates the legacy +MOC metadata directory format is used. + /// Indicates the legacy COM+ metadata directory format is used. /// - Moc = 0x2B4D4F43 + ComPlus = 0x2B4D4F43 } -} \ No newline at end of file +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/MetadataStreamList.cs b/src/AsmResolver.PE/DotNet/Metadata/MetadataStreamList.cs index aaad91be3..e477f8223 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/MetadataStreamList.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/MetadataStreamList.cs @@ -9,50 +9,52 @@ namespace AsmResolver.PE.DotNet.Metadata /// public class MetadataStreamList : LazyList { - private readonly PEReaderContext _context; - private readonly int _numberOfStreams; - private BinaryStreamReader _directoryReader; - private BinaryStreamReader _entriesReader; + private readonly MetadataReaderContext _context; + private readonly MetadataStreamHeader[] _streamHeaders; + private readonly IMetadata _owner; + private readonly BinaryStreamReader _directoryReader; /// /// Prepares a new lazy-initialized metadata stream list. /// + /// The owner of the metadata stream list. /// The reader context. + /// The stream headers. /// The input stream containing the metadata directory. - /// The input stream containing the metadata stream entries. - /// The number of streams. public MetadataStreamList( - PEReaderContext context, - in BinaryStreamReader directoryReader, - in BinaryStreamReader entriesReader, - int numberOfStreams) + IMetadata owner, + MetadataReaderContext context, + MetadataStreamHeader[] streamHeaders, + in BinaryStreamReader directoryReader) { _context = context ?? throw new ArgumentNullException(nameof(context)); + _streamHeaders = streamHeaders; + _owner = owner; _directoryReader = directoryReader; - _entriesReader = entriesReader; - _numberOfStreams = numberOfStreams; } /// - public override int Count => IsInitialized ? Items.Count : _numberOfStreams; + public override int Count => IsInitialized ? Items.Count : _streamHeaders.Length; /// protected override void Initialize() { - var headers = new MetadataStreamHeader[_numberOfStreams]; - for (int i = 0; i < _numberOfStreams; i++) - headers[i] = MetadataStreamHeader.FromReader(ref _entriesReader); - - for (int i = 0; i < _numberOfStreams; i++) + foreach (var header in _streamHeaders) { - var header = headers[i]; - - var streamReader = _directoryReader.ForkAbsolute(_directoryReader.Offset + header.Offset, headers[i].Size); - var stream = _context.Parameters.MetadataStreamReader.ReadStream(_context, header, ref streamReader); - + var streamReader = _directoryReader.ForkAbsolute(_directoryReader.Offset + header.Offset, header.Size); + var stream = _context.MetadataStreamReader.ReadStream(_context, header, ref streamReader); Items.Add(stream); } } + /// + protected override void PostInitialize() + { + for (int i = 0; i < Items.Count; i++) + { + if (Items[i] is ILazyMetadataStream lazyStream) + lazyStream.Initialize(_owner); + } + } } } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Pdb/PdbStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Pdb/PdbStream.cs new file mode 100644 index 000000000..4c4d7e002 --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Pdb/PdbStream.cs @@ -0,0 +1,128 @@ +using System; +using System.Linq; +using AsmResolver.IO; +using AsmResolver.PE.DotNet.Metadata.Tables; + +namespace AsmResolver.PE.DotNet.Metadata.Pdb +{ + /// + /// Represents the metadata stream containing Portable PDB debug data that is associated to a .NET module. + /// + public class PdbStream : SegmentBase, IMetadataStream + { + /// + /// The default name of a PDB stream, as described in the specification provided by Portable PDB v1.0. + /// + public const string DefaultName = "#Pdb"; + + /// + public string Name + { + get; + set; + } = DefaultName; + + /// + public virtual bool CanRead => false; + + /// + /// Gets the unique identifier representing the debugging metadata blob content. + /// + public byte[] Id + { + get; + } = new byte[20]; + + /// + /// Gets or sets the token of the entry point method, or 9 if not applicable. + /// + /// + /// This should be the same value as stored in the metadata header. + /// + public MetadataToken EntryPoint + { + get; + set; + } + + /// + /// Gets an array of row counts of every portable PDB table in the tables stream. + /// + public uint[] TypeSystemRowCounts + { + get; + } = new uint[(int) TableIndex.Max]; + + /// + /// Synchronizes the row counts stored in with the tables in the provided + /// tables stream. + /// + /// The tables stream to pull the data from. + public void UpdateRowCounts(TablesStream stream) + { + for (TableIndex i = 0; i < TableIndex.MaxTypeSystemTableIndex; i++) + { + if (i.IsValidTableIndex()) + TypeSystemRowCounts[(int) i] = (uint) stream.GetTable(i).Count; + } + } + + /// + /// Synchronizes the row counts stored in with the tables in the provided + /// tables stream row counts. + /// + /// The tables stream row counts to pull in. + public void UpdateRowCounts(uint[] rowCounts) + { + for (TableIndex i = 0; i < TableIndex.MaxTypeSystemTableIndex && (int) i < rowCounts.Length; i++) + { + if (i.IsValidTableIndex()) + TypeSystemRowCounts[(int) i] = rowCounts[(int) i]; + } + } + + /// + /// Computes the valid bitmask for the type system table rows referenced by this pdb stream. + /// + /// The bitmask. + public ulong ComputeReferencedTypeSystemTables() + { + ulong result = 0; + + for (int i = 0; i < TypeSystemRowCounts.Length; i++) + { + if (TypeSystemRowCounts[i] != 0) + result |= 1UL << i; + } + + return result; + } + + /// + public virtual BinaryStreamReader CreateReader() => throw new NotSupportedException(); + + /// + public override uint GetPhysicalSize() + { + return 20 // ID + + sizeof(uint) // EntryPoint + + sizeof(ulong) // ReferencedTypeSystemTables + + 4 * (uint) TypeSystemRowCounts.Count(c => c != 0); // TypeSystemTableRows. + } + + /// + public override void Write(IBinaryStreamWriter writer) + { + writer.WriteBytes(Id); + writer.WriteUInt32(EntryPoint.ToUInt32()); + writer.WriteUInt64(ComputeReferencedTypeSystemTables()); + + foreach (uint count in TypeSystemRowCounts) + { + if (count != 0) + writer.WriteUInt32(count); + } + } + + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Pdb/SerializedPdbStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Pdb/SerializedPdbStream.cs new file mode 100644 index 000000000..9a04fc7ac --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Pdb/SerializedPdbStream.cs @@ -0,0 +1,63 @@ +using AsmResolver.IO; + +namespace AsmResolver.PE.DotNet.Metadata.Pdb +{ + /// + /// Provides an implementation of a PDB stream that obtains GUIDs from a readable segment in a file. + /// + public class SerializedPdbStream : PdbStream + { + private readonly BinaryStreamReader _reader; + + /// + /// Creates a new PDB stream with the provided byte array as the raw contents of the stream. + /// + /// The raw contents of the stream. + public SerializedPdbStream(byte[] rawData) + : this(DefaultName, new BinaryStreamReader(rawData)) + { + } + + /// + /// Creates a new PDB stream with the provided byte array as the raw contents of the stream. + /// + /// The name of the stream. + /// The raw contents of the stream. + public SerializedPdbStream(string name, byte[] rawData) + : this(name, new BinaryStreamReader(rawData)) + { + } + + /// + /// Creates a new PDB stream with the provided file segment reader as the raw contents of the stream. + /// + /// The name of the stream. + /// The raw contents of the stream. + public SerializedPdbStream(string name, in BinaryStreamReader reader) + { + _reader = reader; + + Name = name; + Offset = reader.Offset; + Rva = reader.Rva; + + var headerReader = reader.Fork(); + + headerReader.ReadBytes(Id, 0, Id.Length); + EntryPoint = headerReader.ReadUInt32(); + + ulong mask = headerReader.ReadUInt64(); + for (int i = 0; i < 64; i++) + { + if (((mask >> i) & 1) != 0) + TypeSystemRowCounts[i] = headerReader.ReadUInt32(); + } + } + + /// + public override bool CanRead => true; + + /// + public override BinaryStreamReader CreateReader() => _reader.Fork(); + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/SerializedMetadata.cs b/src/AsmResolver.PE/DotNet/Metadata/SerializedMetadata.cs index 1c3ab7bb2..16c3080bc 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/SerializedMetadata.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/SerializedMetadata.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Text; using AsmResolver.IO; +using AsmResolver.PE.DotNet.Metadata.Tables; namespace AsmResolver.PE.DotNet.Metadata { @@ -10,10 +11,9 @@ namespace AsmResolver.PE.DotNet.Metadata /// public class SerializedMetadata : Metadata { - private readonly PEReaderContext _context; - private readonly BinaryStreamReader _streamEntriesReader; + private readonly MetadataReaderContext _context; private readonly BinaryStreamReader _streamContentsReader; - private readonly int _numberOfStreams; + private readonly MetadataStreamHeader[] _streamHeaders; /// /// Reads a metadata directory from an input stream. @@ -23,7 +23,7 @@ public class SerializedMetadata : Metadata /// Occurs when any of the arguments are null. /// Occurs when an unsupported metadata directory format was encountered. /// Occurs when the metadata directory header is invalid. - public SerializedMetadata(PEReaderContext context, ref BinaryStreamReader directoryReader) + public SerializedMetadata(MetadataReaderContext context, ref BinaryStreamReader directoryReader) { if (!directoryReader.IsValid) throw new ArgumentNullException(nameof(directoryReader)); @@ -33,15 +33,17 @@ public SerializedMetadata(PEReaderContext context, ref BinaryStreamReader direct Rva = directoryReader.Rva; _streamContentsReader = directoryReader.Fork(); + _streamHeaders = Array.Empty(); + // Verify signature. var signature = (MetadataSignature) directoryReader.ReadUInt32(); switch (signature) { case MetadataSignature.Bsjb: // BSJB header is the default header. break; - case MetadataSignature.Moc: - _context.NotSupported("Old +MOC metadata header format is not supported."); + case MetadataSignature.ComPlus: + _context.NotSupported("Old COM+ metadata header format is not supported."); return; default: @@ -49,10 +51,12 @@ public SerializedMetadata(PEReaderContext context, ref BinaryStreamReader direct return; } + // Header fields. MajorVersion = directoryReader.ReadUInt16(); MinorVersion = directoryReader.ReadUInt16(); Reserved = directoryReader.ReadUInt32(); + // Version string. uint versionLength = directoryReader.ReadUInt32(); if (!directoryReader.CanRead(versionLength)) { @@ -60,26 +64,34 @@ public SerializedMetadata(PEReaderContext context, ref BinaryStreamReader direct return; } - var versionBytes = new byte[versionLength]; + byte[] versionBytes = new byte[versionLength]; directoryReader.ReadBytes(versionBytes, 0, versionBytes.Length); VersionString = Encoding.ASCII.GetString(versionBytes); + // Remainder of all header fields. Flags = directoryReader.ReadUInt16(); - _numberOfStreams = directoryReader.ReadInt16(); - _streamEntriesReader = directoryReader.Fork(); + int numberOfStreams = directoryReader.ReadInt16(); + + // Eagerly read stream headers to determine if we are EnC metadata. + _streamHeaders = new MetadataStreamHeader[numberOfStreams]; + for (int i = 0; i < numberOfStreams; i++) + { + _streamHeaders[i] = MetadataStreamHeader.FromReader(ref directoryReader); + if (_streamHeaders[i].Name == TablesStream.EncStreamName) + IsEncMetadata = true; + } } /// protected override IList GetStreams() { - if (_numberOfStreams == 0) + if (_streamHeaders.Length == 0) return base.GetStreams(); - return new MetadataStreamList( + return new MetadataStreamList(this, _context, - _streamContentsReader, - _streamEntriesReader, - _numberOfStreams); + _streamHeaders, + _streamContentsReader); } } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Strings/SerializedStringsStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Strings/SerializedStringsStream.cs index 55d2f9299..ea17a11a1 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Strings/SerializedStringsStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Strings/SerializedStringsStream.cs @@ -17,7 +17,7 @@ public class SerializedStringsStream : StringsStream /// /// The raw contents of the stream. public SerializedStringsStream(byte[] rawData) - : this(DefaultName, ByteArrayDataSource.CreateReader(rawData)) + : this(DefaultName, new BinaryStreamReader(rawData)) { } @@ -27,7 +27,7 @@ public SerializedStringsStream(byte[] rawData) /// The name of the stream. /// The raw contents of the stream. public SerializedStringsStream(string name, byte[] rawData) - : this(name, ByteArrayDataSource.CreateReader(rawData)) + : this(name, new BinaryStreamReader(rawData)) { } @@ -65,22 +65,7 @@ public SerializedStringsStream(string name, in BinaryStreamReader reader) if (!_cachedStrings.TryGetValue(index, out var value) && index < _reader.Length) { var stringsReader = _reader.ForkRelative(index); - byte[] rawData = stringsReader.ReadBytesUntil(0); - - if (rawData.Length == 0) - { - value = Utf8String.Empty; - } - else - { - // Trim off null terminator byte if its present. - int actualLength = rawData.Length; - if (rawData[actualLength - 1] == 0) - actualLength--; - - value = new Utf8String(rawData, 0, actualLength); - } - + value = stringsReader.ReadUtf8String(); _cachedStrings[index] = value; } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/CodedIndex.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/CodedIndex.cs index 27f1fbc6a..8817f38dc 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/CodedIndex.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/CodedIndex.cs @@ -8,13 +8,13 @@ public enum CodedIndex /// /// Indicates the index is an index to a member in either the TypeRef, TypeDef or TypeSpec table. /// - TypeDefOrRef = 45, - + TypeDefOrRef = ColumnType.TypeDefOrRef, + /// /// Indicates the index is an index to a member in either the Field, Parameter or Property table. /// HasConstant, - + /// /// Indicates the index is an index to a member in one of the following tables: /// MethodDef, Field, TypeRef, TypeDef, Parameter, InterfaceImpl, MemberRef, Module, DeclSecurity, Property, Event, @@ -22,57 +22,66 @@ public enum CodedIndex /// GenericParamConstraint or MethodSpec. /// HasCustomAttribute, - + /// /// Indicates the index is an index to a member in either the Field or Parameter table. /// HasFieldMarshal, - + /// /// Indicates the index is an index to a member in either the TypeDef, MethodDef or Assembly table. /// HasDeclSecurity, - + /// /// Indicates the index is an index to a member in either the TypeDef, TypeRef, ModuleRef, MethodDef or TypeSpec /// table. /// MemberRefParent, - + /// /// Indicates the index is an index to a member in either the Event or Property table. /// HasSemantics, - + /// /// Indicates the index is an index to a member in either the MethodDef or MemberRef table. /// MethodDefOrRef, - + /// /// Indicates the index is an index to a member in either the Field or MethodDef table. /// MemberForwarded, - + /// /// Indicates the index is an index to a member in either the File, AssemblyRef or ExportedType table. /// Implementation, - + /// - /// Indicates the index is an index to a member in either the MethodDef or MemberRef table. + /// Indicates the index is an index to a member in either the MethodDef or MemberRef table. /// CustomAttributeType, - + /// /// Indicates the index is an index to a member in either the Module, ModuleRef, AssemblyRef or TypeRef table. /// ResolutionScope, - + /// /// Indicates the index is an index to a member in either the TypeDef or MethodDef table. /// TypeOrMethodDef, + + /// + /// Indicates the index is an index to a member in one of the following tables: + /// MethodDef, Field, TypeRef, TypeDef, Parameter, InterfaceImpl, MemberRef, Module, DeclSecurity, Property, + /// Event, StandAloneSig, ModuleRef, TypeSpec, Assembly, AssemblyRef, File, ExportedType, ManifestResource, + /// GenericParam, GenericParamConstraint, MethodSpec, Document, LocalScope, LocalVariable, LocalConstant, + /// or ImportScope + /// + HasCustomDebugInformation } - -} \ No newline at end of file + +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/ColumnType.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/ColumnType.cs index c24876da4..5e794615d 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/ColumnType.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/ColumnType.cs @@ -8,6 +8,7 @@ namespace AsmResolver.PE.DotNet.Metadata.Tables /// public enum ColumnType { + // Normal type system indices (in sync with TableIndex). Module = 0, TypeRef = 1, TypeDef = 2, @@ -53,7 +54,18 @@ public enum ColumnType GenericParam = 42, MethodSpec = 43, GenericParamConstraint = 44, - + + // PortablePDB indices (in sync with TableIndex). + Document = 0x30, + MethodDebugInformation = 0x31, + LocalScope = 0x32, + LocalVariable = 0x33, + LocalConstant = 0x34, + ImportScope = 0x35, + StateMachineMethod = 0x36, + CustomDebugInformation = 0x37, + + // Coded indices (in sync with CodedIndex). TypeDefOrRef, HasConstant, HasCustomAttribute, @@ -67,13 +79,16 @@ public enum ColumnType CustomAttributeType, ResolutionScope, TypeOrMethodDef, - - String, + HasCustomDebugInformation, + + // Heap indices. + String, Blob, Guid, - + + // Primitives. Byte = 0x8000001, UInt16 = 0x8000002, UInt32 = 0x8000004, } -} \ No newline at end of file +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/ContinuousMetadataRange.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/ContinuousMetadataRange.cs deleted file mode 100644 index 84128c52c..000000000 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/ContinuousMetadataRange.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; - -namespace AsmResolver.PE.DotNet.Metadata.Tables -{ - /// - /// Represents a simple range of metadata tokens that is continuous from the start to the end of the range. - /// - public class ContinuousMetadataRange : MetadataRange - { - /// - /// Creates a new continuous metadata range, indicating the table, the start- and end row within the table. - /// - /// The table. - /// The starting row identifier. - /// The ending row identifier. This identifier is exclusive. - public ContinuousMetadataRange(TableIndex table, uint startRid, uint endRid) - : base(table, startRid, endRid) - { - } - - /// - public override IEnumerator GetEnumerator() - { - return new Enumerator(this); - } - - /// - /// Provides an implementation of an enumerator for a continuous metadata range. - /// - public struct Enumerator : IEnumerator - { - private readonly ContinuousMetadataRange _range; - private uint _currentRid; - - /// - /// Creates a new enumerator for the provided continuous range. - /// - /// The range. - public Enumerator(ContinuousMetadataRange range) - { - _range = range; - _currentRid = _range.StartRid - 1; - } - - /// - public MetadataToken Current => new(_range.Table, _currentRid); - - object IEnumerator.Current => Current; - - /// - public bool MoveNext() - { - if (_currentRid < _range.EndRid - 1) - { - _currentRid++; - return true; - } - - return false; - } - - /// - public void Reset() - { - _currentRid = _range.StartRid - 1; - } - - void IDisposable.Dispose() - { - } - } - } -} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/IMetadataTable.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/IMetadataTable.cs index 353a82bc3..0b2352cb4 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/IMetadataTable.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/IMetadataTable.cs @@ -37,6 +37,15 @@ IndexSize IndexSize set; } + /// + /// Gets or sets a value indicating whether the table is considered sorted. + /// + bool IsSorted + { + get; + set; + } + /// /// Gets the contents of a row by its row identifier. /// @@ -44,12 +53,21 @@ IndexSize IndexSize /// The row. IMetadataRow GetByRid(uint rid); + /// + /// Attempts to get the contents of a cell in the table by its row identifier and column index. + /// + /// The row identifier. + /// The column index. + /// When successful, the contents of the cell, converted to an unsigned integer. + /// true if the cell existed and was obtained successfully, false otherwise. + bool TryGetCell(uint rid, int column, out uint value); + /// /// Attempts to get the contents of a row by its row identifier. /// /// The row identifier. /// When successful, the read row. - /// true if the RID existed an the row was obtained successfully, false otherwise. + /// true if the RID existed and the row was obtained successfully, false otherwise. bool TryGetByRid(uint rid, out IMetadataRow row); /// diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/IndexEncoder.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/IndexEncoder.cs index 690ba9dff..de4f81473 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/IndexEncoder.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/IndexEncoder.cs @@ -27,7 +27,7 @@ public IndexEncoder(TablesStream tableStream, params TableIndex[] tables) _tableIndexBitCount = (int)Math.Ceiling(Math.Log(tables.Length, 2)); _tableIndexBitMask = (int)(Math.Pow(2, _tableIndexBitCount) - 1); _maxSmallTableMemberCount = ushort.MaxValue >> _tableIndexBitCount; - + } /// @@ -37,11 +37,13 @@ public IndexSize IndexSize { get { - int maxCount = _tables - .Select(table => _tableStream.GetTable(table).Count) - .Max(); + uint maxCount = 0; + foreach (var table in _tables) + maxCount = Math.Max(maxCount, _tableStream.GetTableRowCount(table)); - return maxCount > _maxSmallTableMemberCount ? IndexSize.Long : IndexSize.Short; + return maxCount > _maxSmallTableMemberCount + ? IndexSize.Long + : IndexSize.Short; } } @@ -71,11 +73,11 @@ public MetadataToken DecodeIndex(uint codedIndex) { long tableIndex = codedIndex & _tableIndexBitMask; uint rowIndex = codedIndex >> _tableIndexBitCount; - - return new MetadataToken(tableIndex >= _tables.Length - ? TableIndex.Module + + return new MetadataToken(tableIndex >= _tables.Length + ? TableIndex.Module : _tables[tableIndex], rowIndex); } } -} \ No newline at end of file +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataRange.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataRange.cs index 16116f60f..afd594473 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataRange.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataRange.cs @@ -1,39 +1,57 @@ +using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace AsmResolver.PE.DotNet.Metadata.Tables { /// /// Represents a range of metadata tokens, indicated by a starting and ending row identifier within a metadata table. /// - public abstract class MetadataRange : IEnumerable + public readonly struct MetadataRange : IEnumerable, IEquatable { /// /// Represents the empty metadata range. /// - public static readonly MetadataRange Empty = new ContinuousMetadataRange(TableIndex.Module, 1, 1); - + public static readonly MetadataRange Empty = new(TableIndex.Module, 1, 1); + /// /// Initializes the range. /// /// The table. /// The starting row identifier. /// The ending row identifier. This identifier is exclusive. - protected MetadataRange(TableIndex table, uint startRid, uint endRid) + public MetadataRange(TableIndex table, uint startRid, uint endRid) { Table = table; StartRid = startRid; EndRid = endRid; + RedirectionTable = null; } - + /// - /// Gets the index of the metadata table this range is targeting. + /// Initializes the range. + /// + /// The table that is used for translating raw indices. + /// The table. + /// The starting row identifier. + /// The ending row identifier. This identifier is exclusive. + public MetadataRange(IMetadataTable redirectionTable, TableIndex table, uint startRid, uint endRid) + { + Table = table; + StartRid = startRid; + EndRid = endRid; + RedirectionTable = redirectionTable; + } + + /// + /// Gets the index of the metadata table this range is targeting. /// public TableIndex Table { get; } - + /// /// Gets the first row identifier that this range includes. /// @@ -43,7 +61,7 @@ public uint StartRid } /// - /// Gets the row identifier indicating the end of the range. The range excludes this row identifier. + /// Gets the row identifier indicating the end of the range. The range excludes this row identifier. /// public uint EndRid { @@ -55,12 +73,134 @@ public uint EndRid /// public int Count => (int) (EndRid - StartRid); + /// + /// Gets a value indicating whether the range is empty or not. + /// + public bool IsEmpty => EndRid == StartRid; + + /// + /// Gets the table that is used for translating raw indices. + /// + public IMetadataTable? RedirectionTable + { + get; + } + + /// + /// Gets a value indicating whether the range is associated to a redirection table. + /// + [MemberNotNullWhen(true, nameof(RedirectionTable))] + public bool IsRedirected => RedirectionTable is not null; + + /// + /// Obtains an enumerator that enumerates all metadata tokens within the range. + /// + /// + public Enumerator GetEnumerator() => new(this); + /// - public abstract IEnumerator GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + public override string ToString() + { + var start = new MetadataToken(Table, StartRid); + var end = new MetadataToken(Table, EndRid); + return $"[0x{start.ToString()}..0x{end.ToString()})"; + } + + /// + public bool Equals(MetadataRange other) + { + if (IsEmpty && other.IsEmpty) + return true; + + return Table == other.Table + && StartRid == other.StartRid + && EndRid == other.EndRid + && Equals(RedirectionTable, other.RedirectionTable); + } + + /// + public override bool Equals(object? obj) + { + return obj is MetadataRange other && Equals(other); + } + + /// + public override int GetHashCode() { - return GetEnumerator(); + if (IsEmpty) + return 0; + + unchecked + { + int hashCode = (int) Table; + hashCode = (hashCode * 397) ^ (int) StartRid; + hashCode = (hashCode * 397) ^ (int) EndRid; + hashCode = (hashCode * 397) ^ (RedirectionTable is not null ? RedirectionTable.GetHashCode() : 0); + return hashCode; + } + } + + /// + /// Represents an enumerator that enumerates all metadata tokens within a token range. + /// + public struct Enumerator : IEnumerator + { + private readonly MetadataRange _range; + private uint _currentRid; + + /// + /// Initializes a new token enumerator. + /// + /// The range to enumerate from. + public Enumerator(MetadataRange range) + { + _range = range; + _currentRid = range.StartRid - 1; + } + + /// + public MetadataToken Current + { + get + { + uint actualRid; + + if (!_range.IsRedirected) + actualRid = _currentRid; + else + _range.RedirectionTable.TryGetCell(_currentRid, 0, out actualRid); + + return new MetadataToken(_range.Table, actualRid); + } + } + + /// + object IEnumerator.Current => Current; + + /// + public bool MoveNext() + { + if (_currentRid < _range.EndRid - 1) + { + _currentRid++; + return true; + } + + return false; + } + + /// + public void Reset() => _currentRid = 0; + + /// + public void Dispose() + { + } } } -} \ No newline at end of file +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataTable.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataTable.cs index 2b8f01739..724d69d37 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataTable.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataTable.cs @@ -25,9 +25,21 @@ public class MetadataTable : IMetadataTable, ICollection /// The index of the table. /// The layout of the table. public MetadataTable(TableIndex tableIndex, TableLayout layout) + : this(tableIndex, layout, false) + { + } + + /// + /// Creates a new metadata table using the provided layout. + /// + /// The index of the table. + /// The layout of the table. + /// Indicates the table is sorted or not. + public MetadataTable(TableIndex tableIndex, TableLayout layout, bool isSorted) { TableIndex = tableIndex; Layout = layout; + IsSorted = isSorted; } /// @@ -55,6 +67,13 @@ public TableLayout Layout set => Rows[index] = value; } + /// + public bool IsSorted + { + get; + set; + } + /// IMetadataRow IMetadataTable.this[int index] { @@ -88,6 +107,15 @@ protected RefList Rows /// public virtual int Count => Rows.Count; + /// + /// Gets or sets the total number of rows that the underlying array can store. + /// + public int Capacity + { + get => Rows.Capacity; + set => Rows.Capacity = value; + } + /// public bool IsReadOnly => false; // TODO: it might be necessary later to make this configurable. @@ -152,6 +180,19 @@ protected RefList Rows /// The row. public TRow GetByRid(uint rid) => this[(int) (rid - 1)]; + /// + public bool TryGetCell(uint rid, int column, out uint value) + { + if (column >= 0 && column < Layout.Columns.Count && TryGetByRid(rid, out var row)) + { + value = row[column]; + return true; + } + + value = 0; + return false; + } + IMetadataRow IMetadataTable.GetByRid(uint rid) => GetByRid(rid); /// diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataToken.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataToken.cs index 56d79aa17..a28bd44fb 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataToken.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataToken.cs @@ -10,7 +10,7 @@ namespace AsmResolver.PE.DotNet.Metadata.Tables /// /// Represents the zero metadata token, or the absence of a metadata token. /// - public static readonly MetadataToken Zero = new MetadataToken(0); + public static readonly MetadataToken Zero = new(0); /// /// Converts a 32-bit integer to a metadata token. diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/RedirectedMetadataRange.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/RedirectedMetadataRange.cs deleted file mode 100644 index a8c2f4553..000000000 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/RedirectedMetadataRange.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; - -namespace AsmResolver.PE.DotNet.Metadata.Tables -{ - /// - /// Provides an implementation of a metadata range that is adjusted by an redirect metadata table (such as the field, - /// method, event or property pointer table). - /// - public class RedirectedMetadataRange : MetadataRange - { - /// - /// Creates a new range of metadata tokens that is adjusted by a redirection table. - /// - /// The table providing the redirections. - /// The table. - /// The starting row identifier. - /// The ending row identifier. This identifier is exclusive. - public RedirectedMetadataRange(IMetadataTable indirectTable, TableIndex table, uint startRid, uint endRid) - : base(table, startRid, endRid) - { - IndirectTable = indirectTable; - } - - /// - /// Gets the table responsible for redirecting metadata tokens. - /// - public IMetadataTable IndirectTable - { - get; - } - - /// - public override IEnumerator GetEnumerator() - { - return new Enumerator(this); - } - - /// - /// Provides an implementation of an enumerator for a redirected metadata range. - /// - public struct Enumerator : IEnumerator - { - private readonly RedirectedMetadataRange _range; - private uint _currentRid; - - /// - /// Creates a new enumerator for the provided continuous range. - /// - /// The range. - public Enumerator(RedirectedMetadataRange range) - { - _range = range; - _currentRid = _range.StartRid - 1; - } - - /// - public MetadataToken Current - { - get - { - uint actualRid = _currentRid - 1 < _range.IndirectTable.Count - ? _range.IndirectTable[(int) (_currentRid - 1)][0] - : _currentRid - 1; - return new MetadataToken(_range.Table, actualRid); - } - } - - object IEnumerator.Current => Current; - - /// - public bool MoveNext() - { - if (_currentRid < _range.EndRid - 1) - { - _currentRid++; - return true; - } - - return false; - } - - /// - public void Reset() - { - _currentRid = _range.StartRid - 1; - } - - void IDisposable.Dispose() - { - } - } - } -} \ No newline at end of file diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/CustomDebugInformationRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/CustomDebugInformationRow.cs new file mode 100644 index 000000000..1959a9233 --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/CustomDebugInformationRow.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows +{ + /// + /// Represents a single row in the Portable PDB custom debug information metadata table. + /// + public struct CustomDebugInformationRow : IMetadataRow + { + /// + /// Creates a new row for the Portable PDB custom debug information metadata table. + /// + /// + /// A coded index defining the member that this debug information is associated to. + /// + /// + /// An index into the GUID stream referencing the type of debug data that is stored in this record. + /// + /// + /// An index into the blob stream referencing the data of the record. + /// + public CustomDebugInformationRow(uint parent, uint kind, uint value) + { + Parent = parent; + Kind = kind; + Value = value; + } + + /// + public TableIndex TableIndex => TableIndex.CustomDebugInformation; + + /// + public int Count => 3; + + /// + public uint this[int index] => index switch + { + 0 => Parent, + 1 => Kind, + 2 => Value, + _ => throw new IndexOutOfRangeException() + }; + + /// + /// Gets or sets a coded index defining the member that this debug information is associated to. + /// + public uint Parent + { + get; + set; + } + + /// + /// Gets or sets an index into the GUID stream referencing the type of debug data that is stored in this record. + /// + public uint Kind + { + get; + set; + } + + /// + /// Gets or sets an index into the blob stream referencing the data of the record. + /// + public uint Value + { + get; + set; + } + + /// + /// Reads a single Portable PDB custom debug information row from an input stream. + /// + /// The input stream. + /// The layout of the custom debug information table. + /// The row. + public static CustomDebugInformationRow FromReader(ref BinaryStreamReader reader, TableLayout layout) + { + return new CustomDebugInformationRow( + reader.ReadIndex((IndexSize) layout.Columns[0].Size), + reader.ReadIndex((IndexSize) layout.Columns[1].Size), + reader.ReadIndex((IndexSize) layout.Columns[2].Size)); + } + + /// + public void Write(IBinaryStreamWriter writer, TableLayout layout) + { + writer.WriteIndex(Parent, (IndexSize) layout.Columns[0].Size); + writer.WriteIndex(Kind, (IndexSize) layout.Columns[1].Size); + writer.WriteIndex(Value, (IndexSize) layout.Columns[2].Size); + } + + /// + /// Determines whether this row is considered equal to the provided custom debug information row. + /// + /// The other row. + /// true if the rows are equal, false otherwise. + public bool Equals(CustomDebugInformationRow other) + { + return Parent == other.Parent && Kind == other.Kind && Value == other.Value; + } + + /// + public override bool Equals(object? obj) + { + return obj is CustomDebugInformationRow other && Equals(other); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = (int) Parent; + hashCode = (hashCode * 397) ^ (int) Kind; + hashCode = (hashCode * 397) ^ (int) Value; + return hashCode; + } + } + + /// + public override string ToString() => $"({Parent:X8}, {Kind:X8}, {Value:X8})"; + + /// + public IEnumerator GetEnumerator() => new MetadataRowColumnEnumerator(this); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/DocumentRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/DocumentRow.cs new file mode 100644 index 000000000..0d8d8378b --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/DocumentRow.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows +{ + /// + /// Represents a single row in the Portable PDB Document metadata table. + /// + public struct DocumentRow : IMetadataRow + { + /// + /// Creates a new row for the Portable PDB Document metadata table. + /// + /// The index into the blob stream referencing the name of the document. + /// The index into the GUID stream referencing the hash algorithm identifier. + /// The index into the blob stream referencing the hash of the document. + /// The index into the GUID stream referencing the language identifier. + public DocumentRow(uint name, uint hashAlgorithm, uint hash, uint language) + { + Name = name; + HashAlgorithm = hashAlgorithm; + Hash = hash; + Language = language; + } + + /// + public TableIndex TableIndex => TableIndex.Document; + + /// + public int Count => 4; + + /// + public uint this[int index] => index switch + { + 0 => Name, + 1 => HashAlgorithm, + 2 => Hash, + 3 => Language, + _ => throw new IndexOutOfRangeException() + }; + + /// + /// Gets or sets an index into the blob stream referencing the name of the document. + /// + public uint Name + { + get; + set; + } + + /// + /// Gets or sets an index into the GUID stream referencing the hash algorithm identifier. + /// + public uint HashAlgorithm + { + get; + set; + } + + /// + /// Gets or sets an index into the blob stream referencing the hash of the document. + /// + public uint Hash + { + get; + set; + } + + /// + /// Gets or sets an index into the GUID stream referencing the language identifier. + /// + public uint Language + { + get; + set; + } + + /// + /// Reads a single Portable PDB Document row from an input stream. + /// + /// The input stream. + /// The layout of the document table. + /// The row. + public static DocumentRow FromReader(ref BinaryStreamReader reader, TableLayout layout) + { + return new DocumentRow( + reader.ReadIndex((IndexSize) layout.Columns[0].Size), + reader.ReadIndex((IndexSize) layout.Columns[1].Size), + reader.ReadIndex((IndexSize) layout.Columns[2].Size), + reader.ReadIndex((IndexSize) layout.Columns[3].Size)); + } + + /// + public void Write(IBinaryStreamWriter writer, TableLayout layout) + { + writer.WriteIndex(Name, (IndexSize) layout.Columns[0].Size); + writer.WriteIndex(HashAlgorithm, (IndexSize) layout.Columns[1].Size); + writer.WriteIndex(Hash, (IndexSize) layout.Columns[2].Size); + writer.WriteIndex(Language, (IndexSize) layout.Columns[3].Size); + } + + /// + /// Determines whether this row is considered equal to the provided document row. + /// + /// The other row. + /// true if the rows are equal, false otherwise. + public bool Equals(DocumentRow other) + { + return Name == other.Name + && HashAlgorithm == other.HashAlgorithm + && Hash == other.Hash + && Language == other.Language; + } + + /// + public override bool Equals(object? obj) => obj is DocumentRow other && Equals(other); + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = (int) Name; + hashCode = (hashCode * 397) ^ (int) HashAlgorithm; + hashCode = (hashCode * 397) ^ (int) Hash; + hashCode = (hashCode * 397) ^ (int) Language; + return hashCode; + } + } + + /// + public override string ToString() => $"({Name:X8}, {HashAlgorithm:X8}, {Hash:X8}, {Language:X8})"; + + /// + public IEnumerator GetEnumerator() => new MetadataRowColumnEnumerator(this); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldRvaRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldRvaRow.cs index 97d7b6b92..d99f13746 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldRvaRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldRvaRow.cs @@ -17,10 +17,10 @@ public struct FieldRvaRow : IMetadataRow /// The input stream. /// The layout of the field RVA table. /// The row. - public static FieldRvaRow FromReader(PEReaderContext context, ref BinaryStreamReader reader, TableLayout layout) + public static FieldRvaRow FromReader(MetadataReaderContext context, ref BinaryStreamReader reader, TableLayout layout) { return new FieldRvaRow( - context.File.GetReferenceToRva(reader.ReadUInt32()), + context.ReferenceFactory.GetReferenceToRva(reader.ReadUInt32()), reader.ReadIndex((IndexSize) layout.Columns[1].Size)); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ImportScopeRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ImportScopeRow.cs new file mode 100644 index 000000000..6badacd1c --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ImportScopeRow.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows +{ + /// + /// Represents a single row in the Portable PDB import scope metadata table. + /// + public struct ImportScopeRow : IMetadataRow + { + /// + /// Creates a new row for the Portable PDB import scope metadata table. + /// + /// + /// An index into the import parent scope defining the parent scope, or 0 if it is the root scope. + /// + /// + /// An index into the blob stream referencing the imports that this scope defines. + /// + public ImportScopeRow(uint parent, uint imports) + { + Parent = parent; + Imports = imports; + } + + /// + public TableIndex TableIndex => TableIndex.ImportScope; + + /// + public int Count => 2; + + /// + public uint this[int index] => index switch + { + 0 => Parent, + 1 => Imports, + _ => throw new IndexOutOfRangeException() + }; + + /// + /// Gets or sets an index into the import parent scope defining the parent scope, or 0 if it is the root scope. + /// + public uint Parent + { + get; + set; + } + + /// + /// Gets or sets an index into the blob stream referencing the imports that this scope defines. + /// + public uint Imports + { + get; + set; + } + /// + /// Reads a single Portable PDB import scope row from an input stream. + /// + /// The input stream. + /// The layout of the import scope table. + /// The row. + public static ImportScopeRow FromReader(ref BinaryStreamReader reader, TableLayout layout) + { + return new ImportScopeRow( + reader.ReadIndex((IndexSize) layout.Columns[0].Size), + reader.ReadIndex((IndexSize) layout.Columns[1].Size)); + } + + /// + public void Write(IBinaryStreamWriter writer, TableLayout layout) + { + writer.WriteIndex(Parent, (IndexSize) layout.Columns[0].Size); + writer.WriteIndex(Imports, (IndexSize) layout.Columns[1].Size); + } + + /// + /// Determines whether this row is considered equal to the provided import scope row. + /// + /// The other row. + /// true if the rows are equal, false otherwise. + public bool Equals(ImportScopeRow other) + { + return Parent == other.Parent && Imports == other.Imports; + } + + /// + public override bool Equals(object? obj) + { + return obj is ImportScopeRow other && Equals(other); + } + + /// + public override int GetHashCode() + { + unchecked + { + return ((int) Parent * 397) ^ (int) Imports; + } + } + + /// + public override string ToString() => $"({Parent:X8}, {Imports:X8})"; + + /// + public IEnumerator GetEnumerator() => new MetadataRowColumnEnumerator(this); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalConstantRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalConstantRow.cs new file mode 100644 index 000000000..553195366 --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalConstantRow.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows +{ + /// + /// Represents a single row in the Portable PDB local constant metadata table. + /// + public struct LocalConstantRow : IMetadataRow + { + /// + /// Creates a new row for the Portable PDB Local Constant metadata table. + /// + /// An index into the strings stream referencing the name of the constant. + /// An index into the blob stream referencing the signature of the constant. + public LocalConstantRow(uint name, uint signature) + { + Name = name; + Signature = signature; + } + + /// + public TableIndex TableIndex => TableIndex.LocalConstant; + + /// + public int Count => 2; + + /// + public uint this[int index] => index switch + { + 0 => Name, + 1 => Signature, + _ => throw new IndexOutOfRangeException() + }; + + /// + /// Gets or sets an index into the strings stream referencing the name of the constant. + /// + public uint Name + { + get; + set; + } + + /// + /// Gets or sets an index into the blob stream referencing the signature of the constant. + /// + public uint Signature + { + get; + set; + } + + /// + /// Reads a single Portable PDB local constant row from an input stream. + /// + /// The input stream. + /// The layout of the local constant table. + /// The row. + public static LocalConstantRow FromReader(ref BinaryStreamReader reader, TableLayout layout) + { + return new LocalConstantRow( + reader.ReadIndex((IndexSize) layout.Columns[0].Size), + reader.ReadIndex((IndexSize) layout.Columns[1].Size)); + } + + /// + public void Write(IBinaryStreamWriter writer, TableLayout layout) + { + writer.WriteIndex(Name, (IndexSize) layout.Columns[0].Size); + writer.WriteIndex(Signature, (IndexSize) layout.Columns[1].Size); + } + + /// + /// Determines whether this row is considered equal to the provided local constant row. + /// + /// The other row. + /// true if the rows are equal, false otherwise. + public bool Equals(LocalConstantRow other) + { + return Name == other.Name && Signature == other.Signature; + } + + /// + public override bool Equals(object? obj) + { + return obj is LocalConstantRow other && Equals(other); + } + + /// + public override int GetHashCode() + { + unchecked + { + return ((int) Name * 397) ^ (int) Signature; + } + } + + /// + public override string ToString() => $"({Name:X8}, {Signature:X8})"; + + /// + public IEnumerator GetEnumerator() => new MetadataRowColumnEnumerator(this); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalScopeRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalScopeRow.cs new file mode 100644 index 000000000..9fa9c387e --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalScopeRow.cs @@ -0,0 +1,180 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows +{ + /// + /// Represents a single row in the Portable PDB local scope metadata table. + /// + public struct LocalScopeRow : IMetadataRow + { + /// + /// Creates a new row for the Portable PDB Local Scope metadata table. + /// + /// An index into the method table that defines the scope. + /// An index into the import scope table that defines the scope. + /// An index into the local variable table referencing the first local variable in the method. + /// An index into the local constant table referencing the first constant in the method. + /// The starting CIL offset of the scope. + /// The number of CIL bytes the scope spans. + public LocalScopeRow(uint method, uint importScope, uint variableList, uint constantList, uint startOffset, uint length) + { + Method = method; + ImportScope = importScope; + VariableList = variableList; + ConstantList = constantList; + StartOffset = startOffset; + Length = length; + } + + /// + public TableIndex TableIndex => TableIndex.Document; + + /// + public int Count => 6; + + /// + public uint this[int index] => index switch + { + 0 => Method, + 1 => ImportScope, + 2 => VariableList, + 3 => ConstantList, + 4 => StartOffset, + 5 => Length, + _ => throw new IndexOutOfRangeException() + }; + + /// + /// Gets or sets an index into the method table that defines the scope. + /// + public uint Method + { + get; + set; + } + + /// + /// Gets or sets an index into the import scope table that defines the scope. + /// + public uint ImportScope + { + get; + set; + } + + /// + /// Gets or sets an index into the local variable table referencing the first local variable in the method. + /// + public uint VariableList + { + get; + set; + } + + /// + /// Gets or sets an index into the local constant table referencing the first constant in the method. + /// + public uint ConstantList + { + get; + set; + } + + /// + /// Gets or sets The starting CIL offset of the scope. + /// + public uint StartOffset + { + get; + set; + } + + /// + /// Gets or sets the number of CIL bytes the scope spans. + /// + public uint Length + { + get; + set; + } + + /// + /// Reads a single Portable PDB local scope row from an input stream. + /// + /// The input stream. + /// The layout of the local socpe table. + /// The row. + public static LocalScopeRow FromReader(ref BinaryStreamReader reader, TableLayout layout) + { + return new LocalScopeRow( + reader.ReadIndex((IndexSize) layout.Columns[0].Size), + reader.ReadIndex((IndexSize) layout.Columns[1].Size), + reader.ReadIndex((IndexSize) layout.Columns[2].Size), + reader.ReadIndex((IndexSize) layout.Columns[3].Size), + reader.ReadUInt32(), + reader.ReadUInt32()); + } + + /// + public void Write(IBinaryStreamWriter writer, TableLayout layout) + { + writer.WriteIndex(Method, (IndexSize) layout.Columns[0].Size); + writer.WriteIndex(ImportScope, (IndexSize) layout.Columns[1].Size); + writer.WriteIndex(VariableList, (IndexSize) layout.Columns[2].Size); + writer.WriteIndex(ConstantList, (IndexSize) layout.Columns[3].Size); + writer.WriteUInt32(StartOffset); + writer.WriteUInt32(Length); + } + + /// + /// Determines whether this row is considered equal to the provided local scope row. + /// + /// The other row. + /// true if the rows are equal, false otherwise. + public bool Equals(LocalScopeRow other) + { + return Method == other.Method + && ImportScope == other.ImportScope + && VariableList == other.VariableList + && ConstantList == other.ConstantList + && StartOffset == other.StartOffset + && Length == other.Length; + } + + /// + public override bool Equals(object? obj) + { + return obj is LocalScopeRow other && Equals(other); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = (int) Method; + hashCode = (hashCode * 397) ^ (int) ImportScope; + hashCode = (hashCode * 397) ^ (int) VariableList; + hashCode = (hashCode * 397) ^ (int) ConstantList; + hashCode = (hashCode * 397) ^ (int) StartOffset; + hashCode = (hashCode * 397) ^ (int) Length; + return hashCode; + } + } + + /// + public override string ToString() + { + return $"({Method:X8}, {ImportScope:X8}, {VariableList:X8}, {ConstantList:X8}, {StartOffset:X8}, {Length:X8})"; + } + + /// + public IEnumerator GetEnumerator() => new MetadataRowColumnEnumerator(this); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalVariableAttributes.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalVariableAttributes.cs new file mode 100644 index 000000000..503f2f555 --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalVariableAttributes.cs @@ -0,0 +1,16 @@ +using System; + +namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows +{ + /// + /// Provides members defining all possible flags that can be assigned to a local variable. + /// + [Flags] + public enum LocalVariableAttributes : ushort + { + /// + /// Indicates the local variable should be hidden in a debugger view. + /// + DebuggerHidden = 0x0001 + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalVariableRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalVariableRow.cs new file mode 100644 index 000000000..1c27f04f0 --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/LocalVariableRow.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows +{ + /// + /// Represents a single row in the Portable PDB local variable metadata table. + /// + public struct LocalVariableRow : IMetadataRow + { + /// + /// Creates a new row for the Portable PDB Local Variable metadata table. + /// + /// The attributes associated to the local variable. + /// The index of the local variable. + /// An index into the strings stream referencing the name of the local variable. + public LocalVariableRow(LocalVariableAttributes attributes, ushort index, uint name) + { + Attributes = attributes; + Index = index; + Name = name; + } + + /// + public TableIndex TableIndex => TableIndex.LocalVariable; + + /// + public int Count => 3; + + /// + public uint this[int index] => index switch + { + 0 => (uint) Attributes, + 1 => Index, + 2 => Name, + _ => throw new IndexOutOfRangeException() + }; + + /// + /// Gets or sets the attributes associated to the local variable. + /// + public LocalVariableAttributes Attributes + { + get; + set; + } + + /// + /// Gets or sets the index of the local variable. + /// + public ushort Index + { + get; + set; + } + + /// + /// Gets or sets an index into the strings stream referencing the name of the local variable. + /// + public uint Name + { + get; + set; + } + + /// + /// Reads a single Portable PDB local variable row from an input stream. + /// + /// The input stream. + /// The layout of the local variable table. + /// The row. + public static LocalVariableRow FromReader(ref BinaryStreamReader reader, TableLayout layout) + { + return new LocalVariableRow( + (LocalVariableAttributes) reader.ReadUInt16(), + reader.ReadUInt16(), + reader.ReadIndex((IndexSize) layout.Columns[2].Size)); + } + + /// + public void Write(IBinaryStreamWriter writer, TableLayout layout) + { + writer.WriteUInt16((ushort) Attributes); + writer.WriteUInt16(Index); + writer.WriteIndex(Name, (IndexSize) layout.Columns[2].Size); + } + + /// + /// Determines whether this row is considered equal to the provided local variable row. + /// + /// The other row. + /// true if the rows are equal, false otherwise. + public bool Equals(LocalVariableRow other) + { + return Attributes == other.Attributes + && Index == other.Index + && Name == other.Name; + } + + /// + public override bool Equals(object? obj) + { + return obj is LocalScopeRow other && Equals(other); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = (int) Attributes; + hashCode = (hashCode * 397) ^ Index; + hashCode = (hashCode * 397) ^ (int) Name; + return hashCode; + } + } + + /// + public override string ToString() => $"({(ushort) Attributes:X4}, {Index:X4}, {Name:X8})"; + + /// + public IEnumerator GetEnumerator() => new MetadataRowColumnEnumerator(this); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDebugInformationRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDebugInformationRow.cs new file mode 100644 index 000000000..9cfbb821b --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDebugInformationRow.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows +{ + /// + /// Represents a single row in the Portable PDB Document metadata table. + /// + public struct MethodDebugInformationRow : IMetadataRow + { + /// + /// Creates a new row for the Portable PDB Method Debug Information metadata table. + /// + /// + /// The index into the Document table referencing the document that declares the method. + /// + /// + /// The index into the blob stream referencing an array of sequence points that make up the method. + /// + public MethodDebugInformationRow(uint document, uint sequencePoints) + { + Document = document; + SequencePoints = sequencePoints; + } + + /// + public TableIndex TableIndex => TableIndex.Document; + + /// + public int Count => 2; + + /// + public uint this[int index] => index switch + { + 0 => Document, + 1 => SequencePoints, + _ => throw new IndexOutOfRangeException() + }; + + /// + /// Gets or sets an index into the Document table referencing the document that declares the method, or 0 + /// if the method does not have sequence points or spans multiple documents. + /// + public uint Document + { + get; + set; + } + + /// + /// Gets or sets an index into the blob stream referencing an array of sequence points that make up the method, + /// or 0 if no sequence points are available. + /// + public uint SequencePoints + { + get; + set; + } + + /// + /// Reads a single Portable PDB Method Debug Information row from an input stream. + /// + /// The input stream. + /// The layout of the method debug information table. + /// The row. + public static MethodDebugInformationRow FromReader(ref BinaryStreamReader reader, TableLayout layout) + { + return new MethodDebugInformationRow( + reader.ReadIndex((IndexSize) layout.Columns[0].Size), + reader.ReadIndex((IndexSize) layout.Columns[1].Size)); + } + + /// + public void Write(IBinaryStreamWriter writer, TableLayout layout) + { + writer.WriteIndex(Document, (IndexSize) layout.Columns[0].Size); + writer.WriteIndex(SequencePoints, (IndexSize) layout.Columns[1].Size); + } + + /// + /// Determines whether this row is considered equal to the provided method debug information row. + /// + /// The other row. + /// true if the rows are equal, false otherwise. + public bool Equals(MethodDebugInformationRow other) + { + return Document == other.Document && SequencePoints == other.SequencePoints; + } + + /// + public override bool Equals(object? obj) + { + return obj is MethodDebugInformationRow other && Equals(other); + } + + /// + public override int GetHashCode() + { + unchecked + { + return ((int) Document * 397) ^ (int) SequencePoints; + } + } + + /// + public override string ToString() => $"({Document:X8}, {SequencePoints:X8})"; + + /// + public IEnumerator GetEnumerator() + { + return new MetadataRowColumnEnumerator(this); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDefinitionRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDefinitionRow.cs index 2b6df546f..6cd6e5481 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDefinitionRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDefinitionRow.cs @@ -17,10 +17,10 @@ public struct MethodDefinitionRow : IMetadataRow /// The input stream. /// The layout of the method definition table. /// The row. - public static MethodDefinitionRow FromReader(PEReaderContext context, ref BinaryStreamReader reader, TableLayout layout) + public static MethodDefinitionRow FromReader(MetadataReaderContext context, ref BinaryStreamReader reader, TableLayout layout) { return new MethodDefinitionRow( - context.File.GetReferenceToRva(reader.ReadUInt32()), + context.ReferenceFactory.GetReferenceToRva(reader.ReadUInt32()), (MethodImplAttributes) reader.ReadUInt16(), (MethodAttributes) reader.ReadUInt16(), reader.ReadIndex((IndexSize) layout.Columns[3].Size), diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/StateMachineMethodRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/StateMachineMethodRow.cs new file mode 100644 index 000000000..914125195 --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/StateMachineMethodRow.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.PE.DotNet.Metadata.Tables.Rows +{ + /// + /// Represents a single row in the Portable PDB state machine method metadata table. + /// + public struct StateMachineMethodRow : IMetadataRow + { + /// + /// Creates a new row for the Portable PDB state machine method metadata table. + /// + /// + /// An index into the method table referencing the MoveNext method of an async state machine. + /// + /// + /// An index into the method table referencing the kickoff method of an async state machine. + /// + public StateMachineMethodRow(uint moveNextMethod, uint kickoffMethod) + { + MoveNextMethod = moveNextMethod; + KickoffMethod = kickoffMethod; + } + + /// + public TableIndex TableIndex => TableIndex.StateMachineMethod; + + /// + public int Count => 2; + + /// + public uint this[int index] => index switch + { + 0 => MoveNextMethod, + 1 => KickoffMethod, + _ => throw new IndexOutOfRangeException() + }; + + /// + /// Gets or sets an index into the method table referencing the MoveNext method of an async state machine. + /// + public uint MoveNextMethod + { + get; + set; + } + + /// + /// Gets or sets an index into the method table referencing the kickoff method of an async state machine. + /// + public uint KickoffMethod + { + get; + set; + } + + /// + /// Reads a single Portable PDB state machine method row from an input stream. + /// + /// The input stream. + /// The layout of the state machine method table. + /// The row. + public static StateMachineMethodRow FromReader(ref BinaryStreamReader reader, TableLayout layout) + { + return new StateMachineMethodRow( + reader.ReadIndex((IndexSize) layout.Columns[0].Size), + reader.ReadIndex((IndexSize) layout.Columns[1].Size)); + } + + /// + public void Write(IBinaryStreamWriter writer, TableLayout layout) + { + writer.WriteIndex(MoveNextMethod, (IndexSize) layout.Columns[0].Size); + writer.WriteIndex(KickoffMethod, (IndexSize) layout.Columns[1].Size); + } + + /// + /// Determines whether this row is considered equal to the provided state machine method row. + /// + /// The other row. + /// true if the rows are equal, false otherwise. + public bool Equals(StateMachineMethodRow other) + { + return MoveNextMethod == other.MoveNextMethod && KickoffMethod == other.KickoffMethod; + } + + /// + public override bool Equals(object? obj) + { + return obj is StateMachineMethodRow other && Equals(other); + } + + /// + public override int GetHashCode() + { + unchecked + { + return ((int) MoveNextMethod * 397) ^ (int) KickoffMethod; + } + } + + /// + public override string ToString() => $"({MoveNextMethod:X8}, {KickoffMethod:X8})"; + + /// + public IEnumerator GetEnumerator() => new MetadataRowColumnEnumerator(this); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedMetadataTable.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedMetadataTable.cs index 0529b40f3..b39575f70 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedMetadataTable.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedMetadataTable.cs @@ -27,7 +27,7 @@ public class SerializedMetadataTable : MetadataTable /// The input stream. /// The layout of the table. public delegate TRow ReadRowExtendedDelegate( - PEReaderContext context, + MetadataReaderContext context, ref BinaryStreamReader reader, TableLayout layout); @@ -42,9 +42,15 @@ public class SerializedMetadataTable : MetadataTable /// The input stream. /// The index of the table. /// The layout of the table. + /// Indicates the table is sorted or not. /// The method to use for reading each row in the table. - public SerializedMetadataTable(in BinaryStreamReader reader, TableIndex tableIndex, TableLayout originalLayout, ReadRowDelegate readRow) - : base(tableIndex, originalLayout) + public SerializedMetadataTable( + in BinaryStreamReader reader, + TableIndex tableIndex, + TableLayout originalLayout, + bool isSorted, + ReadRowDelegate readRow) + : base(tableIndex, originalLayout, isSorted) { _reader = reader; _originalLayout = originalLayout; @@ -59,14 +65,16 @@ public SerializedMetadataTable(in BinaryStreamReader reader, TableIndex tableInd /// The input stream. /// The index of the table. /// The layout of the table. + /// Indicates the table is sorted or not. /// The method to use for reading each row in the table. public SerializedMetadataTable( - PEReaderContext context, + MetadataReaderContext context, in BinaryStreamReader reader, TableIndex tableIndex, TableLayout originalLayout, + bool isSorted, ReadRowExtendedDelegate readRow) - : this(reader, tableIndex, originalLayout, + : this(reader, tableIndex, originalLayout, isSorted, (ref BinaryStreamReader r, TableLayout l) => readRow(context, ref r, l)) { } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedTableStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedTableStream.cs index f2a910eb8..becb9201e 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedTableStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/SerializedTableStream.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using AsmResolver.IO; +using AsmResolver.PE.DotNet.Metadata.Pdb; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; namespace AsmResolver.PE.DotNet.Metadata.Tables @@ -9,25 +10,35 @@ namespace AsmResolver.PE.DotNet.Metadata.Tables /// /// Provides an implementation of a tables stream that obtains tables from a readable segment in a file. /// - public class SerializedTableStream : TablesStream + public class SerializedTableStream : TablesStream, ILazyMetadataStream { - private readonly PEReaderContext _context; + private readonly MetadataReaderContext _context; private readonly BinaryStreamReader _reader; private readonly ulong _validMask; private readonly ulong _sortedMask; private readonly uint[] _rowCounts; - private readonly IndexSize[] _indexSizes; private readonly uint _headerSize; private bool _tablesInitialized; + /// + /// Same as but may contain row counts from an external tables stream. + /// This is required for metadata directories containing Portable PDB debug data. + /// + private uint[]? _combinedRowCounts; + + /// + /// Contains the initial sizes of every column type. + /// + private IndexSize[]? _indexSizes; + /// /// Creates a new tables stream with the provided byte array as the raw contents of the stream. /// /// The reader context. /// The name of the stream. /// The raw contents of the stream. - public SerializedTableStream(PEReaderContext context, string name, byte[] rawData) - : this(context, name, ByteArrayDataSource.CreateReader(rawData)) + public SerializedTableStream(MetadataReaderContext context, string name, byte[] rawData) + : this(context, name, new BinaryStreamReader(rawData)) { } @@ -37,7 +48,7 @@ public SerializedTableStream(PEReaderContext context, string name, byte[] rawDat /// The reader context. /// The name of the stream. /// The raw contents of the stream. - public SerializedTableStream(PEReaderContext context, string name, in BinaryStreamReader reader) + public SerializedTableStream(MetadataReaderContext context, string name, in BinaryStreamReader reader) { Name = name ?? throw new ArgumentNullException(nameof(name)); _context = context ?? throw new ArgumentNullException(nameof(context)); @@ -54,15 +65,12 @@ public SerializedTableStream(PEReaderContext context, string name, in BinaryStre Log2LargestRid = headerReader.ReadByte(); _validMask = headerReader.ReadUInt64(); _sortedMask = headerReader.ReadUInt64(); - _rowCounts = ReadRowCounts(ref headerReader); if (HasExtraData) ExtraData = headerReader.ReadUInt32(); _headerSize = headerReader.RelativeOffset; - - _indexSizes = InitializeIndexSizes(); } /// @@ -73,19 +81,55 @@ public SerializedTableStream(PEReaderContext context, string name, in BinaryStre private uint[] ReadRowCounts(ref BinaryStreamReader reader) { - const TableIndex maxTableIndex = TableIndex.GenericParamConstraint; + uint[] result = new uint[(int) TableIndex.Max]; - var result = new uint[(int) maxTableIndex + 1 ]; - for (TableIndex i = 0; i <= maxTableIndex; i++) - result[(int) i] = HasTable(_validMask, i) ? reader.ReadUInt32() : 0; + for (TableIndex i = 0; i < TableIndex.Max; i++) + { + result[(int) i] = HasTable(_validMask, i) + ? reader.ReadUInt32() + : 0; + } return result; } + /// + public void Initialize(IMetadata parentMetadata) + { + if (parentMetadata.TryGetStream(out PdbStream? pdbStream)) + { + // Metadata that contains a PDB stream should use the row counts provided in the pdb stream + // for computing the size of a column. + _combinedRowCounts = new uint[_rowCounts.Length]; + ExternalRowCounts = new uint[(int) TableIndex.Document]; + + for (int i = 0; i < (int) TableIndex.Document; i++) + { + _combinedRowCounts[i] = pdbStream.TypeSystemRowCounts[i]; + ExternalRowCounts[i] = pdbStream.TypeSystemRowCounts[i]; + } + + for (int i = (int) TableIndex.Document; i < (int) TableIndex.Max; i++) + _combinedRowCounts[i] = _rowCounts[i]; + + } + else + { + // Otherwise, just use the original row counts array. + _combinedRowCounts = _rowCounts; + } + + _indexSizes = InitializeIndexSizes(); + } + /// protected override uint GetColumnSize(ColumnType columnType) { - if (_tablesInitialized || (int) columnType >= _indexSizes.Length) + if (_tablesInitialized) + return base.GetColumnSize(columnType); + if (_indexSizes is null) + throw new InvalidOperationException("Serialized tables stream is not fully initialized yet."); + if ((int) columnType >= _indexSizes.Length) return base.GetColumnSize(columnType); return (uint) _indexSizes[(int) columnType]; } @@ -149,6 +193,15 @@ private IndexSize[] InitializeIndexSizes() // TypeOrMethodDef GetCodedIndexSize(TableIndex.TypeDef, TableIndex.Method), + + // HasCustomDebugInformation + GetCodedIndexSize(TableIndex.Method, TableIndex.Field, TableIndex.TypeRef, TableIndex.TypeDef, + TableIndex.Param, TableIndex.InterfaceImpl, TableIndex.MemberRef, TableIndex.Module, + TableIndex.DeclSecurity, TableIndex.Property, TableIndex.Event, TableIndex.StandAloneSig, + TableIndex.ModuleRef, TableIndex.TypeSpec, TableIndex.Assembly, TableIndex.AssemblyRef, + TableIndex.File, TableIndex.ExportedType, TableIndex.ManifestResource, TableIndex.GenericParam, + TableIndex.GenericParamConstraint, TableIndex.MethodSpec, TableIndex.Document, + TableIndex.LocalScope, TableIndex.LocalVariable, TableIndex.LocalConstant, TableIndex.ImportScope) }); return result.ToArray(); @@ -156,19 +209,22 @@ private IndexSize[] InitializeIndexSizes() private IndexSize GetCodedIndexSize(params TableIndex[] tables) { + if (_combinedRowCounts is null) + throw new InvalidOperationException("Serialized tables stream is not fully initialized yet."); + int tableIndexBitCount = (int) Math.Ceiling(Math.Log(tables.Length, 2)); int maxSmallTableMemberCount = ushort.MaxValue >> tableIndexBitCount; - return tables.Select(t => _rowCounts[(int) t]).All(c => c < maxSmallTableMemberCount) + return tables.Select(t => _combinedRowCounts[(int) t]).All(c => c < maxSmallTableMemberCount) ? IndexSize.Short : IndexSize.Long; } /// - protected override IList GetTables() + protected override IList GetTables() { uint offset = _headerSize; - var tables = new IMetadataTable[] + var tables = new IMetadataTable?[] { CreateNextTable(TableIndex.Module, ref offset, ModuleDefinitionRow.FromReader), CreateNextTable(TableIndex.TypeRef, ref offset, TypeReferenceRow.FromReader), @@ -215,6 +271,17 @@ protected override IList GetTables() CreateNextTable(TableIndex.GenericParam, ref offset, GenericParameterRow.FromReader), CreateNextTable(TableIndex.MethodSpec, ref offset, MethodSpecificationRow.FromReader), CreateNextTable(TableIndex.GenericParamConstraint, ref offset, GenericParameterConstraintRow.FromReader), + null, + null, + null, + CreateNextTable(TableIndex.Document, ref offset, DocumentRow.FromReader), + CreateNextTable(TableIndex.MethodDebugInformation, ref offset, MethodDebugInformationRow.FromReader), + CreateNextTable(TableIndex.LocalScope, ref offset, LocalScopeRow.FromReader), + CreateNextTable(TableIndex.LocalVariable, ref offset, LocalVariableRow.FromReader), + CreateNextTable(TableIndex.LocalConstant, ref offset, LocalConstantRow.FromReader), + CreateNextTable(TableIndex.ImportScope, ref offset, ImportScopeRow.FromReader), + CreateNextTable(TableIndex.StateMachineMethod, ref offset, StateMachineMethodRow.FromReader), + CreateNextTable(TableIndex.CustomDebugInformation, ref offset, CustomDebugInformationRow.FromReader), }; _tablesInitialized = true; return tables; @@ -239,6 +306,7 @@ private BinaryStreamReader CreateNextRawTableReader(TableIndex currentIndex, ref CreateNextRawTableReader(index, ref offset), index, TableLayouts[(int) index], + IsSorted(_sortedMask, index), readRow); } @@ -253,6 +321,7 @@ private BinaryStreamReader CreateNextRawTableReader(TableIndex currentIndex, ref CreateNextRawTableReader(index, ref offset), index, TableLayouts[(int) index], + IsSorted(_sortedMask, index), readRow); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndex.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndex.cs index d9ca9e3bc..8ee7dd66b 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndex.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndex.cs @@ -53,8 +53,21 @@ public enum TableIndex : byte GenericParam = 42, MethodSpec = 43, GenericParamConstraint = 44, - Max = GenericParamConstraint + 1, + + MaxTypeSystemTableIndex = GenericParamConstraint, + + Document = 0x30, + MethodDebugInformation = 0x31, + LocalScope = 0x32, + LocalVariable = 0x33, + LocalConstant = 0x34, + ImportScope = 0x35, + StateMachineMethod = 0x36, + CustomDebugInformation = 0x37, + + Max = CustomDebugInformation + 1, String = 0x70 } + } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndexExtensions.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndexExtensions.cs new file mode 100644 index 000000000..e9573dfb2 --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/TableIndexExtensions.cs @@ -0,0 +1,19 @@ +namespace AsmResolver.PE.DotNet.Metadata.Tables; + +/// +/// Provides extension methods to the enumeration. +/// +public static class TableIndexExtensions +{ + /// + /// Determines whether the provided index is a valid table index. + /// + /// The index. + /// true if valid, false otherwise. + public static bool IsValidTableIndex(this TableIndex index) + { + return index is >= TableIndex.Module and <= TableIndex.GenericParamConstraint + or >= TableIndex.Document and <= TableIndex.CustomDebugInformation + or TableIndex.String; + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.Tables.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.Tables.cs new file mode 100644 index 000000000..877d34129 --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.Tables.cs @@ -0,0 +1,474 @@ +using System.Collections.Generic; +using AsmResolver.PE.DotNet.Metadata.Tables.Rows; + +namespace AsmResolver.PE.DotNet.Metadata.Tables +{ + public partial class TablesStream + { + /// + /// Obtains the collection of tables in the tables stream. + /// + /// The tables, including empty tables if there are any. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetTables() + { + var layouts = TableLayouts; + return new IMetadataTable?[] + { + new MetadataTable(TableIndex.Module, layouts[0]), + new MetadataTable(TableIndex.TypeRef, layouts[1]), + new MetadataTable(TableIndex.TypeDef, layouts[2]), + new MetadataTable(TableIndex.FieldPtr, layouts[3]), + new MetadataTable(TableIndex.Field, layouts[4]), + new MetadataTable(TableIndex.Method, layouts[5]), + new MetadataTable(TableIndex.Method, layouts[6]), + new MetadataTable(TableIndex.ParamPtr, layouts[7]), + new MetadataTable(TableIndex.Param, layouts[8]), + new MetadataTable(TableIndex.InterfaceImpl, layouts[9], true), + new MetadataTable(TableIndex.MemberRef, layouts[10]), + new MetadataTable(TableIndex.Constant, layouts[11], true), + new MetadataTable(TableIndex.CustomAttribute, layouts[12], true), + new MetadataTable(TableIndex.FieldMarshal, layouts[13], true), + new MetadataTable(TableIndex.DeclSecurity, layouts[14], true), + new MetadataTable(TableIndex.ClassLayout, layouts[15], true), + new MetadataTable(TableIndex.FieldLayout, layouts[16], true), + new MetadataTable(TableIndex.StandAloneSig, layouts[17]), + new MetadataTable(TableIndex.EventMap, layouts[18]), + new MetadataTable(TableIndex.EventPtr, layouts[19]), + new MetadataTable(TableIndex.Event, layouts[20]), + new MetadataTable(TableIndex.PropertyMap, layouts[21]), + new MetadataTable(TableIndex.PropertyPtr, layouts[22]), + new MetadataTable(TableIndex.Property, layouts[23]), + new MetadataTable(TableIndex.MethodSemantics, layouts[24], true), + new MetadataTable(TableIndex.MethodImpl, layouts[25], true), + new MetadataTable(TableIndex.ModuleRef, layouts[26]), + new MetadataTable(TableIndex.TypeSpec, layouts[27]), + new MetadataTable(TableIndex.ImplMap, layouts[28], true), + new MetadataTable(TableIndex.FieldRva, layouts[29], true), + new MetadataTable(TableIndex.EncLog, layouts[30]), + new MetadataTable(TableIndex.EncMap, layouts[31]), + new MetadataTable(TableIndex.Assembly, layouts[32]), + new MetadataTable(TableIndex.AssemblyProcessor, layouts[33]), + new MetadataTable(TableIndex.AssemblyOS, layouts[34]), + new MetadataTable(TableIndex.AssemblyRef, layouts[35]), + new MetadataTable(TableIndex.AssemblyRefProcessor, layouts[36]), + new MetadataTable(TableIndex.AssemblyRefProcessor, layouts[37]), + new MetadataTable(TableIndex.File, layouts[38]), + new MetadataTable(TableIndex.ExportedType, layouts[39]), + new MetadataTable(TableIndex.ManifestResource, layouts[40]), + new MetadataTable(TableIndex.NestedClass, layouts[41], true), + new MetadataTable(TableIndex.GenericParam, layouts[42], true), + new MetadataTable(TableIndex.MethodSpec, layouts[43]), + new MetadataTable(TableIndex.GenericParamConstraint, layouts[44], true), + null, + null, + null, + new MetadataTable(TableIndex.Document, layouts[48]), + new MetadataTable(TableIndex.MethodDebugInformation, layouts[49]), + new MetadataTable(TableIndex.LocalScope, layouts[50], true), + new MetadataTable(TableIndex.LocalVariable, layouts[51]), + new MetadataTable(TableIndex.LocalConstant, layouts[52]), + new MetadataTable(TableIndex.ImportScope, layouts[53]), + new MetadataTable(TableIndex.StateMachineMethod, layouts[54]), + new MetadataTable(TableIndex.CustomDebugInformation, layouts[55], true), + }; + } + + private Dictionary CreateIndexEncoders() + { + return new() + { + [CodedIndex.TypeDefOrRef] = new IndexEncoder(this, + TableIndex.TypeDef, TableIndex.TypeRef, TableIndex.TypeSpec), + + [CodedIndex.HasConstant] = new(this, + TableIndex.Field, TableIndex.Param, TableIndex.Property), + + [CodedIndex.HasCustomAttribute] = new(this, + TableIndex.Method, TableIndex.Field, TableIndex.TypeRef, TableIndex.TypeDef, + TableIndex.Param, TableIndex.InterfaceImpl, TableIndex.MemberRef, TableIndex.Module, + TableIndex.DeclSecurity, TableIndex.Property, TableIndex.Event, TableIndex.StandAloneSig, + TableIndex.ModuleRef, TableIndex.TypeSpec, TableIndex.Assembly, TableIndex.AssemblyRef, + TableIndex.File, TableIndex.ExportedType, TableIndex.ManifestResource, TableIndex.GenericParam, + TableIndex.GenericParamConstraint, TableIndex.MethodSpec), + + [CodedIndex.HasFieldMarshal] = new(this, + TableIndex.Field, TableIndex.Param), + + [CodedIndex.HasDeclSecurity] = new(this, + TableIndex.TypeDef, TableIndex.Method, TableIndex.Assembly), + + [CodedIndex.MemberRefParent] = new(this, + TableIndex.TypeDef, TableIndex.TypeRef, TableIndex.ModuleRef, TableIndex.Method, + TableIndex.TypeSpec), + + [CodedIndex.HasSemantics] = new(this, + TableIndex.Event, TableIndex.Property), + + [CodedIndex.MethodDefOrRef] = new(this, + TableIndex.Method, TableIndex.MemberRef), + + [CodedIndex.MemberForwarded] = new(this, + TableIndex.Field, TableIndex.Method), + + [CodedIndex.Implementation] = new(this, + TableIndex.File, TableIndex.AssemblyRef, TableIndex.ExportedType), + + [CodedIndex.CustomAttributeType] = new(this, + 0, 0, TableIndex.Method, TableIndex.MemberRef, 0), + + [CodedIndex.ResolutionScope] = new(this, + TableIndex.Module, TableIndex.ModuleRef, TableIndex.AssemblyRef, TableIndex.TypeRef), + + [CodedIndex.TypeOrMethodDef] = new(this, + TableIndex.TypeDef, TableIndex.Method), + + [CodedIndex.HasCustomDebugInformation] = new(this, + TableIndex.Method, TableIndex.Field, TableIndex.TypeRef, TableIndex.TypeDef, TableIndex.Param, + TableIndex.InterfaceImpl, TableIndex.MemberRef, TableIndex.Module, TableIndex.DeclSecurity, + TableIndex.Property, TableIndex.Event, TableIndex.StandAloneSig, TableIndex.ModuleRef, + TableIndex.TypeSpec, TableIndex.Assembly, TableIndex.AssemblyRef, TableIndex.File, + TableIndex.ExportedType, TableIndex.ManifestResource, TableIndex.GenericParam, + TableIndex.GenericParamConstraint, TableIndex.MethodSpec, TableIndex.Document, + TableIndex.LocalScope, TableIndex.LocalVariable, TableIndex.LocalConstant, TableIndex.ImportScope) + }; + } + + /// + /// Gets an ordered collection of the current table layouts. + /// + /// The table layouts. + protected TableLayout[] GetTableLayouts() => new[] + { + // Module + new TableLayout( + new ColumnLayout("Generation", ColumnType.UInt16), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("Mvid", ColumnType.Guid, GuidIndexSize), + new ColumnLayout("EncId", ColumnType.Guid, GuidIndexSize), + new ColumnLayout("EncBaseId", ColumnType.Guid, GuidIndexSize)), + + // TypeRef + new TableLayout( + new ColumnLayout("ResolutionScope", ColumnType.ResolutionScope, + GetColumnSize(ColumnType.ResolutionScope)), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("Namespace", ColumnType.Guid, StringIndexSize)), + + // TypeDef + new TableLayout( + new ColumnLayout("Flags", ColumnType.UInt32), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("Namespace", ColumnType.String, StringIndexSize), + new ColumnLayout("Extends", ColumnType.TypeDefOrRef, + GetColumnSize(ColumnType.TypeDefOrRef)), + new ColumnLayout("FieldList", ColumnType.Field, GetColumnSize(ColumnType.Field)), + new ColumnLayout("MethodList", ColumnType.Method, GetColumnSize(ColumnType.Method))), + + // FieldPtr + new TableLayout( + new ColumnLayout("Field", ColumnType.Field, GetColumnSize(ColumnType.Field))), + + // Field + new TableLayout( + new ColumnLayout("Flags", ColumnType.UInt16), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)), + + // MethodPtr + new TableLayout( + new ColumnLayout("Method", ColumnType.Method, GetColumnSize(ColumnType.Method))), + + // Method + new TableLayout( + new ColumnLayout("RVA", ColumnType.UInt32), + new ColumnLayout("ImplFlags", ColumnType.UInt16), + new ColumnLayout("Flags", ColumnType.UInt16), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize), + new ColumnLayout("ParamList", ColumnType.Param, GetColumnSize(ColumnType.Param))), + + // ParamPtr + new TableLayout( + new ColumnLayout("Parameter", ColumnType.Param, GetColumnSize(ColumnType.Param))), + + // Parameter + new TableLayout( + new ColumnLayout("Flags", ColumnType.UInt16), + new ColumnLayout("Sequence", ColumnType.UInt16), + new ColumnLayout("Name", ColumnType.String, StringIndexSize)), + + // InterfaceImpl + new TableLayout( + new ColumnLayout("Class", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)), + new ColumnLayout("Interface", ColumnType.TypeDefOrRef, GetColumnSize(ColumnType.TypeDefOrRef))), + + // MemberRef + new TableLayout( + new ColumnLayout("Parent", ColumnType.MemberRefParent, GetColumnSize(ColumnType.MemberRefParent)), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)), + + // Constant + new TableLayout( + new ColumnLayout("Type", ColumnType.Byte), + new ColumnLayout("Padding", ColumnType.Byte), + new ColumnLayout("Parent", ColumnType.HasConstant, GetColumnSize(ColumnType.HasConstant)), + new ColumnLayout("Value", ColumnType.Blob, BlobIndexSize)), + + // CustomAttribute + new TableLayout( + new ColumnLayout("Parent", ColumnType.HasCustomAttribute, + GetColumnSize(ColumnType.HasCustomAttribute)), + new ColumnLayout("Type", ColumnType.CustomAttributeType, + GetColumnSize(ColumnType.CustomAttributeType)), + new ColumnLayout("Value", ColumnType.Blob, BlobIndexSize)), + + // FieldMarshal + new TableLayout( + new ColumnLayout("Parent", ColumnType.HasFieldMarshal, GetColumnSize(ColumnType.HasFieldMarshal)), + new ColumnLayout("NativeType", ColumnType.Blob, BlobIndexSize)), + + // DeclSecurity + new TableLayout( + new ColumnLayout("Action", ColumnType.UInt16), + new ColumnLayout("Parent", ColumnType.HasDeclSecurity, GetColumnSize(ColumnType.HasDeclSecurity)), + new ColumnLayout("PermissionSet", ColumnType.Blob, BlobIndexSize)), + + // ClassLayout + new TableLayout( + new ColumnLayout("PackingSize", ColumnType.UInt16), + new ColumnLayout("ClassSize", ColumnType.UInt32), + new ColumnLayout("Parent", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef))), + + // FieldLayout + new TableLayout( + new ColumnLayout("Offset", ColumnType.UInt32), + new ColumnLayout("Field", ColumnType.TypeDef, GetColumnSize(ColumnType.Field))), + + // StandAloneSig + new TableLayout( + new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)), + + // EventMap + new TableLayout( + new ColumnLayout("Parent", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)), + new ColumnLayout("EventList", ColumnType.Event, GetColumnSize(ColumnType.Event))), + + // EventPtr + new TableLayout( + new ColumnLayout("Event", ColumnType.Event, GetColumnSize(ColumnType.Event))), + + // Event + new TableLayout( + new ColumnLayout("Flags", ColumnType.UInt16), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("EventType", ColumnType.TypeDefOrRef, GetColumnSize(ColumnType.TypeDefOrRef))), + + // PropertyMap + new TableLayout( + new ColumnLayout("Parent", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)), + new ColumnLayout("PropertyList", ColumnType.Event, GetColumnSize(ColumnType.Property))), + + // PropertyPtr + new TableLayout( + new ColumnLayout("Property", ColumnType.Property, GetColumnSize(ColumnType.Property))), + + // Property + new TableLayout( + new ColumnLayout("Flags", ColumnType.UInt16), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("PropertyType", ColumnType.Blob, BlobIndexSize)), + + // MethodSemantics + new TableLayout( + new ColumnLayout("Semantic", ColumnType.UInt16), + new ColumnLayout("Method", ColumnType.Method, GetColumnSize(ColumnType.Method)), + new ColumnLayout("Association", ColumnType.HasSemantics, GetColumnSize(ColumnType.HasSemantics))), + + // MethodImpl + new TableLayout( + new ColumnLayout("Class", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)), + new ColumnLayout("MethodBody", ColumnType.MethodDefOrRef, GetColumnSize(ColumnType.MethodDefOrRef)), + new ColumnLayout("MethodDeclaration", ColumnType.MethodDefOrRef, + GetColumnSize(ColumnType.MethodDefOrRef))), + + // ModuleRef + new TableLayout( + new ColumnLayout("Name", ColumnType.String, StringIndexSize)), + + // TypeSpec + new TableLayout( + new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)), + + // ImplMap + new TableLayout( + new ColumnLayout("MappingFlags", ColumnType.UInt16), + new ColumnLayout("MemberForwarded", ColumnType.MemberForwarded, + GetColumnSize(ColumnType.MemberForwarded)), + new ColumnLayout("ImportName", ColumnType.String, StringIndexSize), + new ColumnLayout("ImportScope", ColumnType.ModuleRef, GetColumnSize(ColumnType.ModuleRef))), + + // FieldRva + new TableLayout( + new ColumnLayout("RVA", ColumnType.UInt32), + new ColumnLayout("Field", ColumnType.Field, GetColumnSize(ColumnType.Field))), + + // EncLog + new TableLayout( + new ColumnLayout("Token", ColumnType.UInt32), + new ColumnLayout("FuncCode", ColumnType.UInt32)), + + // EncMap + new TableLayout( + new ColumnLayout("Token", ColumnType.UInt32)), + + // Assembly + new TableLayout( + new ColumnLayout("HashAlgId", ColumnType.UInt32), + new ColumnLayout("MajorVersion", ColumnType.UInt16), + new ColumnLayout("MinorVersion", ColumnType.UInt16), + new ColumnLayout("BuildNumber", ColumnType.UInt16), + new ColumnLayout("RevisionNumber", ColumnType.UInt16), + new ColumnLayout("Flags", ColumnType.UInt32), + new ColumnLayout("PublicKey", ColumnType.Blob, BlobIndexSize), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("Culture", ColumnType.String, StringIndexSize)), + + // AssemblyProcessor + new TableLayout( + new ColumnLayout("Processor", ColumnType.UInt32)), + + // AssemblyOS + new TableLayout( + new ColumnLayout("PlatformId", ColumnType.UInt32), + new ColumnLayout("MajorVersion", ColumnType.UInt32), + new ColumnLayout("MinorVersion", ColumnType.UInt32)), + + // AssemblyRef + new TableLayout( + new ColumnLayout("MajorVersion", ColumnType.UInt16), + new ColumnLayout("MinorVersion", ColumnType.UInt16), + new ColumnLayout("BuildNumber", ColumnType.UInt16), + new ColumnLayout("RevisionNumber", ColumnType.UInt16), + new ColumnLayout("Flags", ColumnType.UInt32), + new ColumnLayout("PublicKeyOrToken", ColumnType.Blob, BlobIndexSize), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("Culture", ColumnType.String, StringIndexSize), + new ColumnLayout("HashValue", ColumnType.Blob, BlobIndexSize)), + + // AssemblyRefProcessor + new TableLayout( + new ColumnLayout("Processor", ColumnType.UInt32), + new ColumnLayout("AssemblyRef", ColumnType.AssemblyRef, GetColumnSize(ColumnType.AssemblyRef))), + + // AssemblyRefOS + new TableLayout( + new ColumnLayout("PlatformId", ColumnType.UInt32), + new ColumnLayout("MajorVersion", ColumnType.UInt32), + new ColumnLayout("MinorVersion", ColumnType.UInt32), + new ColumnLayout("AssemblyRef", ColumnType.AssemblyRef, GetColumnSize(ColumnType.AssemblyRef))), + + // File + new TableLayout( + new ColumnLayout("Flags", ColumnType.UInt32), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("HashValue", ColumnType.Blob, BlobIndexSize)), + + // ExportedType + new TableLayout( + new ColumnLayout("Flags", ColumnType.UInt32), + new ColumnLayout("TypeDefId", ColumnType.UInt32), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("Namespace", ColumnType.String, StringIndexSize), + new ColumnLayout("Implementation", ColumnType.Implementation, + GetColumnSize(ColumnType.Implementation))), + + // ManifestResource + new TableLayout( + new ColumnLayout("Offset", ColumnType.UInt32), + new ColumnLayout("Flags", ColumnType.UInt32), + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("Implementation", ColumnType.Implementation, + GetColumnSize(ColumnType.Implementation))), + + // NestedClass + new TableLayout( + new ColumnLayout("NestedClass", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)), + new ColumnLayout("EnclosingClass", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef))), + + // GenericParam + new TableLayout( + new ColumnLayout("Number", ColumnType.UInt16), + new ColumnLayout("Flags", ColumnType.UInt16), + new ColumnLayout("Owner", ColumnType.TypeOrMethodDef, GetColumnSize(ColumnType.TypeOrMethodDef)), + new ColumnLayout("EnclosingClass", ColumnType.String, StringIndexSize)), + + // MethodSpec + new TableLayout( + new ColumnLayout("Method", ColumnType.Method, GetColumnSize(ColumnType.MethodDefOrRef)), + new ColumnLayout("Instantiation", ColumnType.Blob, BlobIndexSize)), + + // GenericParamConstraint + new TableLayout( + new ColumnLayout("Owner", ColumnType.GenericParam, GetColumnSize(ColumnType.GenericParam)), + new ColumnLayout("Constraint", ColumnType.TypeDefOrRef, GetColumnSize(ColumnType.TypeDefOrRef))), + + // Unused + default, + default, + default, + + // Document + new TableLayout( + new ColumnLayout("Name", ColumnType.Blob, BlobIndexSize), + new ColumnLayout("HashAlgorithm", ColumnType.Guid, GuidIndexSize), + new ColumnLayout("Hash", ColumnType.Blob, BlobIndexSize), + new ColumnLayout("Language", ColumnType.Guid, GuidIndexSize)), + + // MethodDebugInformation + new TableLayout( + new ColumnLayout("Document", ColumnType.Document, GetColumnSize(ColumnType.Document)), + new ColumnLayout("SequencePoints", ColumnType.Blob, BlobIndexSize)), + + // LocalScope + new TableLayout( + new ColumnLayout("Method", ColumnType.Method, GetColumnSize(ColumnType.Method)), + new ColumnLayout("ImportScope", ColumnType.ImportScope, GetColumnSize(ColumnType.ImportScope)), + new ColumnLayout("VariableList", ColumnType.LocalVariable, GetColumnSize(ColumnType.LocalVariable)), + new ColumnLayout("ConstantList", ColumnType.LocalConstant, GetColumnSize(ColumnType.LocalConstant)), + new ColumnLayout("StartOffset", ColumnType.UInt32), + new ColumnLayout("Length", ColumnType.UInt32)), + + // LocalVariable + new TableLayout( + new ColumnLayout("Attributes", ColumnType.UInt16), + new ColumnLayout("Index", ColumnType.UInt16), + new ColumnLayout("VariableList", ColumnType.String, StringIndexSize)), + + // LocalConstant + new TableLayout( + new ColumnLayout("Name", ColumnType.String, StringIndexSize), + new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)), + + // ImportScope + new TableLayout( + new ColumnLayout("Parent", ColumnType.ImportScope, GetColumnSize(ColumnType.ImportScope)), + new ColumnLayout("Imports", ColumnType.Blob, BlobIndexSize)), + + // StateMachineMethod + new TableLayout( + new ColumnLayout("MoveNextMethod", ColumnType.Method, GetColumnSize(ColumnType.Method)), + new ColumnLayout("KickoffMethod", ColumnType.Method, GetColumnSize(ColumnType.Method))), + + // CustomDebugInformation + new TableLayout( + new ColumnLayout("Parent", ColumnType.HasCustomDebugInformation, + GetColumnSize(ColumnType.HasCustomDebugInformation)), + new ColumnLayout("Kind", ColumnType.Guid, GuidIndexSize), + new ColumnLayout("Value", ColumnType.Blob, BlobIndexSize)), + }; + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs index d8f673e46..67efc4806 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using AsmResolver.IO; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; @@ -9,7 +10,7 @@ namespace AsmResolver.PE.DotNet.Metadata.Tables /// /// Represents the metadata stream containing tables defining each member in a .NET assembly. /// - public class TablesStream : SegmentBase, IMetadataStream + public partial class TablesStream : SegmentBase, IMetadataStream { /// /// The default name of a table stream using the compressed format. @@ -32,7 +33,7 @@ public class TablesStream : SegmentBase, IMetadataStream public const string UncompressedStreamName = "#Schema"; private readonly Dictionary _indexEncoders; - private readonly LazyVariable> _tables; + private readonly LazyVariable> _tables; private readonly LazyVariable> _layouts; /// @@ -41,7 +42,7 @@ public class TablesStream : SegmentBase, IMetadataStream public TablesStream() { _layouts = new LazyVariable>(GetTableLayouts); - _tables = new LazyVariable>(GetTables); + _tables = new LazyVariable>(GetTables); _indexEncoders = CreateIndexEncoders(); } @@ -192,6 +193,29 @@ public uint ExtraData set; } + /// + /// Gets a value indicating whether the tables stream is assigned with row counts that originate from an + /// external .NET metadata file. + /// + /// + /// This value is typically set to false, except for Portable PDB metadata table streams. + /// + [MemberNotNullWhen(true, nameof(ExternalRowCounts))] + public bool HasExternalRowCounts => ExternalRowCounts is not null; + + /// + /// Gets or sets an array of row counts originating from an external .NET metadata file that this table stream + /// should consider when encoding indices. + /// + /// + /// This value is typically null, except for Portable PDB metadata table streams. + /// + public uint[]? ExternalRowCounts + { + get; + set; + } + /// /// Gets a collection of all tables in the tables stream. /// @@ -199,7 +223,7 @@ public uint ExtraData /// This collection always contains all tables, in the same order as defines, regardless /// of whether a table actually has elements or not. /// - protected IList Tables => _tables.Value; + protected IList Tables => _tables.Value; /// /// Gets the layout of all tables in the stream. @@ -209,6 +233,36 @@ public uint ExtraData /// public virtual BinaryStreamReader CreateReader() => throw new NotSupportedException(); + /// + /// Obtains the implied table row count for the provided table index. + /// + /// The table index. + /// The row count. + /// + /// This method takes any external row counts from into account. + /// + public uint GetTableRowCount(TableIndex table) + { + return HasExternalRowCounts && (int) table < ExternalRowCounts.Length + ? ExternalRowCounts[(int) table] + : (uint) GetTable(table).Count; + } + + /// + /// Obtains the implied table index size for the provided table index. + /// + /// The table index. + /// The index size. + /// + /// This method takes any external row counts from into account. + /// + public IndexSize GetTableIndexSize(TableIndex table) + { + return GetTableRowCount(table) > 0xFFFF + ? IndexSize.Long + : IndexSize.Short; + } + /// /// Updates the layouts of each metadata table, according to the property. /// @@ -220,7 +274,7 @@ protected void SynchronizeTableLayoutsWithFlags() { var layouts = GetTableLayouts(); for (int i = 0; i < Tables.Count; i++) - Tables[i].UpdateTableLayout(layouts[i]); + Tables[i]?.UpdateTableLayout(layouts[i]); } /// @@ -270,11 +324,11 @@ public override void Write(IBinaryStreamWriter writer) protected virtual ulong ComputeValidBitmask() { // TODO: make more configurable (maybe add IMetadataTable.IsPresent property?). - ulong result = 0; + for (int i = 0; i < Tables.Count; i++) { - if (Tables[i].Count > 0) + if (Tables[i]?.Count > 0) result |= 1UL << i; } @@ -288,9 +342,40 @@ protected virtual ulong ComputeValidBitmask() /// The valid bitmask. protected virtual ulong ComputeSortedBitmask() { - // TODO: make more configurable (maybe add IMetadataTable.IsSorted property?). + ulong result = 0; + + bool containsTypeSystemData = false; + bool containsPdbData = false; + + // Determine which tables are marked as sorted. + for (int i = 0; i < Tables.Count; i++) + { + if (Tables[i] is not { } table) + continue; + + if (table.IsSorted) + result |= 1UL << i; - return 0x000016003301FA00; + if (table.Count > 0) + { + if (i <= (int) TableIndex.MaxTypeSystemTableIndex) + containsTypeSystemData = true; + else + containsPdbData = true; + } + } + + const ulong typeSystemMask = (1UL << (int) TableIndex.MaxTypeSystemTableIndex + 1) - 1; + const ulong pdbMask = ((1UL << (int) TableIndex.Max) - 1) & ~typeSystemMask; + + // Backwards compatibility: Ensure that only the bits are set in the sorted mask if the metadata + // actually contains the type system and/or pdb tables. + if (!containsTypeSystemData) + result &= ~typeSystemMask; + if (!containsPdbData) + result &= ~pdbMask; + + return result; } /// @@ -301,7 +386,7 @@ protected virtual ulong ComputeSortedBitmask() protected virtual int GetTablesCount(ulong validBitmask) { int count = 0; - for (TableIndex i = 0; i < (TableIndex) Tables.Count; i++) + for (TableIndex i = 0; i < TableIndex.Max; i++) { if (HasTable(validBitmask, i)) count++; @@ -337,7 +422,7 @@ protected virtual uint GetTablesSize(ulong validBitmask) /// The valid bitmask, indicating all present tables in the stream. protected virtual void WriteRowCounts(IBinaryStreamWriter writer, ulong validBitmask) { - for (TableIndex i = 0; i < (TableIndex) Tables.Count; i++) + for (TableIndex i = 0; i <= TableIndex.Max; i++) { if (HasTable(validBitmask, i)) writer.WriteInt32(GetTable(i).Count); @@ -380,111 +465,13 @@ protected static bool IsSorted(ulong sortedMask, TableIndex table) return ((sortedMask >> (int) table) & 1) != 0; } - /// - /// Obtains the collection of tables in the tables stream. - /// - /// The tables, including empty tables if there are any. - /// - /// This method is called upon initialization of the property. - /// - protected virtual IList GetTables() - { - var layouts = TableLayouts; - return new IMetadataTable[] - { - new MetadataTable(TableIndex.Module, layouts[0]), - new MetadataTable(TableIndex.TypeRef, layouts[1]), - new MetadataTable(TableIndex.TypeDef, layouts[2]), - new MetadataTable(TableIndex.FieldPtr, layouts[3]), - new MetadataTable(TableIndex.Field, layouts[4]), - new MetadataTable(TableIndex.Method, layouts[5]), - new MetadataTable(TableIndex.Method, layouts[6]), - new MetadataTable(TableIndex.ParamPtr, layouts[7]), - new MetadataTable(TableIndex.Param, layouts[8]), - new MetadataTable(TableIndex.InterfaceImpl, layouts[9]), - new MetadataTable(TableIndex.MemberRef, layouts[10]), - new MetadataTable(TableIndex.Constant, layouts[11]), - new MetadataTable(TableIndex.CustomAttribute, layouts[12]), - new MetadataTable(TableIndex.FieldMarshal, layouts[13]), - new MetadataTable(TableIndex.DeclSecurity, layouts[14]), - new MetadataTable(TableIndex.ClassLayout, layouts[15]), - new MetadataTable(TableIndex.FieldLayout, layouts[16]), - new MetadataTable(TableIndex.StandAloneSig, layouts[17]), - new MetadataTable(TableIndex.EventMap, layouts[18]), - new MetadataTable(TableIndex.EventPtr, layouts[19]), - new MetadataTable(TableIndex.Event, layouts[20]), - new MetadataTable(TableIndex.PropertyMap, layouts[21]), - new MetadataTable(TableIndex.PropertyPtr, layouts[22]), - new MetadataTable(TableIndex.Property, layouts[23]), - new MetadataTable(TableIndex.MethodSemantics, layouts[24]), - new MetadataTable(TableIndex.MethodImpl, layouts[25]), - new MetadataTable(TableIndex.ModuleRef, layouts[26]), - new MetadataTable(TableIndex.TypeSpec, layouts[27]), - new MetadataTable(TableIndex.ImplMap, layouts[28]), - new MetadataTable(TableIndex.FieldRva, layouts[29]), - new MetadataTable(TableIndex.EncLog, layouts[30]), - new MetadataTable(TableIndex.EncMap, layouts[31]), - new MetadataTable(TableIndex.Assembly, layouts[32]), - new MetadataTable(TableIndex.AssemblyProcessor, layouts[33]), - new MetadataTable(TableIndex.AssemblyOS, layouts[34]), - new MetadataTable(TableIndex.AssemblyRef, layouts[35]), - new MetadataTable(TableIndex.AssemblyRefProcessor, layouts[36]), - new MetadataTable(TableIndex.AssemblyRefProcessor, layouts[37]), - new MetadataTable(TableIndex.File, layouts[38]), - new MetadataTable(TableIndex.ExportedType, layouts[39]), - new MetadataTable(TableIndex.ManifestResource, layouts[40]), - new MetadataTable(TableIndex.NestedClass, layouts[41]), - new MetadataTable(TableIndex.GenericParam, layouts[42]), - new MetadataTable(TableIndex.MethodSpec, layouts[43]), - new MetadataTable(TableIndex.GenericParamConstraint, layouts[44]), - }; - } - - private Dictionary CreateIndexEncoders() - { - return new() - { - [CodedIndex.TypeDefOrRef] = new IndexEncoder(this, - TableIndex.TypeDef, TableIndex.TypeRef, TableIndex.TypeSpec), - [CodedIndex.HasConstant] = new(this, - TableIndex.Field, TableIndex.Param, TableIndex.Property), - [CodedIndex.HasCustomAttribute] = new(this, - TableIndex.Method, TableIndex.Field, TableIndex.TypeRef, TableIndex.TypeDef, - TableIndex.Param, TableIndex.InterfaceImpl, TableIndex.MemberRef, TableIndex.Module, - TableIndex.DeclSecurity, TableIndex.Property, TableIndex.Event, TableIndex.StandAloneSig, - TableIndex.ModuleRef, TableIndex.TypeSpec, TableIndex.Assembly, TableIndex.AssemblyRef, - TableIndex.File, TableIndex.ExportedType, TableIndex.ManifestResource, TableIndex.GenericParam, - TableIndex.GenericParamConstraint, TableIndex.MethodSpec), - [CodedIndex.HasFieldMarshal] = new(this, - TableIndex.Field, TableIndex.Param), - [CodedIndex.HasDeclSecurity] = new(this, - TableIndex.TypeDef, TableIndex.Method, TableIndex.Assembly), - [CodedIndex.MemberRefParent] = new(this, - TableIndex.TypeDef, TableIndex.TypeRef, TableIndex.ModuleRef, - TableIndex.Method, TableIndex.TypeSpec), - [CodedIndex.HasSemantics] = new(this, - TableIndex.Event, TableIndex.Property), - [CodedIndex.MethodDefOrRef] = new(this, - TableIndex.Method, TableIndex.MemberRef), - [CodedIndex.MemberForwarded] = new(this, - TableIndex.Field, TableIndex.Method), - [CodedIndex.Implementation] = new(this, - TableIndex.File, TableIndex.AssemblyRef, TableIndex.ExportedType), - [CodedIndex.CustomAttributeType] = new(this, - 0, 0, TableIndex.Method, TableIndex.MemberRef, 0), - [CodedIndex.ResolutionScope] = new(this, - TableIndex.Module, TableIndex.ModuleRef, TableIndex.AssemblyRef, TableIndex.TypeRef), - [CodedIndex.TypeOrMethodDef] = new(this, - TableIndex.TypeDef, TableIndex.Method) - }; - } - /// /// Gets a table by its table index. /// /// The table index. /// The table. - public virtual IMetadataTable GetTable(TableIndex index) => Tables[(int) index]; + public virtual IMetadataTable GetTable(TableIndex index) => + Tables[(int) index] ?? throw new ArgumentOutOfRangeException(nameof(index)); /// /// Gets a table by its row type. @@ -506,7 +493,7 @@ public virtual MetadataTable GetTable() public virtual MetadataTable GetTable(TableIndex index) where TRow : struct, IMetadataRow { - return (MetadataTable) Tables[(int) index]; + return (MetadataTable) (Tables[(int) index] ?? throw new ArgumentOutOfRangeException(nameof(index))); } private IndexSize GetStreamIndexSize(int bitIndex) => (IndexSize) (((((int) Flags >> bitIndex) & 1) + 1) * 2); @@ -526,29 +513,25 @@ protected virtual uint GetColumnSize(ColumnType columnType) { if (_layouts.IsInitialized) { - if (columnType <= ColumnType.GenericParamConstraint) - return (uint) Tables[(int) columnType].IndexSize; - if (columnType <= ColumnType.TypeOrMethodDef) - return (uint) GetIndexEncoder((CodedIndex) columnType).IndexSize; + switch (columnType) + { + case <= ColumnType.CustomDebugInformation: + return (uint) GetTableIndexSize((TableIndex) columnType); + case <= ColumnType.HasCustomDebugInformation: + return (uint) GetIndexEncoder((CodedIndex) columnType).IndexSize; + } } - switch (columnType) + return columnType switch { - case ColumnType.Blob: - return (uint) BlobIndexSize; - case ColumnType.String: - return (uint) StringIndexSize; - case ColumnType.Guid: - return (uint) GuidIndexSize; - case ColumnType.Byte: - return sizeof(byte); - case ColumnType.UInt16: - return sizeof(ushort); - case ColumnType.UInt32: - return sizeof(uint); - default: - return sizeof(uint); - } + ColumnType.Blob => (uint) BlobIndexSize, + ColumnType.String => (uint) StringIndexSize, + ColumnType.Guid => (uint) GuidIndexSize, + ColumnType.Byte => sizeof(byte), + ColumnType.UInt16 => sizeof(ushort), + ColumnType.UInt32 => sizeof(uint), + _ => sizeof(uint) + }; } /// @@ -558,197 +541,6 @@ protected virtual uint GetColumnSize(ColumnType columnType) /// The encoder. public IndexEncoder GetIndexEncoder(CodedIndex index) => _indexEncoders[index]; - /// - /// Gets an ordered collection of the current table layouts. - /// - /// The table layouts. - protected TableLayout[] GetTableLayouts() - { - var result = new[] - { - new TableLayout( - new ColumnLayout("Generation", ColumnType.UInt16), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("Mvid", ColumnType.Guid, GuidIndexSize), - new ColumnLayout("EncId", ColumnType.Guid, GuidIndexSize), - new ColumnLayout("EncBaseId", ColumnType.Guid, GuidIndexSize)), - new TableLayout( - new ColumnLayout("ResolutionScope", ColumnType.ResolutionScope, - GetColumnSize(ColumnType.ResolutionScope)), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("Namespace", ColumnType.Guid, StringIndexSize)), - new TableLayout( - new ColumnLayout("Flags", ColumnType.UInt32), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("Namespace", ColumnType.String, StringIndexSize), - new ColumnLayout("Extends", ColumnType.TypeDefOrRef, - GetColumnSize(ColumnType.TypeDefOrRef)), - new ColumnLayout("FieldList", ColumnType.Field, GetColumnSize(ColumnType.Field)), - new ColumnLayout("MethodList", ColumnType.Method, GetColumnSize(ColumnType.Method))), - new TableLayout( - new ColumnLayout("Field", ColumnType.Field, GetColumnSize(ColumnType.Field))), - new TableLayout( - new ColumnLayout("Flags", ColumnType.UInt16), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("Method", ColumnType.Method, GetColumnSize(ColumnType.Method))), - new TableLayout( - new ColumnLayout("RVA", ColumnType.UInt32), - new ColumnLayout("ImplFlags", ColumnType.UInt16), - new ColumnLayout("Flags", ColumnType.UInt16), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize), - new ColumnLayout("ParamList", ColumnType.Param, GetColumnSize(ColumnType.Param))), - new TableLayout( - new ColumnLayout("Parameter", ColumnType.Param, GetColumnSize(ColumnType.Param))), - new TableLayout( - new ColumnLayout("Flags", ColumnType.UInt16), - new ColumnLayout("Sequence", ColumnType.UInt16), - new ColumnLayout("Name", ColumnType.String, StringIndexSize)), - new TableLayout( - new ColumnLayout("Class", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)), - new ColumnLayout("Interface", ColumnType.TypeDefOrRef, GetColumnSize(ColumnType.TypeDefOrRef))), - new TableLayout( - new ColumnLayout("Parent", ColumnType.MemberRefParent, GetColumnSize(ColumnType.MemberRefParent)), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("Type", ColumnType.Byte), - new ColumnLayout("Padding", ColumnType.Byte), - new ColumnLayout("Parent", ColumnType.HasConstant, GetColumnSize(ColumnType.HasConstant)), - new ColumnLayout("Value", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("Parent", ColumnType.HasCustomAttribute, GetColumnSize(ColumnType.HasCustomAttribute)), - new ColumnLayout("Type", ColumnType.CustomAttributeType, GetColumnSize(ColumnType.CustomAttributeType)), - new ColumnLayout("Value", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("Parent", ColumnType.HasFieldMarshal, GetColumnSize(ColumnType.HasFieldMarshal)), - new ColumnLayout("NativeType", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("Action", ColumnType.UInt16), - new ColumnLayout("Parent", ColumnType.HasDeclSecurity, GetColumnSize(ColumnType.HasDeclSecurity)), - new ColumnLayout("PermissionSet", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("PackingSize", ColumnType.UInt16), - new ColumnLayout("ClassSize", ColumnType.UInt32), - new ColumnLayout("Parent", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef))), - new TableLayout( - new ColumnLayout("Offset", ColumnType.UInt32), - new ColumnLayout("Field", ColumnType.TypeDef, GetColumnSize(ColumnType.Field))), - new TableLayout( - new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("Parent", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)), - new ColumnLayout("EventList", ColumnType.Event, GetColumnSize(ColumnType.Event))), - new TableLayout( - new ColumnLayout("Event", ColumnType.Event, GetColumnSize(ColumnType.Event))), - new TableLayout( - new ColumnLayout("Flags", ColumnType.UInt16), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("EventType", ColumnType.TypeDefOrRef, GetColumnSize(ColumnType.TypeDefOrRef))), - new TableLayout( - new ColumnLayout("Parent", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)), - new ColumnLayout("PropertyList", ColumnType.Event, GetColumnSize(ColumnType.Property))), - new TableLayout( - new ColumnLayout("Property", ColumnType.Property, GetColumnSize(ColumnType.Property))), - new TableLayout( - new ColumnLayout("Flags", ColumnType.UInt16), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("PropertyType", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("Semantic", ColumnType.UInt16), - new ColumnLayout("Method", ColumnType.Method, GetColumnSize(ColumnType.Method)), - new ColumnLayout("Association", ColumnType.HasSemantics, GetColumnSize(ColumnType.HasSemantics))), - new TableLayout( - new ColumnLayout("Class", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)), - new ColumnLayout("MethodBody", ColumnType.MethodDefOrRef, GetColumnSize(ColumnType.MethodDefOrRef)), - new ColumnLayout("MethodDeclaration", ColumnType.MethodDefOrRef, GetColumnSize(ColumnType.MethodDefOrRef))), - new TableLayout( - new ColumnLayout("Name", ColumnType.String, StringIndexSize)), - new TableLayout( - new ColumnLayout("Signature", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("MappingFlags", ColumnType.UInt16), - new ColumnLayout("MemberForwarded", ColumnType.MemberForwarded, GetColumnSize(ColumnType.MemberForwarded)), - new ColumnLayout("ImportName", ColumnType.String, StringIndexSize), - new ColumnLayout("ImportScope", ColumnType.ModuleRef, GetColumnSize(ColumnType.ModuleRef))), - new TableLayout( - new ColumnLayout("RVA", ColumnType.UInt32), - new ColumnLayout("Field", ColumnType.Field, GetColumnSize(ColumnType.Field))), - new TableLayout( - new ColumnLayout("Token", ColumnType.UInt32), - new ColumnLayout("FuncCode", ColumnType.UInt32)), - new TableLayout( - new ColumnLayout("Token", ColumnType.UInt32)), - new TableLayout( - new ColumnLayout("HashAlgId", ColumnType.UInt32), - new ColumnLayout("MajorVersion", ColumnType.UInt16), - new ColumnLayout("MinorVersion", ColumnType.UInt16), - new ColumnLayout("BuildNumber", ColumnType.UInt16), - new ColumnLayout("RevisionNumber", ColumnType.UInt16), - new ColumnLayout("Flags", ColumnType.UInt32), - new ColumnLayout("PublicKey", ColumnType.Blob, BlobIndexSize), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("Culture", ColumnType.String, StringIndexSize)), - new TableLayout( - new ColumnLayout("Processor", ColumnType.UInt32)), - new TableLayout( - new ColumnLayout("PlatformId", ColumnType.UInt32), - new ColumnLayout("MajorVersion", ColumnType.UInt32), - new ColumnLayout("MinorVersion", ColumnType.UInt32)), - new TableLayout( - new ColumnLayout("MajorVersion", ColumnType.UInt16), - new ColumnLayout("MinorVersion", ColumnType.UInt16), - new ColumnLayout("BuildNumber", ColumnType.UInt16), - new ColumnLayout("RevisionNumber", ColumnType.UInt16), - new ColumnLayout("Flags", ColumnType.UInt32), - new ColumnLayout("PublicKeyOrToken", ColumnType.Blob, BlobIndexSize), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("Culture", ColumnType.String, StringIndexSize), - new ColumnLayout("HashValue", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("Processor", ColumnType.UInt32), - new ColumnLayout("AssemblyRef", ColumnType.AssemblyRef, GetColumnSize(ColumnType.AssemblyRef))), - new TableLayout( - new ColumnLayout("PlatformId", ColumnType.UInt32), - new ColumnLayout("MajorVersion", ColumnType.UInt32), - new ColumnLayout("MinorVersion", ColumnType.UInt32), - new ColumnLayout("AssemblyRef", ColumnType.AssemblyRef, GetColumnSize(ColumnType.AssemblyRef))), - new TableLayout( - new ColumnLayout("Flags", ColumnType.UInt32), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("HashValue", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("Flags", ColumnType.UInt32), - new ColumnLayout("TypeDefId", ColumnType.UInt32), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("Namespace", ColumnType.String, StringIndexSize), - new ColumnLayout("Implementation", ColumnType.Implementation, GetColumnSize(ColumnType.Implementation))), - new TableLayout( - new ColumnLayout("Offset", ColumnType.UInt32), - new ColumnLayout("Flags", ColumnType.UInt32), - new ColumnLayout("Name", ColumnType.String, StringIndexSize), - new ColumnLayout("Implementation", ColumnType.Implementation, GetColumnSize(ColumnType.Implementation))), - new TableLayout( - new ColumnLayout("NestedClass", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef)), - new ColumnLayout("EnclosingClass", ColumnType.TypeDef, GetColumnSize(ColumnType.TypeDef))), - new TableLayout( - new ColumnLayout("Number", ColumnType.UInt16), - new ColumnLayout("Flags", ColumnType.UInt16), - new ColumnLayout("Owner", ColumnType.TypeOrMethodDef, GetColumnSize(ColumnType.TypeOrMethodDef)), - new ColumnLayout("EnclosingClass", ColumnType.String, StringIndexSize)), - new TableLayout( - new ColumnLayout("Method", ColumnType.Method, GetColumnSize(ColumnType.MethodDefOrRef)), - new ColumnLayout("Instantiation", ColumnType.Blob, BlobIndexSize)), - new TableLayout( - new ColumnLayout("Owner", ColumnType.GenericParam, GetColumnSize(ColumnType.GenericParam)), - new ColumnLayout("Constraint", ColumnType.TypeDefOrRef, GetColumnSize(ColumnType.TypeDefOrRef))), - }; - - return result; - } - /// /// Gets the range of metadata tokens referencing fields that a type defines. /// @@ -818,10 +610,10 @@ protected TableLayout[] GetTableLayouts() // Check if redirect table is present. var redirectTable = GetTable(redirectTableIndex); if (redirectTable.Count > 0) - return new RedirectedMetadataRange(redirectTable, memberTableIndex, startRid, endRid); + return new MetadataRange(redirectTable, memberTableIndex, startRid, endRid); // If not, its a simple range. - return new ContinuousMetadataRange(memberTableIndex, startRid, endRid); + return new MetadataRange(memberTableIndex, startRid, endRid); } } } diff --git a/src/AsmResolver.PE/DotNet/Metadata/UserStrings/SerializedUserStringsStream.cs b/src/AsmResolver.PE/DotNet/Metadata/UserStrings/SerializedUserStringsStream.cs index b50077898..b86bdafb8 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/UserStrings/SerializedUserStringsStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/UserStrings/SerializedUserStringsStream.cs @@ -17,7 +17,7 @@ public class SerializedUserStringsStream : UserStringsStream /// /// The raw contents of the stream. public SerializedUserStringsStream(byte[] rawData) - : this(DefaultName, ByteArrayDataSource.CreateReader(rawData)) + : this(DefaultName, new BinaryStreamReader(rawData)) { } @@ -27,7 +27,7 @@ public SerializedUserStringsStream(byte[] rawData) /// The name of the stream. /// The raw contents of the stream. public SerializedUserStringsStream(string name, byte[] rawData) - : this(name, ByteArrayDataSource.CreateReader(rawData)) + : this(name, new BinaryStreamReader(rawData)) { } diff --git a/src/AsmResolver.PE/DotNet/SerializedDotNetDirectory.cs b/src/AsmResolver.PE/DotNet/SerializedDotNetDirectory.cs index c75dd2a25..203c6e485 100644 --- a/src/AsmResolver.PE/DotNet/SerializedDotNetDirectory.cs +++ b/src/AsmResolver.PE/DotNet/SerializedDotNetDirectory.cs @@ -39,7 +39,7 @@ public SerializedDotNetDirectory(PEReaderContext context, ref BinaryStreamReader MinorRuntimeVersion = reader.ReadUInt16(); _metadataDirectory = DataDirectory.FromReader(ref reader); Flags = (DotNetDirectoryFlags) reader.ReadUInt32(); - Entrypoint = reader.ReadUInt32(); + EntryPoint = reader.ReadUInt32(); _resourcesDirectory = DataDirectory.FromReader(ref reader); _strongNameDirectory = DataDirectory.FromReader(ref reader); _codeManagerDirectory = DataDirectory.FromReader(ref reader); @@ -60,8 +60,7 @@ public SerializedDotNetDirectory(PEReaderContext context, ref BinaryStreamReader return null; } - return new SerializedMetadata(_context, ref directoryReader); - + return DotNet.Metadata.Metadata.FromReader(directoryReader, MetadataReaderContext.FromReaderContext(_context)); } /// @@ -126,7 +125,7 @@ public SerializedDotNetDirectory(PEReaderContext context, ref BinaryStreamReader } var vtables = new VTableFixupsDirectory(); - vtables.UpdateOffsets(directoryReader.Offset, directoryReader.Rva); + vtables.UpdateOffsets(_context.GetRelocation(directoryReader.Offset, directoryReader.Rva)); for (int i = 0; i < directoryReader.Length / 8; i++) { diff --git a/src/AsmResolver.PE/DotNet/StrongName/StrongNamePrivateKey.cs b/src/AsmResolver.PE/DotNet/StrongName/StrongNamePrivateKey.cs index dc517f811..d004e5059 100644 --- a/src/AsmResolver.PE/DotNet/StrongName/StrongNamePrivateKey.cs +++ b/src/AsmResolver.PE/DotNet/StrongName/StrongNamePrivateKey.cs @@ -22,7 +22,7 @@ public class StrongNamePrivateKey : StrongNamePublicKey /// Occurs when an invalid or unsupported algorithm is specified. public new static StrongNamePrivateKey FromFile(string path) { - var reader = ByteArrayDataSource.CreateReader(System.IO.File.ReadAllBytes(path)); + var reader = new BinaryStreamReader(System.IO.File.ReadAllBytes(path)); return FromReader(ref reader); } diff --git a/src/AsmResolver.PE/DotNet/StrongName/StrongNamePublicKey.cs b/src/AsmResolver.PE/DotNet/StrongName/StrongNamePublicKey.cs index ae410cbb8..cc47acef3 100644 --- a/src/AsmResolver.PE/DotNet/StrongName/StrongNamePublicKey.cs +++ b/src/AsmResolver.PE/DotNet/StrongName/StrongNamePublicKey.cs @@ -24,7 +24,7 @@ public class StrongNamePublicKey : StrongNameKeyStructure /// Occurs when an invalid or unsupported algorithm is specified. public static StrongNamePublicKey FromFile(string path) { - var reader = ByteArrayDataSource.CreateReader(System.IO.File.ReadAllBytes(path)); + var reader = new BinaryStreamReader(System.IO.File.ReadAllBytes(path)); return FromReader(ref reader); } diff --git a/src/AsmResolver.PE/DotNet/StrongName/StrongNameSigner.cs b/src/AsmResolver.PE/DotNet/StrongName/StrongNameSigner.cs index 7dc1e3c1a..c2679ced9 100644 --- a/src/AsmResolver.PE/DotNet/StrongName/StrongNameSigner.cs +++ b/src/AsmResolver.PE/DotNet/StrongName/StrongNameSigner.cs @@ -101,7 +101,7 @@ public void SignImage(Stream imageStream, AssemblyHashAlgorithm hashAlgorithm) hashBuilder.ZeroRange(new OffsetRange(peChecksumOffset, peChecksumOffset + sizeof(uint))); // Zero certificate directory entry. - uint optionalHeaderSize = file.OptionalHeader.Magic == OptionalHeaderMagic.Pe32 + uint optionalHeaderSize = file.OptionalHeader.Magic == OptionalHeaderMagic.PE32 ? OptionalHeader.OptionalHeader32SizeExcludingDataDirectories : OptionalHeader.OptionalHeader64SizeExcludingDataDirectories; ulong certificateEntryOffset = file.OptionalHeader.Offset diff --git a/src/AsmResolver.PE/DotNet/VTableFixups/VTableFixup.cs b/src/AsmResolver.PE/DotNet/VTableFixups/VTableFixup.cs index 97d19c050..4b2ebcbe4 100644 --- a/src/AsmResolver.PE/DotNet/VTableFixups/VTableFixup.cs +++ b/src/AsmResolver.PE/DotNet/VTableFixups/VTableFixup.cs @@ -41,8 +41,8 @@ public VTableTokenCollection Tokens ushort entries = reader.ReadUInt16(); var vtable = new VTableFixup((VTableType) reader.ReadUInt16()); - vtable.UpdateOffsets(offset, rva); - vtable.Tokens.UpdateOffsets(tableReader.Offset, tableReader.Rva); + vtable.UpdateOffsets(context.GetRelocation(offset, rva)); + vtable.Tokens.UpdateOffsets(context.GetRelocation(tableReader.Offset, tableReader.Rva)); for (int i = 0; i < entries; i++) { diff --git a/src/AsmResolver.PE/DotNet/VTableFixups/VTableFixupsDirectory.cs b/src/AsmResolver.PE/DotNet/VTableFixups/VTableFixupsDirectory.cs index d5c9e9ae2..acf7c4db5 100644 --- a/src/AsmResolver.PE/DotNet/VTableFixups/VTableFixupsDirectory.cs +++ b/src/AsmResolver.PE/DotNet/VTableFixups/VTableFixupsDirectory.cs @@ -27,10 +27,10 @@ public uint Rva public bool CanUpdateOffsets => true; /// - public void UpdateOffsets(ulong newOffset, uint newRva) + public void UpdateOffsets(in RelocationParameters parameters) { - Offset = newOffset; - Rva = newRva; + Offset = parameters.Offset; + Rva = parameters.Rva; } /// diff --git a/src/AsmResolver.PE/DotNet/VTableFixups/VTableTokenCollection.cs b/src/AsmResolver.PE/DotNet/VTableFixups/VTableTokenCollection.cs index 27f8998ac..4420f1fc2 100644 --- a/src/AsmResolver.PE/DotNet/VTableFixups/VTableTokenCollection.cs +++ b/src/AsmResolver.PE/DotNet/VTableFixups/VTableTokenCollection.cs @@ -45,10 +45,10 @@ protected override void InsertItem(int index, MetadataToken item) } /// - public void UpdateOffsets(ulong newOffset, uint newRva) + public void UpdateOffsets(in RelocationParameters parameters) { - Offset = newOffset; - Rva = newRva; + Offset = parameters.Offset; + Rva = parameters.Rva; } /// diff --git a/src/AsmResolver.PE/Exceptions/X64/X64UnwindInfo.cs b/src/AsmResolver.PE/Exceptions/X64/X64UnwindInfo.cs index 4074e0bdf..d00d712bf 100644 --- a/src/AsmResolver.PE/Exceptions/X64/X64UnwindInfo.cs +++ b/src/AsmResolver.PE/Exceptions/X64/X64UnwindInfo.cs @@ -1,5 +1,6 @@ using System; using AsmResolver.IO; +using AsmResolver.PE.File.Headers; namespace AsmResolver.PE.Exceptions.X64 { @@ -151,7 +152,7 @@ public ISegmentReference ExceptionHandlerData public static X64UnwindInfo FromReader(PEReaderContext context, ref BinaryStreamReader reader) { var result = new X64UnwindInfo(); - result.UpdateOffsets(reader.Offset, reader.Rva); + result.UpdateOffsets(context.GetRelocation(reader.Offset, reader.Rva)); result._firstByte = reader.ReadByte(); result.SizeOfProlog = reader.ReadByte(); diff --git a/src/AsmResolver.PE/Exports/Builder/ExportDirectoryBuffer.cs b/src/AsmResolver.PE/Exports/Builder/ExportDirectoryBuffer.cs index 8bf5526fd..8409daf39 100644 --- a/src/AsmResolver.PE/Exports/Builder/ExportDirectoryBuffer.cs +++ b/src/AsmResolver.PE/Exports/Builder/ExportDirectoryBuffer.cs @@ -83,10 +83,10 @@ public void AddDirectory(IExportDirectory exportDirectory) } /// - public override void UpdateOffsets(ulong newOffset, uint newRva) + public override void UpdateOffsets(in RelocationParameters parameters) { - base.UpdateOffsets(newOffset, newRva); - _contentsBuilder.UpdateOffsets(newOffset + ExportDirectoryHeaderSize, newRva + ExportDirectoryHeaderSize); + base.UpdateOffsets(parameters); + _contentsBuilder.UpdateOffsets(parameters.WithAdvance(ExportDirectoryHeaderSize)); } /// diff --git a/src/AsmResolver.PE/Imports/Builder/HintNameTableBuffer.cs b/src/AsmResolver.PE/Imports/Builder/HintNameTableBuffer.cs index f2a94395f..62275e716 100644 --- a/src/AsmResolver.PE/Imports/Builder/HintNameTableBuffer.cs +++ b/src/AsmResolver.PE/Imports/Builder/HintNameTableBuffer.cs @@ -15,23 +15,23 @@ public class HintNameTableBuffer : SegmentBase private uint _length; /// - public override void UpdateOffsets(ulong newOffset, uint newRva) + public override void UpdateOffsets(in RelocationParameters parameters) { - base.UpdateOffsets(newOffset, newRva); + base.UpdateOffsets(parameters); - ulong currentOffset = newOffset; + ulong currentOffset = parameters.Offset; foreach (var module in _modules) { foreach (var entry in module.Symbols) { if (entry.IsImportByName) { - _hintNameOffsets[entry] = (uint) (currentOffset - newOffset); + _hintNameOffsets[entry] = (uint) (currentOffset - parameters.Offset); currentOffset += (uint) (sizeof(ushort) + Encoding.ASCII.GetByteCount(entry.Name) + 1); currentOffset = currentOffset.Align(2); } } - _moduleNameOffsets[module] = (uint) (currentOffset - newOffset); + _moduleNameOffsets[module] = (uint) (currentOffset - parameters.Offset); if (module.Name is not null) currentOffset += (uint) Encoding.ASCII.GetByteCount(module.Name); @@ -39,7 +39,7 @@ public override void UpdateOffsets(ulong newOffset, uint newRva) currentOffset++; } - _length = (uint) (currentOffset - newOffset); + _length = (uint) (currentOffset - parameters.Offset); } /// diff --git a/src/AsmResolver.PE/Imports/Builder/ImportAddressDirectoryBuffer.cs b/src/AsmResolver.PE/Imports/Builder/ImportAddressDirectoryBuffer.cs index 7bcc57074..8b24c41d0 100644 --- a/src/AsmResolver.PE/Imports/Builder/ImportAddressDirectoryBuffer.cs +++ b/src/AsmResolver.PE/Imports/Builder/ImportAddressDirectoryBuffer.cs @@ -17,17 +17,19 @@ public ImportAddressDirectoryBuffer(HintNameTableBuffer hintNameTable, bool is32 } /// - public override void UpdateOffsets(ulong newOffset, uint newRva) + public override void UpdateOffsets(in RelocationParameters parameters) { - base.UpdateOffsets(newOffset, newRva); + base.UpdateOffsets(parameters); - foreach (var module in Modules) + var current = parameters; + for (int i = 0; i < Modules.Count; i++) { + var module = Modules[i]; + var thunkTable = GetModuleThunkTable(module); uint size = thunkTable.GetPhysicalSize(); - thunkTable.UpdateOffsets(newOffset, newRva); - newOffset += size; - newRva += size; + thunkTable.UpdateOffsets(current); + current.Advance(size); } } diff --git a/src/AsmResolver.PE/Imports/Builder/ImportDirectoryBuffer.cs b/src/AsmResolver.PE/Imports/Builder/ImportDirectoryBuffer.cs index bda2b1f17..5bf7adb61 100644 --- a/src/AsmResolver.PE/Imports/Builder/ImportDirectoryBuffer.cs +++ b/src/AsmResolver.PE/Imports/Builder/ImportDirectoryBuffer.cs @@ -42,23 +42,22 @@ public override void AddModule(IImportedModule module) } /// - public override void UpdateOffsets(ulong newOffset, uint newRva) + public override void UpdateOffsets(in RelocationParameters parameters) { - base.UpdateOffsets(newOffset, newRva); + base.UpdateOffsets(parameters); - newOffset += _entriesLength; - newRva += _entriesLength; + var current = parameters.WithAdvance(_entriesLength); foreach (var module in Modules) { var thunkTable = GetModuleThunkTable(module); uint size = thunkTable.GetPhysicalSize(); - thunkTable.UpdateOffsets(newOffset, newRva); - newOffset += size; - newRva += size; + thunkTable.UpdateOffsets(current); + + current.Advance(size); } - HintNameTable.UpdateOffsets(newOffset, newRva); + HintNameTable.UpdateOffsets(current); } /// diff --git a/src/AsmResolver.PE/Imports/SerializedImportedModule.cs b/src/AsmResolver.PE/Imports/SerializedImportedModule.cs index c2a4c5e9b..da74d77c8 100644 --- a/src/AsmResolver.PE/Imports/SerializedImportedModule.cs +++ b/src/AsmResolver.PE/Imports/SerializedImportedModule.cs @@ -68,7 +68,7 @@ protected override IList GetSymbols() if (IsEmpty) return result; - bool is32Bit = _context.File.OptionalHeader.Magic == OptionalHeaderMagic.Pe32; + bool is32Bit = _context.File.OptionalHeader.Magic == OptionalHeaderMagic.PE32; (ulong ordinalMask, int pointerSize) = is32Bit ? (0x8000_0000ul, sizeof(uint)) : (0x8000_0000_0000_0000ul, sizeof(ulong)); diff --git a/src/AsmResolver.PE/PEImage.cs b/src/AsmResolver.PE/PEImage.cs index 814ef7926..f1e61fbad 100644 --- a/src/AsmResolver.PE/PEImage.cs +++ b/src/AsmResolver.PE/PEImage.cs @@ -209,7 +209,7 @@ public OptionalHeaderMagic PEKind { get; set; - } = OptionalHeaderMagic.Pe32; + } = OptionalHeaderMagic.PE32; /// public SubSystem SubSystem diff --git a/src/AsmResolver.PE/PEReaderContext.cs b/src/AsmResolver.PE/PEReaderContext.cs index 300247911..ec20bdc7f 100644 --- a/src/AsmResolver.PE/PEReaderContext.cs +++ b/src/AsmResolver.PE/PEReaderContext.cs @@ -1,6 +1,7 @@ using System; using System.Runtime.CompilerServices; using AsmResolver.PE.File; +using AsmResolver.PE.File.Headers; namespace AsmResolver.PE { @@ -29,7 +30,7 @@ public PEReaderContext(IPEFile file, PEReaderParameters parameters) File = file; Parameters = parameters; } - + /// /// Gets the original PE file that is being parsed. /// @@ -46,10 +47,22 @@ public PEReaderParameters Parameters get; } + /// + /// Creates relocation parameters based on the current PE file that is being read. + /// + /// The offset of the segment. + /// The relative virtual address of the segment. + /// The created relocation parameters. + public RelocationParameters GetRelocation(ulong offset, uint rva) + { + return new RelocationParameters(File.OptionalHeader.ImageBase, offset, rva, + File.OptionalHeader.Magic == OptionalHeaderMagic.PE32); + } + /// public void MarkAsFatal() => Parameters.ErrorListener.MarkAsFatal(); /// public void RegisterException(Exception exception) => Parameters.ErrorListener.RegisterException(exception); } -} \ No newline at end of file +} diff --git a/src/AsmResolver.PE/Platforms/Amd64Platform.cs b/src/AsmResolver.PE/Platforms/Amd64Platform.cs index 8ba103667..895e39c9d 100644 --- a/src/AsmResolver.PE/Platforms/Amd64Platform.cs +++ b/src/AsmResolver.PE/Platforms/Amd64Platform.cs @@ -25,14 +25,14 @@ public static Amd64Platform Instance public override bool IsClrBootstrapperRequired => false; /// - public override RelocatableSegment CreateThunkStub(ulong imageBase, ISymbol entrypoint) + public override RelocatableSegment CreateThunkStub(ISymbol entryPoint) { - var segment = new CodeSegment(imageBase, new byte[] + var segment = new CodeSegment(new byte[] { 0x48, 0xA1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // rex.w rex.b mov rax, [&symbol] 0xFF, 0xE0 // jmp [rax] }); - segment.AddressFixups.Add(new AddressFixup(2, AddressFixupType.Absolute64BitAddress, entrypoint)); + segment.AddressFixups.Add(new AddressFixup(2, AddressFixupType.Absolute64BitAddress, entryPoint)); return new RelocatableSegment(segment, new[] { diff --git a/src/AsmResolver.PE/Platforms/I386Platform.cs b/src/AsmResolver.PE/Platforms/I386Platform.cs index 15aa92d46..87c431ae0 100644 --- a/src/AsmResolver.PE/Platforms/I386Platform.cs +++ b/src/AsmResolver.PE/Platforms/I386Platform.cs @@ -25,13 +25,13 @@ public static I386Platform Instance public override bool IsClrBootstrapperRequired => true; /// - public override RelocatableSegment CreateThunkStub(ulong imageBase, ISymbol entrypoint) + public override RelocatableSegment CreateThunkStub(ISymbol entryPoint) { - var segment = new CodeSegment(imageBase, new byte[] + var segment = new CodeSegment(new byte[] { - 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00 // jmp [&symbol] + 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00 // jmp [&symbol] }); - segment.AddressFixups.Add(new AddressFixup(2, AddressFixupType.Absolute32BitAddress, entrypoint)); + segment.AddressFixups.Add(new AddressFixup(2, AddressFixupType.Absolute32BitAddress, entryPoint)); return new RelocatableSegment(segment, new[] { diff --git a/src/AsmResolver.PE/Platforms/Platform.cs b/src/AsmResolver.PE/Platforms/Platform.cs index 506e6003d..151b4da02 100644 --- a/src/AsmResolver.PE/Platforms/Platform.cs +++ b/src/AsmResolver.PE/Platforms/Platform.cs @@ -69,10 +69,9 @@ or MachineType.Amd64DotNetFreeBsd /// /// Creates a new thunk stub that transfers control to the provided symbol. /// - /// The image base of the image. - /// The symbol to jump to. + /// The symbol to jump to. /// The created stub. - public abstract RelocatableSegment CreateThunkStub(ulong imageBase, ISymbol entrypoint); + public abstract RelocatableSegment CreateThunkStub(ISymbol entryPoint); /// /// Attempts to extract the original RVA from the code at the provided thunk address reader. diff --git a/src/AsmResolver.PE/Relocations/Builder/RelocationsDirectoryBuffer.cs b/src/AsmResolver.PE/Relocations/Builder/RelocationsDirectoryBuffer.cs index 71d31c474..92aa70a2e 100644 --- a/src/AsmResolver.PE/Relocations/Builder/RelocationsDirectoryBuffer.cs +++ b/src/AsmResolver.PE/Relocations/Builder/RelocationsDirectoryBuffer.cs @@ -58,9 +58,9 @@ private static RelocationBlock GetOrCreateBlock(IDictionary - public override void UpdateOffsets(ulong newOffset, uint newRva) + public override void UpdateOffsets(in RelocationParameters parameters) { - base.UpdateOffsets(newOffset, newRva); + base.UpdateOffsets(parameters); _blocks = null; } diff --git a/src/AsmResolver.PE/Tls/ITlsDirectory.cs b/src/AsmResolver.PE/Tls/ITlsDirectory.cs index b07ec8000..9149c0314 100644 --- a/src/AsmResolver.PE/Tls/ITlsDirectory.cs +++ b/src/AsmResolver.PE/Tls/ITlsDirectory.cs @@ -54,24 +54,6 @@ TlsCharacteristics Characteristics set; } - /// - /// Gets or sets the image base address that the TLS directory assumes. - /// - public ulong ImageBase - { - get; - set; - } - - /// - /// Gets or sets a value indicating whether the TLS directory assumes a 32-bit or 64-bit format. - /// - public bool Is32Bit - { - get; - set; - } - /// /// Obtains a collection of base address relocations that need to be applied to the TLS data directory /// after the image was loaded into memory. diff --git a/src/AsmResolver.PE/Tls/SerializedTlsDirectory.cs b/src/AsmResolver.PE/Tls/SerializedTlsDirectory.cs index 99c32faa1..539c08cc4 100644 --- a/src/AsmResolver.PE/Tls/SerializedTlsDirectory.cs +++ b/src/AsmResolver.PE/Tls/SerializedTlsDirectory.cs @@ -22,18 +22,16 @@ public SerializedTlsDirectory(PEReaderContext context, ref BinaryStreamReader re { _context = context; - ulong imageBase = context.File.OptionalHeader.ImageBase; - bool is32Bit = context.File.OptionalHeader.Magic == OptionalHeaderMagic.Pe32; + var relocation = context.GetRelocation(reader.Offset, reader.Rva); - _templateStart = reader.ReadNativeInt(is32Bit); - _templateEnd = reader.ReadNativeInt(is32Bit); - Index = context.File.GetReferenceToRva((uint)(reader.ReadNativeInt(is32Bit) - imageBase)); - _addressOfCallbacks = reader.ReadNativeInt(is32Bit); + _templateStart = reader.ReadNativeInt(relocation.Is32Bit); + _templateEnd = reader.ReadNativeInt(relocation.Is32Bit); + Index = context.File.GetReferenceToRva((uint)(reader.ReadNativeInt(relocation.Is32Bit) - relocation.ImageBase)); + _addressOfCallbacks = reader.ReadNativeInt(relocation.Is32Bit); SizeOfZeroFill = reader.ReadUInt32(); Characteristics = (TlsCharacteristics) reader.ReadUInt32(); - ImageBase = imageBase; - Is32Bit = is32Bit; + UpdateOffsets(relocation); } /// @@ -70,7 +68,7 @@ protected override TlsCallbackCollection GetCallbackFunctions() var file = _context.File; var optionalHeader = file.OptionalHeader; ulong imageBase = optionalHeader.ImageBase; - bool is32Bit = optionalHeader.Magic == OptionalHeaderMagic.Pe32; + bool is32Bit = optionalHeader.Magic == OptionalHeaderMagic.PE32; if (!file.TryCreateReaderAtRva((uint) (_addressOfCallbacks - imageBase), out var reader)) { diff --git a/src/AsmResolver.PE/Tls/TlsCallbackCollection.cs b/src/AsmResolver.PE/Tls/TlsCallbackCollection.cs index ddeb3f368..dc29af2aa 100644 --- a/src/AsmResolver.PE/Tls/TlsCallbackCollection.cs +++ b/src/AsmResolver.PE/Tls/TlsCallbackCollection.cs @@ -9,6 +9,8 @@ namespace AsmResolver.PE.Tls public class TlsCallbackCollection : Collection, ISegment { private readonly ITlsDirectory _owner; + private ulong _imageBase = 0x00400000; + private bool _is32Bit = true; internal TlsCallbackCollection(ITlsDirectory owner) { @@ -33,16 +35,18 @@ public uint Rva public bool CanUpdateOffsets => true; /// - public void UpdateOffsets(ulong newOffset, uint newRva) + public void UpdateOffsets(in RelocationParameters parameters) { - Offset = newOffset; - Rva = newRva; + Offset = parameters.Offset; + Rva = parameters.Rva; + _imageBase = parameters.ImageBase; + _is32Bit = parameters.Is32Bit; } /// public uint GetPhysicalSize() { - uint pointerSize = (uint) (_owner.Is32Bit ? sizeof(uint) : sizeof(ulong)); + uint pointerSize = (uint) (_is32Bit ? sizeof(uint) : sizeof(ulong)); return (uint) (pointerSize * (Count + 1)); } @@ -52,8 +56,8 @@ public uint GetPhysicalSize() /// public void Write(IBinaryStreamWriter writer) { - ulong imageBase = _owner.ImageBase; - bool is32Bit = _owner.Is32Bit; + ulong imageBase = _imageBase; + bool is32Bit = _is32Bit; for (int i = 0; i < Items.Count; i++) writer.WriteNativeInt(imageBase + Items[i].Rva, is32Bit); diff --git a/src/AsmResolver.PE/Tls/TlsDirectory.cs b/src/AsmResolver.PE/Tls/TlsDirectory.cs index fbe6c101c..af563f6d7 100644 --- a/src/AsmResolver.PE/Tls/TlsDirectory.cs +++ b/src/AsmResolver.PE/Tls/TlsDirectory.cs @@ -12,6 +12,8 @@ public class TlsDirectory : SegmentBase, ITlsDirectory { private readonly LazyVariable _templateData; private TlsCallbackCollection? _callbackFunctions; + private ulong _imageBase = 0x00400000; + private bool _is32Bit = true; /// /// Initializes a new empty TLS data directory. @@ -62,18 +64,12 @@ public TlsCharacteristics Characteristics } /// - public ulong ImageBase + public override void UpdateOffsets(in RelocationParameters parameters) { - get; - set; - } = 0x00400000; - - /// - public bool Is32Bit - { - get; - set; - } = true; + _imageBase = parameters.ImageBase; + _is32Bit = parameters.Is32Bit; + base.UpdateOffsets(in parameters); + } /// /// Obtains the block of template data. @@ -96,29 +92,30 @@ public bool Is32Bit /// public IEnumerable GetRequiredBaseRelocations() { - int pointerSize = Is32Bit ? sizeof(uint) : sizeof(ulong); - var type = Is32Bit ? RelocationType.HighLow : RelocationType.Dir64; + int pointerSize = _is32Bit ? sizeof(uint) : sizeof(ulong); + var type = _is32Bit ? RelocationType.HighLow : RelocationType.Dir64; var result = new List(4 + CallbackFunctions.Count); for (int i = 0; i < 4; i++) result.Add(new BaseRelocation(type, this.ToReference(i * pointerSize))); for (int i = 0; i < CallbackFunctions.Count; i++) result.Add(new BaseRelocation(type, CallbackFunctions.ToReference(i * pointerSize))); + return result; } /// public override uint GetPhysicalSize() { - int pointerSize = Is32Bit ? sizeof(uint) : sizeof(ulong); + int pointerSize = _is32Bit ? sizeof(uint) : sizeof(ulong); return (uint) (pointerSize * 4 + 2 * sizeof(uint)); } /// public override void Write(IBinaryStreamWriter writer) { - ulong imageBase = ImageBase; - bool is32Bit = Is32Bit; + ulong imageBase = _imageBase; + bool is32Bit = _is32Bit; if (TemplateData is { } data) { diff --git a/src/AsmResolver.PE/Win32Resources/Builder/ResourceDirectoryBuffer.cs b/src/AsmResolver.PE/Win32Resources/Builder/ResourceDirectoryBuffer.cs index 987dc8412..ea7bc9067 100644 --- a/src/AsmResolver.PE/Win32Resources/Builder/ResourceDirectoryBuffer.cs +++ b/src/AsmResolver.PE/Win32Resources/Builder/ResourceDirectoryBuffer.cs @@ -104,7 +104,7 @@ private void AddDataEntry(IResourceEntry entry) } /// - public void UpdateOffsets(ulong newOffset, uint newRva) => _segments.UpdateOffsets(newOffset, newRva); + public void UpdateOffsets(in RelocationParameters parameters) => _segments.UpdateOffsets(parameters); /// public uint GetPhysicalSize() => _segments.GetPhysicalSize(); diff --git a/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj b/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj new file mode 100644 index 000000000..091cf3c05 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj @@ -0,0 +1,35 @@ + + + + AsmResolver + Windows PDB models for the AsmResolver executable file inspection toolsuite. + windows pdb symbols + enable + net6.0;netcoreapp3.1;netstandard2.0 + true + true + + + + true + bin\Debug\netstandard2.0\AsmResolver.Symbols.WindowsPdb.xml + + + + true + bin\Release\netstandard2.0\AsmResolver.xml + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ArgumentListLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ArgumentListLeaf.cs new file mode 100644 index 000000000..32e5bccc7 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/ArgumentListLeaf.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; +using System.Threading; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a leaf containing a list of type arguments for a function or method. +/// +public class ArgumentListLeaf : CodeViewLeaf +{ + private IList? _types; + + /// + /// Initializes an empty argument list. + /// + /// The type index to assign to the list. + protected ArgumentListLeaf(uint typeIndex) + : base(typeIndex) + { + } + + /// + /// Creates a new empty argument list. + /// + public ArgumentListLeaf() + : base(0) + { + } + + /// + /// Creates a new argument list. + /// + public ArgumentListLeaf(params CodeViewTypeRecord[] argumentTypes) + : base(0) + { + _types = new List(argumentTypes); + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.ArgList; + + /// + /// Gets an ordered collection of types that correspond to the types of each parameter. + /// + public IList Types + { + get + { + if (_types is null) + Interlocked.CompareExchange(ref _types, GetArgumentTypes(), null); + return _types; + } + } + + /// + /// Obtains the argument types stored in the list. + /// + /// The types. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetArgumentTypes() => new List(); + + /// + public override string ToString() => string.Join(", ", Types); +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ArrayTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ArrayTypeRecord.cs new file mode 100644 index 000000000..1f53f2071 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/ArrayTypeRecord.cs @@ -0,0 +1,123 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a type describing an array of elements. +/// +public class ArrayTypeRecord : CodeViewTypeRecord +{ + private readonly LazyVariable _elementType; + private readonly LazyVariable _indexType; + private readonly LazyVariable _name; + + /// + /// Initializes a new empty array type. + /// + /// The type index to assign to the type. + protected ArrayTypeRecord(uint typeIndex) + : base(typeIndex) + { + _elementType = new LazyVariable(GetElementType); + _indexType = new LazyVariable(GetIndexType); + _name = new LazyVariable(GetName); + } + + /// + /// Creates a new array type. + /// + /// The type of each element in the array. + /// The type to use for indexing into the array. + /// The number of elements in the array. + public ArrayTypeRecord(CodeViewTypeRecord elementType, CodeViewTypeRecord indexType, ulong length) + : base(0) + { + _elementType = new LazyVariable(elementType); + _indexType = new LazyVariable(indexType); + Length = length; + _name = new LazyVariable(Utf8String.Empty); + } + + /// + /// Creates a new array type. + /// + /// The type of each element in the array. + /// The type to use for indexing into the array. + /// The number of elements in the array. + /// The name of the array type. + public ArrayTypeRecord(CodeViewTypeRecord elementType, CodeViewTypeRecord indexType, ulong length, Utf8String name) + : base(0) + { + _elementType = new LazyVariable(elementType); + _indexType = new LazyVariable(indexType); + Length = length; + _name = new LazyVariable(Name); + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Array; + + /// + /// Gets or sets the type of each element in the array. + /// + public CodeViewTypeRecord? ElementType + { + get => _elementType.Value; + set => _elementType.Value = value; + } + + /// + /// Gets or sets the type that is used to index into the array. + /// + public CodeViewTypeRecord? IndexType + { + get => _indexType.Value; + set => _indexType.Value = value; + } + + /// + /// Gets or sets the number of elements in the array. + /// + public ulong Length + { + get; + set; + } + + /// + /// Gets or sets the name of the type. + /// + public Utf8String Name + { + get => _name.Value; + set => _name.Value = value; + } + + /// + /// Obtains the element type of the array. + /// + /// The element type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetElementType() => null; + + /// + /// Obtains the index type of the array. + /// + /// The index type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetIndexType() => null; + + /// + /// Obtains the name type of the array. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String GetName() => Utf8String.Empty; + + /// + public override string ToString() => $"{ElementType}[{Length}]"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/BaseClassField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/BaseClassField.cs new file mode 100644 index 000000000..3a3823945 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/BaseClassField.cs @@ -0,0 +1,62 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a reference to a base class object in a structure. +/// +public class BaseClassField : CodeViewField +{ + private readonly LazyVariable _type; + + /// + /// Initializes an empty base class. + /// + /// The type index to assign to the base class field. + protected BaseClassField(uint typeIndex) + : base(typeIndex) + { + _type = new LazyVariable(GetBaseType); + } + + /// + /// Creates a new base class field. + /// + /// The base type to reference. + public BaseClassField(CodeViewTypeRecord type) + : base(0) + { + _type = new LazyVariable(type); + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.BClass; + + /// + /// Gets or sets the base type that this base class is referencing. + /// + public CodeViewTypeRecord? Type + { + get => _type.Value; + set => _type.Value = value; + } + + /// + /// Gets or sets the offset of the base within the class. + /// + public ulong Offset + { + get; + set; + } + + /// + /// Obtains the base type that the class is referencing. + /// + /// The base type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetBaseType() => null; + + /// + public override string ToString() => Type?.ToString() ?? "<<>>"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/BitFieldTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/BitFieldTypeRecord.cs new file mode 100644 index 000000000..40a01fbb5 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/BitFieldTypeRecord.cs @@ -0,0 +1,75 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a bit field type. +/// +public class BitFieldTypeRecord : CodeViewTypeRecord +{ + private readonly LazyVariable _type; + + /// + /// Initializes an empty bit field record. + /// + /// The type index to assign to the bit field type. + protected BitFieldTypeRecord(uint typeIndex) + : base(typeIndex) + { + _type = new LazyVariable(GetBaseType); + } + + /// + /// Creates a new bit field record. + /// + /// The type of the bit field. + /// The bit index the bit field starts at. + /// The number of bits the bit field spans. + public BitFieldTypeRecord(CodeViewTypeRecord type, byte position, byte length) + : base(0) + { + _type = new LazyVariable(type); + Position = position; + Length = length; + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.BitField; + + /// + /// Gets or sets the base type that this bit field is referencing. + /// + public CodeViewTypeRecord? Type + { + get => _type.Value; + set => _type.Value = value; + } + + /// + /// Gets or sets the bit index that this bit fields starts at. + /// + public byte Position + { + get; + set; + } + + /// + /// Gets or sets the number of bits that this bit fields spans. + /// + public byte Length + { + get; + set; + } + + /// + /// Obtains the base type that the bit field is referencing. + /// + /// The base type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetBaseType() => null; + + /// + public override string ToString() => $"{Type} : {Length}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ClassTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ClassTypeRecord.cs new file mode 100644 index 000000000..3f222effc --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/ClassTypeRecord.cs @@ -0,0 +1,110 @@ +using System; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a class, structure or interface type in a PDB. +/// +public class ClassTypeRecord : CodeViewDerivedTypeRecord +{ + private readonly LazyVariable _uniqueName; + private readonly LazyVariable _vtableShape; + + /// + /// Initializes an empty class type. + /// + /// The kind of type. + /// The type index to assign to the class type. + /// + /// Occurs when the provided kind is not a class, structure or interface. + /// + protected ClassTypeRecord(CodeViewLeafKind kind, uint typeIndex) + : base(typeIndex) + { + if (kind is not (CodeViewLeafKind.Class or CodeViewLeafKind.Structure or CodeViewLeafKind.Interface)) + throw new ArgumentOutOfRangeException(nameof(kind)); + + LeafKind = kind; + _uniqueName = new LazyVariable(GetUniqueName); + _vtableShape = new LazyVariable(GetVTableShape); + } + + /// + /// Creates a new class type record. + /// + /// The kind. + /// The name of the type. + /// The unique mangled name of the type. + /// The size in bytes of the type. + /// Attributes describing the shape of the type. + /// The type that this type is derived from, if any. + /// + /// Occurs when the provided kind is not a class, structure or interface. + /// + public ClassTypeRecord(CodeViewLeafKind kind, Utf8String name, Utf8String uniqueName, ulong size, + StructureAttributes attributes, CodeViewTypeRecord? baseType) + : base(0) + { + if (kind is not (CodeViewLeafKind.Class or CodeViewLeafKind.Structure or CodeViewLeafKind.Interface)) + throw new ArgumentOutOfRangeException(nameof(kind)); + + LeafKind = kind; + Name = name; + _uniqueName = new LazyVariable(uniqueName); + _vtableShape = new LazyVariable(default(VTableShapeLeaf)); + Size = size; + StructureAttributes = attributes; + BaseType = baseType; + } + + /// + public override CodeViewLeafKind LeafKind + { + get; + } + + /// + /// Gets or sets the number bytes that this class spans. + /// + public ulong Size + { + get; + set; + } + + /// + /// Gets or sets the uniquely identifiable name for this type. + /// + public Utf8String UniqueName + { + get => _uniqueName.Value; + set => _uniqueName.Value = value; + } + + /// + /// Gets or sets the shape of the virtual function table of this type, if available. + /// + public VTableShapeLeaf? VTableShape + { + get => _vtableShape.Value; + set => _vtableShape.Value = value; + } + + /// + /// Obtains the uniquely identifiable name of the type. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String GetUniqueName() => Utf8String.Empty; + + /// + /// Obtains the shape of the virtual function table name of the type. + /// + /// The shape. + /// + /// This method is called upon initialization of the property. + /// + protected virtual VTableShapeLeaf? GetVTableShape() => null; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCallingConvention.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCallingConvention.cs new file mode 100644 index 000000000..e17d8f881 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCallingConvention.cs @@ -0,0 +1,132 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Provides members defining all calling conventions that can be specified in a PDB image. +/// +public enum CodeViewCallingConvention : byte +{ + /// + /// Indicates a near call using the cdecl calling convention. + /// + NearC = 0x00, + + /// + /// Indicates a far call using the cdecl calling convention. + /// + FarC = 0x01, + + /// + /// Indicates a near call using the Pascal calling convention. + /// + NearPascal = 0x02, + + /// + /// Indicates a far call using the Pascal calling convention. + /// + FarPascal = 0x03, + + /// + /// Indicates a near call using the fastcall calling convention. + /// + NearFast = 0x04, + + /// + /// Indicates a far call using the fastcall calling convention. + /// + FarFast = 0x05, + + /// + /// Skipped (unused) call index + /// + Skipped = 0x06, + + /// + /// Indicates a near call using the stdcall calling convention. + /// + NearStd = 0x07, + + /// + /// Indicates a far call using the stdcall calling convention. + /// + FarStd = 0x08, + + /// + /// Indicates a near call using the syscall calling convention. + /// + NearSys = 0x09, + + /// + /// Indicates a far call using the syscall calling convention. + /// + FarSys = 0x0a, + + /// + /// Indicates a call using the thiscall calling convention. + /// + ThisCall = 0x0b, + + /// + /// Indicates a call using the MIPS calling convention. + /// + MipsCall = 0x0c, + + /// + /// Indicates a generic calling sequence. + /// + Generic = 0x0d, + + /// + /// Indicates a call using the Alpha calling convention. + /// + AlphaCall = 0x0e, + + /// + /// Indicates a call using the PowerPC calling convention. + /// + PpcCall = 0x0f, + + /// + /// Indicates a call using the Hitachi SuperH calling convention. + /// + ShCall = 0x10, + + /// + /// Indicates a call using the ARM calling convention. + /// + ArmCall = 0x11, + + /// + /// Indicates a call using the AM33 calling convention. + /// + Am33Call = 0x12, + + /// + /// Indicates a call using the TriCore calling convention. + /// + TriCall = 0x13, + + /// + /// Indicates a call using the Hitachi SuperH-5 calling convention. + /// + Sh5Call = 0x14, + + /// + /// Indicates a call using the M32R calling convention. + /// + M32RCall = 0x15, + + /// + /// Indicates a call using the clr calling convention. + /// + ClrCall = 0x16, + + /// + /// Marker for routines always inlined and thus lacking a convention. + /// + Inline = 0x17, + + /// + /// Indicates a near call using the vectorcall calling convention. + /// + NearVector = 0x18, +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCompositeTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCompositeTypeRecord.cs new file mode 100644 index 000000000..9abc46710 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewCompositeTypeRecord.cs @@ -0,0 +1,66 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Provides a base for all code view types that can define one or more fields. +/// +public abstract class CodeViewCompositeTypeRecord : CodeViewTypeRecord +{ + private readonly LazyVariable _name; + private readonly LazyVariable _fields; + + /// + /// Initializes a new empty composite type. + /// + /// The type index to assign to the type. + protected CodeViewCompositeTypeRecord(uint typeIndex) + : base(typeIndex) + { + _name = new LazyVariable(GetName); + _fields = new LazyVariable(GetFields); + } + + /// + /// Gets or sets the structural attributes assigned to the type. + /// + public StructureAttributes StructureAttributes + { + get; + set; + } + + /// + /// Gets or sets the name of the type. + /// + public Utf8String Name + { + get => _name.Value; + set => _name.Value = value; + } + + /// + /// Gets a collection of fields that are defined in the enum. + /// + public FieldListLeaf? Fields + { + get => _fields.Value; + set => _fields.Value = value; + } + + /// + /// Obtains the new name of the type. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String GetName() => Utf8String.Empty; + + /// + /// Obtains the fields defined in the type. + /// + /// The fields. + /// + /// This method is called upon initialization of the property. + /// + protected virtual FieldListLeaf? GetFields() => null; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDataField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDataField.cs new file mode 100644 index 000000000..3821a14fe --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDataField.cs @@ -0,0 +1,52 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a data member in a class or structure type. +/// +public abstract class CodeViewDataField : CodeViewNamedField +{ + private readonly LazyVariable _dataType; + + /// + /// Initializes an empty instance data member. + /// + /// The type index to assign to the member. + protected CodeViewDataField(uint typeIndex) + : base(typeIndex) + { + _dataType = new LazyVariable(GetDataType); + } + + /// + /// Creates a new data member. + /// + /// The data type of the member. + /// The name of the member. + protected CodeViewDataField(CodeViewTypeRecord dataType, Utf8String name) + : base(0) + { + _dataType = new LazyVariable(dataType); + Name = name; + } + + /// + /// Gets or sets the data type of the member. + /// + public CodeViewTypeRecord DataType + { + get => _dataType.Value; + set => _dataType.Value = value; + } + + /// + /// Obtains the data type of the member. + /// + /// The data type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetDataType() => null; + + /// + public override string ToString() => $"{DataType} {Name}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDerivedTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDerivedTypeRecord.cs new file mode 100644 index 000000000..3cd51d0f8 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewDerivedTypeRecord.cs @@ -0,0 +1,37 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Provides a base for all custom types that may be derived from a base type. +/// +public abstract class CodeViewDerivedTypeRecord : CodeViewCompositeTypeRecord +{ + private readonly LazyVariable _baseType; + + /// + protected CodeViewDerivedTypeRecord(uint typeIndex) + : base(typeIndex) + { + _baseType = new LazyVariable(GetBaseType); + } + + /// + /// Gets or sets the base type that this type is deriving from. + /// + public CodeViewTypeRecord? BaseType + { + get => _baseType.Value; + set => _baseType.Value = value; + } + + /// + /// Obtains the type that the type is derived from. + /// + /// The type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetBaseType() => null; + + /// + public override string ToString() => Name; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs new file mode 100644 index 000000000..689105e37 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewField.cs @@ -0,0 +1,32 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a single record in a field list of a TPI or IPI stream. +/// +public abstract class CodeViewField : CodeViewLeaf +{ + /// + /// Initializes an empty CodeView field leaf. + /// + /// The type index to assign to the leaf. + protected CodeViewField(uint typeIndex) + : base(typeIndex) + { + } + + /// + /// Gets or sets the attributes associated to the field. + /// + public CodeViewFieldAttributes Attributes + { + get; + set; + } + + /// + /// Gets a value indicating whether the field is a newly introduced virtual function. + /// + public bool IsIntroducingVirtual => + (Attributes & CodeViewFieldAttributes.IntroducingVirtual) == CodeViewFieldAttributes.IntroducingVirtual + || (Attributes & CodeViewFieldAttributes.PureIntroducingVirtual) == CodeViewFieldAttributes.PureIntroducingVirtual; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewFieldAttributes.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewFieldAttributes.cs new file mode 100644 index 000000000..efbea90ec --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewFieldAttributes.cs @@ -0,0 +1,95 @@ +using System; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Provides members defining all flags that can be assigned to a field, method or class. +/// +[Flags] +public enum CodeViewFieldAttributes : ushort +{ + /// + /// Indicates no attributes were assigned to the field. + /// + None = 0b00000000_00000000, + + /// + /// Indicates the field is marked private. + /// + Private = 0b00000000_00000001, + + /// + /// Indicates the field is marked protected. + /// + Protected = 0b00000000_00000010, + + /// + /// Indicates the field is marked public. + /// + Public = 0b00000000_00000011, + + /// + /// Provides the bit-mask that can be used to extract the access-level of the field. + /// + AccessMask = 0b00000000_00000011, + + /// + /// Indicates the method is a virtual method. + /// + Virtual = 0b00000000_00000100, + + /// + /// Indicates the method is a static method. + /// + Static = 0b00000000_00001000, + + /// + /// Indicates the method can be accessed only from within the current module. + /// + Friend = 0b00000000_00001100, + + /// + /// Indicates the method is a new introducing virtual method. + /// + IntroducingVirtual = 0b00000000_00010000, + + /// + /// Indicates the method is a pure virtual method. + /// + PureVirtual = 0b00000000_00010100, + + /// + /// Indicates the method is a new introducing pure virtual method. + /// + PureIntroducingVirtual = 0b00000000_00011000, + + /// + /// Provides the bit-mask that can be used to extract the method properties of the field. + /// + MethodPropertiesMask = 0b00000000_00011100, + + /// + /// Indicates the field is compiler generated and does not exist. + /// + Pseudo = 0b00000000_00100000, + + /// + /// Indicates the class cannot be inherited. + /// + NoInherit = 0b00000000_01000000, + + /// + /// Indicates the class cannot be constructed. + /// + NoConstruct = 0b00000000_10000000, + + /// + /// Indicates the field is compiler generated but does exist. + /// + CompilerGenerated = 0b00000001_00000000, + + /// + /// Indicates the method cannot be overridden. + /// + Sealed = 0b00000010_00000000 +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs new file mode 100644 index 000000000..c966aa91e --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeaf.cs @@ -0,0 +1,99 @@ +using AsmResolver.IO; +using AsmResolver.Symbols.Pdb.Leaves.Serialized; +using static AsmResolver.Symbols.Pdb.Leaves.CodeViewLeafKind; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a single leaf record in a TPI or IPI stream. +/// +public abstract class CodeViewLeaf +{ + /// + /// Initializes an empty CodeView leaf. + /// + /// The type index to assign to the leaf. + protected CodeViewLeaf(uint typeIndex) + { + TypeIndex = typeIndex; + } + + /// + /// Gets the type kind this record encodes. + /// + public abstract CodeViewLeafKind LeafKind + { + get; + } + + /// + /// Gets the type index the type is associated to. + /// + public uint TypeIndex + { + get; + internal set; + } + + internal static CodeViewLeaf FromReader(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) + { + ushort length = reader.ReadUInt16(); + var dataReader = reader.Fork(); + reader.Offset += length; + + return FromReaderNoHeader(context, typeIndex, ref dataReader); + } + + internal static CodeViewLeaf FromReaderNoHeader( + PdbReaderContext context, + uint typeIndex, + ref BinaryStreamReader dataReader) + { + var kind = (CodeViewLeafKind) dataReader.ReadUInt16(); + return kind switch + { + Array => new SerializedArrayTypeRecord(context, typeIndex, dataReader), + ArgList => new SerializedArgumentListLeaf(context, typeIndex, dataReader), + BClass => new SerializedBaseClassField(context, typeIndex, ref dataReader), + Class or Interface or Structure => new SerializedClassTypeRecord(kind, context, typeIndex, dataReader), + BitField => new SerializedBitFieldTypeRecord(context, typeIndex, dataReader), + Enum => new SerializedEnumTypeRecord(context, typeIndex, dataReader), + Enumerate => new SerializedEnumerateField(context, typeIndex, ref dataReader), + FieldList => new SerializedFieldListLeaf(context, typeIndex, dataReader), + Member => new SerializedInstanceDataField(context, typeIndex, ref dataReader), + Method => new SerializedOverloadedMethod(context, typeIndex, ref dataReader), + MethodList => new SerializedMethodListLeaf(context, typeIndex, dataReader), + MFunction => new SerializedMemberFunctionLeaf(context, typeIndex, dataReader), + Modifier => new SerializedModifierTypeRecord(context, typeIndex, dataReader), + NestType or NestTypeEx => new SerializedNestedTypeField(context, typeIndex, ref dataReader), + OneMethod => new SerializedNonOverloadedMethod(context, typeIndex, ref dataReader), + Pointer => new SerializedPointerTypeRecord(context, typeIndex, dataReader), + Procedure => new SerializedProcedureTypeRecord(context, typeIndex, dataReader), + StMember => new SerializedStaticDataField(context, typeIndex, ref dataReader), + Union => new SerializedUnionTypeRecord(context, typeIndex, dataReader), + VFuncTab => new SerializedVTableField(context, typeIndex, ref dataReader), + VTShape => new SerializedVTableShapeLeaf(context, typeIndex, dataReader), + VBClass or IVBClass => new SerializedVBaseClassField(context, typeIndex, ref dataReader, kind == IVBClass), + _ => new UnknownCodeViewLeaf(kind, dataReader.ReadToEnd()) + }; + } + + internal static object ReadNumeric(ref BinaryStreamReader reader) + { + var kind = (CodeViewLeafKind) reader.ReadUInt16(); + return kind switch + { + < Numeric => (object) (uint) kind, + Char => (char) reader.ReadByte(), + Short => reader.ReadInt16(), + UShort => reader.ReadUInt16(), + Long => reader.ReadInt32(), + ULong => reader.ReadUInt32(), + QuadWord => reader.ReadInt64(), + UQuadWord => reader.ReadUInt64(), + Real32 => reader.ReadSingle(), + Real64 => reader.ReadDouble(), + _ => 0 + }; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeafKind.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeafKind.cs new file mode 100644 index 000000000..6acb79d56 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewLeafKind.cs @@ -0,0 +1,210 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Provides members defining all possible type record kinds that can be stored in a TPI or IPI stream. +/// +public enum CodeViewLeafKind : ushort +{ + /// + /// This is not really a type defined in PDB spec, but is used to indicate simple types. + /// + SimpleType = 0xffff, + +#pragma warning disable CS1591 + Modifier16T = 0x0001, + Pointer16T = 0x0002, + Array16T = 0x0003, + Class16T = 0x0004, + Structure16T = 0x0005, + Union16T = 0x0006, + Enum16T = 0x0007, + Procedure16T = 0x0008, + MFunction16T = 0x0009, + VTShape = 0x000a, + Cobol016T = 0x000b, + Cobol1 = 0x000c, + Barray16T = 0x000d, + Label = 0x000e, + Null = 0x000f, + Nottran = 0x0010, + DimArray16T = 0x0011, + VftPath16T = 0x0012, + PreComp16T = 0x0013, + EndPreComp = 0x0014, + Oem16T = 0x0015, + TypeServerSt = 0x0016, + + Skip16T = 0x0200, + ArgList16T = 0x0201, + DefArg16T = 0x0202, + List = 0x0203, + FieldList16T = 0x0204, + Derived16T = 0x0205, + BitField16T = 0x0206, + MethodList16T = 0x0207, + DimConU16T = 0x0208, + DimConLu16T = 0x0209, + DimVarU16T = 0x020a, + DimVarLu16T = 0x020b, + RefSym = 0x020c, + + BClass16T = 0x0400, + VBClass16T = 0x0401, + IVBClass16T = 0x0402, + EnumerateSt = 0x0403, + FriendFcn16T = 0x0404, + Index16T = 0x0405, + Member16T = 0x0406, + StMember16T = 0x0407, + Method16T = 0x0408, + NestType16T = 0x0409, + VFuncTab16T = 0x040a, + FriendCls16T = 0x040b, + OneMethod16T = 0x040c, + VFuncOff16T = 0x040d, + + Ti16Max = 0x1000, + + Modifier = 0x1001, + Pointer = 0x1002, + ArraySt = 0x1003, + ClassSt = 0x1004, + StructureSt = 0x1005, + UnionSt = 0x1006, + EnumSt = 0x1007, + Procedure = 0x1008, + MFunction = 0x1009, + Cobol0 = 0x100a, + BArray = 0x100b, + DimArraySt = 0x100c, + VftPath = 0x100d, + PreCompSt = 0x100e, + Oem = 0x100f, + AliasSt = 0x1010, + Oem2 = 0x1011, + + Skip = 0x1200, + ArgList = 0x1201, + DefArgSt = 0x1202, + FieldList = 0x1203, + Derived = 0x1204, + BitField = 0x1205, + MethodList = 0x1206, + DimConU = 0x1207, + DimConLu = 0x1208, + DimVarU = 0x1209, + DimVarLu = 0x120a, + + BClass = 0x1400, + VBClass = 0x1401, + IVBClass = 0x1402, + FriendFcnSt = 0x1403, + Index = 0x1404, + MemberSt = 0x1405, + StmemberSt = 0x1406, + MethodSt = 0x1407, + NestTypeSt = 0x1408, + VFuncTab = 0x1409, + FriendCls = 0x140a, + OneMethodSt = 0x140b, + VFuncOff = 0x140c, + NestTypeExSt = 0x140d, + MemberModifySt = 0x140e, + ManagedSt = 0x140f, + + StMax = 0x1500, + + TypeServer = 0x1501, + Enumerate = 0x1502, + Array = 0x1503, + Class = 0x1504, + Structure = 0x1505, + Union = 0x1506, + Enum = 0x1507, + DimArray = 0x1508, + PreComp = 0x1509, + Alias = 0x150a, + DefArg = 0x150b, + FriendFcn = 0x150c, + Member = 0x150d, + StMember = 0x150e, + Method = 0x150f, + NestType = 0x1510, + OneMethod = 0x1511, + NestTypeEx = 0x1512, + MemberModify = 0x1513, + Managed = 0x1514, + TypeServer2 = 0x1515, + + StridedArray = 0x1516, + Hlsl = 0x1517, + ModifierEx = 0x1518, + Interface = 0x1519, + BInterface = 0x151a, + Vector = 0x151b, + Matrix = 0x151c, + + VFTable = 0x151d, + EndOfLeafRecord = VFTable, + + TypeLast, + TypeMax = TypeLast - 1, + + FuncId = 0x1601, + MFuncId = 0x1602, + Buildinfo = 0x1603, + SubstrList = 0x1604, + StringId = 0x1605, + + UdtSrcLine = 0x1606, + UdtModSrcLine = 0x1607, + + IdLast, + IdMax = IdLast - 1, + + Numeric = 0x8000, + Char = 0x8000, + Short = 0x8001, + UShort = 0x8002, + Long = 0x8003, + ULong = 0x8004, + Real32 = 0x8005, + Real64 = 0x8006, + Real80 = 0x8007, + Real128 = 0x8008, + QuadWord = 0x8009, + UQuadWord = 0x800a, + Real48 = 0x800b, + Complex32 = 0x800c, + Complex64 = 0x800d, + Complex80 = 0x800e, + Complex128 = 0x800f, + VarString = 0x8010, + + OctWord = 0x8017, + UOctWord = 0x8018, + + Decimal = 0x8019, + Date = 0x801a, + Utf8String = 0x801b, + + Real16 = 0x801c, + + Pad0 = 0xf0, + Pad1 = 0xf1, + Pad2 = 0xf2, + Pad3 = 0xf3, + Pad4 = 0xf4, + Pad5 = 0xf5, + Pad6 = 0xf6, + Pad7 = 0xf7, + Pad8 = 0xf8, + Pad9 = 0xf9, + Pad10 = 0xfa, + Pad11 = 0xfb, + Pad12 = 0xfc, + Pad13 = 0xfd, + Pad14 = 0xfe, + Pad15 = 0xff, +#pragma warning restore CS1591 +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewNamedField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewNamedField.cs new file mode 100644 index 000000000..3137dabde --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewNamedField.cs @@ -0,0 +1,40 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a single record in a field list that is assigned a name. +/// +public abstract class CodeViewNamedField : CodeViewField +{ + private readonly LazyVariable _name; + + /// + /// Initializes an empty CodeView field leaf. + /// + /// The type index to assign to the leaf. + protected CodeViewNamedField(uint typeIndex) + : base(typeIndex) + { + _name = new LazyVariable(GetName); + } + + /// + /// Gets or sets the name of the field. + /// + public Utf8String Name + { + get => _name.Value; + set => _name.Value = value; + } + + /// + /// Obtains the name of the field. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String GetName() => Utf8String.Empty; + + /// + public override string ToString() => Name; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewTypeRecord.cs new file mode 100644 index 000000000..097f66be0 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/CodeViewTypeRecord.cs @@ -0,0 +1,16 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a single type record in a TPI or IPI stream. +/// +public abstract class CodeViewTypeRecord : CodeViewLeaf +{ + /// + /// Initializes an empty CodeView type record. + /// + /// The type index to assign to the leaf. + protected CodeViewTypeRecord(uint typeIndex) + : base(typeIndex) + { + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/EnumTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/EnumTypeRecord.cs new file mode 100644 index 000000000..a88bc9c73 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/EnumTypeRecord.cs @@ -0,0 +1,33 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents an enum type. +/// +public class EnumTypeRecord : CodeViewDerivedTypeRecord +{ + /// + /// Initializes a new empty enum type. + /// + /// The type index to assign to the enum type. + protected EnumTypeRecord(uint typeIndex) + : base(typeIndex) + { + } + + /// + /// Creates a new enum type. + /// + /// The name of the enum. + /// The underlying type of all members in the enum. + /// The structural attributes assigned to the enum. + public EnumTypeRecord(Utf8String name, CodeViewTypeRecord underlyingType, StructureAttributes attributes) + : base(0) + { + Name = name; + BaseType = underlyingType; + StructureAttributes = attributes; + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Enum; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/EnumerateField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/EnumerateField.cs new file mode 100644 index 000000000..4fe1a97c9 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/EnumerateField.cs @@ -0,0 +1,57 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a single enumerate field leaf in a field list. +/// +public class EnumerateField : CodeViewNamedField +{ + private readonly LazyVariable _value; + + /// + /// Initializes an empty enumerate field leaf. + /// + /// The type index to assign to the enumerate field. + protected EnumerateField(uint typeIndex) + : base(typeIndex) + { + _value = new LazyVariable(GetValue); + } + + /// + /// Creates a new enumerate field leaf. + /// + /// The name of the field. + /// The value assigned to the field. + /// The attributes associated to the field. + public EnumerateField(Utf8String name, object value, CodeViewFieldAttributes attributes) + : base(0) + { + Name = name; + _value = new LazyVariable(value); + Attributes = attributes; + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Enumerate; + + /// + /// Gets or sets the constant value assigned to the field. + /// + public object Value + { + get => _value.Value; + set => _value.Value = value; + } + + /// + /// Obtains the value assigned to the field. + /// + /// The value. + /// + /// This method is called upon initialization of the property. + /// + protected virtual object? GetValue() => null; + + /// + public override string ToString() => $"{Name} = {Value}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/FieldListLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/FieldListLeaf.cs new file mode 100644 index 000000000..5eecc0abe --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/FieldListLeaf.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using System.Threading; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a leaf containing a list of fields. +/// +public class FieldListLeaf : CodeViewLeaf +{ + private IList? _fields; + + /// + /// Initializes an empty field list. + /// + /// The type index to assign to the list. + protected FieldListLeaf(uint typeIndex) + : base(typeIndex) + { + } + + /// + /// Creates a new empty field list. + /// + public FieldListLeaf() + : base(0) + { + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.FieldList; + + /// + /// Gets a collection of fields stored in the list. + /// + public IList Entries + { + get + { + if (_fields is null) + Interlocked.CompareExchange(ref _fields, GetEntries(), null); + return _fields; + } + } + + /// + /// Obtains the fields stored in the list. + /// + /// The fields + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetEntries() => new List(); +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/InstanceDataField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/InstanceDataField.cs new file mode 100644 index 000000000..cc3bf9448 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/InstanceDataField.cs @@ -0,0 +1,43 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents an instance data member in a class or structure type. +/// +public class InstanceDataField : CodeViewDataField +{ + /// + /// Initializes an empty instance data member. + /// + /// The type index to assign to the member. + protected InstanceDataField(uint typeIndex) + : base(typeIndex) + { + } + + /// + /// Creates a new instance data member. + /// + /// The name of the member. + /// The byte offset within the class or structure that the member is stored at. + /// The data type of the member. + public InstanceDataField(Utf8String name, ulong offset, CodeViewTypeRecord dataType) + : base(dataType, name) + { + Offset = offset; + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Member; + + /// + /// Gets or sets the byte offset within the class or structure that the member is stored at. + /// + public ulong Offset + { + get; + set; + } + + /// + public override string ToString() => $"+{Offset:X4}: {DataType} {Name}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionAttributes.cs b/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionAttributes.cs new file mode 100644 index 000000000..2fbcbb55c --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionAttributes.cs @@ -0,0 +1,25 @@ +using System; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Provides members defining all attributes that can be assigned to a member function. +/// +[Flags] +public enum MemberFunctionAttributes : byte +{ + /// + /// Indicates if the function is a C++ style ReturnUDT function. + /// + CxxReturnUdt = 1, + + /// + /// Indicates the function is an instance constructor. + /// + Ctor = 2, + + /// + /// Indicates the function is an instance constructor of a class with virtual bases. + /// + CtorVBase = 4 +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionLeaf.cs new file mode 100644 index 000000000..5069796fb --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/MemberFunctionLeaf.cs @@ -0,0 +1,154 @@ +using System.Linq; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a single instance member function. +/// +public class MemberFunctionLeaf : CodeViewLeaf +{ + private readonly LazyVariable _returnType; + private readonly LazyVariable _declaringType; + private readonly LazyVariable _thisType; + private readonly LazyVariable _argumentList; + + /// + /// Initializes an empty member function. + /// + /// The type index to assign to the function. + protected MemberFunctionLeaf(uint typeIndex) + : base(typeIndex) + { + _returnType = new LazyVariable(GetReturnType); + _declaringType = new LazyVariable(GetDeclaringType); + _thisType = new LazyVariable(GetThisType); + _argumentList = new LazyVariable(GetArguments); + } + + /// + /// Creates a new member function. + /// + /// The return type of the function. + /// The declaring type of the function. + /// The argument types of the function. + public MemberFunctionLeaf(CodeViewTypeRecord returnType, CodeViewTypeRecord declaringType, ArgumentListLeaf arguments) + : base(0) + { + _returnType = new LazyVariable(returnType); + _declaringType = new LazyVariable(declaringType); + _thisType = new LazyVariable(default(CodeViewTypeRecord)); + _argumentList = new LazyVariable(arguments); + CallingConvention = CodeViewCallingConvention.NearC; + Attributes = 0; + ThisAdjuster = 0; + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.MFunction; + + /// + /// Gets or sets the return type of the function. + /// + public CodeViewTypeRecord? ReturnType + { + get => _returnType.Value; + set => _returnType.Value = value; + } + + /// + /// Gets or sets the type that declares this member function. + /// + public CodeViewTypeRecord? DeclaringType + { + get => _declaringType.Value; + set => _declaringType.Value = value; + } + + /// + /// Gets or sets the type of the this pointer that is used to access the member function. + /// + public CodeViewTypeRecord? ThisType + { + get => _thisType.Value; + set => _thisType.Value = value; + } + + /// + /// Gets or sets the convention that is used when calling the member function. + /// + public CodeViewCallingConvention CallingConvention + { + get; + set; + } + + /// + /// Gets or sets the attributes associated to the function. + /// + public MemberFunctionAttributes Attributes + { + get; + set; + } + + /// + /// Gets or sets the list of types of the parameters that this function defines. + /// + public ArgumentListLeaf? Arguments + { + get => _argumentList.Value; + set => _argumentList.Value = value; + } + + /// + /// Gets or sets the offset to adjust the this pointer with before devirtualization of this method. + /// + public uint ThisAdjuster + { + get; + set; + } + + /// + /// Obtains the return type of the function. + /// + /// The return type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetReturnType() => null; + + /// + /// Obtains the declaring type of the function. + /// + /// The declaring type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetDeclaringType() => null; + + /// + /// Obtains the this-type of the function. + /// + /// The this-type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetThisType() => null; + + /// + /// Obtains the argument types of the function. + /// + /// The argument types. + /// + /// This method is called upon initialization of the property. + /// + protected virtual ArgumentListLeaf? GetArguments() => null; + + /// + public override string ToString() + { + string args = string.Join(", ", Arguments?.Types ?? Enumerable.Empty()); + return $"{CallingConvention} {ReturnType} {DeclaringType}::*({args})"; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/MethodListEntry.cs b/src/AsmResolver.Symbols.Pdb/Leaves/MethodListEntry.cs new file mode 100644 index 000000000..447296798 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/MethodListEntry.cs @@ -0,0 +1,94 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents one single entry in a list of overloaded methods. +/// +public class MethodListEntry +{ + private readonly LazyVariable _function; + + /// + /// Initializes an empty method list entry. + /// + protected MethodListEntry() + { + _function = new LazyVariable(GetFunction); + } + + /// + /// Creates a new method list entry. + /// + /// The attributes associated to this method. + /// The referenced function. + public MethodListEntry(CodeViewFieldAttributes attributes, MemberFunctionLeaf function) + { + Attributes = attributes; + _function = new LazyVariable(function); + VTableOffset = 0; + } + + /// + /// Creates a new method list entry. + /// + /// The attributes associated to this method. + /// The referenced function. + /// The offset to the slot the virtual function table that this method occupies. + public MethodListEntry(CodeViewFieldAttributes attributes, MemberFunctionLeaf function, uint vTableOffset) + { + Attributes = attributes; + _function = new LazyVariable(function); + VTableOffset = vTableOffset; + } + + /// + /// Gets or sets the attributes associated to this method. + /// + public CodeViewFieldAttributes Attributes + { + get; + set; + } + + /// + /// Gets or sets the function that is referenced by this method. + /// + public MemberFunctionLeaf? Function + { + get => _function.Value; + set => _function.Value = value; + } + + /// + /// Gets a value indicating whether the function is a newly introduced virtual function. + /// + public bool IsIntroducingVirtual => + (Attributes & CodeViewFieldAttributes.IntroducingVirtual) != 0 + || (Attributes & CodeViewFieldAttributes.PureIntroducingVirtual) != 0; + + /// + /// When this method is an introducing virtual method, gets or sets the offset to the slot the virtual function + /// table that this method occupies. + /// + public uint VTableOffset + { + get; + set; + } + + /// + /// Obtains the function that this method references. + /// + /// The function. + /// + /// This method is called upon initialization of the property. + /// + protected virtual MemberFunctionLeaf? GetFunction() => null; + + /// + public override string ToString() + { + return IsIntroducingVirtual + ? $"{nameof(Attributes)}: {Attributes}, {nameof(Function)}: {Function}, {nameof(VTableOffset)}: {VTableOffset}" + : $"{nameof(Attributes)}: {Attributes}, {nameof(Function)}: {Function}"; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/MethodListLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/MethodListLeaf.cs new file mode 100644 index 000000000..f9538bac3 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/MethodListLeaf.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a leaf record containing a list of overloaded methods. +/// +public class MethodListLeaf : CodeViewLeaf +{ + private IList? _entries; + + /// + /// Initializes an empty method list. + /// + /// The type index to assign to the list. + protected MethodListLeaf(uint typeIndex) + : base(typeIndex) + { + } + + /// + /// Creates a new empty method list. + /// + public MethodListLeaf() + : base(0) + { + } + + /// + /// Creates a new method list. + /// + /// The methods to include. + public MethodListLeaf(params MethodListEntry[] entries) + : base(0) + { + _entries = entries.ToList(); + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.MethodList; + + /// + /// Gets a collection of methods stored in the list. + /// + public IList Entries + { + get + { + if (_entries is null) + Interlocked.CompareExchange(ref _entries, GetEntries(), null); + return _entries; + } + } + + /// + /// Obtains the methods stored in the list. + /// + /// The methods + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetEntries() => new List(); +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ModifierAttributes.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ModifierAttributes.cs new file mode 100644 index 000000000..3c221e900 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/ModifierAttributes.cs @@ -0,0 +1,26 @@ +using System; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Provides members defining all possible modifiers that can be added to a type using a Modifier type record in a +/// TPI or IPI stream. +/// +[Flags] +public enum ModifierAttributes : ushort +{ + /// + /// Indicates the type is marked as const. + /// + Const = 1, + + /// + /// Indicates the type is marked as volatile. + /// + Volatile = 2, + + /// + /// Indicates the type is marked as unaligned. + /// + Unaligned = 4, +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ModifierTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ModifierTypeRecord.cs new file mode 100644 index 000000000..c70e50ed1 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/ModifierTypeRecord.cs @@ -0,0 +1,94 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a type that is annotated with extra modifiers. +/// +public class ModifierTypeRecord : CodeViewTypeRecord +{ + private readonly LazyVariable _baseType; + + /// + /// Initializes a new empty modifier type. + /// + /// The type index to assign to the modifier type. + protected ModifierTypeRecord(uint typeIndex) + : base(typeIndex) + { + _baseType = new LazyVariable(GetBaseType); + } + + /// + /// Creates a new modified type. + /// + /// The type to be modified. + /// The attributes describing the shape of the pointer. + public ModifierTypeRecord(CodeViewTypeRecord type, ModifierAttributes attributes) + : base(0) + { + _baseType = new LazyVariable(type); + Attributes = attributes; + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Modifier; + + /// + /// Gets or sets the type that is annotated. + /// + public CodeViewTypeRecord BaseType + { + get => _baseType.Value; + set => _baseType.Value = value; + } + + /// + /// Gets or sets the annotations that were added to the type. + /// + public ModifierAttributes Attributes + { + get; + set; + } + + /// + /// Gets or sets a value indicating whether the type is marked as const. + /// + public bool IsConst + { + get => (Attributes & ModifierAttributes.Const) != 0; + set => Attributes = (Attributes & ~ModifierAttributes.Const) + | (value ? ModifierAttributes.Const : 0); + } + + /// + /// Gets or sets a value indicating whether the type is marked as volatile. + /// + public bool IsVolatile + { + get => (Attributes & ModifierAttributes.Volatile) != 0; + set => Attributes = (Attributes & ~ModifierAttributes.Volatile) + | (value ? ModifierAttributes.Volatile : 0); + } + + /// + /// Gets or sets a value indicating whether the type is marked as unaligned. + /// + public bool IsUnaligned + { + get => (Attributes & ModifierAttributes.Unaligned) != 0; + set => Attributes = (Attributes & ~ModifierAttributes.Unaligned) + | (value ? ModifierAttributes.Unaligned : 0); + } + + /// + /// Obtains the base type of the modifier type. + /// + /// The base type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetBaseType() => null; + + /// + public override string ToString() => $"{BaseType} {Attributes}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/NestedTypeField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/NestedTypeField.cs new file mode 100644 index 000000000..91bca6ee3 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/NestedTypeField.cs @@ -0,0 +1,69 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a field in a type that references a nested type definition. +/// +public class NestedTypeField : CodeViewNamedField +{ + private readonly LazyVariable _type; + + /// + /// Initializes an empty nested type. + /// + /// The type index to assign to the nested type field. + protected NestedTypeField(uint typeIndex) + : base(typeIndex) + { + _type = new LazyVariable(GetNestedType); + } + + /// + /// Creates a new nested type field. + /// + /// The definition of the nested type + /// The name of the nested type. + public NestedTypeField(CodeViewTypeRecord type, Utf8String name) + : base(0) + { + _type = new LazyVariable(type); + Name = name; + Attributes = 0; + } + + /// + /// Creates a new nested type (extended) field. + /// + /// The definition of the nested type + /// The name of the nested type. + /// The attributes assigned to the type. + public NestedTypeField(CodeViewTypeRecord type, Utf8String name, CodeViewFieldAttributes attributes) + : base(0) + { + _type = new LazyVariable(type); + Name = name; + Attributes = attributes; + } + + /// + public override CodeViewLeafKind LeafKind => Attributes == 0 + ? CodeViewLeafKind.NestType + : CodeViewLeafKind.NestTypeEx; + + /// + /// Gets or sets the definition of the referenced nested type. + /// + public CodeViewTypeRecord? Type + { + get => _type.Value; + set => _type.Value = value; + } + + /// + /// Obtains the definition of the nested type. + /// + /// The type + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetNestedType() => null; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/NonOverloadedMethod.cs b/src/AsmResolver.Symbols.Pdb/Leaves/NonOverloadedMethod.cs new file mode 100644 index 000000000..08bdb113f --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/NonOverloadedMethod.cs @@ -0,0 +1,80 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a single method in a type. +/// +public class NonOverloadedMethod : CodeViewNamedField +{ + private readonly LazyVariable _function; + + /// + /// Initializes an empty non-overloaded method. + /// + /// The type index to assign to the method. + protected NonOverloadedMethod(uint typeIndex) + : base(typeIndex) + { + _function = new LazyVariable(GetFunction); + } + + /// + /// Creates a new overloaded method. + /// + /// The name of the method. + /// The attributes associated to the method. + /// The function that is referenced by the method. + public NonOverloadedMethod(Utf8String name, CodeViewFieldAttributes attributes, MemberFunctionLeaf function) + : base(0) + { + _function = new LazyVariable(function); + Attributes = attributes; + Name = name; + } + + /// + /// Creates a new overloaded method. + /// + /// The name of the method. + /// The attributes associated to the method. + /// The offset to the slot the virtual function table that this method occupies. + /// The function that is referenced by the method. + public NonOverloadedMethod(Utf8String name, CodeViewFieldAttributes attributes, uint vTableOffset, MemberFunctionLeaf function) + : base(0) + { + _function = new LazyVariable(function); + Attributes = attributes; + Name = name; + VTableOffset = vTableOffset; + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.OneMethod; + + /// + /// Gets or sets the function that is referenced by this method. + /// + public MemberFunctionLeaf? Function + { + get => _function.Value; + set => _function.Value = value; + } + + /// + /// When this method is an introducing virtual method, gets or sets the offset to the slot the virtual function + /// table that this method occupies. + /// + public uint VTableOffset + { + get; + set; + } + + /// + /// Obtains the function that this method references. + /// + /// The function. + /// + /// This method is called upon initialization of the property. + /// + protected virtual MemberFunctionLeaf? GetFunction() => null; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/OverloadedMethod.cs b/src/AsmResolver.Symbols.Pdb/Leaves/OverloadedMethod.cs new file mode 100644 index 000000000..a6c17cea0 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/OverloadedMethod.cs @@ -0,0 +1,67 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a method that is overloaded by one or more functions. +/// +public class OverloadedMethod : CodeViewNamedField +{ + private readonly LazyVariable _methods; + + /// + /// Initializes an empty overloaded method. + /// + /// The type index to assign to the method. + protected OverloadedMethod(uint typeIndex) + : base(typeIndex) + { + _methods = new LazyVariable(GetMethods); + } + + /// + /// Creates a new empty overloaded method. + /// + public OverloadedMethod() + : base(0) + { + _methods = new LazyVariable(new MethodListLeaf()); + } + + /// + /// Creates a new overloaded method. + /// + public OverloadedMethod(MethodListLeaf methods) + : base(0) + { + _methods = new LazyVariable(methods); + } + + /// + /// Creates a new overloaded method. + /// + public OverloadedMethod(params MethodListEntry[] methods) + : base(0) + { + _methods = new LazyVariable(new MethodListLeaf(methods)); + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Method; + + /// + /// Gets or sets a list of methods that were overloaded. + /// + public MethodListLeaf? Methods + { + get => _methods.Value; + set => _methods.Value = value; + } + + /// + /// Obtains the list of methods that were overloaded. + /// + /// The methods. + /// + /// This method is called upon initialization of the property. + /// + protected virtual MethodListLeaf? GetMethods() => null; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/PointerAttributes.cs b/src/AsmResolver.Symbols.Pdb/Leaves/PointerAttributes.cs new file mode 100644 index 000000000..a1c9ac743 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/PointerAttributes.cs @@ -0,0 +1,145 @@ +using System; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Provides members defining all possible flags that can be assigned to a pointer type in a TPI or IPI stream. +/// +[Flags] +public enum PointerAttributes : uint +{ + /// + /// Indicates the pointer is a 16 bit pointer. + /// + Near16 = 0b0000000000_000_000000_00000_000_00000, + + /// + /// Indicates the pointer is a 16:16 far pointer. + /// + Far16 = 0b0000000000_000_000000_00000_000_000001, + + /// + /// Indicates the pointer is a 16:16 huge pointer. + /// + Huge16 = 0b0000000000_000_000000_00000_000_000010, + + /// + /// Indicates the pointer is a based on segment. + /// + BasedOnSegment = 0b0000000000_000_000000_00000_000_000011, + + /// + /// Indicates the pointer is a based on value of base. + /// + BasedOnValue = 0b0000000000_000_000000_00000_000_000100, + + /// + /// Indicates the pointer is a based on segment value of base. + /// + BasedOnSegmentValue = 0b0000000000_000_000000_00000_000_000101, + + /// + /// Indicates the pointer is a based on address of base. + /// + BasedOnAddress = 0b0000000000_000_000000_00000_000_000110, + + /// + /// Indicates the pointer is a based on segment address of base. + /// + BasedOnSegmentAddress = 0b0000000000_000_000000_00000_000_000111, + + /// + /// Indicates the pointer is a based on type. + /// + BasedOnType = 0b0000000000_000_000000_00000_000_001000, + + /// + /// Indicates the pointer is a based on self. + /// + BasedOnSelf = 0b0000000000_000_000000_00000_000_001001, + + /// + /// Indicates the pointer is a 32 bit pointer. + /// + Near32 = 0b0000000000_000_000000_00000_000_001010, + + /// + /// Indicates the pointer is a 16:32 pointer. + /// + Far32 = 0b0000000000_000_000000_00000_000_001011, + + /// + /// Indicates the pointer is a 64 bit pointer. + /// + Near64 = 0b0000000000_000_000000_00000_000_001100, + + /// + /// Provides the bit-mask for extracting the pointer kind from the flags. + /// + KindMask = 0b0000000000_000_000000_00000_000_11111, + + /// + /// Indicates the pointer is an "old" reference. + /// + LValueReference = 0b0000000000_000_000000_00000_001_00000, + + /// + /// Indicates the pointer is a pointer to data member. + /// + PointerToDataMember = 0b0000000000_000_000000_00000_010_00000, + + /// + /// Indicates the pointer is a pointer to member function. + /// + PointerToMemberFunction = 0b0000000000_000_000000_00000_011_00000, + + /// + /// Indicates the pointer is an r-value reference. + /// + RValueReference = 0b0000000000_000_000000_00000_100_00000, + + /// + /// Provides the bit-mask for extracting the pointer mode from the flags. + /// + ModeMask = 0b0000000000_000_000000_00000_111_00000, + + /// + /// Indicates the pointer is a "flat" pointer. + /// + Flat32 = 0b0000000000_000_000000_00001_000_00000, + + /// + /// Indicates the pointer is marked volatile. + /// + Volatile = 0b0000000000_000_000000_00010_000_00000, + + /// + /// Indicates the pointer is marked const. + /// + Const = 0b0000000000_000_000000_00100_000_00000, + + /// + /// Indicates the pointer is marked unaligned. + /// + Unaligned = 0b0000000000_000_000000_01000_000_00000, + + /// + /// Indicates the pointer is marked restrict. + /// + Restrict = 0b0000000000_000_000000_10000_000_00000, + + /// + /// Indicates the pointer is a WinRT smart pointer. + /// + WinRTSmartPointer = 0b0000000000_001_000000_00000_000_00000, + + /// + /// Indicates the pointer is a 'this' pointer of a member function with ref qualifier. + /// + LValueRefThisPointer = 0b0000000000_010_000000_00000_000_00000, + + /// + /// Indicates the pointer is a 'this' pointer of a member function with ref qualifier. + /// + RValueRefThisPointer = 0b0000000000_100_000000_00000_000_00000 +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/PointerTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/PointerTypeRecord.cs new file mode 100644 index 000000000..740e792cd --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/PointerTypeRecord.cs @@ -0,0 +1,356 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a pointer type in a TPI or IPI stream. +/// +public class PointerTypeRecord : CodeViewTypeRecord +{ + private readonly LazyVariable _baseType; + + /// + /// Initializes a new empty pointer type. + /// + /// The type index to assign to the type. + protected PointerTypeRecord(uint typeIndex) + : base(typeIndex) + { + _baseType = new LazyVariable(GetBaseType); + } + + /// + /// Creates a new pointer type. + /// + /// The referent type. + /// The attributes describing the shape of the pointer. + public PointerTypeRecord(CodeViewTypeRecord type, PointerAttributes attributes) + : base(0) + { + _baseType = new LazyVariable(type); + Attributes = attributes; + } + + /// + /// Creates a new pointer type. + /// + /// The referent type. + /// The attributes describing the shape of the pointer. + /// The size of the pointer. + public PointerTypeRecord(CodeViewTypeRecord type, PointerAttributes attributes, byte size) + : base(0) + { + _baseType = new LazyVariable(type); + Attributes = attributes; + Size = size; + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Pointer; + + /// + /// Gets or sets the referent type of the pointer. + /// + public CodeViewTypeRecord BaseType + { + get => _baseType.Value; + set => _baseType.Value = value; + } + + /// + /// Gets or sets the attributes describing the shape of the pointer type. + /// + public PointerAttributes Attributes + { + get; + set; + } + + /// + /// Gets or sets the kind of the pointer. + /// + public PointerAttributes Kind + { + get => Attributes & PointerAttributes.KindMask; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) | (value & PointerAttributes.KindMask); + } + + /// + /// Gets or sets a value indicating whether the pointer is a 16 bit pointer. + /// + public bool IsNear16 + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.Near16; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.Near16 : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a 16:16 far pointer. + /// + public bool IsFar16 + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.Far16; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.Far16 : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a 16:16 huge pointer. + /// + public bool IsHuge16 + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.Huge16; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.Huge16 : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a based on segment. + /// + public bool IsBasedOnSegment + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.BasedOnSegment; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.BasedOnSegment : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a based on value of base. + /// + public bool IsBasedOnValue + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.BasedOnValue; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.BasedOnValue : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a based on segment value of base. + /// + public bool IsBasedOnSegmentValue + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.BasedOnSegmentValue; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.BasedOnSegmentValue : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a based on address of base. + /// + public bool IsBasedOnAddress + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.BasedOnAddress; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.BasedOnAddress : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a based on segment address of base. + /// + public bool IsBasedOnSegmentAddress + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.BasedOnSegmentAddress; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.BasedOnSegmentAddress : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a based on type. + /// + public bool IsBasedOnType + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.BasedOnType; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.BasedOnType : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a based on self. + /// + public bool IsBasedOnSelf + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.BasedOnSelf; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.BasedOnSelf : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a 32 bit pointer. + /// + public bool IsNear32 + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.Near32; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.Near32 : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a 16:32 pointer. + /// + public bool IsFar32 + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.Far32; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.Far32 : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a 64 bit pointer. + /// + public bool IsNear64 + { + get => (Attributes & PointerAttributes.KindMask) == PointerAttributes.Near64; + set => Attributes = (Attributes & ~PointerAttributes.KindMask) + | (value ? PointerAttributes.Near64 : 0); + } + + /// + /// Gets or sets the mode of the pointer. + /// + public PointerAttributes Mode + { + get => Attributes & PointerAttributes.ModeMask; + set => Attributes = (Attributes & ~PointerAttributes.ModeMask) | (value & PointerAttributes.ModeMask); + } + + /// + /// Gets or sets a value indicating whether the pointer is an "old" reference. + /// + public bool IsLValueReference + { + get => (Attributes & PointerAttributes.ModeMask) == PointerAttributes.LValueReference; + set => Attributes = (Attributes & ~PointerAttributes.ModeMask) + | (value ? PointerAttributes.LValueReference : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a pointer to data member. + /// + public bool IsPointerToDataMember + { + get => (Attributes & PointerAttributes.ModeMask) == PointerAttributes.PointerToDataMember; + set => Attributes = (Attributes & ~PointerAttributes.ModeMask) + | (value ? PointerAttributes.PointerToDataMember : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a pointer to member function. + /// + public bool IsPointerToMemberFunction + { + get => (Attributes & PointerAttributes.ModeMask) == PointerAttributes.PointerToMemberFunction; + set => Attributes = (Attributes & ~PointerAttributes.ModeMask) + | (value ? PointerAttributes.PointerToMemberFunction : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is an r-value reference. + /// + public bool IsRValueReference + { + get => (Attributes & PointerAttributes.ModeMask) == PointerAttributes.RValueReference; + set => Attributes = (Attributes & ~PointerAttributes.ModeMask) + | (value ? PointerAttributes.RValueReference : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a "flat" pointer. + /// + public bool IsFlat32 + { + get => (Attributes & PointerAttributes.Flat32) != 0; + set => Attributes = (Attributes & ~PointerAttributes.Flat32) + | (value ? PointerAttributes.Flat32 : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is marked volatile. + /// + public bool IsVolatile + { + get => (Attributes & PointerAttributes.Volatile) != 0; + set => Attributes = (Attributes & ~PointerAttributes.Volatile) + | (value ? PointerAttributes.Volatile : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is marked const. + /// + public bool IsConst + { + get => (Attributes & PointerAttributes.Const) != 0; + set => Attributes = (Attributes & ~PointerAttributes.Const) + | (value ? PointerAttributes.Const : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is marked unaligned. + /// + public bool IsUnaligned + { + get => (Attributes & PointerAttributes.Unaligned) != 0; + set => Attributes = (Attributes & ~PointerAttributes.Unaligned) + | (value ? PointerAttributes.Unaligned : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is marked restrict. + /// + public bool IsRestrict + { + get => (Attributes & PointerAttributes.Restrict) != 0; + set => Attributes = (Attributes & ~PointerAttributes.Restrict) + | (value ? PointerAttributes.Restrict : 0); + } + + /// + /// Gets or sets the size of the pointer. + /// + public byte Size + { + get => (byte) (((uint) Attributes >> 0xD) & 0b111111); + set => Attributes = (PointerAttributes) (((uint) Attributes & ~(0b111111u << 0xD)) + | (((uint) value & 0b111111) << 0xD)); + } + + /// + /// Gets or sets a value indicating whether the pointer is a WinRT smart pointer. + /// + public bool IsWinRTSmartPointer + { + get => (Attributes & PointerAttributes.WinRTSmartPointer) != 0; + set => Attributes = (Attributes & ~PointerAttributes.WinRTSmartPointer) + | (value ? PointerAttributes.WinRTSmartPointer : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a 'this' pointer of a member function with ref qualifier. + /// + public bool IsLValueRefThisPointer + { + get => (Attributes & PointerAttributes.LValueRefThisPointer) != 0; + set => Attributes = (Attributes & ~PointerAttributes.LValueRefThisPointer) + | (value ? PointerAttributes.LValueRefThisPointer : 0); + } + + /// + /// Gets or sets a value indicating whether the pointer is a 'this' pointer of a member function with ref qualifier. + /// + public bool IsRValueRefThisPointer + { + get => (Attributes & PointerAttributes.RValueRefThisPointer) != 0; + set => Attributes = (Attributes & ~PointerAttributes.RValueRefThisPointer) + | (value ? PointerAttributes.RValueRefThisPointer : 0); + } + + /// + /// Obtains the base type of the pointer. + /// + /// The base type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetBaseType() => null; + + /// + public override string ToString() => $"({BaseType})*"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/ProcedureTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/ProcedureTypeRecord.cs new file mode 100644 index 000000000..b973fcccb --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/ProcedureTypeRecord.cs @@ -0,0 +1,101 @@ +using System.Linq; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a function pointer or procedure type. +/// +public class ProcedureTypeRecord : CodeViewTypeRecord +{ + private readonly LazyVariable _returnType; + private readonly LazyVariable _argumentList; + + /// + /// Initializes an empty procedure type. + /// + /// The type index to assign to the type. + protected ProcedureTypeRecord(uint typeIndex) + : base(typeIndex) + { + _returnType = new LazyVariable(GetReturnType); + _argumentList = new LazyVariable(GetArguments); + } + + /// + /// Creates a new procedure type. + /// + /// The convention to use when calling the function pointed by values of this type. + /// The return type of the function. + /// The argument type list of the function. + public ProcedureTypeRecord(CodeViewCallingConvention callingConvention, CodeViewTypeRecord returnType, ArgumentListLeaf arguments) + : base(0) + { + CallingConvention = callingConvention; + _returnType = new LazyVariable(returnType); + _argumentList = new LazyVariable(arguments); + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Procedure; + + /// + /// Gets or sets the return type of the function. + /// + public CodeViewTypeRecord? ReturnType + { + get => _returnType.Value; + set => _returnType.Value = value; + } + + /// + /// Gets or sets the convention that is used when calling the member function. + /// + public CodeViewCallingConvention CallingConvention + { + get; + set; + } + + /// + /// Gets or sets the attributes associated to the function. + /// + public MemberFunctionAttributes Attributes + { + get; + set; + } + + /// + /// Gets or sets the list of types of the parameters that this function defines. + /// + public ArgumentListLeaf? Arguments + { + get => _argumentList.Value; + set => _argumentList.Value = value; + } + + /// + /// Obtains the return type of the procedure. + /// + /// The return type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetReturnType() => null; + + /// + /// Obtains the argument types of the procedure.. + /// + /// The argument types. + /// + /// This method is called upon initialization of the property. + /// + protected virtual ArgumentListLeaf? GetArguments() => null; + + /// + public override string ToString() + { + string args = string.Join(", ", Arguments?.Types ?? Enumerable.Empty()); + return $"{CallingConvention} {ReturnType} *({args})"; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArgumentListLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArgumentListLeaf.cs new file mode 100644 index 000000000..a8a54409c --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArgumentListLeaf.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedArgumentListLeaf : ArgumentListLeaf +{ + private readonly PdbReaderContext _context; + private readonly BinaryStreamReader _reader; + + /// + /// Reads a argument list from the provided input stream. + /// + /// The reading context in which the list is situated in. + /// The type index to assign to the list. + /// The input stream to read from. + public SerializedArgumentListLeaf(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _reader = reader; + } + + /// + protected override IList GetArgumentTypes() + { + var reader = _reader.Fork(); + + int count = reader.ReadInt32(); + var result = new List(count); + + for (int i = 0; i < count; i++) + { + uint typeIndex = reader.ReadUInt32(); + if (!_context.ParentImage.TryGetLeafRecord(typeIndex, out var leaf) || leaf is not CodeViewTypeRecord t) + { + _context.Parameters.ErrorListener.BadImage( + $"Argument list {TypeIndex:X8} contains an invalid argument type index {typeIndex:X8}."); + return result; + } + + result.Add(t); + } + + return result; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArrayTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArrayTypeRecord.cs new file mode 100644 index 000000000..273df7536 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedArrayTypeRecord.cs @@ -0,0 +1,52 @@ +using System; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedArrayTypeRecord : ArrayTypeRecord +{ + private readonly PdbReaderContext _context; + private readonly uint _elementTypeIndex; + private readonly uint _indexTypeIndex; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads an array type from the provided input stream. + /// + /// The reading context in which the type is situated in. + /// The type index to assign to the type. + /// The input stream to read from. + public SerializedArrayTypeRecord(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _elementTypeIndex = reader.ReadUInt32(); + _indexTypeIndex = reader.ReadUInt32(); + Length = Convert.ToUInt64(ReadNumeric(ref reader)); + _nameReader = reader.Fork(); + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override CodeViewTypeRecord? GetElementType() + { + return _context.ParentImage.TryGetLeafRecord(_elementTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Array type {TypeIndex:X8} contains an invalid element type index {_elementTypeIndex:X8}."); + } + + /// + protected override CodeViewTypeRecord? GetIndexType() + { + return _context.ParentImage.TryGetLeafRecord(_indexTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Array type {TypeIndex:X8} contains an invalid index type index {_indexTypeIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBaseClassField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBaseClassField.cs new file mode 100644 index 000000000..325e73010 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBaseClassField.cs @@ -0,0 +1,37 @@ +using System; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedBaseClassField : BaseClassField +{ + private readonly PdbReaderContext _context; + private readonly uint _baseTypeIndex; + + /// + /// Reads a base class from the provided input stream. + /// + /// The reading context in which the class is situated in. + /// The type index to assign to the type. + /// The input stream to read from. + public SerializedBaseClassField(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + Attributes = (CodeViewFieldAttributes) reader.ReadUInt16(); + _baseTypeIndex = reader.ReadUInt32(); + Offset = Convert.ToUInt64(ReadNumeric(ref reader)); + } + + /// + protected override CodeViewTypeRecord? GetBaseType() + { + return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Base class {TypeIndex:X8} contains an invalid underlying base type index {_baseTypeIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBitFieldTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBitFieldTypeRecord.cs new file mode 100644 index 000000000..e18e87dfe --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedBitFieldTypeRecord.cs @@ -0,0 +1,36 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedBitFieldTypeRecord : BitFieldTypeRecord +{ + private readonly PdbReaderContext _context; + private readonly uint _baseTypeIndex; + + /// + /// Reads a bit field type from the provided input stream. + /// + /// The reading context in which the bit field type is situated in. + /// The type index to assign to the type. + /// The input stream to read from. + public SerializedBitFieldTypeRecord(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _baseTypeIndex = reader.ReadUInt32(); + Length = reader.ReadByte(); + Position = reader.ReadByte(); + } + + /// + protected override CodeViewTypeRecord? GetBaseType() + { + return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Bit field type {TypeIndex:X8} contains an invalid underlying base type index {_baseTypeIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedClassTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedClassTypeRecord.cs new file mode 100644 index 000000000..3773352ff --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedClassTypeRecord.cs @@ -0,0 +1,83 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedClassTypeRecord : ClassTypeRecord +{ + private readonly PdbReaderContext _context; + private readonly ushort _memberCount; + private readonly uint _baseTypeIndex; + private readonly uint _fieldIndex; + private readonly uint _vTableShapeIndex; + private readonly BinaryStreamReader _nameReader; + private readonly BinaryStreamReader _uniqueNameReader; + + /// + /// Reads a class type from the provided input stream. + /// + /// The kind of type that is being read. + /// The reading context in which the type is situated in. + /// The type index to assign to the type. + /// The input stream to read from. + public SerializedClassTypeRecord(CodeViewLeafKind kind, PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(kind, typeIndex) + { + _context = context; + _memberCount = reader.ReadUInt16(); + StructureAttributes = (StructureAttributes) reader.ReadUInt16(); + _fieldIndex = reader.ReadUInt32(); + _baseTypeIndex = reader.ReadUInt32(); + _vTableShapeIndex = reader.ReadUInt32(); + + Size = (uint) ReadNumeric(ref reader); + + _nameReader = reader.Fork(); + reader.AdvanceUntil(0, true); + _uniqueNameReader = reader.Fork(); + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override Utf8String GetUniqueName() => _uniqueNameReader.Fork().ReadUtf8String(); + + /// + protected override CodeViewTypeRecord? GetBaseType() + { + if (_baseTypeIndex == 0) + return null; + + return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Class type {TypeIndex:X8} contains an invalid underlying enum type index {_baseTypeIndex:X8}."); + } + + /// + protected override FieldListLeaf? GetFields() + { + if (_fieldIndex == 0) + return null; + + return _context.ParentImage.TryGetLeafRecord(_fieldIndex, out var leaf) && leaf is SerializedFieldListLeaf list + ? list + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Class type {TypeIndex:X8} contains an invalid field list index {_fieldIndex:X8}."); + } + + /// + protected override VTableShapeLeaf? GetVTableShape() + { + if (_vTableShapeIndex == 0) + return null; + + return _context.ParentImage.TryGetLeafRecord(_vTableShapeIndex, out var leaf) && leaf is VTableShapeLeaf shape + ? shape + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Class type {TypeIndex:X8} contains an invalid VTable shape index {_fieldIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumTypeRecord.cs new file mode 100644 index 000000000..e671bb176 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumTypeRecord.cs @@ -0,0 +1,56 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedEnumTypeRecord : EnumTypeRecord +{ + private readonly PdbReaderContext _context; + private readonly ushort _memberCount; + private readonly uint _underlyingType; + private readonly uint _fieldIndex; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a constant symbol from the provided input stream. + /// + /// The reading context in which the symbol is situated in. + /// The type index to assign to the enum type. + /// The input stream to read from. + public SerializedEnumTypeRecord(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _memberCount = reader.ReadUInt16(); + StructureAttributes = (StructureAttributes) reader.ReadUInt16(); + _underlyingType = reader.ReadUInt32(); + _fieldIndex = reader.ReadUInt32(); + _nameReader = reader; + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override CodeViewTypeRecord? GetBaseType() + { + return _context.ParentImage.TryGetLeafRecord(_underlyingType, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Enum type {TypeIndex:X8} contains an invalid underlying enum type index {_underlyingType:X8}."); + } + + /// + protected override FieldListLeaf? GetFields() + { + if (_fieldIndex == 0) + return null; + + return _context.ParentImage.TryGetLeafRecord(_fieldIndex, out var leaf) && leaf is FieldListLeaf list + ? list + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Enum type {TypeIndex:X8} contains an invalid field list index {_fieldIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumerateField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumerateField.cs new file mode 100644 index 000000000..91457f27f --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedEnumerateField.cs @@ -0,0 +1,32 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedEnumerateField : EnumerateField +{ + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a enumerate field list from the provided input stream. + /// + /// The reading context in which the enumerate field is situated in. + /// The type index to assign to the enum type. + /// The input stream to read from. + public SerializedEnumerateField(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) + : base(typeIndex) + { + Attributes = (CodeViewFieldAttributes) reader.ReadUInt16(); + + // We need to eagerly initialize the value because it is the only way to know how large the leaf is. + Value = ReadNumeric(ref reader); + + _nameReader = reader; + reader.AdvanceUntil(0, true); + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedFieldListLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedFieldListLeaf.cs new file mode 100644 index 000000000..2eddbc7d3 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedFieldListLeaf.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +internal class SerializedFieldListLeaf : FieldListLeaf +{ + private readonly PdbReaderContext _context; + private readonly BinaryStreamReader _reader; + + /// + /// Reads a field list from the provided input stream. + /// + /// The reading context in which the list is situated in. + /// The type index to assign to the enum type. + /// The input stream to read from. + public SerializedFieldListLeaf(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _reader = reader; + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.FieldList; + + protected override IList GetEntries() + { + var reader = _reader.Fork(); + var result = new List(); + + while (reader.CanRead(sizeof(ushort))) + { + // Skip padding bytes. + while (reader.CanRead(sizeof(byte))) + { + var b = (CodeViewLeafKind) reader.ReadByte(); + if (b < CodeViewLeafKind.Pad0) + { + reader.Offset--; + break; + } + } + + if (!reader.CanRead(sizeof(byte))) + break; + + // Read field. + var leaf = FromReaderNoHeader(_context, 0, ref reader); + if (leaf is CodeViewField field) + result.Add(field); + else + _context.Parameters.ErrorListener.BadImage($"Field list contains a non-field leaf {leaf.LeafKind}."); + } + + return result; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedInstanceDataField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedInstanceDataField.cs new file mode 100644 index 000000000..c5fcf252e --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedInstanceDataField.cs @@ -0,0 +1,49 @@ +using System; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedInstanceDataField : InstanceDataField +{ + private readonly PdbReaderContext _context; + private readonly uint _dataTypeIndex; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads an instance data member list from the provided input stream. + /// + /// The reading context in which the member is situated in. + /// The type index to assign to the member. + /// The input stream to read from. + public SerializedInstanceDataField(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) + : base(typeIndex) + { + Attributes = (CodeViewFieldAttributes) reader.ReadUInt16(); + _dataTypeIndex = reader.ReadUInt32(); + + // We need to eagerly initialize the offset because it is the only way to know how large the leaf is. + Offset = Convert.ToUInt64(ReadNumeric(ref reader)); + + _context = context; + _nameReader = reader; + reader.AdvanceUntil(0, true); + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override CodeViewTypeRecord? GetDataType() + { + if (_dataTypeIndex == 0) + return null; + + return _context.ParentImage.TryGetLeafRecord(_dataTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Instance data member {TypeIndex:X8} contains an invalid data type index {_dataTypeIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMemberFunctionLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMemberFunctionLeaf.cs new file mode 100644 index 000000000..1b0cc2253 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMemberFunctionLeaf.cs @@ -0,0 +1,75 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedMemberFunctionLeaf : MemberFunctionLeaf +{ + private readonly PdbReaderContext _context; + private readonly uint _returnTypeIndex; + private readonly uint _declaringTypeIndex; + private readonly uint _thisTypeIndex; + private readonly ushort _parameterCount; + private readonly uint _argumentListIndex; + + /// + /// Reads a member function from the provided input stream. + /// + /// The reading context in which the function is situated in. + /// The index to assign to the type. + /// The input stream to read from. + public SerializedMemberFunctionLeaf(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _returnTypeIndex = reader.ReadUInt32(); + _declaringTypeIndex = reader.ReadUInt32(); + _thisTypeIndex = reader.ReadUInt32(); + CallingConvention = (CodeViewCallingConvention) reader.ReadByte(); + Attributes = (MemberFunctionAttributes) reader.ReadByte(); + _parameterCount = reader.ReadUInt16(); + _argumentListIndex = reader.ReadUInt32(); + ThisAdjuster = reader.ReadUInt32(); + } + + /// + protected override CodeViewTypeRecord? GetReturnType() + { + return _context.ParentImage.TryGetLeafRecord(_returnTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Member function {TypeIndex:X8} contains an invalid return type index {_returnTypeIndex:X8}."); + } + + /// + protected override CodeViewTypeRecord? GetDeclaringType() + { + return _context.ParentImage.TryGetLeafRecord(_declaringTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Member function {TypeIndex:X8} contains an invalid declaring type index {_declaringTypeIndex:X8}."); + } + + /// + protected override CodeViewTypeRecord? GetThisType() + { + return _context.ParentImage.TryGetLeafRecord(_thisTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Member function {TypeIndex:X8} contains an invalid this-type index {_thisTypeIndex:X8}."); + } + + /// + protected override ArgumentListLeaf? GetArguments() + { + if (_argumentListIndex == 0) + return null; + + return _context.ParentImage.TryGetLeafRecord(_argumentListIndex, out var leaf) && leaf is ArgumentListLeaf list + ? list + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Member function {TypeIndex:X8} contains an invalid argument list index {_argumentListIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListEntry.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListEntry.cs new file mode 100644 index 000000000..af17da782 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListEntry.cs @@ -0,0 +1,35 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedMethodListEntry : MethodListEntry +{ + private readonly PdbReaderContext _context; + private readonly uint _functionIndex; + + /// + /// Reads a member function from the provided input stream. + /// + /// The reading context in which the type is situated in. + /// The input stream to read from. + public SerializedMethodListEntry(PdbReaderContext context, ref BinaryStreamReader reader) + { + _context = context; + Attributes = (CodeViewFieldAttributes) reader.ReadUInt16(); + reader.ReadUInt16(); // padding + _functionIndex = reader.ReadUInt32(); + VTableOffset = IsIntroducingVirtual ? reader.ReadUInt32() : 0; + } + + /// + protected override MemberFunctionLeaf? GetFunction() + { + return _context.ParentImage.TryGetLeafRecord(_functionIndex, out var leaf) && leaf is MemberFunctionLeaf type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Method list entry contains an invalid function type index {_functionIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListLeaf.cs new file mode 100644 index 000000000..36927a164 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedMethodListLeaf.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedMethodListLeaf : MethodListLeaf +{ + private readonly PdbReaderContext _context; + private readonly BinaryStreamReader _reader; + + /// + /// Reads a method list from the provided input stream. + /// + /// The reading context in which the list is situated in. + /// The type index to assign to the enum type. + /// The input stream to read from. + public SerializedMethodListLeaf(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _reader = reader; + } + + /// + protected override IList GetEntries() + { + var result = new List(); + + var reader = _reader.Fork(); + while (reader.CanRead(8)) + result.Add(new SerializedMethodListEntry(_context, ref reader)); + + return result; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedModifierTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedModifierTypeRecord.cs new file mode 100644 index 000000000..a6c9b5bf3 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedModifierTypeRecord.cs @@ -0,0 +1,35 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedModifierTypeRecord : ModifierTypeRecord +{ + private readonly PdbReaderContext _context; + private readonly uint _baseTypeIndex; + + /// + /// Reads a pointer type from the provided input stream. + /// + /// The reading context in which the type is situated in. + /// The index to assign to the type. + /// The input stream to read from. + public SerializedModifierTypeRecord(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _baseTypeIndex = reader.ReadUInt32(); + Attributes = (ModifierAttributes) reader.ReadUInt32(); + } + + /// + protected override CodeViewTypeRecord? GetBaseType() + { + return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Modifier type {TypeIndex:X8} contains an invalid underlying base type index {_baseTypeIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNestedTypeField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNestedTypeField.cs new file mode 100644 index 000000000..9ebefafc0 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNestedTypeField.cs @@ -0,0 +1,41 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedNestedTypeField : NestedTypeField +{ + private readonly PdbReaderContext _context; + private readonly uint _typeIndex; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a nested type field from the provided input stream. + /// + /// The reading context in which the field is situated in. + /// The index to assign to the field. + /// The input stream to read from. + public SerializedNestedTypeField(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + Attributes = (CodeViewFieldAttributes) reader.ReadUInt16(); + _typeIndex = reader.ReadUInt32(); + _nameReader = reader.Fork(); + reader.AdvanceUntil(0, true); + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override CodeViewTypeRecord? GetNestedType() + { + return _context.ParentImage.TryGetLeafRecord(_typeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Nested type {TypeIndex:X8} contains an invalid type index {_typeIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNonOverloadedMethod.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNonOverloadedMethod.cs new file mode 100644 index 000000000..08347ed9d --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedNonOverloadedMethod.cs @@ -0,0 +1,43 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedNonOverloadedMethod : NonOverloadedMethod +{ + private readonly PdbReaderContext _context; + private readonly uint _functionIndex; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a non-overloaded method from the provided input stream. + /// + /// The reading context in which the method is situated in. + /// The index to assign to the type. + /// The input stream to read from. + public SerializedNonOverloadedMethod(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + Attributes = (CodeViewFieldAttributes) reader.ReadUInt16(); + _functionIndex = reader.ReadUInt32(); + if (IsIntroducingVirtual) + VTableOffset = reader.ReadUInt32(); + _nameReader = reader.Fork(); + reader.AdvanceUntil(0, true); + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override MemberFunctionLeaf? GetFunction() + { + return _context.ParentImage.TryGetLeafRecord(_functionIndex, out var leaf) && leaf is MemberFunctionLeaf type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Method {TypeIndex:X8} contains an invalid function type index {_functionIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedOverloadedMethod.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedOverloadedMethod.cs new file mode 100644 index 000000000..3342850e3 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedOverloadedMethod.cs @@ -0,0 +1,45 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedOverloadedMethod : OverloadedMethod +{ + private readonly PdbReaderContext _context; + private readonly ushort _functionCount; + private readonly uint _methodListIndex; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads an overloaded method from the provided input stream. + /// + /// The reading context in which the method is situated in. + /// The index to assign to the type. + /// The input stream to read from. + public SerializedOverloadedMethod(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _functionCount = reader.ReadUInt16(); + _methodListIndex = reader.ReadUInt32(); + _nameReader = reader.Fork(); + reader.AdvanceUntil(0, true); + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override MethodListLeaf? GetMethods() + { + if (_methodListIndex == 0) + return null; + + return _context.ParentImage.TryGetLeafRecord(_methodListIndex, out var leaf) && leaf is MethodListLeaf list + ? list + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Overloaded method {TypeIndex:X8} contains an invalid field list index {_methodListIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedPointerTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedPointerTypeRecord.cs new file mode 100644 index 000000000..74a481134 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedPointerTypeRecord.cs @@ -0,0 +1,38 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedPointerTypeRecord : PointerTypeRecord +{ + private readonly PdbReaderContext _context; + private readonly uint _baseTypeIndex; + + /// + /// Reads a pointer type from the provided input stream. + /// + /// The reading context in which the type is situated in. + /// The index to assign to the type. + /// The input stream to read from. + public SerializedPointerTypeRecord(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _baseTypeIndex = reader.ReadUInt32(); + Attributes = (PointerAttributes) reader.ReadUInt32(); + + // TODO: member pointer info + } + + /// + protected override CodeViewTypeRecord? GetBaseType() + { + return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Pointer {TypeIndex:X8} contains an invalid underlying base type index {_baseTypeIndex:X8}."); + + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedProcedureTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedProcedureTypeRecord.cs new file mode 100644 index 000000000..90465a8a9 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedProcedureTypeRecord.cs @@ -0,0 +1,52 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedProcedureTypeRecord : ProcedureTypeRecord +{ + private readonly PdbReaderContext _context; + private readonly uint _returnTypeIndex; + private readonly ushort _parameterCount; + private readonly uint _argumentListIndex; + + /// + /// Reads a procedure type from the provided input stream. + /// + /// The reading context in which the type is situated in. + /// The index to assign to the type. + /// The input stream to read from. + public SerializedProcedureTypeRecord(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _returnTypeIndex = reader.ReadUInt32(); + CallingConvention = (CodeViewCallingConvention) reader.ReadByte(); + Attributes = (MemberFunctionAttributes) reader.ReadByte(); + _parameterCount = reader.ReadUInt16(); + _argumentListIndex = reader.ReadUInt32(); + } + + /// + protected override CodeViewTypeRecord? GetReturnType() + { + return _context.ParentImage.TryGetLeafRecord(_returnTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Procedure type {TypeIndex:X8} contains an invalid return type index {_returnTypeIndex:X8}."); + } + + /// + protected override ArgumentListLeaf? GetArguments() + { + if (_argumentListIndex == 0) + return null; + + return _context.ParentImage.TryGetLeafRecord(_argumentListIndex, out var leaf) && leaf is ArgumentListLeaf list + ? list + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Procedure type {TypeIndex:X8} contains an invalid argument list index {_argumentListIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedStaticDataField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedStaticDataField.cs new file mode 100644 index 000000000..4196ac5c9 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedStaticDataField.cs @@ -0,0 +1,45 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedStaticDataField : StaticDataField +{ + private readonly PdbReaderContext _context; + private readonly uint _dataTypeIndex; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a static data member list from the provided input stream. + /// + /// The reading context in which the member is situated in. + /// The type index to assign to the member. + /// The input stream to read from. + public SerializedStaticDataField(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) + : base(typeIndex) + { + Attributes = (CodeViewFieldAttributes) reader.ReadUInt16(); + _dataTypeIndex = reader.ReadUInt32(); + + _context = context; + _nameReader = reader; + reader.AdvanceUntil(0, true); + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override CodeViewTypeRecord? GetDataType() + { + if (_dataTypeIndex == 0) + return null; + + return _context.ParentImage.TryGetLeafRecord(_dataTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Static data member {TypeIndex:X8} contains an invalid data type index {_dataTypeIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedUnionTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedUnionTypeRecord.cs new file mode 100644 index 000000000..3a0169a39 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedUnionTypeRecord.cs @@ -0,0 +1,55 @@ +using System; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedUnionTypeRecord : UnionTypeRecord +{ + private readonly PdbReaderContext _context; + private readonly ushort _memberCount; + private readonly uint _fieldIndex; + private readonly BinaryStreamReader _nameReader; + private readonly BinaryStreamReader _uniqueNameReader; + + /// + /// Reads a union type from the provided input stream. + /// + /// The reading context in which the type is situated in. + /// The index to assign to the type. + /// The input stream to read from. + public SerializedUnionTypeRecord(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + _memberCount = reader.ReadUInt16(); + StructureAttributes = (StructureAttributes) reader.ReadUInt16(); + _fieldIndex = reader.ReadUInt32(); + + Size = Convert.ToUInt64(ReadNumeric(ref reader)); + + _nameReader = reader.Fork(); + reader.AdvanceUntil(0, true); + _uniqueNameReader = reader.Fork(); + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override Utf8String GetUniqueName() => _uniqueNameReader.Fork().ReadUtf8String(); + + /// + protected override FieldListLeaf? GetFields() + { + if (_fieldIndex == 0) + return null; + + return _context.ParentImage.TryGetLeafRecord(_fieldIndex, out var leaf) && leaf is SerializedFieldListLeaf list + ? list + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Union type {TypeIndex:X8} contains an invalid field list index {_fieldIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVBaseClassField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVBaseClassField.cs new file mode 100644 index 000000000..5be9b1165 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVBaseClassField.cs @@ -0,0 +1,55 @@ +using System; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedVBaseClassField : VBaseClassField +{ + private readonly PdbReaderContext _context; + private readonly uint _baseTypeIndex; + private readonly uint _basePointerIndex; + + /// + /// Reads a virtual base class from the provided input stream. + /// + /// The reading context in which the class is situated in. + /// The type index to assign to the type. + /// The input stream to read from. + /// true if the field is an indirect virtual base class, false otherwise. + public SerializedVBaseClassField( + PdbReaderContext context, + uint typeIndex, + ref BinaryStreamReader reader, + bool isIndirect) + : base(typeIndex) + { + _context = context; + Attributes = (CodeViewFieldAttributes) reader.ReadUInt16(); + _baseTypeIndex = reader.ReadUInt32(); + _basePointerIndex = reader.ReadUInt32(); + PointerOffset = Convert.ToUInt64(ReadNumeric(ref reader)); + TableOffset = Convert.ToUInt64(ReadNumeric(ref reader)); + IsIndirect = isIndirect; + } + + /// + protected override CodeViewTypeRecord? GetBaseType() + { + return _context.ParentImage.TryGetLeafRecord(_baseTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Virtual base class {TypeIndex:X8} contains an invalid base type index {_baseTypeIndex:X8}."); + } + + /// + protected override CodeViewTypeRecord? GetBasePointerType() + { + return _context.ParentImage.TryGetLeafRecord(_basePointerIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Virtual base class {TypeIndex:X8} contains an invalid base pointer type index {_basePointerIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableField.cs new file mode 100644 index 000000000..66ed003c7 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableField.cs @@ -0,0 +1,35 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedVTableField : VTableField +{ + private readonly PdbReaderContext _context; + private readonly uint _pointerTypeIndex; + + /// + /// Reads a virtual function table field from the provided input stream. + /// + /// The reading context in which the field is situated in. + /// The type index to assign to the field. + /// The input stream to read from. + public SerializedVTableField(PdbReaderContext context, uint typeIndex, ref BinaryStreamReader reader) + : base(typeIndex) + { + _context = context; + reader.ReadUInt16(); // padding + _pointerTypeIndex = reader.ReadUInt32(); + } + + /// + protected override CodeViewTypeRecord? GetPointerType() + { + return _context.ParentImage.TryGetLeafRecord(_pointerTypeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Virtual function table type {TypeIndex:X8} contains an invalid pointer type index {_pointerTypeIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableShapeLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableShapeLeaf.cs new file mode 100644 index 000000000..b8b7f0396 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/Serialized/SerializedVTableShapeLeaf.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Leaves.Serialized; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedVTableShapeLeaf : VTableShapeLeaf +{ + private readonly ushort _count; + private readonly BinaryStreamReader _entriesReader; + + /// + /// Reads a virtual function table shape from the provided input stream. + /// + /// The reading context in which the shape is situated in. + /// The index to assign to the shape. + /// The input stream to read from. + public SerializedVTableShapeLeaf(PdbReaderContext context, uint typeIndex, BinaryStreamReader reader) + : base(typeIndex) + { + _count = reader.ReadUInt16(); + _entriesReader = reader; + } + + /// + protected override IList GetEntries() + { + var result = new List(_count); + var reader = _entriesReader.Fork(); + + // Entries are stored as 4-bit values. + byte currentByte = 0; + for (int i = 0; i < _count; i++) + { + if (i % 2 == 0) + { + currentByte = reader.ReadByte(); + result.Add((VTableShapeEntry) (currentByte & 0xF)); + } + else + { + result.Add((VTableShapeEntry) ((currentByte >> 4) & 0xF)); + } + } + + return result; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeKind.cs b/src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeKind.cs new file mode 100644 index 000000000..77cbbff33 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeKind.cs @@ -0,0 +1,250 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Provides members defining all basic type kinds that can be used as a type index. +/// +/// +/// Reference: https://llvm.org/docs/PDB/TpiStream.html +/// +public enum SimpleTypeKind : uint +{ + /// + /// Indicates the type index indicates the absence of a specific type or type category. + /// + None = 0x0000, + + /// + /// Indicates the type index references the void type. + /// + Void = 0x0003, + + /// + /// Indicates the type index references a type that is not translated by CVPack. + /// + NotTranslated = 0x0007, + + /// + /// Indicates the type index references the OLE/COM HRESULT type. + /// + HResult = 0x0008, + + /// + /// Indicates the type index references the 8 bit signed character type. + /// + SignedCharacter = 0x0010, + + /// + /// Indicates the type index references the 8 bit unsigned character type. + /// + UnsignedCharacter = 0x0020, + + /// + /// Indicates the type index references the narrow character type. + /// + NarrowCharacter = 0x0070, + + /// + /// Indicates the type index references the wide character type. + /// + WideCharacter = 0x0071, + + /// + /// Indicates the type index references the char16_t type. + /// + Character16 = 0x007a, + + /// + /// Indicates the type index references the char32_t type. + /// + Character32 = 0x007b, + + /// + /// Indicates the type index references the char8_t type. + /// + Character8 = 0x007c, + + /// + /// Indicates the type index references the 8 bit signed int type. + /// + SByte = 0x0068, + + /// + /// Indicates the type index references the 8 bit unsigned int type. + /// + Byte = 0x0069, + + /// + /// Indicates the type index references the 16 bit signed type. + /// + Int16Short = 0x0011, + + /// + /// Indicates the type index references the 16 bit unsigned type. + /// + UInt16Short = 0x0021, + + /// + /// Indicates the type index references the 16 bit signed int type. + /// + Int16 = 0x0072, + + /// + /// Indicates the type index references the 16 bit unsigned int type. + /// + UInt16 = 0x0073, + + /// + /// Indicates the type index references the 32 bit signed type. + /// + Int32Long = 0x0012, + + /// + /// Indicates the type index references the 32 bit unsigned type. + /// + UInt32Long = 0x0022, + + /// + /// Indicates the type index references the 32 bit signed int type. + /// + Int32 = 0x0074, + + /// + /// Indicates the type index references the 32 bit unsigned int type. + /// + UInt32 = 0x0075, + + /// + /// Indicates the type index references the 64 bit signed type. + /// + Int64Quad = 0x0013, + + /// + /// Indicates the type index references the 64 bit unsigned type. + /// + UInt64Quad = 0x0023, + + /// + /// Indicates the type index references the 64 bit signed int type. + /// + Int64 = 0x0076, + + /// + /// Indicates the type index references the 64 bit unsigned int type. + /// + UInt64 = 0x0077, + + /// + /// Indicates the type index references the 128 bit signed int type. + /// + Int128Oct = 0x0014, + + /// + /// Indicates the type index references the 128 bit unsigned int type. + /// + UInt128Oct = 0x0024, + + /// + /// Indicates the type index references the 128 bit signed int type. + /// + Int128 = 0x0078, + + /// + /// Indicates the type index references the 128 bit unsigned int type. + /// + UInt128 = 0x0079, + + /// + /// Indicates the type index references the 16 bit real type. + /// + Float16 = 0x0046, + + /// + /// Indicates the type index references the 32 bit real type. + /// + Float32 = 0x0040, + + /// + /// Indicates the type index references the 32 bit PP real type. + /// + Float32PartialPrecision = 0x0045, + + /// + /// Indicates the type index references the 48 bit real type. + /// + Float48 = 0x0044, + + /// + /// Indicates the type index references the 64 bit real type. + /// + Float64 = 0x0041, + + /// + /// Indicates the type index references the 80 bit real type. + /// + Float80 = 0x0042, + + /// + /// Indicates the type index references the 128 bit real type. + /// + Float128 = 0x0043, + + /// + /// Indicates the type index references the 16 bit complex type. + /// + Complex16 = 0x0056, + + /// + /// Indicates the type index references the 32 bit complex type. + /// + Complex32 = 0x0050, + + /// + /// Indicates the type index references the 32 bit PP complex type. + /// + Complex32PartialPrecision = 0x0055, + + /// + /// Indicates the type index references the 48 bit complex type. + /// + Complex48 = 0x0054, + + /// + /// Indicates the type index references the 64 bit complex type. + /// + Complex64 = 0x0051, + + /// + /// Indicates the type index references the 80 bit complex type. + /// + Complex80 = 0x0052, + + /// + /// Indicates the type index references the 128 bit complex type. + /// + Complex128 = 0x0053, + + /// + /// Indicates the type index references the 8 bit boolean type. + /// + Boolean8 = 0x0030, + + /// + /// Indicates the type index references the 16 bit boolean type. + /// + Boolean16 = 0x0031, + + /// + /// Indicates the type index references the 32 bit boolean type. + /// + Boolean32 = 0x0032, + + /// + /// Indicates the type index references the 64 bit boolean type. + /// + Boolean64 = 0x0033, + + /// + /// Indicates the type index references the 128 bit boolean type. + /// + Boolean128 = 0x0034, +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeMode.cs b/src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeMode.cs new file mode 100644 index 000000000..7dff91423 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeMode.cs @@ -0,0 +1,50 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Provides members defining all possible modes that a simple type in a PDB image can be set to. +/// +/// +/// Reference: https://llvm.org/docs/PDB/TpiStream.html +/// +public enum SimpleTypeMode +{ + /// + /// Indicates the type is not a pointer. + /// + Direct = 0, + + /// + /// Indicates the type is a near pointer. + /// + NearPointer = 1, + + /// + /// Indicates the type is a far pointer. + /// + FarPointer = 2, + + /// + /// Indicates the type is a huge pointer. + /// + HugePointer = 3, + + /// + /// Indicates the type is a 32 bit near pointer. + /// + NearPointer32 = 4, + + /// + /// Indicates the type is a 32 bit far pointer. + /// + FarPointer32 = 5, + + /// + /// Indicates the type is a 64 bit near pointer. + /// + NearPointer64 = 6, + + /// + /// Indicates the type is a 128 bit near pointer. + /// + NearPointer128 = 7, +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeRecord.cs new file mode 100644 index 000000000..8b36de431 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/SimpleTypeRecord.cs @@ -0,0 +1,60 @@ +using System; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a simple type referenced by a simple type index. +/// +public class SimpleTypeRecord : CodeViewTypeRecord +{ + /// + /// Constructs a new simple type based on the provided type index. + /// + /// The type index. + public SimpleTypeRecord(uint typeIndex) + : base(typeIndex) + { + } + + /// + /// Constructs a new simple type with the provided type kind. + /// + /// The type kind. + public SimpleTypeRecord(SimpleTypeKind kind) + : base((uint) kind) + { + } + + /// + /// Constructs a new simple type with the provided type kind and mode. + /// + /// The type kind. + /// The mode indicating the pointer specifiers added to the type. + public SimpleTypeRecord(SimpleTypeKind kind, SimpleTypeMode mode) + : base((uint) kind | ((uint) mode << 8)) + { + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.SimpleType; + + /// + /// Gets the kind of the simple type. + /// + public SimpleTypeKind Kind => (SimpleTypeKind) (TypeIndex & 0b1111_1111); + + /// + /// Gets the mode describing the pointer specifiers that are added to the simple type. + /// + public SimpleTypeMode Mode => (SimpleTypeMode) ((TypeIndex >> 8) & 0b1111); + + /// + /// Gets a value indicating whether the type is a pointer or not. + /// + public bool IsPointer => Mode != SimpleTypeMode.Direct; + + /// + public override string ToString() => Mode == SimpleTypeMode.Direct + ? Kind.ToString() + : $"{Kind}*"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/StaticDataField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/StaticDataField.cs new file mode 100644 index 000000000..b06398da4 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/StaticDataField.cs @@ -0,0 +1,29 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a static data member in a class or structure type. +/// +public class StaticDataField : CodeViewDataField +{ + /// + /// Initializes an empty static data field. + /// + /// The type index to assign to the field. + protected StaticDataField(uint typeIndex) + : base(typeIndex) + { + } + + /// + /// Creates a new static data member. + /// + /// The name. + /// The data type of the member. + public StaticDataField(Utf8String name, CodeViewTypeRecord dataType) + : base(dataType, name) + { + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.StMember; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/StructureAttributes.cs b/src/AsmResolver.Symbols.Pdb/Leaves/StructureAttributes.cs new file mode 100644 index 000000000..64f582c6b --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/StructureAttributes.cs @@ -0,0 +1,80 @@ +using System; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Provides members defining all possible attributes that can be assigned to a structure, class or enum type symbol. +/// +[Flags] +public enum StructureAttributes : ushort +{ + /// + /// Indicates the structure is packed. + /// + Packed = 0b00000000_00000001, + + /// + /// Indicates the structure defines constructors or destructors. + /// + Ctor = 0b00000000_00000010, + + /// + /// Indicates the structure defines overloaded operators. + /// + OvlOps = 0b00000000_00000100, + + /// + /// Indicates the structure is a nested class. + /// + IsNested = 0b00000000_00001000, + + /// + /// Indicates the structure defines nested types. + /// + CNested = 0b00000000_00010000, + + /// + /// Indicates the structure defines an overloaded assignment (=) operator. + /// + OpAssign = 0b00000000_00100000, + + /// + /// Indicates the structure defines casting methods. + /// + OpCast = 0b00000000_01000000, + + /// + /// Indicates the structure true is a forward reference. + /// + FwdRef = 0b00000000_10000000, + + /// + /// Indicates the structure is a scoped definition. + /// + Scoped = 0b00000001_00000000, + + /// + /// Indicates the structure has a decorated name following the regular naming conventions. + /// + HasUniqueName = 0b00000010_00000000, + + /// + /// Indicates the structure cannot be used as a base class. + /// + Sealed = 0b00000100_00000000, + + /// + /// Defines the mask for the floating point type that is used within this structure. + /// + HfaMask = 0b00011000_00000000, + + /// + /// Indicates the structure is an intrinsic type. + /// + Intrinsic = 0b00100000_00000000, + + /// + /// Defines the mask for the MoCOM type kind. + /// + MoComMask = 0b11000000_00000000, +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/UnionTypeRecord.cs b/src/AsmResolver.Symbols.Pdb/Leaves/UnionTypeRecord.cs new file mode 100644 index 000000000..a28d73ceb --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/UnionTypeRecord.cs @@ -0,0 +1,70 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a type of a value that may have several representations or formats within the same position of memory. +/// +public class UnionTypeRecord : CodeViewCompositeTypeRecord +{ + private readonly LazyVariable _uniqueName; + + /// + /// Initializes an empty union type. + /// + /// The type index to assign to the union. + protected UnionTypeRecord(uint typeIndex) + : base(typeIndex) + { + _uniqueName = new LazyVariable(GetUniqueName); + } + + /// + /// Creates a new union type with the provided size. + /// + /// The total size in bytes of the union. + public UnionTypeRecord(ulong size) + : base(0) + { + _uniqueName = new LazyVariable(Utf8String.Empty); + Size = size; + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.Union; + + /// + /// Gets or sets the total size in bytes of the union type. + /// + public ulong Size + { + get; + set; + } + + /// + /// Gets or sets the uniquely identifiable name for this type. + /// + public Utf8String UniqueName + { + get => _uniqueName.Value; + set => _uniqueName.Value = value; + } + + /// + /// Obtains the uniquely identifiable name of the type. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String? GetUniqueName() => null; + + /// + public override string ToString() + { + if (!Utf8String.IsNullOrEmpty(Name)) + return Name; + if (!Utf8String.IsNullOrEmpty(UniqueName)) + return UniqueName; + return $""; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/UnknownCodeViewLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/UnknownCodeViewLeaf.cs new file mode 100644 index 000000000..7fd7898f1 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/UnknownCodeViewLeaf.cs @@ -0,0 +1,47 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents an unknown or unsupported CodeView type record. +/// +public class UnknownCodeViewLeaf : CodeViewLeaf +{ + /// + /// Creates a new unknown type record. + /// + /// The type of symbol. + /// The raw data stored in the record. + public UnknownCodeViewLeaf(CodeViewLeafKind leafKind, byte[] data) + : this(0, leafKind, data) + { + } + + /// + /// Creates a new unknown type record. + /// + /// The type index to assign to the type + /// The type of symbol. + /// The raw data stored in the record. + internal UnknownCodeViewLeaf(uint typeIndex, CodeViewLeafKind leafKind, byte[] data) + : base(typeIndex) + { + LeafKind = leafKind; + Data = data; + } + + /// + public override CodeViewLeafKind LeafKind + { + get; + } + + /// + /// Gets the raw data stored in the record. + /// + public byte[] Data + { + get; + } + + /// + public override string ToString() => $"{LeafKind.ToString()} ({Data.Length.ToString()} bytes)"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/VBaseClassField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/VBaseClassField.cs new file mode 100644 index 000000000..99d5edf0e --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/VBaseClassField.cs @@ -0,0 +1,115 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents a direct or indirect reference to a virtual base class object in a structure. +/// +public class VBaseClassField : CodeViewField +{ + private readonly LazyVariable _baseType; + private readonly LazyVariable _basePointerType; + + /// + /// Initializes a new empty virtual base class field. + /// + /// The type index to assign to the field. + protected VBaseClassField(uint typeIndex) + : base(typeIndex) + { + _baseType = new LazyVariable(GetBaseType); + _basePointerType = new LazyVariable(GetBasePointerType); + } + + /// + /// Creates a new virtual base class field. + /// + /// The type to reference as base type. + /// The type of the virtual base pointer. + /// The offset of the virtual base pointer + /// The offset from the base table. + /// true if the field is an indirect virtual base class, false otherwise. + public VBaseClassField( + CodeViewTypeRecord baseType, + CodeViewTypeRecord pointerType, + ulong pointerOffset, + ulong tableOffset, + bool isIndirect) + : base(0) + { + _baseType = new LazyVariable(baseType); + _basePointerType = new LazyVariable(pointerType); + PointerOffset = pointerOffset; + TableOffset = tableOffset; + IsIndirect = isIndirect; + } + + /// + /// Gets or sets a value indicating whether the virtual base class is an indirect base class. + /// + public bool IsIndirect + { + get; + set; + } + + /// + public override CodeViewLeafKind LeafKind => IsIndirect + ? CodeViewLeafKind.IVBClass + : CodeViewLeafKind.VBClass; + + /// + /// Gets or sets the base type that this base class is referencing. + /// + public CodeViewTypeRecord? Type + { + get => _baseType.Value; + set => _baseType.Value = value; + } + + /// + /// Gets or sets the type of the base pointer that this base class uses. + /// + public CodeViewTypeRecord? PointerType + { + get => _basePointerType.Value; + set => _basePointerType.Value = value; + } + + /// + /// Gets or sets the virtual base pointer offset relative to the address point. + /// + public ulong PointerOffset + { + get; + set; + } + + /// + /// Gets or sets the virtual base pointer offset relative to the virtual base table. + /// + public ulong TableOffset + { + get; + set; + } + + /// + /// Obtains the base type that the class is referencing. + /// + /// The base type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetBaseType() => null; + + /// + /// Obtains the type of the base pointer that the class is uses. + /// + /// The base pointer type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetBasePointerType() => null; + + /// + public override string ToString() => Type?.ToString() ?? "<<>>"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/VTableField.cs b/src/AsmResolver.Symbols.Pdb/Leaves/VTableField.cs new file mode 100644 index 000000000..11355cad4 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/VTableField.cs @@ -0,0 +1,50 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Represents the virtual function table field in a class. +/// +public class VTableField : CodeViewField +{ + private readonly LazyVariable _type; + + /// + /// Initializes an empty virtual function table field. + /// + /// The type index to assign to the type. + protected VTableField(uint typeIndex) + : base(typeIndex) + { + _type = new LazyVariable(GetPointerType); + } + + /// + /// Creates a new virtual function table field. + /// + /// The pointer type to use. + public VTableField(CodeViewTypeRecord pointerType) + : base(0) + { + _type = new LazyVariable(pointerType); + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.VFuncTab; + + /// + /// Gets or sets the pointer type of the virtual function table. + /// + public CodeViewTypeRecord? PointerType + { + get => _type.Value; + set => _type.Value = value; + } + + /// + /// Obtains the pointer type that the virtual function table type. + /// + /// The pointer type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetPointerType() => null; +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/VTableShapeEntry.cs b/src/AsmResolver.Symbols.Pdb/Leaves/VTableShapeEntry.cs new file mode 100644 index 000000000..abbb9bba9 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/VTableShapeEntry.cs @@ -0,0 +1,17 @@ +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Provides members defining all possible types that a single entry in a can be. +/// +public enum VTableShapeEntry : byte +{ +#pragma warning disable CS1591 + Near = 0x00, + Far = 0x01, + Thin = 0x02, + Outer = 0x03, + Meta = 0x04, + Near32 = 0x05, + Far32 = 0x06, +#pragma warning restore CS1591 +} diff --git a/src/AsmResolver.Symbols.Pdb/Leaves/VTableShapeLeaf.cs b/src/AsmResolver.Symbols.Pdb/Leaves/VTableShapeLeaf.cs new file mode 100644 index 000000000..508664c32 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Leaves/VTableShapeLeaf.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using System.Threading; + +namespace AsmResolver.Symbols.Pdb.Leaves; + +/// +/// Describes the shape of the virtual function table of a class or structure type. +/// +public class VTableShapeLeaf : CodeViewLeaf +{ + private IList? _entries; + + /// + /// Initializes a new empty virtual function table shape. + /// + /// The type index to assign to the shape. + protected VTableShapeLeaf(uint typeIndex) + : base(typeIndex) + { + } + + /// + /// Creates a new empty virtual function table shape. + /// + public VTableShapeLeaf() + : base(0) + { + } + + /// + /// Creates a new virtual function table shape with the provided entries. + /// + public VTableShapeLeaf(params VTableShapeEntry[] entries) + : base(0) + { + _entries = new List(entries); + } + + /// + public override CodeViewLeafKind LeafKind => CodeViewLeafKind.VTShape; + + /// + /// Gets the list of entries that defines the shape of the virtual function table. + /// + public IList Entries + { + get + { + if (_entries is null) + Interlocked.CompareExchange(ref _entries, GetEntries(), null); + return _entries; + } + } + + /// + /// Obtains the list of entries stored in the shape. + /// + /// The entries. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetEntries() => new List(); +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiAttributes.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiAttributes.cs new file mode 100644 index 000000000..3fb37697a --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiAttributes.cs @@ -0,0 +1,30 @@ +using System; + +namespace AsmResolver.Symbols.Pdb.Metadata.Dbi; + +/// +/// Provides members defining all attributes that can be assigned to a single DBI stream. +/// +[Flags] +public enum DbiAttributes : ushort +{ + /// + /// Indicates no attributes were assigned. + /// + None = 0, + + /// + /// Indicates the program was linked in an incremental manner. + /// + IncrementallyLinked = 1, + + /// + /// Indicates private symbols were stripped from the PDB file. + /// + PrivateSymbolsStripped = 2, + + /// + /// Indicates the program was linked using link.exe with the undocumented /DEBUG:CTYPES flag. + /// + HasConflictingTypes = 4, +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs new file mode 100644 index 000000000..247bd21ac --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs @@ -0,0 +1,595 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using AsmResolver.IO; +using AsmResolver.PE.File.Headers; + +namespace AsmResolver.Symbols.Pdb.Metadata.Dbi; + +/// +/// Represents the DBI Stream (also known as the Debug Information stream). +/// +public class DbiStream : SegmentBase +{ + /// + /// Gets the default fixed MSF stream index for the DBI stream. + /// + public const int StreamIndex = 3; + + private IList? _modules; + private IList? _sectionContributions; + private IList? _sectionMaps; + private readonly LazyVariable _typeServerMapStream; + private readonly LazyVariable _ecStream; + private IList? _sourceFiles; + private IList? _extraStreamIndices; + + /// + /// Creates a new empty DBI stream. + /// + public DbiStream() + { + _typeServerMapStream = new LazyVariable(GetTypeServerMapStream); + _ecStream = new LazyVariable(GetECStream); + IsNewVersionFormat = true; + } + + /// + /// Gets or sets the version signature assigned to the DBI stream. + /// + /// + /// This value should always be -1 for valid PDB files. + /// + public int VersionSignature + { + get; + set; + } = -1; + + /// + /// Gets or sets the version number of the DBI header. + /// + /// + /// Modern tooling only recognize the VC7.0 file format. + /// + public DbiStreamVersion VersionHeader + { + get; + set; + } = DbiStreamVersion.V70; + + /// + /// Gets or sets the number of times the DBI stream has been written. + /// + public uint Age + { + get; + set; + } = 1; + + /// + /// Gets or sets the MSF stream index of the Global Symbol Stream. + /// + public ushort GlobalStreamIndex + { + get; + set; + } + + /// + /// Gets or sets a bitfield containing the major and minor version of the toolchain that was used to build the program. + /// + public ushort BuildNumber + { + get; + set; + } + + /// + /// Gets or sets a value indicating that the DBI stream is using the new file format (NewDBI). + /// + public bool IsNewVersionFormat + { + get => (BuildNumber & 0x8000) != 0; + set => BuildNumber = (ushort) ((BuildNumber & ~0x8000) | (value ? 0x8000 : 0)); + } + + /// + /// Gets or sets the major version of the toolchain that was used to build the program. + /// + public byte BuildMajorVersion + { + get => (byte) ((BuildNumber >> 8) & 0x7F); + set => BuildNumber = (ushort) ((BuildNumber & ~0x7F00) | (value << 8)); + } + + /// + /// Gets or sets the minor version of the toolchain that was used to build the program. + /// + public byte BuildMinorVersion + { + get => (byte) (BuildNumber & 0xFF); + set => BuildNumber = (ushort) ((BuildNumber & ~0x00FF) | value); + } + + /// + /// Gets or sets the MSF stream index of the Public Symbol Stream. + /// + public ushort PublicStreamIndex + { + get; + set; + } + + /// + /// Gets or sets the version number of mspdbXXXX.dll that was used to produce this PDB file. + /// + public ushort PdbDllVersion + { + get; + set; + } + + /// + /// Gets or sets the MSF stream index of the Symbol Record Stream. + /// + public ushort SymbolRecordStreamIndex + { + get; + set; + } + + /// + /// Unknown. + /// + public ushort PdbDllRbld + { + get; + set; + } + + /// + /// Gets or sets the MSF stream index of the MFC type server. + /// + public uint MfcTypeServerIndex + { + get; + set; + } + + /// + /// Gets or sets attributes associated to the DBI stream. + /// + public DbiAttributes Attributes + { + get; + set; + } + + /// + /// Gets or sets the machine type the program was compiled for. + /// + public MachineType Machine + { + get; + set; + } + + /// + /// Gets a collection of modules (object files) that were linked together into the program. + /// + public IList Modules + { + get + { + if (_modules is null) + Interlocked.CompareExchange(ref _modules, GetModules(), null); + return _modules; + } + } + + /// + /// Gets a collection of section contributions describing the layout of the sections of the final executable file. + /// + public IList SectionContributions + { + get + { + if (_sectionContributions is null) + Interlocked.CompareExchange(ref _sectionContributions, GetSectionContributions(), null); + return _sectionContributions; + } + } + + /// + /// Gets a collection of section mappings stored in the section mapping sub stream. + /// + /// + /// The exact purpose of this is unknown, but it seems to be always containing a copy of the sections in the final + /// executable file. + /// + public IList SectionMaps + { + get + { + if (_sectionMaps is null) + Interlocked.CompareExchange(ref _sectionMaps, GetSectionMaps(), null); + return _sectionMaps; + } + } + + /// + /// Gets or sets the contents of the type server map sub stream. + /// + /// + /// The exact purpose and layout of this sub stream is unknown, hence this property exposes the stream as + /// a raw segment. + /// + public ISegment? TypeServerMapStream + { + get => _typeServerMapStream.Value; + set => _typeServerMapStream.Value = value; + } + + /// + /// Gets or sets the contents of the Edit-and-Continue sub stream. + /// + /// + /// The exact purpose and layout of this sub stream is unknown, hence this property exposes the stream as + /// a raw segment. + /// + public ISegment? ECStream + { + get => _ecStream.Value; + set => _ecStream.Value = value; + } + + /// + /// Gets a collection of source files assigned to each module in . + /// + /// + /// Every collection of source files within this list corresponds to exactly the module within + /// at the same index. For example, the first source file list in this collection is the source file list of the + /// first module. + /// + public IList SourceFiles + { + get + { + if (_sourceFiles is null) + Interlocked.CompareExchange(ref _sourceFiles, GetSourceFiles(), null); + return _sourceFiles; + } + } + + /// + /// Gets a collection of indices referring to additional debug streams in the MSF file. + /// + public IList ExtraStreamIndices + { + get + { + if (_extraStreamIndices is null) + Interlocked.CompareExchange(ref _extraStreamIndices, GetExtraStreamIndices(), null); + return _extraStreamIndices; + } + } + + /// + /// Reads a single DBI stream from the provided input stream. + /// + /// The input stream. + /// The parsed DBI stream. + public static DbiStream FromReader(BinaryStreamReader reader) => new SerializedDbiStream(reader); + + /// + /// Obtains the list of module descriptors. + /// + /// The module descriptors + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetModules() => new List(); + + /// + /// Obtains the list of section contributions. + /// + /// The section contributions. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetSectionContributions() => new List(); + + /// + /// Obtains the list of section maps. + /// + /// The section maps. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetSectionMaps() => new List(); + + /// + /// Obtains the contents of the type server map sub stream. + /// + /// The contents of the sub stream. + /// + /// This method is called upon initialization of the property. + /// + protected virtual ISegment? GetTypeServerMapStream() => null; + + /// + /// Obtains the contents of the EC sub stream. + /// + /// The contents of the sub stream. + /// + /// This method is called upon initialization of the property. + /// + protected virtual ISegment? GetECStream() => null; + + /// + /// Obtains a table that assigns a list of source files to every module referenced in the PDB file. + /// + /// The table. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetSourceFiles() => new List(); + + /// + /// Obtains the list of indices referring to additional debug streams in the MSF file. + /// + /// The list of indices. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetExtraStreamIndices() => new List(); + + /// + public override uint GetPhysicalSize() + { + return GetHeaderSize() + + GetModuleStreamSize() + + GetSectionContributionStreamSize() + + GetSectionMapStreamSize() + + GetSourceInfoStreamSize() + + GetTypeServerMapStreamSize() + + GetECStreamSize() + + GetOptionalDebugStreamSize() + ; + } + + private static uint GetHeaderSize() + { + return sizeof(int) // VersionSignature + + sizeof(DbiStreamVersion) // VersionHeader + + sizeof(uint) // Age + + sizeof(ushort) // GlobalStreamIndex + + sizeof(ushort) // BuildNumber + + sizeof(ushort) // PublicStreamIndex + + sizeof(ushort) // PdbDllVersion + + sizeof(ushort) // SymbolRecordStreamIndex + + sizeof(ushort) // PdbDllRbld + + sizeof(uint) // ModuleInfoSize + + sizeof(uint) // SectionContributionSize + + sizeof(uint) // SectionMapSize + + sizeof(uint) // SourceInfoSize + + sizeof(uint) // TypeServerMapSize + + sizeof(uint) // MfcTypeServerIndex + + sizeof(uint) // OptionalDebugStreamSize + + sizeof(uint) // ECStreamSize + + sizeof(DbiAttributes) // Attributes + + sizeof(MachineType) // MachineType + + sizeof(uint) // Padding + ; + } + + private uint GetModuleStreamSize() + { + return ((uint) Modules.Sum(m => m.GetPhysicalSize())).Align(sizeof(uint)); + } + + private uint GetSectionContributionStreamSize() + { + return sizeof(uint) // version + + SectionContribution.EntrySize * (uint) SectionContributions.Count; + } + + private uint GetSectionMapStreamSize() + { + return sizeof(ushort) // Count + + sizeof(ushort) // LogCount + + SectionMap.EntrySize * (uint) SectionMaps.Count; + } + + private uint GetSourceInfoStreamSize() + { + uint totalFileCount = 0; + uint nameBufferSize = 0; + var stringOffsets = new Dictionary(); + + // Simulate the construction of name buffer + for (int i = 0; i < SourceFiles.Count; i++) + { + var collection = SourceFiles[i]; + totalFileCount += (uint) collection.Count; + + for (int j = 0; j < collection.Count; j++) + { + // If name is not added yet, "append" to the name buffer. + var name = collection[j]; + if (!stringOffsets.ContainsKey(name)) + { + stringOffsets[name] = nameBufferSize; + nameBufferSize += (uint) name.ByteCount + 1u; + } + } + } + + return (sizeof(ushort) // ModuleCount + + sizeof(ushort) // SourceFileCount + + sizeof(ushort) * (uint) SourceFiles.Count // ModuleIndices + + sizeof(ushort) * (uint) SourceFiles.Count // SourceFileCounts + + sizeof(uint) * totalFileCount // NameOffsets + + nameBufferSize // NameBuffer + ).Align(4); + } + + private uint GetTypeServerMapStreamSize() + { + return TypeServerMapStream?.GetPhysicalSize().Align(sizeof(uint)) ?? 0u; + } + + private uint GetOptionalDebugStreamSize() + { + return (uint) (ExtraStreamIndices.Count * sizeof(ushort)); + } + + private uint GetECStreamSize() + { + return ECStream?.GetPhysicalSize() ?? 0u; + } + + /// + public override void Write(IBinaryStreamWriter writer) + { + WriteHeader(writer); + WriteModuleStream(writer); + WriteSectionContributionStream(writer); + WriteSectionMapStream(writer); + WriteSourceInfoStream(writer); + WriteTypeServerMapStream(writer); + WriteECStream(writer); + WriteOptionalDebugStream(writer); + } + + private void WriteHeader(IBinaryStreamWriter writer) + { + writer.WriteInt32(VersionSignature); + writer.WriteUInt32((uint) VersionHeader); + writer.WriteUInt32(Age); + writer.WriteUInt16(GlobalStreamIndex); + writer.WriteUInt16(BuildNumber); + writer.WriteUInt16(PublicStreamIndex); + writer.WriteUInt16(PdbDllVersion); + writer.WriteUInt16(SymbolRecordStreamIndex); + writer.WriteUInt16(PdbDllRbld); + + writer.WriteUInt32(GetModuleStreamSize()); + writer.WriteUInt32(GetSectionContributionStreamSize()); + writer.WriteUInt32(GetSectionMapStreamSize()); + writer.WriteUInt32(GetSourceInfoStreamSize()); + writer.WriteUInt32(GetTypeServerMapStreamSize()); + + writer.WriteUInt32(MfcTypeServerIndex); + + writer.WriteUInt32(GetOptionalDebugStreamSize()); + writer.WriteUInt32(GetECStreamSize()); + + writer.WriteUInt16((ushort) Attributes); + writer.WriteUInt16((ushort) Machine); + + writer.WriteUInt32(0); + } + + private void WriteModuleStream(IBinaryStreamWriter writer) + { + var modules = Modules; + for (int i = 0; i < modules.Count; i++) + modules[i].Write(writer); + + writer.Align(sizeof(uint)); + } + + private void WriteSectionContributionStream(IBinaryStreamWriter writer) + { + // TODO: make customizable + writer.WriteUInt32((uint) SectionContributionStreamVersion.Ver60); + + var contributions = SectionContributions; + for (int i = 0; i < contributions.Count; i++) + contributions[i].Write(writer); + + writer.Align(sizeof(uint)); + } + + private void WriteSectionMapStream(IBinaryStreamWriter writer) + { + var maps = SectionMaps; + + // Count and LogCount. + writer.WriteUInt16((ushort) maps.Count); + writer.WriteUInt16((ushort) maps.Count); + + // Entries. + for (int i = 0; i < maps.Count; i++) + maps[i].Write(writer); + + writer.Align(sizeof(uint)); + } + + private void WriteSourceInfoStream(IBinaryStreamWriter writer) + { + var sourceFiles = SourceFiles; + int totalFileCount = sourceFiles.Sum(x => x.Count); + + // Module and total file count (truncated to 16 bits) + writer.WriteUInt16((ushort) (sourceFiles.Count & 0xFFFF)); + writer.WriteUInt16((ushort) (totalFileCount & 0xFFFF)); + + // Module indices. Unsure if this is correct, but this array does not seem to be really used by the ref impl. + for (ushort i = 0; i < sourceFiles.Count; i++) + writer.WriteUInt16(i); + + // Source file counts. + for (int i = 0; i < sourceFiles.Count; i++) + writer.WriteUInt16((ushort) sourceFiles[i].Count); + + // Build up string buffer and name offset table. + using var stringBuffer = new MemoryStream(); + var stringWriter = new BinaryStreamWriter(stringBuffer); + var stringOffsets = new Dictionary(); + + for (int i = 0; i < sourceFiles.Count; i++) + { + var collection = sourceFiles[i]; + for (int j = 0; j < collection.Count; j++) + { + // If not present already, append to string buffer. + var name = collection[j]; + if (!stringOffsets.TryGetValue(name, out uint offset)) + { + offset = (uint) stringWriter.Offset; + stringOffsets[name] = offset; + stringWriter.WriteBytes(name.GetBytesUnsafe()); + stringWriter.WriteByte(0); + } + + // Write name offset + writer.WriteUInt32(offset); + } + } + + // Write string buffer. + writer.WriteBytes(stringBuffer.ToArray()); + + writer.Align(sizeof(uint)); + } + + private void WriteTypeServerMapStream(IBinaryStreamWriter writer) + { + TypeServerMapStream?.Write(writer); + writer.Align(sizeof(uint)); + } + + private void WriteOptionalDebugStream(IBinaryStreamWriter writer) + { + var extraIndices = ExtraStreamIndices; + + for (int i = 0; i < extraIndices.Count; i++) + writer.WriteUInt16(extraIndices[i]); + } + + private void WriteECStream(IBinaryStreamWriter writer) => ECStream?.Write(writer); +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStreamVersion.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStreamVersion.cs new file mode 100644 index 000000000..fe76de860 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStreamVersion.cs @@ -0,0 +1,15 @@ +namespace AsmResolver.Symbols.Pdb.Metadata.Dbi; + +/// +/// Provides members defining all possible DBI stream format version numbers. +/// +public enum DbiStreamVersion +{ +#pragma warning disable CS1591 + VC41 = 930803, + V50 = 19960307, + V60 = 19970606, + V70 = 19990903, + V110 = 20091201 +#pragma warning restore CS1591 +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/ModuleDescriptor.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/ModuleDescriptor.cs new file mode 100644 index 000000000..174ec2dab --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/ModuleDescriptor.cs @@ -0,0 +1,207 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Metadata.Dbi; + +/// +/// Represents a reference to a single module (object file) that was linked into a program. +/// +public class ModuleDescriptor : IWritable +{ + /// + /// Gets or sets a description of the section within the final binary which contains code + /// and/or data from this module. + /// + public SectionContribution SectionContribution + { + get; + set; + } = new(); + + /// + /// Gets or sets the attributes assigned to this module descriptor. + /// + public ModuleDescriptorAttributes Attributes + { + get; + set; + } + + /// + /// Gets or sets the index of the type server for this module. + /// + public ushort TypeServerIndex + { + get => (ushort) ((ushort) Attributes >> 8); + set => Attributes = (Attributes & ~ModuleDescriptorAttributes.TsmMask) | (ModuleDescriptorAttributes) (value << 8); + } + + /// + /// Gets or sets the MSF stream index of the stream that the symbols of this module. + /// + public ushort SymbolStreamIndex + { + get; + set; + } + + /// + /// Gets or sets the size of the CodeView data within the module's symbol stream. + /// + public uint SymbolDataSize + { + get; + set; + } + + /// + /// Gets or sets the size of the C11-style CodeView data within the module's symbol stream. + /// + public uint SymbolC11DataSize + { + get; + set; + } + + /// + /// Gets or sets the size of the C13-style CodeView data within the module's symbol stream. + /// + public uint SymbolC13DataSize + { + get; + set; + } + + /// + /// Gets or sets the number of source files that contributed to this module during the compilation. + /// + public ushort SourceFileCount + { + get; + set; + } + + /// + /// Gets or sets the offset in the names buffer of the primary translation unit. + /// + /// + /// For most compilers this value is set to zero. + /// + public uint SourceFileNameIndex + { + get; + set; + } + + /// + /// Gets or sets the offset in the names buffer of the PDB file. + /// + /// + /// For most modules (except the special * LINKER * module) this value is set to zero. + /// + public uint PdbFilePathNameIndex + { + get; + set; + } + + /// + /// Gets or sets the name of the module. + /// + /// + /// This is often a full path to the object file that was passed into link.exe directly, or a string in the + /// form of Import:dll_name + /// + public Utf8String? ModuleName + { + get; + set; + } + + /// + /// Gets or sets the name of the object file name. + /// + /// + /// In the case this module is linked directly passed to link.exe, this is the same as . + /// If the module comes from an archive, this is the full path to that archive. + /// + public Utf8String? ObjectFileName + { + get; + set; + } + + /// + /// Parses a single module descriptor from the provided input stream. + /// + /// The input stream. + /// THe parsed module descriptor. + public static ModuleDescriptor FromReader(ref BinaryStreamReader reader) + { + var result = new ModuleDescriptor(); + + reader.ReadUInt32(); + result.SectionContribution = SectionContribution.FromReader(ref reader); + result.Attributes = (ModuleDescriptorAttributes) reader.ReadUInt16(); + result.SymbolStreamIndex = reader.ReadUInt16(); + result.SymbolDataSize = reader.ReadUInt32(); + result.SymbolC11DataSize = reader.ReadUInt32(); + result.SymbolC13DataSize = reader.ReadUInt32(); + result.SourceFileCount = reader.ReadUInt16(); + reader.ReadUInt16(); + reader.ReadUInt32(); + result.SourceFileNameIndex = reader.ReadUInt32(); + result.PdbFilePathNameIndex = reader.ReadUInt32(); + result.ModuleName = reader.ReadUtf8String(); + result.ObjectFileName = reader.ReadUtf8String(); + reader.Align(4); + + return result; + } + + /// + public uint GetPhysicalSize() + { + return (sizeof(uint) // Unused1 + + SectionContribution.GetPhysicalSize() // SectionContribution + + sizeof(ModuleDescriptorAttributes) // Attributes + + sizeof(ushort) // SymbolStreamIndex + + sizeof(uint) // SymbolDataSize + + sizeof(uint) // SymbolC11DataSize + + sizeof(uint) // SymbolC13DataSize + + sizeof(ushort) // SourceFileCount + + sizeof(ushort) // Padding + + sizeof(uint) // Unused2 + + sizeof(uint) // SourceFileNameIndex + + sizeof(uint) // PdbFilePathNameIndex + + (uint) (ModuleName?.ByteCount ?? 0) + 1 // ModuleName + + (uint) (ObjectFileName?.ByteCount ?? 0) + 1 // ObjectFileName + ).Align(4); + } + + /// + public void Write(IBinaryStreamWriter writer) + { + writer.WriteUInt32(0); + SectionContribution.Write(writer); + writer.WriteUInt16((ushort) Attributes); + writer.WriteUInt16(SymbolStreamIndex); + writer.WriteUInt32(SymbolDataSize); + writer.WriteUInt32(SymbolC11DataSize); + writer.WriteUInt32(SymbolC13DataSize); + writer.WriteUInt16(SourceFileCount); + writer.WriteUInt16(0); + writer.WriteUInt32(0); + writer.WriteUInt32(SourceFileNameIndex); + writer.WriteUInt32(PdbFilePathNameIndex); + if (ModuleName is not null) + writer.WriteBytes(ModuleName.GetBytesUnsafe()); + writer.WriteByte(0); + if (ObjectFileName is not null) + writer.WriteBytes(ObjectFileName.GetBytesUnsafe()); + writer.WriteByte(0); + writer.Align(4); + } + + /// + public override string ToString() => ModuleName ?? ObjectFileName ?? "?"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/ModuleDescriptorAttributes.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/ModuleDescriptorAttributes.cs new file mode 100644 index 000000000..5b6a018c0 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/ModuleDescriptorAttributes.cs @@ -0,0 +1,25 @@ +using System; + +namespace AsmResolver.Symbols.Pdb.Metadata.Dbi; + +/// +/// Defines all possible flags that can be assigned to a module descriptor. +/// +[Flags] +public enum ModuleDescriptorAttributes : ushort +{ + /// + /// Indicates the module has been written to since reading the PDB. + /// + Dirty = 1, + + /// + /// Indicates the module contains Edit & Continue information. + /// + EC = 2, + + /// + /// Provides a mask for the type server index that is stored within the flags. + /// + TsmMask = 0xFF00, +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionContribution.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionContribution.cs new file mode 100644 index 000000000..83daaae3e --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionContribution.cs @@ -0,0 +1,128 @@ +using System.Text; +using AsmResolver.IO; +using AsmResolver.PE.File.Headers; + +namespace AsmResolver.Symbols.Pdb.Metadata.Dbi; + +/// +/// Describes the section in the final executable file that a particular object or module is stored at. +/// +public class SectionContribution : IWritable +{ + /// + /// The total size in bytes of a single on the disk. + /// + public const int EntrySize = + sizeof(ushort) // Section + + sizeof(ushort) // Padding1 + + sizeof(uint) // Offset + + sizeof(uint) // Size + + sizeof(uint) // Characteristics + + sizeof(ushort) // ModuleIndex + + sizeof(ushort) // Padding2 + + sizeof(uint) // DataCrc + + sizeof(uint) // RelocCrc + ; + + /// + /// Gets or sets the index of the section. + /// + public ushort Section + { + get; + set; + } + + /// + /// Gets or sets the offset within the section that this contribution starts at. + /// + public uint Offset + { + get; + set; + } + + /// + /// Gets or sets the size of the section contribution. + /// + public uint Size + { + get; + set; + } + + /// + /// Gets or sets the section flags and permissions associated to this section contribution. + /// + public SectionFlags Characteristics + { + get; + set; + } + + /// + /// Gets or sets the index of the module. + /// + public ushort ModuleIndex + { + get; + set; + } + + /// + /// Gets or sets a cyclic redundancy code that can be used to verify the data section of this contribution. + /// + public uint DataCrc + { + get; + set; + } + + /// + /// Gets or sets a cyclic redundancy code that can be used to verify the relocation section of this contribution. + /// + public uint RelocCrc + { + get; + set; + } + + /// + /// Parses a single section contribution from the provided input stream. + /// + /// The input stream. + /// The parsed section contribution. + public static SectionContribution FromReader(ref BinaryStreamReader reader) + { + var result = new SectionContribution(); + + result.Section = reader.ReadUInt16(); + reader.ReadUInt16(); + result.Offset = reader.ReadUInt32(); + result.Size = reader.ReadUInt32(); + result.Characteristics = (SectionFlags) reader.ReadUInt32(); + result.ModuleIndex = reader.ReadUInt16(); + reader.ReadUInt16(); + result.DataCrc = reader.ReadUInt32(); + result.RelocCrc = reader.ReadUInt32(); + + return result; + } + + /// + public uint GetPhysicalSize() => EntrySize; + + /// + public void Write(IBinaryStreamWriter writer) + { + writer.WriteUInt16(Section); + writer.WriteUInt16(0); + writer.WriteUInt32(Offset); + writer.WriteUInt32(Size); + writer.WriteUInt32((uint) Characteristics); + writer.WriteUInt16(ModuleIndex); + writer.WriteUInt16(0); + writer.WriteUInt32(DataCrc); + writer.WriteUInt32(RelocCrc); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionContributionStreamVersion.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionContributionStreamVersion.cs new file mode 100644 index 000000000..48daaf3a4 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionContributionStreamVersion.cs @@ -0,0 +1,17 @@ +namespace AsmResolver.Symbols.Pdb.Metadata.Dbi; + +/// +/// Provides members defining all valid versions of the Section Contribution sub stream's file format. +/// +public enum SectionContributionStreamVersion : uint +{ + /// + /// Indicates version 6.0 is used. + /// + Ver60 = 0xeffe0000 + 19970605, + + /// + /// Indicates version 2.0 is used. + /// + V2 = 0xeffe0000 + 20140516 +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionMap.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionMap.cs new file mode 100644 index 000000000..bf43a7e56 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionMap.cs @@ -0,0 +1,131 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Metadata.Dbi; + +/// +/// Represents a single entry in the Section Map sub stream of the DBI stream. +/// +public class SectionMap : IWritable +{ + /// + /// The total size in bytes of a single on the disk. + /// + public const int EntrySize = + sizeof(ushort) // Attributes + + sizeof(ushort) // Ovl + + sizeof(ushort) // Group + + sizeof(ushort) // Frame + + sizeof(ushort) // SectionName + + sizeof(ushort) // ClassName + + sizeof(uint) // Offset + + sizeof(uint) // SectionLength + ; + + /// + /// Gets or sets the attributes assigned to this section map. + /// + public SectionMapAttributes Attributes + { + get; + set; + } + + /// + /// Gets or sets the logical overlay number of this section map. + /// + public ushort LogicalOverlayNumber + { + get; + set; + } + + /// + /// Gets or sets the group index into the descriptor array. + /// + public ushort Group + { + get; + set; + } + + /// + /// Gets or sets the frame index. + /// + public ushort Frame + { + get; + set; + } + + /// + /// Gets or sets the byte offset of the segment or group name in string table, or 0xFFFF if no name was assigned. + /// + public ushort SectionName + { + get; + set; + } + + /// + /// Gets or sets the byte offset of the class in the string table, or 0xFFFF if no name was assigned.. + /// + public ushort ClassName + { + get; + set; + } + + /// + /// Gets or sets the byte offset of the logical segment within physical segment. If group is set in flags, this is the offset of the group. + /// + public uint Offset + { + get; + set; + } + + /// + /// Gets or sets the number of bytes that the segment or group consists of. + /// + public uint SectionLength + { + get; + set; + } + + /// + /// Parses a single section map from the provided input stream. + /// + /// The input stream. + /// The parsed section map. + public static SectionMap FromReader(ref BinaryStreamReader reader) + { + return new SectionMap + { + Attributes = (SectionMapAttributes) reader.ReadUInt16(), + LogicalOverlayNumber = reader.ReadUInt16(), + Group = reader.ReadUInt16(), + Frame = reader.ReadUInt16(), + SectionName = reader.ReadUInt16(), + ClassName = reader.ReadUInt16(), + Offset = reader.ReadUInt32(), + SectionLength = reader.ReadUInt32() + }; + } + + /// + public uint GetPhysicalSize() => EntrySize; + + /// + public void Write(IBinaryStreamWriter writer) + { + writer.WriteUInt16((ushort) Attributes); + writer.WriteUInt16(LogicalOverlayNumber); + writer.WriteUInt16(Group); + writer.WriteUInt16(Frame); + writer.WriteUInt16(SectionName); + writer.WriteUInt16(ClassName); + writer.WriteUInt32(Offset); + writer.WriteUInt32(SectionLength); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionMapAttributes.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionMapAttributes.cs new file mode 100644 index 000000000..7f6449772 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionMapAttributes.cs @@ -0,0 +1,42 @@ +namespace AsmResolver.Symbols.Pdb.Metadata.Dbi; + +/// +/// Provides members describing all possible attributes that can be assigned to a single section map entry. +/// +public enum SectionMapAttributes : ushort +{ + /// + /// Indicates the segment is readable. + /// + Read = 1 << 0, + + /// + /// Indicates the segment is writable. + /// + Write = 1 << 1, + + /// + /// Indicates the segment is executable. + /// + Execute = 1 << 2, + + /// + /// Indicates the descriptor describes a 32-bit linear address. + /// + AddressIs32Bit = 1 << 3, + + /// + /// Indicates the frame represents a selector. + /// + IsSelector = 1 << 8, + + /// + /// Indicates the frame represents an absolute address. + /// + IsAbsoluteAddress = 1 << 9, + + /// + /// Indicates the descriptor represents a group. + /// + IsGroup = 1 << 10 +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs new file mode 100644 index 000000000..962f02fc7 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections.Generic; +using AsmResolver.IO; +using AsmResolver.PE.File.Headers; + +namespace AsmResolver.Symbols.Pdb.Metadata.Dbi; + +/// +/// Implements a DBI stream that pulls its data from an input stream. +/// +public class SerializedDbiStream : DbiStream +{ + private readonly BinaryStreamReader _moduleInfoReader; + private readonly BinaryStreamReader _sectionContributionReader; + private readonly BinaryStreamReader _sectionMapReader; + private readonly BinaryStreamReader _sourceInfoReader; + private readonly BinaryStreamReader _typeServerMapReader; + private readonly BinaryStreamReader _optionalDebugHeaderReader; + private readonly BinaryStreamReader _ecReader; + + /// + /// Parses a DBI stream from an input stream reader. + /// + /// The input stream. + public SerializedDbiStream(BinaryStreamReader reader) + { + VersionSignature = reader.ReadInt32(); + VersionHeader = (DbiStreamVersion) reader.ReadUInt32(); + Age = reader.ReadUInt32(); + GlobalStreamIndex = reader.ReadUInt16(); + BuildNumber = reader.ReadUInt16(); + if (!IsNewVersionFormat) + throw new NotSupportedException("The DBI stream uses the legacy file format, which is not supported."); + + PublicStreamIndex = reader.ReadUInt16(); + PdbDllVersion = reader.ReadUInt16(); + SymbolRecordStreamIndex = reader.ReadUInt16(); + PdbDllRbld = reader.ReadUInt16(); + + uint moduleInfoSize = reader.ReadUInt32(); + uint sectionContributionSize = reader.ReadUInt32(); + uint sectionMapSize = reader.ReadUInt32(); + uint sourceInfoSize = reader.ReadUInt32(); + uint typeServerMapSize = reader.ReadUInt32(); + + MfcTypeServerIndex = reader.ReadUInt32(); + + uint optionalDebugHeaderSize = reader.ReadUInt32(); + uint ecSize = reader.ReadUInt32(); + + Attributes = (DbiAttributes) reader.ReadUInt16(); + Machine = (MachineType) reader.ReadUInt16(); + + _ = reader.ReadUInt32(); + + _moduleInfoReader = reader.ForkRelative(reader.RelativeOffset, moduleInfoSize); + reader.Offset += moduleInfoSize; + _sectionContributionReader = reader.ForkRelative(reader.RelativeOffset, sectionContributionSize); + reader.Offset += sectionContributionSize; + _sectionMapReader = reader.ForkRelative(reader.RelativeOffset, sectionMapSize); + reader.Offset += sectionMapSize; + _sourceInfoReader = reader.ForkRelative(reader.RelativeOffset, sourceInfoSize); + reader.Offset += sourceInfoSize; + _typeServerMapReader = reader.ForkRelative(reader.RelativeOffset, typeServerMapSize); + reader.Offset += typeServerMapSize; + _ecReader = reader.ForkRelative(reader.RelativeOffset, ecSize); + reader.Offset += ecSize; + _optionalDebugHeaderReader = reader.ForkRelative(reader.RelativeOffset, optionalDebugHeaderSize); + reader.Offset += optionalDebugHeaderSize; + } + + /// + protected override IList GetModules() + { + var result = new List(); + + var reader = _moduleInfoReader.Fork(); + while (reader.CanRead(1)) + result.Add(ModuleDescriptor.FromReader(ref reader)); + + return result; + } + + /// + protected override IList GetSectionContributions() + { + var result = new List(); + + var reader = _sectionContributionReader.Fork(); + var version = (SectionContributionStreamVersion) reader.ReadUInt32(); + + while (reader.CanRead(1)) + { + result.Add(SectionContribution.FromReader(ref reader)); + if (version == SectionContributionStreamVersion.V2) + reader.ReadUInt32(); + } + + return result; + } + + /// + protected override IList GetSectionMaps() + { + var reader = _sectionMapReader.Fork(); + + ushort count = reader.ReadUInt16(); + ushort logCount = reader.ReadUInt16(); + + var result = new List(count); + for (int i = 0; i < count; i++) + result.Add(SectionMap.FromReader(ref reader)); + + return result; + } + + /// + protected override ISegment? GetTypeServerMapStream() + { + var reader = _typeServerMapReader.Fork(); + return reader.Length != 0 + ? reader.ReadSegment(reader.Length) + : null; + } + + /// + protected override ISegment? GetECStream() + { + var reader = _ecReader.Fork(); + return reader.Length != 0 + ? reader.ReadSegment(reader.Length) + : null; + } + + /// + protected override IList GetSourceFiles() + { + var reader = _sourceInfoReader.Fork(); + + ushort moduleCount = reader.ReadUInt16(); + ushort sourceFileCount = reader.ReadUInt16(); + + // Read module indices. + ushort[] moduleIndices = new ushort[moduleCount]; + for (int i = 0; i < moduleCount; i++) + moduleIndices[i] = reader.ReadUInt16(); + + // Read module source file counts. + int actualFileCount = 0; + ushort[] moduleFileCounts = new ushort[moduleCount]; + for (int i = 0; i < moduleCount; i++) + { + ushort count = reader.ReadUInt16(); + moduleFileCounts[i] = count; + actualFileCount += count; + } + + // Scope on the name buffer. + var stringReaderBuffer = reader.ForkRelative((uint) (reader.RelativeOffset + actualFileCount * sizeof(uint))); + + // Construct source file lists. + var result = new List(moduleCount); + for (int i = 0; i < moduleCount; i++) + { + var files = new SourceFileCollection(moduleIndices[i]); + ushort fileCount = moduleFileCounts[i]; + + // Read all file paths for this module. + for (int j = 0; j < fileCount; j++) + { + uint nameOffset = reader.ReadUInt32(); + var nameReader = stringReaderBuffer.ForkRelative(nameOffset); + files.Add(nameReader.ReadUtf8String()); + } + + result.Add(files); + } + + return result; + } + + /// + protected override IList GetExtraStreamIndices() + { + var reader = _optionalDebugHeaderReader.Fork(); + + var result = new List((int) (reader.Length / sizeof(ushort))); + + while (reader.CanRead(sizeof(ushort))) + result.Add(reader.ReadUInt16()); + + return result; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SourceFileCollection.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SourceFileCollection.cs new file mode 100644 index 000000000..b7ef673e6 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SourceFileCollection.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; + +namespace AsmResolver.Symbols.Pdb.Metadata.Dbi; + +/// +/// Represents a collection of file paths used as input source code for a single module. +/// +public class SourceFileCollection : List +{ + /// + /// Creates a new empty source file collection. + /// + public SourceFileCollection() + { + } + + /// + /// Creates a new empty source file collection. + /// + /// The original module index for which this collection was compiled. + public SourceFileCollection(uint originalModuleIndex) + { + OriginalModuleIndex = originalModuleIndex; + } + + /// + /// Gets the original module index for which this collection was compiled (if available). + /// + /// + /// The exact purpose of this number is unclear, as this number cannot be reliably used as an index within the + /// DBI stream's module list. Use the index of this list within instead. + /// + public uint OriginalModuleIndex + { + get; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStream.cs new file mode 100644 index 000000000..eae02174c --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStream.cs @@ -0,0 +1,192 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Metadata.Info; + +/// +/// Represents the PDB Info Stream (also known as the PDB stream) +/// +public class InfoStream : SegmentBase +{ + /// + /// Gets the default fixed MSF stream index for the PDB Info stream. + /// + public const int StreamIndex = 1; + + private const int HeaderSize = + sizeof(InfoStreamVersion) // Version + + sizeof(uint) // Signature + + sizeof(uint) // Aage + + 16 //UniqueId + + sizeof(uint) // NameBufferSize + ; + + private IDictionary? _streamIndices; + private IList? _features; + + /// + /// Gets or sets the version of the file format of the PDB info stream. + /// + /// + /// Modern tooling only recognize the VC7.0 file format. + /// + public InfoStreamVersion Version + { + get; + set; + } = InfoStreamVersion.VC70; + + /// + /// Gets or sets the 32-bit UNIX time-stamp of the PDB file. + /// + public uint Signature + { + get; + set; + } + + /// + /// Gets or sets the number of times the PDB file has been written. + /// + public uint Age + { + get; + set; + } = 1; + + /// + /// Gets or sets the unique identifier assigned to the PDB file. + /// + public Guid UniqueId + { + get; + set; + } + + /// + /// Gets a mapping from stream names to their respective stream index within the underlying MSF file. + /// + public IDictionary StreamIndices + { + get + { + if (_streamIndices is null) + Interlocked.CompareExchange(ref _streamIndices, GetStreamIndices(), null); + return _streamIndices; + } + } + + /// + /// Gets a list of characteristics that this PDB has. + /// + public IList Features + { + get + { + if (_features is null) + Interlocked.CompareExchange(ref _features, GetFeatures(), null); + return _features; + } + } + + /// + /// Reads a single PDB info stream from the provided input stream. + /// + /// The input stream. + /// The parsed info stream. + public static InfoStream FromReader(BinaryStreamReader reader) => new SerializedInfoStream(reader); + + /// + /// Obtains the stream name to index mapping of the PDB file. + /// + /// The mapping. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IDictionary GetStreamIndices() => new Dictionary(); + + /// + /// Obtains the features of the PDB file. + /// + /// The features. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetFeatures() => new List + { + PdbFeature.VC140 + }; + + /// + public override uint GetPhysicalSize() + { + uint totalSize = HeaderSize; + + // Name buffer + foreach (var entry in StreamIndices) + totalSize += (uint) entry.Key.ByteCount + 1u; + + // Stream indices hash table. + totalSize += StreamIndices.GetPdbHashTableSize(ComputeStringHash); + + // Last NI + totalSize += sizeof(uint); + + // Feature codes. + totalSize += (uint) Features.Count * sizeof(PdbFeature); + + return totalSize; + } + + /// + public override void Write(IBinaryStreamWriter writer) + { + // Write basic info stream header. + writer.WriteUInt32((uint) Version); + writer.WriteUInt32(Signature); + writer.WriteUInt32(Age); + writer.WriteBytes(UniqueId.ToByteArray()); + + // Construct name buffer, keeping track of the offsets of every name. + using var nameBuffer = new MemoryStream(); + var nameWriter = new BinaryStreamWriter(nameBuffer); + + var stringOffsets = new Dictionary(); + foreach (var entry in StreamIndices) + { + uint offset = (uint) nameWriter.Offset; + nameWriter.WriteBytes(entry.Key.GetBytesUnsafe()); + nameWriter.WriteByte(0); + stringOffsets.Add(entry.Key, offset); + } + + writer.WriteUInt32((uint) nameBuffer.Length); + writer.WriteBytes(nameBuffer.ToArray()); + + // Write the hash table. + StreamIndices.WriteAsPdbHashTable(writer, + ComputeStringHash, + (key, value) => (stringOffsets[key], (uint) value)); + + // last NI, safe to put always zero. + writer.WriteUInt32(0); + + // Write feature codes. + var features = Features; + for (int i = 0; i < features.Count; i++) + writer.WriteUInt32((uint) features[i]); + } + + private static uint ComputeStringHash(Utf8String str) + { + // Note: The hash of a single entry is **deliberately** truncated to a 16 bit number. This is because + // the reference implementation of the name table returns a number of type HASH, which is a typedef + // for "unsigned short". If we don't do this, this will result in wrong buckets being filled in the + // hash table, and thus the serialization would fail. See NMTNI::hash() in Microsoft/microsoft-pdb. + + return (ushort) PdbHash.ComputeV1(str); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStreamVersion.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStreamVersion.cs new file mode 100644 index 000000000..d377f5ba1 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStreamVersion.cs @@ -0,0 +1,20 @@ +namespace AsmResolver.Symbols.Pdb.Metadata.Info; + +/// +/// Provides members defining all possible stream file format versions that PDB defines. +/// +public enum InfoStreamVersion +{ +#pragma warning disable CS1591 + VC2 = 19941610, + VC4 = 19950623, + VC41 = 19950814, + VC50 = 19960307, + VC98 = 19970604, + VC70Dep = 19990604, + VC70 = 20000404, + VC80 = 20030901, + VC110 = 20091201, + VC140 = 20140508, +#pragma warning restore CS1591 +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Info/PdbFeature.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Info/PdbFeature.cs new file mode 100644 index 000000000..16e5d3a66 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Info/PdbFeature.cs @@ -0,0 +1,28 @@ +namespace AsmResolver.Symbols.Pdb.Metadata.Info; + +/// +/// Provides members defining all possible features that a PDB can have. +/// +public enum PdbFeature : uint +{ + /// + /// Indicates no other feature flags are present, and that an IPI stream is present. + /// + VC110 = 20091201, + + /// + /// Indicates that other feature flags may be present, and that an IPI stream is present. + /// + VC140 = 20140508, + + /// + /// Indicates types can be duplicated in the TPI stream. + /// + NoTypeMerge = 0x4D544F4E, + + /// + /// Indicates the program was linked with /DEBUG:FASTLINK, and all type information is contained in the original + /// object files instead of TPI and IPI streams. + /// + MinimalDebugInfo = 0x494E494D, +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Info/SerializedInfoStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Info/SerializedInfoStream.cs new file mode 100644 index 000000000..f448ce67f --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Info/SerializedInfoStream.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Metadata.Info; + +/// +/// Implements an PDB info stream that pulls its data from an input stream. +/// +public class SerializedInfoStream : InfoStream +{ + private readonly BinaryStreamReader _reader; + private ulong _featureOffset; + + /// + /// Parses a PDB info stream from an input stream reader. + /// + /// The input stream. + public SerializedInfoStream(BinaryStreamReader reader) + { + Version = (InfoStreamVersion) reader.ReadUInt32(); + Signature = reader.ReadUInt32(); + Age = reader.ReadUInt32(); + + byte[] guidBytes = new byte[16]; + reader.ReadBytes(guidBytes, 0, guidBytes.Length); + + UniqueId = new Guid(guidBytes); + + _reader = reader; + } + + /// + protected override IDictionary GetStreamIndices() + { + var reader = _reader.Fork(); + uint length = reader.ReadUInt32(); + + var stringsReader = reader.ForkRelative(reader.RelativeOffset, length); + var hashTableReader = reader.ForkRelative(reader.RelativeOffset + length); + + var result = PdbHashTable.FromReader(ref hashTableReader, (key, value) => + { + var stringReader = stringsReader.ForkRelative(key); + var keyString = stringReader.ReadUtf8String(); + return (keyString, (int) value); + }); + + hashTableReader.ReadUInt32(); // lastNi (unused). + + _featureOffset = hashTableReader.Offset; + return result; + } + + /// + protected override IList GetFeatures() + { + // We need to read the stream name->index mapping to be able to read the features list of the PDB. + _ = StreamIndices; + + var result = new List(); + + var reader = _reader.ForkAbsolute(_featureOffset); + while (reader.CanRead(sizeof(uint))) + result.Add((PdbFeature) reader.ReadUInt32()); + + return result; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/PdbHash.cs b/src/AsmResolver.Symbols.Pdb/Metadata/PdbHash.cs new file mode 100644 index 000000000..ed5a6a03b --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/PdbHash.cs @@ -0,0 +1,49 @@ +namespace AsmResolver.Symbols.Pdb.Metadata; + +/// +/// Provides methods for computing hash codes for a PDB hash table. +/// +public static class PdbHash +{ + /// + /// Computes the V1 hash code for a UTF-8 string. + /// + /// The string to compute the hash for. + /// The hash code. + /// + /// See PDB/include/misc.h for reference implementation. + /// + public static unsafe uint ComputeV1(Utf8String value) + { + uint result = 0; + + uint count = (uint) value.ByteCount; + + fixed (byte* ptr = value.GetBytesUnsafe()) + { + byte* p = ptr; + + while (count >= 4) + { + result ^= *(uint*) p; + count -= 4; + p += 4; + } + + if (count >= 2) + { + result ^= *(ushort*) p; + count -= 2; + p += 2; + } + + if (count == 1) + result ^= *p; + } + + result |= 0x20202020; + result ^= result >> 11; + + return result ^ (result >> 16); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/PdbHashTable.cs b/src/AsmResolver.Symbols.Pdb/Metadata/PdbHashTable.cs new file mode 100644 index 000000000..fb58f25be --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/PdbHashTable.cs @@ -0,0 +1,212 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Metadata; + +/// +/// Provides methods for serializing and deserializing dictionaries as PDB hash tables. +/// +public static class PdbHashTable +{ + // Reference implementation from PDB/include/map.h + // Specifically, Map::load, Map::find and Map::save. + + /// + /// Reads a single PDB hash table from the input stream and converts it into a dictionary. + /// + /// The input stream to read from. + /// A function that maps the raw key-value pairs into high level constructs. + /// The type of keys in the final dictionary. + /// The type of values in the final dictionary. + /// The reconstructed dictionary. + public static Dictionary FromReader( + ref BinaryStreamReader reader, + Func mapper) + where TKey : notnull + { + uint count = reader.ReadUInt32(); + reader.ReadUInt32(); // Capacity + + uint presentWordCount = reader.ReadUInt32(); + reader.RelativeOffset += presentWordCount * sizeof(uint); + + uint deletedWordCount = reader.ReadUInt32(); + reader.RelativeOffset += deletedWordCount * sizeof(uint); + + var result = new Dictionary((int) count); + for (int i = 0; i < count; i++) + { + (uint rawKey, uint rawValue) = (reader.ReadUInt32(), reader.ReadUInt32()); + var (key, value) = mapper(rawKey, rawValue); + result.Add(key, value); + } + + return result; + } + + /// + /// Computes the number of bytes required to store the provided dictionary as a PDB hash table. + /// + /// The dictionary to serialize. + /// A function that computes the hash code for a single key within the dictionary. + /// The type of keys in the input dictionary. + /// The type of values in the input dictionary. + /// The number of bytes required. + public static uint GetPdbHashTableSize( + this IDictionary dictionary, + Func hasher) + where TKey : notnull + { + var info = dictionary.ToPdbHashTable(hasher, null); + + return sizeof(uint) // Count + + sizeof(uint) // Capacity + + sizeof(uint) // Present bitvector word count + + info.PresentWordCount * sizeof(uint) // Present bitvector words + + sizeof(uint) // Deleted bitvector word count (== 0) + + (sizeof(uint) + sizeof(uint)) * (uint) dictionary.Count + ; + } + + /// + /// Serializes a dictionary to a PDB hash table to an output stream. + /// + /// The dictionary to serialize. + /// The output stream to write to. + /// A function that computes the hash code for a single key within the dictionary. + /// A function that maps every key-value pair to raw key-value uint32 pairs. + /// The type of keys in the input dictionary. + /// The type of values in the input dictionary. + public static void WriteAsPdbHashTable( + this IDictionary dictionary, + IBinaryStreamWriter writer, + Func hasher, + Func mapper) + where TKey : notnull + { + var hashTable = dictionary.ToPdbHashTable(hasher, mapper); + + // Write count and capacity. + writer.WriteInt32(dictionary.Count); + writer.WriteUInt32(hashTable.Capacity); + + // Determine which words in the present bitvector to write. + uint wordCount = (hashTable.Capacity + sizeof(uint) - 1) / sizeof(uint); + uint[] words = new uint[wordCount]; + hashTable.Present.CopyTo(words, 0); + while (wordCount > 0 && words[wordCount - 1] == 0) + wordCount--; + + // Write the present bitvector. + writer.WriteUInt32(wordCount); + for (int i = 0; i < wordCount; i++) + writer.WriteUInt32(words[i]); + + // Write deleted bitvector. We just always do 0 (i.e. no deleted buckets). + writer.WriteUInt32(0); + + // Write all buckets. + for (int i = 0; i < hashTable.Keys!.Length; i++) + { + if (hashTable.Present.Get(i)) + { + writer.WriteUInt32(hashTable.Keys![i]); + writer.WriteUInt32(hashTable.Values![i]); + } + } + } + + private static HashTableInfo ToPdbHashTable( + this IDictionary dictionary, + Func hasher, + Func? mapper) + where TKey : notnull + { + uint capacity = ComputeRequiredCapacity(dictionary.Count); + + // Avoid allocating buckets if we actually don't need to (e.g. if we're simply measuring the total size). + uint[]? keys; + uint[]? values; + + if (mapper is null) + { + keys = null; + values = null; + } + else + { + keys = new uint[capacity]; + values = new uint[capacity]; + } + + var present = new BitArray((int) capacity, false); + + // Fill in buckets. + foreach (var item in dictionary) + { + // Find empty bucket to place key-value pair in. + uint hash = hasher(item.Key); + uint index = hash % capacity; + while (present.Get((int) index)) + index = (index + 1) % capacity; + + // Mark bucket as used. + present.Set((int) index, true); + + // Store key-value pair. + if (mapper is not null) + { + (uint key, uint value) = mapper(item.Key, item.Value); + keys![index] = key; + values![index] = value; + } + } + + // Determine final word count in present bit vector. + uint wordCount = (capacity + sizeof(uint) - 1) / sizeof(uint); + uint[] words = new uint[wordCount]; + present.CopyTo(words, 0); + while (wordCount > 0 && words[wordCount - 1] == 0) + wordCount--; + + return new HashTableInfo(capacity, keys, values, present, wordCount); + } + + private static uint ComputeRequiredCapacity(int totalItemCount) + { + // "Simulate" adding all items to the hash table, effectively calculating the capacity of the map. + // TODO: This can probably be calculated with a single formula instead. + + uint capacity = 1; + for (int i = 0; i <= totalItemCount; i++) + { + // Reference implementation allows only 67% of the capacity to be used. + uint maxLoad = capacity * 2 / 3 + 1; + if (i >= maxLoad) + capacity = 2 * maxLoad; + } + + return capacity; + } + + private readonly struct HashTableInfo + { + public readonly uint Capacity; + public readonly uint[]? Keys; + public readonly uint[]? Values; + public readonly BitArray Present; + public readonly uint PresentWordCount; + + public HashTableInfo(uint capacity, uint[]? keys, uint[]? values, BitArray present, uint presentWordCount) + { + Capacity = capacity; + Keys = keys; + Values = values; + Present = present; + PresentWordCount = presentWordCount; + } + } + +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/SerializedTpiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/SerializedTpiStream.cs new file mode 100644 index 000000000..3a4c144c7 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/SerializedTpiStream.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Metadata.Tpi; + +/// +/// Provides a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedTpiStream : TpiStream +{ + private readonly BinaryStreamReader _recordsReader; + private List<(uint Offset, uint Length)>? _recordOffsets; + + /// + /// Reads a TPI stream from the provided input stream. + /// + /// The input stream to read from. + public SerializedTpiStream(BinaryStreamReader reader) + { + Version = (TpiStreamVersion) reader.ReadUInt32(); + if (Version != TpiStreamVersion.V80) + throw new NotSupportedException($"Unsupported TPI file format version {Version}."); + + uint headerSize = reader.ReadUInt32(); + if (headerSize != TpiStreamHeaderSize) + throw new NotSupportedException("Invalid TPI header size."); + + TypeIndexBegin = reader.ReadUInt32(); + TypeIndexEnd = reader.ReadUInt32(); + TypeRecordsByteCount = reader.ReadUInt32(); + HashStreamIndex = reader.ReadUInt16(); + HashAuxStreamIndex = reader.ReadUInt16(); + HashKeySize = reader.ReadUInt32(); + HashBucketCount = reader.ReadUInt32(); + HashValueBufferOffset = reader.ReadUInt32(); + HashValueBufferLength = reader.ReadUInt32(); + IndexOffsetBufferOffset = reader.ReadUInt32(); + IndexOffsetBufferLength = reader.ReadUInt32(); + HashAdjBufferOffset = reader.ReadUInt32(); + HashAdjBufferLength = reader.ReadUInt32(); + + _recordsReader = reader.ForkRelative(reader.RelativeOffset, TypeRecordsByteCount); + } + + [MemberNotNull(nameof(_recordOffsets))] + private void EnsureRecordOffsetMappingInitialized() + { + if (_recordOffsets is null) + Interlocked.CompareExchange(ref _recordOffsets, GetRecordOffsets(), null); + } + + private List<(uint Offset, uint Length)> GetRecordOffsets() + { + int count = (int) (TypeIndexEnd - TypeIndexBegin); + var result = new List<(uint Offset, uint Length)>(count); + + var reader = _recordsReader.Fork(); + while (reader.CanRead(sizeof(ushort) * 2)) + { + uint offset = reader.RelativeOffset; + ushort length = reader.ReadUInt16(); + result.Add((offset, length)); + reader.Offset += length; + } + + return result; + } + + /// + public override bool TryGetLeafRecordReader(uint typeIndex, out BinaryStreamReader reader) + { + EnsureRecordOffsetMappingInitialized(); + + typeIndex -= TypeIndexBegin; + if (typeIndex >= _recordOffsets.Count) + { + reader = default; + return false; + } + + (uint offset, uint length) = _recordOffsets[(int) typeIndex]; + reader = _recordsReader.ForkRelative(offset, length + sizeof(ushort)); + return true; + } + + /// + protected override void WriteTypeRecords(IBinaryStreamWriter writer) + { + _recordsReader.Fork().WriteToOutput(writer); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStream.cs new file mode 100644 index 000000000..f3a5440e3 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStream.cs @@ -0,0 +1,233 @@ +using System; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Metadata.Tpi; + +/// +/// Represents the Type Information (TPI) stream in a PDB file. +/// +public abstract class TpiStream : SegmentBase +{ + /// + /// Gets the default fixed MSF stream index for the TPI stream. + /// + public const int StreamIndex = 2; + + internal const uint TpiStreamHeaderSize = + sizeof(TpiStreamVersion) // Version + + sizeof(uint) // HeaderSize + + sizeof(uint) // TypeIndexBegin + + sizeof(uint) // TypeIndexEnd + + sizeof(uint) // TypeRecordBytes + + sizeof(ushort) // HashStreamIndex + + sizeof(ushort) // HashAuxStreamIndex + + sizeof(uint) // HashKeySize + + sizeof(uint) // NumHashBuckets + + sizeof(uint) // HashValueBufferOffset + + sizeof(uint) // HashValueBufferLength + + sizeof(uint) // IndexOffsetBufferOffset + + sizeof(uint) // IndexOffsetBufferLength + + sizeof(uint) // HashAdjBufferOffset + + sizeof(uint) // HashAdjBufferLength + ; + + /// + /// Gets or sets the version of the file format that the TPI stream is using. + /// + public TpiStreamVersion Version + { + get; + set; + } = TpiStreamVersion.V80; + + /// + /// Gets or sets the index of the first type record in the stream. + /// + public uint TypeIndexBegin + { + get; + set; + } = 0x1000; + + /// + /// Gets or sets the index of the last type record in the stream. + /// + public uint TypeIndexEnd + { + get; + set; + } + + /// + /// Gets or sets the amount of bytes the full type record data requires. + /// + public uint TypeRecordsByteCount + { + get; + set; + } + + /// + /// Gets or sets the MSF stream index of the hash table for every record type in the stream (if available). + /// + /// + /// When this value is set to -1 (0xFFFF), then there is no hash stream stored in the PDB file. + /// + public ushort HashStreamIndex + { + get; + set; + } + + /// + /// Gets or sets the MSF stream index of the auxiliary hash table for every record type in the stream. + /// + /// + /// When this value is set to -1 (0xFFFF), then there is no hash stream stored in the PDB file. + /// The exact purpose of this stream is unknown, and usually this stream is not present. + /// + public ushort HashAuxStreamIndex + { + get; + set; + } + + /// + /// Gets or sets the number of bytes that a single hash value in the type hash stream consists of. + /// + public uint HashKeySize + { + get; + set; + } + + /// + /// Gets or sets the number of buckets used in the type record hash table. + /// + public uint HashBucketCount + { + get; + set; + } + + /// + /// Gets or sets the offset within the TPI hash stream pointing to the start of the list of hash values. + /// + public uint HashValueBufferOffset + { + get; + set; + } + + /// + /// Gets or sets the number of bytes within the TPI hash stream that the list of hash values consists of. + /// + public uint HashValueBufferLength + { + get; + set; + } + + /// + /// Gets or sets the offset within the TPI hash stream pointing to the start of the list of type record indices. + /// + public uint IndexOffsetBufferOffset + { + get; + set; + } + + /// + /// Gets or sets the number of bytes within the TPI hash stream that the list of type record indices consists of. + /// + public uint IndexOffsetBufferLength + { + get; + set; + } + + /// + /// Gets or sets the offset within the TPI hash stream pointing to the start of the list of hash-index pairs. + /// + public uint HashAdjBufferOffset + { + get; + set; + } + + /// + /// Gets or sets the number of bytes within the TPI hash stream that the list of hash-index pairs consists of. + /// + public uint HashAdjBufferLength + { + get; + set; + } + + /// + /// Reads a TPI stream from the provided input stream. + /// + /// The input stream. + /// The TPI stream. + public static TpiStream FromReader(BinaryStreamReader reader) => new SerializedTpiStream(reader); + + /// + /// Attempts to get a reader object that starts at the beginning of a leaf record for the provided type index. + /// + /// The type index to get the reader for. + /// The obtained reader object. + /// + /// true if the provided type index was valid and a reader object was constructed successfully, + /// false otherwise. + /// + public abstract bool TryGetLeafRecordReader(uint typeIndex, out BinaryStreamReader reader); + + /// + /// Gets a reader object that starts at the beginning of a leaf record for the provided type index. + /// + /// The type index to get the reader for. + /// The obtained reader object. + /// Occurs when the provided type index was invalid. + public BinaryStreamReader GetLeafRecordReader(uint typeIndex) + { + if (!TryGetLeafRecordReader(typeIndex, out var reader)) + throw new ArgumentException("Invalid type index."); + + return reader; + } + + /// + public override uint GetPhysicalSize() => TpiStreamHeaderSize + TypeRecordsByteCount; + + /// + public override void Write(IBinaryStreamWriter writer) + { + WriteHeader(writer); + WriteTypeRecords(writer); + } + + private void WriteHeader(IBinaryStreamWriter writer) + { + writer.WriteUInt32((uint) Version); + writer.WriteUInt32(TpiStreamHeaderSize); + writer.WriteUInt32(TypeIndexBegin); + writer.WriteUInt32(TypeIndexEnd); + writer.WriteUInt32(TypeRecordsByteCount); + writer.WriteUInt16(HashStreamIndex); + writer.WriteUInt16(HashAuxStreamIndex); + writer.WriteUInt32(HashKeySize); + writer.WriteUInt32(HashBucketCount); + writer.WriteUInt32(HashValueBufferOffset); + writer.WriteUInt32(HashValueBufferLength); + writer.WriteUInt32(IndexOffsetBufferOffset); + writer.WriteUInt32(IndexOffsetBufferLength); + writer.WriteUInt32(HashAdjBufferOffset); + writer.WriteUInt32(HashAdjBufferLength); + } + + /// + /// Writes all type records stored in the TPI stream to the provided output stream. + /// + /// The output stream. + protected abstract void WriteTypeRecords(IBinaryStreamWriter writer); +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStreamVersion.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStreamVersion.cs new file mode 100644 index 000000000..bf65e07ec --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Tpi/TpiStreamVersion.cs @@ -0,0 +1,15 @@ +namespace AsmResolver.Symbols.Pdb.Metadata.Tpi; + +/// +/// Provides members defining all known file formats for the TPI stream. +/// +public enum TpiStreamVersion : uint +{ +#pragma warning disable CS1591 + V40 = 19950410, + V41 = 19951122, + V50 = 19961031, + V70 = 19990903, + V80 = 20040203, +#pragma warning restore CS1591 +} diff --git a/src/AsmResolver.Symbols.Pdb/Msf/Builder/FreeBlockMap.cs b/src/AsmResolver.Symbols.Pdb/Msf/Builder/FreeBlockMap.cs new file mode 100644 index 000000000..dea1712e0 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Msf/Builder/FreeBlockMap.cs @@ -0,0 +1,38 @@ +using System.Collections; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Msf.Builder; + +/// +/// Represents a block within a MSF file that contains information on which blocks in the MSF file are free to use. +/// +public class FreeBlockMap : SegmentBase +{ + /// + /// Creates a new empty free block map. + /// + /// The size of a single block in the MSF file. + public FreeBlockMap(uint blockSize) + { + BitField = new BitArray((int) blockSize * 8, true); + } + + /// + /// Gets the bit field indicating which blocks in the MSF file are free to use. + /// + public BitArray BitField + { + get; + } + + /// + public override uint GetPhysicalSize() => (uint) (BitField.Count / 8); + + /// + public override void Write(IBinaryStreamWriter writer) + { + byte[] data = new byte[BitField.Count / 8]; + BitField.CopyTo(data, 0); + writer.WriteBytes(data); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Msf/Builder/IMsfFileBuilder.cs b/src/AsmResolver.Symbols.Pdb/Msf/Builder/IMsfFileBuilder.cs new file mode 100644 index 000000000..b51dcfe0b --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Msf/Builder/IMsfFileBuilder.cs @@ -0,0 +1,14 @@ +namespace AsmResolver.Symbols.Pdb.Msf.Builder; + +/// +/// Provides members for constructing new MSF files. +/// +public interface IMsfFileBuilder +{ + /// + /// Reconstructs a new writable MSF file buffer from an instance of . + /// + /// The file to reconstruct. + /// The reconstructed buffer. + MsfFileBuffer CreateFile(MsfFile file); +} diff --git a/src/AsmResolver.Symbols.Pdb/Msf/Builder/MsfFileBuffer.cs b/src/AsmResolver.Symbols.Pdb/Msf/Builder/MsfFileBuffer.cs new file mode 100644 index 000000000..865715037 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Msf/Builder/MsfFileBuffer.cs @@ -0,0 +1,205 @@ +using System; +using System.Collections.Generic; +using System.IO; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Msf.Builder; + +/// +/// Represents a mutable buffer for building up a new MSF file. +/// +public class MsfFileBuffer : SegmentBase +{ + private readonly Dictionary _blockIndices = new(); + private readonly List _freeBlockMaps = new(2); + private readonly List _blocks; + + /// + /// Creates a new empty MSF file buffer. + /// + /// The block size to use. + public MsfFileBuffer(uint blockSize) + { + SuperBlock = new MsfSuperBlock + { + Signature = MsfSuperBlock.BigMsfSignature, + BlockSize = blockSize, + FreeBlockMapIndex = 1, + BlockCount = 3, + }; + + _blocks = new List((int) blockSize); + + InsertBlock(0, SuperBlock); + var fpm = GetOrCreateFreeBlockMap(1, out _); + InsertBlock(2, null); + + fpm.BitField[0] = false; + fpm.BitField[1] = false; + fpm.BitField[2] = false; + } + + /// + /// Gets the super block of the MSF file that is being constructed. + /// + public MsfSuperBlock SuperBlock + { + get; + } + + /// + /// Determines whether a block in the MSF file buffer is available or not. + /// + /// The index of the block. + /// true if the block is available, false otherwise. + public bool BlockIsAvailable(int blockIndex) + { + var freeBlockMap = GetOrCreateFreeBlockMap(blockIndex, out int offset); + if (offset < 3 && (blockIndex == 0 || offset > 0)) + return false; + return freeBlockMap.BitField[offset]; + } + + /// + /// Inserts a block of the provided MSF stream into the buffer. + /// + /// The MSF file index to insert the block into. + /// The stream to pull a chunk from. + /// The index of the chunk to store at the provided block index. + /// + /// Occurs when the index provided by is already in use. + /// + public void InsertBlock(int blockIndex, MsfStream stream, int chunkIndex) + { + var fpm = GetOrCreateFreeBlockMap(blockIndex, out int offset); + if (!fpm.BitField[offset]) + throw new ArgumentException($"Block {blockIndex} is already in use."); + + uint blockSize = SuperBlock.BlockSize; + var segment = new DataSourceSegment( + stream.Contents, + stream.Contents.BaseAddress + (ulong) (chunkIndex * blockSize), + (uint) (chunkIndex * blockSize), + (uint) Math.Min(stream.Contents.Length - (ulong) (chunkIndex * blockSize), blockSize)); + + InsertBlock(blockIndex, segment); + + int[] indices = GetMutableBlockIndicesForStream(stream); + indices[chunkIndex] = blockIndex; + + fpm.BitField[offset] = false; + } + + private void InsertBlock(int blockIndex, ISegment? segment) + { + // Ensure enough blocks are present in the backing-buffer. + while (_blocks.Count <= blockIndex) + _blocks.Add(null); + + // Insert block and update super block. + _blocks[blockIndex] = segment; + SuperBlock.BlockCount = (uint) _blocks.Count; + } + + private FreeBlockMap GetOrCreateFreeBlockMap(int blockIndex, out int offset) + { + int index = Math.DivRem(blockIndex, (int) SuperBlock.BlockSize, out offset); + while (_freeBlockMaps.Count <= index) + { + var freeBlockMap = new FreeBlockMap(SuperBlock.BlockSize); + _freeBlockMaps.Add(freeBlockMap); + InsertBlock(index + (int) SuperBlock.FreeBlockMapIndex, freeBlockMap); + } + + return _freeBlockMaps[index]; + } + + private int[] GetMutableBlockIndicesForStream(MsfStream stream) + { + if (!_blockIndices.TryGetValue(stream, out int[]? indices)) + { + indices = new int[stream.GetRequiredBlockCount(SuperBlock.BlockSize)]; + _blockIndices.Add(stream, indices); + } + + return indices; + } + + /// + /// Gets the allocated indices for the provided MSF stream. + /// + /// The stream. + /// The block indices. + public int[] GetBlockIndicesForStream(MsfStream stream) => (int[]) GetMutableBlockIndicesForStream(stream).Clone(); + + /// + /// Constructs a new MSF stream containing the stream directory. + /// + /// The files that the directory should list. + /// The constructed stream. + /// + /// This method does not add the stream to the buffer, nor does it update the super block. + /// + public MsfStream CreateStreamDirectory(IList streams) + { + using var contents = new MemoryStream(); + var writer = new BinaryStreamWriter(contents); + + // Stream count. + writer.WriteInt32(streams.Count); + + // Stream sizes. + for (int i = 0; i < streams.Count; i++) + writer.WriteUInt32((uint) streams[i].Contents.Length); + + // Stream indices. + for (int i = 0; i < streams.Count; i++) + { + int[] indices = GetMutableBlockIndicesForStream(streams[i]); + foreach (int index in indices) + writer.WriteInt32(index); + } + + return new MsfStream(contents.ToArray()); + } + + /// + /// Creates a new MSF stream containing the block indices of the stream directory. + /// + /// The stream directory to store the indices for. + /// The constructed stream. + /// + /// This method does not add the stream to the buffer, nor does it update the super block. + /// + public MsfStream CreateStreamDirectoryMap(MsfStream streamDirectory) + { + using var contents = new MemoryStream(); + var writer = new BinaryStreamWriter(contents); + + int[] indices = GetMutableBlockIndicesForStream(streamDirectory); + foreach (int index in indices) + writer.WriteInt32(index); + + return new MsfStream(contents.ToArray()); + } + + /// + public override uint GetPhysicalSize() => SuperBlock.BlockCount * SuperBlock.BlockSize; + + /// + public override void Write(IBinaryStreamWriter writer) + { + foreach (var block in _blocks) + { + if (block is null) + { + writer.WriteZeroes((int) SuperBlock.BlockSize); + } + else + { + block.Write(writer); + writer.Align(SuperBlock.BlockSize); + } + } + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Msf/Builder/SequentialMsfFileBuilder.cs b/src/AsmResolver.Symbols.Pdb/Msf/Builder/SequentialMsfFileBuilder.cs new file mode 100644 index 000000000..ca02da2a9 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Msf/Builder/SequentialMsfFileBuilder.cs @@ -0,0 +1,62 @@ +namespace AsmResolver.Symbols.Pdb.Msf.Builder; + +/// +/// Provides an implementation of the that places all blocks of every stream in sequence, +/// and effectively defragments the file system. +/// +public class SequentialMsfFileBuilder : IMsfFileBuilder +{ + /// + /// Gets the default instance of the class. + /// + public static SequentialMsfFileBuilder Instance + { + get; + } = new(); + + /// + public MsfFileBuffer CreateFile(MsfFile file) + { + var result = new MsfFileBuffer(file.BlockSize); + + // Block 0, 1, and 2 are reserved for the super block, FPM1 and FPM2. + int currentIndex = 3; + + // Add streams in sequence. + for (int i = 0; i < file.Streams.Count; i++) + AddStream(result, file.Streams[i], ref currentIndex); + + // Construct and add stream directory. + var directory = result.CreateStreamDirectory(file.Streams); + result.SuperBlock.DirectoryByteCount = (uint) directory.Contents.Length; + AddStream(result, directory, ref currentIndex); + + // Construct and add stream directory map. + var directoryMap = result.CreateStreamDirectoryMap(directory); + result.SuperBlock.DirectoryMapIndex = (uint) currentIndex; + AddStream(result, directoryMap, ref currentIndex); + + return result; + } + + private static void AddStream(MsfFileBuffer buffer, MsfStream stream, ref int currentIndex) + { + int blockCount = stream.GetRequiredBlockCount(buffer.SuperBlock.BlockSize); + + for (int j = 0; j < blockCount; j++, currentIndex++) + { + // Skip over any of the FPM indices. + switch (currentIndex % 4096) + { + case 1: + currentIndex += 2; + break; + case 2: + currentIndex++; + break; + } + + buffer.InsertBlock(currentIndex, stream, j); + } + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Msf/MsfFile.cs b/src/AsmResolver.Symbols.Pdb/Msf/MsfFile.cs new file mode 100644 index 000000000..a58775dd5 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Msf/MsfFile.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using AsmResolver.Collections; +using AsmResolver.IO; +using AsmResolver.Symbols.Pdb.Msf.Builder; + +namespace AsmResolver.Symbols.Pdb.Msf; + +/// +/// Models a file that is in the Microsoft Multi-Stream Format (MSF). +/// +public class MsfFile +{ + private uint _blockSize; + private IList? _streams; + + /// + /// Gets or sets the size of each block in the MSF file. + /// + /// + /// Occurs when the provided value is neither 512, 1024, 2048 or 4096. + /// + public uint BlockSize + { + get => _blockSize; + set + { + if (value is not (512 or 1024 or 2048 or 4096)) + { + throw new ArgumentOutOfRangeException( + nameof(value), + "Block size must be either 512, 1024, 2048 or 4096 bytes."); + } + + _blockSize = value; + } + } + + /// + /// Gets a collection of streams that are present in the MSF file. + /// + public IList Streams + { + get + { + if (_streams is null) + Interlocked.CompareExchange(ref _streams, GetStreams(), null); + return _streams; + } + } + + /// + /// Creates a new empty MSF file with a default block size of 4096. + /// + public MsfFile() + : this(4096) + { + } + + /// + /// Creates a new empty MSF file with the provided block size. + /// + /// The block size to use. This must be a value of 512, 1024, 2048 or 4096. + /// Occurs when an invalid block size was provided. + public MsfFile(uint blockSize) + { + BlockSize = blockSize; + } + + /// + /// Reads an MSF file from a file on the disk. + /// + /// The path to the file to read. + /// The read MSF file. + public static MsfFile FromFile(string path) => FromFile(UncachedFileService.Instance.OpenFile(path)); + + /// + /// Reads an MSF file from an input file. + /// + /// The file to read. + /// The read MSF file. + public static MsfFile FromFile(IInputFile file) => FromReader(file.CreateReader()); + + /// + /// Interprets a byte array as an MSF file. + /// + /// The data to interpret. + /// The read MSF file. + public static MsfFile FromBytes(byte[] data) => FromReader(new BinaryStreamReader(data)); + + /// + /// Reads an MSF file from the provided input stream reader. + /// + /// The reader. + /// The read MSF file. + public static MsfFile FromReader(BinaryStreamReader reader) => new SerializedMsfFile(reader); + + /// + /// Obtains the list of streams stored in the MSF file. + /// + /// The streams. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetStreams() => new OwnedCollection(this); + + /// + /// Reconstructs and writes the MSF file to the disk. + /// + /// The path of the file to write to. + public void Write(string path) + { + using var fs = File.Create(path); + Write(fs); + } + + /// + /// Reconstructs and writes the MSF file to an output stream. + /// + /// The output stream. + public void Write(Stream stream) => Write(new BinaryStreamWriter(stream)); + + /// + /// Reconstructs and writes the MSF file to an output stream. + /// + /// The output stream. + public void Write(IBinaryStreamWriter writer) => Write(writer, SequentialMsfFileBuilder.Instance); + + /// + /// Reconstructs and writes the MSF file to an output stream. + /// + /// The output stream. + /// The builder to use for reconstructing the MSF file. + public void Write(IBinaryStreamWriter writer, IMsfFileBuilder builder) => builder.CreateFile(this).Write(writer); +} diff --git a/src/AsmResolver.Symbols.Pdb/Msf/MsfStream.cs b/src/AsmResolver.Symbols.Pdb/Msf/MsfStream.cs new file mode 100644 index 000000000..f5e871498 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Msf/MsfStream.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using AsmResolver.Collections; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Msf; + +/// +/// Represents a single stream in an Multi-Stream Format (MSF) file. +/// +public class MsfStream : IOwnedCollectionElement +{ + /// + /// Creates a new MSF stream with the provided contents. + /// + /// The raw data of the stream. + public MsfStream(byte[] data) + : this(new ByteArrayDataSource(data)) + { + } + + /// + /// Creates a new MSF stream with the provided data source as contents. + /// + /// The data source containing the raw data of the stream. + public MsfStream(IDataSource contents) + { + Contents = contents; + OriginalBlockIndices = Array.Empty(); + } + + /// + /// Initializes an MSF stream with a data source and a list of original block indices that the stream was based on. + /// + /// The data source containing the raw data of the stream. + /// The original block indices that this MSF stream was based on. + public MsfStream(IDataSource contents, IEnumerable originalBlockIndices) + { + Contents = contents; + OriginalBlockIndices = originalBlockIndices.ToArray(); + } + + /// + /// Gets the parent MSF file that this stream is embedded in. + /// + public MsfFile? Parent + { + get; + private set; + } + + MsfFile? IOwnedCollectionElement.Owner + { + get => Parent; + set => Parent = value; + } + + /// + /// Gets or sets the contents of the stream. + /// + public IDataSource Contents + { + get; + set; + } + + /// + /// Gets a collection of block indices that this stream was based of (if available). + /// + public IReadOnlyList OriginalBlockIndices + { + get; + } + + /// + /// Gets the amount of blocks that is required to store this MSF stream. + /// + /// The number of blocks. + /// Occurs when the stream is not added to a file. + public int GetRequiredBlockCount() + { + if (Parent is null) + { + throw new InvalidOperationException( + "Determining the required block count of a stream requires the stream to be added to an MSF file."); + } + + return GetRequiredBlockCount(Parent.BlockSize); + } + + /// + /// Gets the amount of blocks that is required to store this MSF stream, given the provided block size. + /// + /// The block size. + /// The number of blocks. + public int GetRequiredBlockCount(uint blockSize) + { + return (int) ((Contents.Length + blockSize - 1) / blockSize); + } + + /// + /// Creates a new binary reader that reads the raw contents of the stream. + /// + /// The constructed binary reader. + public BinaryStreamReader CreateReader() => new(Contents, Contents.BaseAddress, 0, (uint) Contents.Length); +} diff --git a/src/AsmResolver.Symbols.Pdb/Msf/MsfStreamDataSource.cs b/src/AsmResolver.Symbols.Pdb/Msf/MsfStreamDataSource.cs new file mode 100644 index 000000000..fa0e96cef --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Msf/MsfStreamDataSource.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Msf; + +/// +/// Implements a data source for a single MSF stream that pulls data from multiple (fragmented) blocks. +/// +public class MsfStreamDataSource : IDataSource +{ + private readonly IDataSource[] _blocks; + private readonly long _blockSize; + + /// + /// Creates a new MSF stream data source. + /// + /// The length of the stream. + /// The size of an individual block. + /// The blocks + /// + /// Occurs when the total size of the provided blocks is smaller than + /// * . + /// + public MsfStreamDataSource(ulong length, uint blockSize, IEnumerable blocks) + : this(length, blockSize, blocks.Select(x => new ByteArrayDataSource(x))) + { + } + + /// + /// Creates a new MSF stream data source. + /// + /// The length of the stream. + /// The size of an individual block. + /// The blocks + /// + /// Occurs when the total size of the provided blocks is smaller than + /// * . + /// + public MsfStreamDataSource(ulong length, uint blockSize, IEnumerable blocks) + { + Length = length; + _blocks = blocks.ToArray(); + _blockSize = blockSize; + + if (length > (ulong) (_blocks.Length * blockSize)) + throw new ArgumentException("Provided length is larger than the provided blocks combined."); + } + + /// + public ulong BaseAddress => 0; + + /// + public ulong Length + { + get; + } + + /// + public byte this[ulong address] + { + get + { + if (!IsValidAddress(address)) + throw new IndexOutOfRangeException(); + + var block = GetBlockAndOffset(address, out ulong offset); + return block[block.BaseAddress + offset]; + } + } + + /// + public bool IsValidAddress(ulong address) => address < Length; + + /// + public int ReadBytes(ulong address, byte[] buffer, int index, int count) + { + int totalReadCount = 0; + int remainingBytes = Math.Min(count, (int) (Length - (address - BaseAddress))); + + while (remainingBytes > 0) + { + // Obtain current block and offset within block. + var block = GetBlockAndOffset(address, out ulong offset); + + // Read available bytes. + int readCount = Math.Min(remainingBytes, (int) _blockSize); + int actualReadCount = block.ReadBytes(block.BaseAddress + offset, buffer, index, readCount); + + // Move to the next block. + totalReadCount += actualReadCount; + address += (ulong) actualReadCount; + index += actualReadCount; + remainingBytes -= actualReadCount; + } + + return totalReadCount; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private IDataSource GetBlockAndOffset(ulong address, out ulong offset) + { + var block = _blocks[Math.DivRem((long) address, _blockSize, out long x)]; + offset = (ulong) x; + return block; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Msf/MsfSuperBlock.cs b/src/AsmResolver.Symbols.Pdb/Msf/MsfSuperBlock.cs new file mode 100644 index 000000000..cba31d1c8 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Msf/MsfSuperBlock.cs @@ -0,0 +1,132 @@ +using System; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Msf; + +/// +/// Represents the first block in a Multi-Stream Format (MSF) file. +/// +public sealed class MsfSuperBlock : SegmentBase +{ + // Used in MSF v2.0 + internal static readonly byte[] SmallMsfSignature = + { + 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, 0x43, 0x2f, 0x43, 0x2b, 0x2b, 0x20, 0x70, 0x72, + 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x20, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x20, 0x32, 0x2e, 0x30, + 0x30, 0x0d, 0x0a, 0x1a, 0x4a, 0x47 + }; + + // Used in MSF v7.0 + internal static readonly byte[] BigMsfSignature = + { + 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, 0x43, 0x2f, 0x43, 0x2b, 0x2b, 0x20, + 0x4d, 0x53, 0x46, 0x20, 0x37, 0x2e, 0x30, 0x30, 0x0d, 0x0a, 0x1a, 0x44, 0x53, 0x00, 0x00, 0x00 + }; + + /// + /// Gets or sets the magic file signature in the super block, identifying the format version of the MSF file. + /// + public byte[] Signature + { + get; + set; + } = (byte[]) BigMsfSignature.Clone(); + + /// + /// Gets or sets the size of an individual block in bytes. + /// + public uint BlockSize + { + get; + set; + } + + /// + /// Gets or sets the index of the block containing a bitfield indicating which blocks in the entire MSF file are + /// in use or not. + /// + public uint FreeBlockMapIndex + { + get; + set; + } + + /// + /// Gets or sets the total number of blocks in the MSF file. + /// + public uint BlockCount + { + get; + set; + } + + /// + /// Gets or sets the number of bytes of the stream directory in the MSF file. + /// + public uint DirectoryByteCount + { + get; + set; + } + + /// + /// Gets or sets the index of the block containing all block indices that make up the stream directory of the MSF + /// file. + /// + public uint DirectoryMapIndex + { + get; + set; + } + + /// + /// Reads a single MSF super block from the provided input stream. + /// + /// The input stream. + /// The parsed MSF super block. + /// Occurs when the super block is malformed. + public static MsfSuperBlock FromReader(ref BinaryStreamReader reader) + { + var result = new MsfSuperBlock(); + + // Check MSF header. + result.Signature = new byte[BigMsfSignature.Length]; + int count = reader.ReadBytes(result.Signature, 0, result.Signature.Length); + if (count != BigMsfSignature.Length || !ByteArrayEqualityComparer.Instance.Equals(result.Signature, BigMsfSignature)) + throw new BadImageFormatException("File does not start with a valid or supported MSF file signature."); + + result.BlockSize = reader.ReadUInt32(); + if (result.BlockSize is not (512 or 1024 or 2048 or 4096)) + throw new BadImageFormatException("Block size must be either 512, 1024, 2048 or 4096 bytes."); + + // We don't really use the free block map as we are not fully implementing the NTFS-esque file system, but we + // validate its contents regardless as a sanity check. + result.FreeBlockMapIndex = reader.ReadUInt32(); + if (result.FreeBlockMapIndex is not (1 or 2)) + throw new BadImageFormatException($"Free block map index must be 1 or 2, but was {result.FreeBlockMapIndex}."); + + result.BlockCount = reader.ReadUInt32(); + + result.DirectoryByteCount = reader.ReadUInt32(); + reader.Offset += sizeof(uint); + result.DirectoryMapIndex = reader.ReadUInt32(); + + return result; + } + + /// + public override uint GetPhysicalSize() => (uint) BigMsfSignature.Length + sizeof(uint) * 6; + + /// + public override void Write(IBinaryStreamWriter writer) + { + writer.WriteBytes(Signature); + writer.WriteUInt32(BlockSize); + writer.WriteUInt32(FreeBlockMapIndex); + writer.WriteUInt32(BlockCount); + writer.WriteUInt32(DirectoryByteCount); + writer.WriteUInt32(0); + writer.WriteUInt32(DirectoryMapIndex); + } + +} diff --git a/src/AsmResolver.Symbols.Pdb/Msf/SerializedMsfFile.cs b/src/AsmResolver.Symbols.Pdb/Msf/SerializedMsfFile.cs new file mode 100644 index 000000000..5570f188d --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Msf/SerializedMsfFile.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using AsmResolver.Collections; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Msf; + +/// +/// Provides an implementation for an MSF file that is read from an input file. +/// +/// +/// Currently, this model only supports version 7.0 of the file format. +/// +public class SerializedMsfFile : MsfFile +{ + private readonly BinaryStreamReader _reader; + private readonly MsfSuperBlock _originalSuperBlock; + private readonly IDataSource?[] _blocks; + + /// + /// Interprets an input stream as an MSF file version 7.0. + /// + /// The input stream. + /// Occurs when the MSF file is malformed. + public SerializedMsfFile(BinaryStreamReader reader) + { + _originalSuperBlock = MsfSuperBlock.FromReader(ref reader); + + BlockSize = _originalSuperBlock.BlockSize; + _blocks = new IDataSource?[_originalSuperBlock.BlockCount]; + _reader = reader; + } + + private IDataSource GetBlock(int index) + { + if (_blocks[index] is null) + { + // We lazily initialize all blocks by slicing the original data source of the reader. + var block = new DataSourceSlice( + _reader.DataSource, + _reader.DataSource.BaseAddress + (ulong) (index * _originalSuperBlock.BlockSize), + _originalSuperBlock.BlockSize); + + Interlocked.CompareExchange(ref _blocks[index], block, null); + } + + return _blocks[index]!; + } + + /// + protected override IList GetStreams() + { + // Get the block indices of the Stream Directory stream. + var indicesBlock = GetBlock((int) _originalSuperBlock.DirectoryMapIndex); + var indicesReader = new BinaryStreamReader(indicesBlock, indicesBlock.BaseAddress, 0, + GetBlockCount(_originalSuperBlock.DirectoryByteCount) * sizeof(uint)); + + // Access the Stream Directory stream. + var directoryStream = CreateStreamFromIndicesReader(ref indicesReader, _originalSuperBlock.DirectoryByteCount); + var directoryReader = directoryStream.CreateReader(); + + // Stream Directory format is as follows: + // - stream count: uint32 + // - stream sizes: uint32[stream count] + // - stream indices: uint32[stream count][] + + int streamCount = directoryReader.ReadInt32(); + + // Read sizes. + uint[] streamSizes = new uint[streamCount]; + for (int i = 0; i < streamCount; i++) + streamSizes[i] = directoryReader.ReadUInt32(); + + // Construct streams. + var result = new OwnedCollection(this, streamCount); + for (int i = 0; i < streamCount; i++) + { + // A size of 0xFFFFFFFF indicates the stream does not exist. + if (streamSizes[i] == uint.MaxValue) + continue; + + result.Add(CreateStreamFromIndicesReader(ref directoryReader, streamSizes[i])); + } + + return result; + } + + private MsfStream CreateStreamFromIndicesReader(ref BinaryStreamReader indicesReader, uint streamSize) + { + // Read all indices. + int[] indices = new int[GetBlockCount(streamSize)]; + for (int i = 0; i < indices.Length; i++) + indices[i] = indicesReader.ReadInt32(); + + // Transform indices to blocks. + var blocks = new IDataSource[indices.Length]; + for (int i = 0; i < blocks.Length; i++) + blocks[i] = GetBlock(indices[i]); + + // Construct stream. + var dataSource = new MsfStreamDataSource(streamSize, _originalSuperBlock.BlockSize, blocks); + return new MsfStream(dataSource, indices); + } + + private uint GetBlockCount(uint streamSize) + { + return (streamSize + _originalSuperBlock.BlockSize - 1) / _originalSuperBlock.BlockSize; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/PdbImage.cs b/src/AsmResolver.Symbols.Pdb/PdbImage.cs new file mode 100644 index 000000000..0abd232a9 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/PdbImage.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using AsmResolver.IO; +using AsmResolver.Symbols.Pdb.Leaves; +using AsmResolver.Symbols.Pdb.Msf; +using AsmResolver.Symbols.Pdb.Records; + +namespace AsmResolver.Symbols.Pdb; + +/// +/// Represents a single Program Debug Database (PDB) image. +/// +public class PdbImage +{ + private IList? _symbols; + private ConcurrentDictionary _simpleTypes = new(); + + /// + /// Gets a collection of all symbols stored in the PDB image. + /// + public IList Symbols + { + get + { + if (_symbols is null) + Interlocked.CompareExchange(ref _symbols, GetSymbols(), null); + return _symbols; + } + } + + /// + /// Reads a PDB image from the provided input file. + /// + /// The path to the PDB file. + /// The read PDB image. + public static PdbImage FromFile(string path) => FromFile(MsfFile.FromFile(path)); + + /// + /// Reads a PDB image from the provided input file. + /// + /// The input file. + /// The read PDB image. + public static PdbImage FromFile(IInputFile file) => FromFile(MsfFile.FromFile(file)); + + /// + /// Interprets a byte array as a PDB image. + /// + /// The data to interpret. + /// The read PDB image. + public static PdbImage FromBytes(byte[] data) => FromFile(MsfFile.FromBytes(data)); + + /// + /// Reads an PDB image from the provided input stream reader. + /// + /// The reader. + /// The read PDB image. + public static PdbImage FromReader(BinaryStreamReader reader) => FromFile(MsfFile.FromReader(reader)); + + /// + /// Loads a PDB image from the provided MSF file. + /// + /// The MSF file. + /// The read PDB image. + public static PdbImage FromFile(MsfFile file) + { + return FromFile(file, new PdbReaderParameters(ThrowErrorListener.Instance)); + } + + /// + /// Loads a PDB image from the provided MSF file. + /// + /// The MSF file. + /// The parameters to use while reading the PDB image. + /// The read PDB image. + public static PdbImage FromFile(MsfFile file, PdbReaderParameters readerParameters) + { + return new SerializedPdbImage(file, readerParameters); + } + + /// + /// Attempts to obtain a type record from the TPI or IPI stream based on its type index. + /// + /// The type index. + /// The resolved type. + /// true if the type was found, false otherwise. + public virtual bool TryGetLeafRecord(uint typeIndex, [NotNullWhen(true)] out CodeViewLeaf? leaf) + { + typeIndex &= 0x7fffffff; + if (typeIndex is > 0 and < 0x1000) + { + leaf = _simpleTypes.GetOrAdd(typeIndex, i => new SimpleTypeRecord(i)); + return true; + } + + leaf = null; + return false; + } + + /// + /// Obtains a type record from the TPI or IPI stream based on its type index. + /// + /// The type index. + /// The resolved type. + /// Occurs when the type index is invalid. + public CodeViewLeaf GetLeafRecord(uint typeIndex) + { + if (!TryGetLeafRecord(typeIndex, out var type)) + throw new ArgumentException("Invalid type index."); + return type; + } + + /// + /// Obtains a collection of symbols stored in the symbol record stream of the PDB image. + /// + /// The symbols. + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetSymbols() => new List(); +} diff --git a/src/AsmResolver.Symbols.Pdb/PdbReaderContext.cs b/src/AsmResolver.Symbols.Pdb/PdbReaderContext.cs new file mode 100644 index 000000000..9e2939315 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/PdbReaderContext.cs @@ -0,0 +1,34 @@ +namespace AsmResolver.Symbols.Pdb; + +/// +/// Provides a context in which a PDB image reader exists in. This includes the PDB image as well as reader parameters. +/// +public class PdbReaderContext +{ + /// + /// Creates a new PDB reader context. + /// + /// The image for which the data is to be read. + /// The parameters used while reading the PDB image. + public PdbReaderContext(SerializedPdbImage parentImage, PdbReaderParameters parameters) + { + ParentImage = parentImage; + Parameters = parameters; + } + + /// + /// Gets the image for which the data is read. + /// + public SerializedPdbImage ParentImage + { + get; + } + + /// + /// Gets the parameters used for reading the data. + /// + public PdbReaderParameters Parameters + { + get; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/PdbReaderParameters.cs b/src/AsmResolver.Symbols.Pdb/PdbReaderParameters.cs new file mode 100644 index 000000000..7368424b0 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/PdbReaderParameters.cs @@ -0,0 +1,33 @@ +namespace AsmResolver.Symbols.Pdb; + +/// +/// Provides parameters for configuring the reading process of a PDB image. +/// +public class PdbReaderParameters +{ + /// + /// Creates new PDB reader parameters. + /// + public PdbReaderParameters() + : this(ThrowErrorListener.Instance) + { + } + + /// + /// Creates new PDB reader parameters with the provided error listener object. + /// + /// The object used for receiving parser errors. + public PdbReaderParameters(IErrorListener errorListener) + { + ErrorListener = errorListener; + } + + /// + /// Gets or sets the object responsible for receiving and processing parser errors. + /// + public IErrorListener ErrorListener + { + get; + set; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbol.cs new file mode 100644 index 000000000..5869c3acd --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbol.cs @@ -0,0 +1,40 @@ +using AsmResolver.IO; +using AsmResolver.Symbols.Pdb.Records.Serialized; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents a single symbol record within the symbol record stream of a PDB file. +/// +public abstract class CodeViewSymbol +{ + /// + /// Gets the type of symbol this record encodes. + /// + public abstract CodeViewSymbolType CodeViewSymbolType + { + get; + } + + /// + /// Reads a single symbol record from the input stream. + /// + /// The reading context in which the symbol is situated in. + /// The input stream. + /// The read symbol. + public static CodeViewSymbol FromReader(PdbReaderContext context, ref BinaryStreamReader reader) + { + ushort length = reader.ReadUInt16(); + var type = (CodeViewSymbolType) reader.ReadUInt16(); + var dataReader = reader.ForkRelative(reader.RelativeOffset, (uint) (length - 2)); + reader.Offset += (ulong) (length - 2); + + return type switch + { + CodeViewSymbolType.Pub32 => new SerializedPublicSymbol(dataReader), + CodeViewSymbolType.Udt => new SerializedUserDefinedTypeSymbol(context, dataReader), + CodeViewSymbolType.Constant => new SerializedConstantSymbol(context, dataReader), + _ => new UnknownSymbol(type, dataReader.ReadToEnd()) + }; + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbolType.cs b/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbolType.cs new file mode 100644 index 000000000..11bb6e3ad --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/CodeViewSymbolType.cs @@ -0,0 +1,865 @@ +namespace AsmResolver.Symbols.Pdb.Records; + +// Reference: microsoft-pdb/include/cvinfo.h + +/// +/// Provides members defining all symbol record types that can be stored in a PDB symbol stream. +/// +public enum CodeViewSymbolType : ushort +{ +#pragma warning disable CS1591 + /// + /// Indicates the symbol is a Compile flags symbol + /// + Compile = 0x0001, + + /// + /// Indicates the symbol is a Register variable + /// + Register16T = 0x0002, + + /// + /// Indicates the symbol is a constant symbol + /// + Constant16T = 0x0003, + + /// + /// Indicates the symbol is a User defined type + /// + Udt16T = 0x0004, + + /// + /// Indicates the symbol is a Start Search + /// + SSearch = 0x0005, + + /// + /// Indicates the symbol is a Block, procedure, "with" or thunk end + /// + End = 0x0006, + + /// + /// Indicates the symbol is a Reserve symbol space in $$Symbols table + /// + Skip = 0x0007, + + /// + /// Indicates the symbol is a Reserved symbol for CV internal use + /// + CVReserve = 0x0008, + + /// + /// Indicates the symbol is a path to object file name + /// + ObjnameSt = 0x0009, + + /// + /// Indicates the symbol is a end of argument/return list + /// + EndArg = 0x000a, + + /// + /// Indicates the symbol is a special UDT for cobol that does not symbol pack + /// + CobolUdt16T = 0x000b, + + /// + /// Indicates the symbol is a multiple register variable + /// + ManyReg16T = 0x000c, + + /// + /// Indicates the symbol is a return description symbol + /// + Return = 0x000d, + + /// + /// Indicates the symbol is a description of this pointer on entry + /// + EntryThis = 0x000e, + + /// + /// Indicates the symbol is a BP-relative + /// + BPRel16 = 0x0100, + + /// + /// Indicates the symbol is a Module-local symbol + /// + Ldata16 = 0x0101, + + /// + /// Indicates the symbol is a Global data symbol + /// + GData16 = 0x0102, + + /// + /// Indicates the symbol is a a public symbol + /// + Pub16 = 0x0103, + + /// + /// Indicates the symbol is a Local procedure start + /// + LProc16 = 0x0104, + + /// + /// Indicates the symbol is a Global procedure start + /// + GProc16 = 0x0105, + + /// + /// Indicates the symbol is a Thunk Start + /// + Thunk16 = 0x0106, + + /// + /// Indicates the symbol is a block start + /// + Block16 = 0x0107, + + /// + /// Indicates the symbol is a with start + /// + With16 = 0x0108, + + /// + /// Indicates the symbol is a code label + /// + Label16 = 0x0109, + + /// + /// Indicates the symbol is a change execution model + /// + CexModel16 = 0x010a, + + /// + /// Indicates the symbol is a address of virtual function table + /// + VFTable16 = 0x010b, + + /// + /// Indicates the symbol is a register relative address + /// + RegRel16 = 0x010c, + + /// + /// Indicates the symbol is a BP-relative + /// + BBRel3216T = 0x0200, + + /// + /// Indicates the symbol is a Module-local symbol + /// + LData3216T = 0x0201, + + /// + /// Indicates the symbol is a Global data symbol + /// + GData3216T = 0x0202, + + /// + /// Indicates the symbol is a a public symbol (CV internal reserved) + /// + Pub3216T = 0x0203, + + /// + /// Indicates the symbol is a Local procedure start + /// + LProc3216T = 0x0204, + + /// + /// Indicates the symbol is a Global procedure start + /// + GProc3216T = 0x0205, + + /// + /// Indicates the symbol is a Thunk Start + /// + Thunk32St = 0x0206, + + /// + /// Indicates the symbol is a block start + /// + Block32St = 0x0207, + + /// + /// Indicates the symbol is a with start + /// + With32St = 0x0208, + + /// + /// Indicates the symbol is a code label + /// + Label32St = 0x0209, + + /// + /// Indicates the symbol is a change execution model + /// + CexModel32 = 0x020a, + + /// + /// Indicates the symbol is a address of virtual function table + /// + VFTable3216T = 0x020b, + + /// + /// Indicates the symbol is a register relative address + /// + RegRel3216T = 0x020c, + + /// + /// Indicates the symbol is a local thread storage + /// + LThread3216T = 0x020d, + + /// + /// Indicates the symbol is a global thread storage + /// + GThread3216T = 0x020e, + + /// + /// Indicates the symbol is a static link for MIPS EH implementation + /// + SLink32 = 0x020f, + + /// + /// Indicates the symbol is a Local procedure start + /// + LProcMip16T = 0x0300, + + /// + /// Indicates the symbol is a Global procedure start + /// + GProcMip16T = 0x0301, + + // if these ref symbols have names following then the names are in ST format + /// + /// Indicates the symbol is a Reference to a procedure + /// + ProcRefSt = 0x0400, + + /// + /// Indicates the symbol is a Reference to data + /// + DataRefSt = 0x0401, + + /// + /// Indicates the symbol is a Used for page alignment of symbols + /// + Align = 0x0402, + + /// + /// Indicates the symbol is a Local Reference to a procedure + /// + LProcRefSt = 0x0403, + + /// + /// Indicates the symbol is a OEM defined symbol + /// + Oem = 0x0404, + + // sym records with 32-bit types embedded instead of 16-bit + // all have 0x1000 bit set for easy identification + // only do the 32-bit target versions since we don't really + // care about 16-bit ones anymore. + // TI16_MAX = 0x1000, + + /// + /// Indicates the symbol is a Register variable + /// + RegisterSt = 0x1001, + + /// + /// Indicates the symbol is a constant symbol + /// + ConstantSt = 0x1002, + + /// + /// Indicates the symbol is a User defined type + /// + UdtSt = 0x1003, + + /// + /// Indicates the symbol is a special UDT for cobol that does not symbol pack + /// + CobolUdtSt = 0x1004, + + /// + /// Indicates the symbol is a multiple register variable + /// + ManyRegSt = 0x1005, + + /// + /// Indicates the symbol is a BP-relative + /// + BBRel32St = 0x1006, + + /// + /// Indicates the symbol is a Module-local symbol + /// + LData32St = 0x1007, + + /// + /// Indicates the symbol is a Global data symbol + /// + GData32St = 0x1008, + + /// + /// Indicates the symbol is a a public symbol (CV internal reserved) + /// + Pub32St = 0x1009, + + /// + /// Indicates the symbol is a Local procedure start + /// + LProc32St = 0x100a, + + /// + /// Indicates the symbol is a Global procedure start + /// + GProc32St = 0x100b, + + /// + /// Indicates the symbol is a address of virtual function table + /// + VFTable32 = 0x100c, + + /// + /// Indicates the symbol is a register relative address + /// + RegRel32St = 0x100d, + + /// + /// Indicates the symbol is a local thread storage + /// + LThread32St = 0x100e, + + /// + /// Indicates the symbol is a global thread storage + /// + GThread32St = 0x100f, + + /// + /// Indicates the symbol is a Local procedure start + /// + LProcMipSt = 0x1010, + + /// + /// Indicates the symbol is a Global procedure start + /// + GProcMipSt = 0x1011, + + /// + /// Indicates the symbol is a extra frame and proc information + /// + FrameProc = 0x1012, + + /// + /// Indicates the symbol is a extended compile flags and info + /// + Compile2St = 0x1013, + + /// + /// Indicates the symbol is a multiple register variable + /// + ManyReg2St = 0x1014, + + /// + /// Indicates the symbol is a Local procedure start (IA64) + /// + LProcIa64St = 0x1015, + + /// + /// Indicates the symbol is a Global procedure start (IA64) + /// + GProcIa64St = 0x1016, + + /// + /// Indicates the symbol is a local IL sym with field for local slot index + /// + LocalSlotSt = 0x1017, + + /// + /// Indicates the symbol is a local IL sym with field for parameter slot index + /// + ParamSlotSt = 0x1018, + + /// + /// Indicates the symbol is a Annotation string literals + /// + Annotation = 0x1019, + + /// + /// Indicates the symbol is a Global proc + /// + GManProcSt = 0x101a, + + /// + /// Indicates the symbol is a Local proc + /// + LManProcSt = 0x101b, + + /// + /// Reserved + /// + Reserved1 = 0x101c, + + /// + /// Reserved + /// + Reserved2 = 0x101d, + + /// + /// Reserved + /// + Reserved3 = 0x101e, + + /// + /// Reserved + /// + RESERVED4 = 0x101f, + + LManDataSt = 0x1020, + + GManDataSt = 0x1021, + + ManFrameRelSt = 0x1022, + + ManRegisterSt = 0x1023, + + ManSlotSt = 0x1024, + + ManManyRegSt = 0x1025, + + ManRegRelSt = 0x1026, + + ManManyReg2St = 0x1027, + + /// + /// Indicates the symbol is a Index for type referenced by name from metadata + /// + ManTypRef = 0x1028, + + /// + /// Indicates the symbol is a Using namespace + /// + UNamespaceSt = 0x1029, + + // Symbols w/ SZ name fields. All name fields contain utf8 encoded strings. + /// + /// Indicates the symbol is a starting point for SZ name symbols + /// + StMax = 0x1100, + + /// + /// Indicates the symbol is a path to object file name + /// + ObjName = 0x1101, + + /// + /// Indicates the symbol is a Thunk Start + /// + Thunk32 = 0x1102, + + /// + /// Indicates the symbol is a block start + /// + Block32 = 0x1103, + + /// + /// Indicates the symbol is a with start + /// + With32 = 0x1104, + + /// + /// Indicates the symbol is a code label + /// + Label32 = 0x1105, + + /// + /// Indicates the symbol is a Register variable + /// + Register = 0x1106, + + /// + /// Indicates the symbol is a constant symbol + /// + Constant = 0x1107, + + /// + /// Indicates the symbol is a User defined type + /// + Udt = 0x1108, + + /// + /// Indicates the symbol is a special UDT for cobol that does not symbol pack + /// + CobolUdt = 0x1109, + + /// + /// Indicates the symbol is a multiple register variable + /// + ManyReg = 0x110a, + + /// + /// Indicates the symbol is a BP-relative + /// + BBRel32 = 0x110b, + + /// + /// Indicates the symbol is a Module-local symbol + /// + LData32 = 0x110c, + + /// + /// Indicates the symbol is a Global data symbol + /// + GData32 = 0x110d, + + /// + /// Indicates the symbol is a a public symbol (CV internal reserved) + /// + Pub32 = 0x110e, + + /// + /// Indicates the symbol is a Local procedure start + /// + LProc32 = 0x110f, + + /// + /// Indicates the symbol is a Global procedure start + /// + GProc32 = 0x1110, + + /// + /// Indicates the symbol is a register relative address + /// + RegRel32 = 0x1111, + + /// + /// Indicates the symbol is a local thread storage + /// + LThread32 = 0x1112, + + /// + /// Indicates the symbol is a global thread storage + /// + GThread32 = 0x1113, + + /// + /// Indicates the symbol is a Local procedure start + /// + LProcMips = 0x1114, + + /// + /// Indicates the symbol is a Global procedure start + /// + GProcMips = 0x1115, + + /// + /// Indicates the symbol is a extended compile flags and info + /// + Compile2 = 0x1116, + + /// + /// Indicates the symbol is a multiple register variable + /// + ManyReg2 = 0x1117, + + /// + /// Indicates the symbol is a Local procedure start (IA64) + /// + LprocIa64 = 0x1118, + + /// + /// Indicates the symbol is a Global procedure start (IA64) + /// + GProcIa64 = 0x1119, + + /// + /// Indicates the symbol is a local IL sym with field for local slot index + /// + LocalSlot = 0x111a, + + /// + /// Indicates the symbol is a alias for LOCALSLOT + /// + Slot = LocalSlot, + + /// + /// Indicates the symbol is a local IL sym with field for parameter slot index + /// + ParamSlot = 0x111b, + + // symbols to support managed code debugging + LManData = 0x111c, + GManData = 0x111d, + ManFrameRel = 0x111e, + ManRegister = 0x111f, + ManSlot = 0x1120, + ManManyReg = 0x1121, + ManRegRel = 0x1122, + ManManyReg2 = 0x1123, + + /// + /// Indicates the symbol is a Using namespace + /// + UNamespace = 0x1124, + + // ref symbols with name fields + /// + /// Indicates the symbol is a Reference to a procedure + /// + ProcRef = 0x1125, + + /// + /// Indicates the symbol is a Reference to data + /// + DataRef = 0x1126, + + /// + /// Indicates the symbol is a Local Reference to a procedure + /// + LProcRef = 0x1127, + + /// + /// Indicates the symbol is a Reference to an ANNOTATION symbol + /// + AnnotationRef = 0x1128, + + /// + /// Indicates the symbol is a Reference to one of the many MANPROCSYM's + /// + TokenRef = 0x1129, + + // continuation of managed symbols + /// + /// Indicates the symbol is a Global proc + /// + GManProc = 0x112a, + + /// + /// Indicates the symbol is a Local proc + /// + LManProc = 0x112b, + + // short, light-weight thunks + /// + /// Indicates the symbol is a trampoline thunks + /// + Trampoline = 0x112c, + + /// + /// Indicates the symbol is a constants with metadata type info + /// + ManConstant = 0x112d, + + // native attributed local/parms + /// + /// Indicates the symbol is a relative to virtual frame ptr + /// + AttrFrameRel = 0x112e, + + /// + /// Indicates the symbol is a stored in a register + /// + AttrRegister = 0x112f, + + /// + /// Indicates the symbol is a relative to register (alternate frame ptr) + /// + AttrRegRel = 0x1130, + + /// + /// Indicates the symbol is a stored in >1 register + /// + AttrManyReg = 0x1131, + + // Separated code (from the compiler) support + SepCode = 0x1132, + + /// + /// Indicates the symbol is a defines a local symbol in optimized code + /// + Local2005 = 0x1133, + + /// + /// Indicates the symbol is a defines a single range of addresses in which symbol can be evaluated + /// + DefRange2005 = 0x1134, + + /// + /// Indicates the symbol is a defines ranges of addresses in which symbol can be evaluated + /// + DefRange22005 = 0x1135, + + /// + /// Indicates the symbol is a A COFF section in a PE executable + /// + Section = 0x1136, + + /// + /// Indicates the symbol is a A COFF group + /// + CoffGroup = 0x1137, + + /// + /// Indicates the symbol is a A export + /// + Export = 0x1138, + + /// + /// Indicates the symbol is a Indirect call site information + /// + CallSiteInfo = 0x1139, + + /// + /// Indicates the symbol is a Security cookie information + /// + FrameCookie = 0x113a, + + /// + /// Indicates the symbol is a Discarded by LINK /OPT:REF (experimental, see richards) + /// + Discarded = 0x113b, + + /// + /// Indicates the symbol is a Replacement for COMPILE2 + /// + Compile3 = 0x113c, + + /// + /// Indicates the symbol is a Environment block split off from COMPILE2 + /// + EnvBlock = 0x113d, + + /// + /// Indicates the symbol is a defines a local symbol in optimized code + /// + Local = 0x113e, + + /// + /// Indicates the symbol is a defines a single range of addresses in which symbol can be evaluated + /// + DefRange = 0x113f, + + /// + /// Indicates the symbol is a ranges for a subfield + /// + DefRangeSubField = 0x1140, + + /// + /// Indicates the symbol is a ranges for en-registered symbol + /// + DefRangeRegister = 0x1141, + + /// + /// Indicates the symbol is a range for stack symbol. + /// + DefRangeFramePointerRel = 0x1142, + + /// + /// Indicates the symbol is a ranges for en-registered field of symbol + /// + DefRangeSubFieldRegister = 0x1143, + + /// + /// Indicates the symbol is a range for stack symbol span valid full scope of function body, gap might apply. + /// + DefRangeFramePointerRelFullScope = 0x1144, + + /// + /// Indicates the symbol is a range for symbol address as register + offset. + /// + DefRangeRegisterRel = 0x1145, + + // PROC symbols that reference ID instead of type + LProc32Id = 0x1146, + GProc32Id = 0x1147, + LProcMipId = 0x1148, + GProcMipId = 0x1149, + LProcIa64Id = 0x114a, + GProcIa64Id = 0x114b, + + /// + /// Indicates the symbol is a build information. + /// + BuildInfo = 0x114c, + + /// + /// Indicates the symbol is a inlined function callsite. + /// + InlineSite = 0x114d, + InlineSiteEnd = 0x114e, + ProcIdEnd = 0x114f, + + DefRangeHlsl = 0x1150, + GDataHlsl = 0x1151, + LDataHlsl = 0x1152, + + FileStatic = 0x1153, + + /// + /// Indicates the symbol is a DPC groupshared variable + /// + LocalDpcGroupShared = 0x1154, + + /// + /// Indicates the symbol is a DPC local procedure start + /// + LProc32Dpc = 0x1155, + LProc32DpcId = 0x1156, + + /// + /// Indicates the symbol is a DPC pointer tag definition range + /// + DefRangeDpcPtrTag = 0x1157, + + /// + /// Indicates the symbol is a DPC pointer tag value to symbol record map + /// + DpcSymTagMap = 0x1158, + + ArmSwitchTable = 0x1159, + Callees = 0x115a, + Callers = 0x115b, + PogoData = 0x115c, + + /// + /// Indicates the symbol is a extended inline site information + /// + InlineSite2 = 0x115d, + + /// + /// Indicates the symbol is a heap allocation site + /// + HeapAllocSite = 0x115e, + + /// + /// Indicates the symbol is a only generated at link time + /// + ModTypeRef = 0x115f, + + /// + /// Indicates the symbol is a only generated at link time for mini PDB + /// + RefMiniPdb = 0x1160, + + /// + /// Indicates the symbol is a only generated at link time for mini PDB + /// + PdbMap = 0x1161, + + GDataHlsl32 = 0x1162, + LDataHlsl32 = 0x1163, + + GDataHlsl32Ex = 0x1164, + LDataHlsl32Ex = 0x1165, + + RecTypeMax, // one greater than last + RecTypeLast = RecTypeMax - 1, + RecTypePad = RecTypeMax + 0x100 // Used *only* to verify symbol record types so that current PDB code can potentially read + // future PDBs (assuming no format change, etc). +#pragma warning restore CS1591 +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/ConstantSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/ConstantSymbol.cs new file mode 100644 index 000000000..5bdc45c4c --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/ConstantSymbol.cs @@ -0,0 +1,85 @@ +using AsmResolver.Symbols.Pdb.Leaves; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents a single constant symbol. +/// +public class ConstantSymbol : CodeViewSymbol +{ + private readonly LazyVariable _name; + private readonly LazyVariable _type; + + /// + /// Initializes a named constant + /// + protected ConstantSymbol() + { + _name = new LazyVariable(GetName); + _type = new LazyVariable(GetConstantType); + } + + /// + /// Defines a new named constant. + /// + /// The name of the type. + /// The type. + /// The value to assign to the constant. + public ConstantSymbol(Utf8String name, CodeViewTypeRecord type, ushort value) + { + _name = new LazyVariable(name); + _type = new LazyVariable(type); + Value = value; + } + + /// + public override CodeViewSymbolType CodeViewSymbolType => CodeViewSymbolType.Constant; + + /// + /// Gets or sets the value type of the constant. + /// + public CodeViewTypeRecord Type + { + get => _type.Value; + set => _type.Value = value; + } + + /// + /// Gets or sets the numerical value assigned to the constant. + /// + public ushort Value + { + get; + set; + } + + /// + /// Gets or sets the name of the constant. + /// + public Utf8String Name + { + get => _name.Value; + set => _name.Value = value; + } + + /// + /// Obtains the name of the constant. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String GetName() => Utf8String.Empty; + + /// + /// Obtains the value type of the constant. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetConstantType() => null; + + /// + public override string ToString() => $"{CodeViewSymbolType}: {Type} {Name} = {Value}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/PublicSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/PublicSymbol.cs new file mode 100644 index 000000000..08515a42b --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/PublicSymbol.cs @@ -0,0 +1,123 @@ +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents a public symbol stored in a PDB symbol stream. +/// +public class PublicSymbol : CodeViewSymbol +{ + private readonly LazyVariable _name; + + /// + /// Initializes a new empty public symbol. + /// + protected PublicSymbol() + { + _name = new LazyVariable(GetName); + } + + /// + /// Creates a new public symbol. + /// + /// The segment index. + /// The offset within the segment the symbol starts at. + /// The name of the symbol. + /// The attributes associated to the symbol. + public PublicSymbol(ushort segment, uint offset, Utf8String name, PublicSymbolAttributes attributes) + { + Segment = segment; + Offset = offset; + _name = new LazyVariable(name); + Attributes = attributes; + } + + /// + public override CodeViewSymbolType CodeViewSymbolType => CodeViewSymbolType.Pub32; + + /// + /// Gets or sets the file segment index this symbol is located in. + /// + public ushort Segment + { + get; + set; + } + + /// + /// Gets or sets the offset within the file that this symbol is defined at. + /// + public uint Offset + { + get; + set; + } + + /// + /// Gets or sets attributes associated to the public symbol. + /// + public PublicSymbolAttributes Attributes + { + get; + set; + } + + /// + /// Gets or sets a value indicating whether the symbol is a code symbol. + /// + public bool IsCode + { + get => (Attributes & PublicSymbolAttributes.Code) != 0; + set => Attributes = (Attributes & ~PublicSymbolAttributes.Code) + | (value ? PublicSymbolAttributes.Code : 0); + } + + /// + /// Gets or sets a value indicating whether the symbol is a function symbol. + /// + public bool IsFunction + { + get => (Attributes & PublicSymbolAttributes.Function) != 0; + set => Attributes = (Attributes & ~PublicSymbolAttributes.Function) + | (value ? PublicSymbolAttributes.Function : 0); + } + + /// + /// Gets or sets a value indicating whether the symbol involves managed code. + /// + public bool IsManaged + { + get => (Attributes & PublicSymbolAttributes.Managed) != 0; + set => Attributes = (Attributes & ~PublicSymbolAttributes.Managed) + | (value ? PublicSymbolAttributes.Managed : 0); + } + + /// + /// Gets or sets a value indicating whether the symbol involves MSIL code. + /// + public bool IsMsil + { + get => (Attributes & PublicSymbolAttributes.Msil) != 0; + set => Attributes = (Attributes & ~PublicSymbolAttributes.Msil) + | (value ? PublicSymbolAttributes.Msil : 0); + } + + /// + /// Gets or sets the name of the symbol. + /// + public Utf8String Name + { + get => _name.Value; + set => _name.Value = value; + } + + /// + /// Obtains the name of the public symbol. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String GetName() => Utf8String.Empty; + + /// + public override string ToString() => $"{CodeViewSymbolType}: [{Segment:X4}:{Offset:X8}] {Name}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/PublicSymbolAttributes.cs b/src/AsmResolver.Symbols.Pdb/Records/PublicSymbolAttributes.cs new file mode 100644 index 000000000..a5e5824bd --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/PublicSymbolAttributes.cs @@ -0,0 +1,35 @@ +using System; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Provides members defining all flags that can be associated to a public symbol. +/// +[Flags] +public enum PublicSymbolAttributes : uint +{ + /// + /// Indicates no flags are assigned to the symbol. + /// + None = 0, + + /// + /// Indicates the symbol is a code symbol. + /// + Code = 0x00000001, + + /// + /// Indicates the symbol is a function. + /// + Function = 0x00000002, + + /// + /// Indicates the symbol involves managed code. + /// + Managed = 0x00000004, + + /// + /// Indicates the symbol involves MSIL code. + /// + Msil = 0x00000008, +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedConstantSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedConstantSymbol.cs new file mode 100644 index 000000000..3c3c31c53 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedConstantSymbol.cs @@ -0,0 +1,39 @@ +using AsmResolver.IO; +using AsmResolver.Symbols.Pdb.Leaves; + +namespace AsmResolver.Symbols.Pdb.Records.Serialized; + +/// +/// Represents a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedConstantSymbol : ConstantSymbol +{ + private readonly PdbReaderContext _context; + private readonly uint _typeIndex; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a constant symbol from the provided input stream. + /// + /// The reading context in which the symbol is situated in. + /// The input stream to read from. + public SerializedConstantSymbol(PdbReaderContext context, BinaryStreamReader reader) + { + _context = context; + _typeIndex = reader.ReadUInt32(); + Value = reader.ReadUInt16(); + _nameReader = reader; + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override CodeViewTypeRecord? GetConstantType() + { + return _context.ParentImage.TryGetLeafRecord(_typeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"Constant contains an invalid type index {_typeIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedPublicSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedPublicSymbol.cs new file mode 100644 index 000000000..f1a815036 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedPublicSymbol.cs @@ -0,0 +1,26 @@ +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Records.Serialized; + +/// +/// Represents a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedPublicSymbol : PublicSymbol +{ + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a public symbol from the provided input stream. + /// + /// The input stream to read from. + public SerializedPublicSymbol(BinaryStreamReader reader) + { + Attributes = (PublicSymbolAttributes) reader.ReadUInt32(); + Offset = reader.ReadUInt32(); + Segment = reader.ReadUInt16(); + _nameReader = reader; + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedUserDefinedTypeSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedUserDefinedTypeSymbol.cs new file mode 100644 index 000000000..fdcce9a7f --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/Serialized/SerializedUserDefinedTypeSymbol.cs @@ -0,0 +1,38 @@ +using AsmResolver.IO; +using AsmResolver.Symbols.Pdb.Leaves; + +namespace AsmResolver.Symbols.Pdb.Records.Serialized; + +/// +/// Represents a lazily initialized implementation of that is read from a PDB image. +/// +public class SerializedUserDefinedTypeSymbol : UserDefinedTypeSymbol +{ + private readonly PdbReaderContext _context; + private readonly uint _typeIndex; + private readonly BinaryStreamReader _nameReader; + + /// + /// Reads a user-defined type symbol from the provided input stream. + /// + /// The reading context in which the symbol is situated in. + /// The input stream to read from. + public SerializedUserDefinedTypeSymbol(PdbReaderContext context, BinaryStreamReader reader) + { + _context = context; + _typeIndex = reader.ReadUInt32(); + _nameReader = reader; + } + + /// + protected override Utf8String GetName() => _nameReader.Fork().ReadUtf8String(); + + /// + protected override CodeViewTypeRecord? GetSymbolType() + { + return _context.ParentImage.TryGetLeafRecord(_typeIndex, out var leaf) && leaf is CodeViewTypeRecord type + ? type + : _context.Parameters.ErrorListener.BadImageAndReturn( + $"User-defined type contains an invalid type index {_typeIndex:X8}."); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/UnknownSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/UnknownSymbol.cs new file mode 100644 index 000000000..983173876 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/UnknownSymbol.cs @@ -0,0 +1,35 @@ +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents a symbol record for which the format is unknown or unsupported. +/// +public class UnknownSymbol : CodeViewSymbol +{ + /// + /// Creates a new unknown symbol record. + /// + /// The type of symbol. + /// The raw data stored in the record. + public UnknownSymbol(CodeViewSymbolType codeViewSymbolType, byte[] data) + { + CodeViewSymbolType = codeViewSymbolType; + Data = data; + } + + /// + public override CodeViewSymbolType CodeViewSymbolType + { + get; + } + + /// + /// Gets the raw data stored in the record. + /// + public byte[] Data + { + get; + } + + /// + public override string ToString() => $"{CodeViewSymbolType.ToString()} ({Data.Length.ToString()} bytes)"; +} diff --git a/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs b/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs new file mode 100644 index 000000000..4e84a3aa3 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Records/UserDefinedTypeSymbol.cs @@ -0,0 +1,74 @@ +using AsmResolver.Symbols.Pdb.Leaves; + +namespace AsmResolver.Symbols.Pdb.Records; + +/// +/// Represents a user-defined type symbol in a PDB symbol stream, mapping a symbol to a type in the TPI stream. +/// +public class UserDefinedTypeSymbol : CodeViewSymbol +{ + private readonly LazyVariable _name; + private readonly LazyVariable _type; + + /// + /// Initializes a new empty user-defined type symbol. + /// + protected UserDefinedTypeSymbol() + { + _name = new LazyVariable(GetName); + _type = new LazyVariable(GetSymbolType); + } + + /// + /// Defines a new user-defined type. + /// + /// The name of the type. + /// The type. + public UserDefinedTypeSymbol(Utf8String name, CodeViewTypeRecord type) + { + _name = new LazyVariable(name); + _type = new LazyVariable(type); + } + + /// + public override CodeViewSymbolType CodeViewSymbolType => CodeViewSymbolType.Udt; + + /// + /// Gets or sets the name of the type. + /// + public Utf8String Name + { + get => _name.Value; + set => _name.Value = value; + } + + /// + /// Gets or sets the index associated to the type. + /// + public CodeViewTypeRecord Type + { + get => _type.Value; + set => _type.Value = value; + } + + /// + /// Obtains the new name of the type. + /// + /// The name. + /// + /// This method is called upon initialization of the property. + /// + protected virtual Utf8String GetName() => Utf8String.Empty; + + /// + /// Obtains the type that is referenced by this symbol. + /// + /// The type. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CodeViewTypeRecord? GetSymbolType() => null; + + /// + public override string ToString() => $"{CodeViewSymbolType}: {Type} {Name}"; +} diff --git a/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs b/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs new file mode 100644 index 000000000..32bc7dcd6 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/SerializedPdbImage.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using AsmResolver.Symbols.Pdb.Leaves; +using AsmResolver.Symbols.Pdb.Metadata.Dbi; +using AsmResolver.Symbols.Pdb.Metadata.Info; +using AsmResolver.Symbols.Pdb.Metadata.Tpi; +using AsmResolver.Symbols.Pdb.Msf; +using AsmResolver.Symbols.Pdb.Records; + +namespace AsmResolver.Symbols.Pdb; + +/// +/// Provides an implementation for a PDB image that is read from an input MSF file. +/// +public class SerializedPdbImage : PdbImage +{ + private const int MinimalRequiredStreamCount = 5; + private readonly MsfFile _file; + private CodeViewLeaf?[]? _leaves; + + /// + /// Interprets a PDB image from the provided MSF file. + /// + /// The MSF file to read from. + /// The parameters to use while reading the PDB image. + public SerializedPdbImage(MsfFile file, PdbReaderParameters readerParameters) + { + _file = file; + + if (file.Streams.Count < MinimalRequiredStreamCount) + throw new BadImageFormatException("MSF does not contain the minimal required amount of streams."); + + InfoStream = InfoStream.FromReader(file.Streams[InfoStream.StreamIndex].CreateReader()); + DbiStream = DbiStream.FromReader(file.Streams[DbiStream.StreamIndex].CreateReader()); + TpiStream = TpiStream.FromReader(file.Streams[TpiStream.StreamIndex].CreateReader()); + + ReaderContext = new PdbReaderContext(this, readerParameters); + } + + internal PdbReaderContext ReaderContext + { + get; + } + + internal InfoStream InfoStream + { + get; + } + + internal DbiStream DbiStream + { + get; + } + + internal TpiStream TpiStream + { + get; + } + + [MemberNotNull(nameof(_leaves))] + private void EnsureLeavesInitialized() + { + if (_leaves is null) + { + Interlocked.CompareExchange(ref _leaves, + new CodeViewLeaf?[TpiStream.TypeIndexEnd - TpiStream.TypeIndexBegin], null); + } + } + + /// + public override bool TryGetLeafRecord(uint typeIndex, [NotNullWhen(true)] out CodeViewLeaf? leaf) + { + if (typeIndex < TpiStream.TypeIndexBegin) + return base.TryGetLeafRecord(typeIndex, out leaf); + + EnsureLeavesInitialized(); + + if (typeIndex >= TpiStream.TypeIndexBegin && typeIndex < TpiStream.TypeIndexEnd) + { + leaf = _leaves[typeIndex - TpiStream.TypeIndexBegin]; + if (leaf is null && TpiStream.TryGetLeafRecordReader(typeIndex, out var reader)) + { + leaf = CodeViewLeaf.FromReader(ReaderContext, typeIndex, ref reader); + Interlocked.CompareExchange(ref _leaves[typeIndex - TpiStream.TypeIndexBegin], leaf, null); + } + + leaf = _leaves[typeIndex - TpiStream.TypeIndexBegin]; + return leaf is not null; + } + + leaf = null; + return false; + } + + /// + protected override IList GetSymbols() + { + var result = new List(); + + int index = DbiStream.SymbolRecordStreamIndex; + if (index >= _file.Streams.Count) + return result; + + var reader = _file.Streams[DbiStream.SymbolRecordStreamIndex].CreateReader(); + while (reader.CanRead(sizeof(ushort) * 2)) + result.Add(CodeViewSymbol.FromReader(ReaderContext, ref reader)); + + return result; + } +} diff --git a/src/AsmResolver/AsmResolver.csproj b/src/AsmResolver/AsmResolver.csproj index 07dbf2281..b7685bc8a 100644 --- a/src/AsmResolver/AsmResolver.csproj +++ b/src/AsmResolver/AsmResolver.csproj @@ -22,7 +22,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/AsmResolver/Collections/LazyList.cs b/src/AsmResolver/Collections/LazyList.cs index 90b584a75..6a88b6193 100644 --- a/src/AsmResolver/Collections/LazyList.cs +++ b/src/AsmResolver/Collections/LazyList.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using System.Threading; namespace AsmResolver.Collections { @@ -12,7 +13,25 @@ namespace AsmResolver.Collections [DebuggerDisplay("Count = {" + nameof(Count) + "}")] public abstract class LazyList : IList { - private readonly List _items = new(); + private readonly ReaderWriterLockSlim _lock = new(LockRecursionPolicy.NoRecursion); + private readonly List _items; + + /// + /// Creates a new, empty, uninitialized list. + /// + public LazyList() + { + _items = new List(); + } + + /// + /// Creates a new, empty, uninitialized list. + /// + /// The initial number of elements the list can store. + public LazyList(int capacity) + { + _items = new List(capacity); + } /// public TItem this[int index] @@ -24,8 +43,11 @@ public abstract class LazyList : IList } set { - EnsureIsInitialized(); - OnSetItem(index, value); + lock (_items) + { + EnsureIsInitialized(); + OnSetItem(index, value); + } } } @@ -39,6 +61,15 @@ public virtual int Count } } + /// + /// Gets or sets the total number of elements the list can contain before it has to resize its internal buffer. + /// + public int Capacity + { + get => _items.Capacity; + set => _items.Capacity = value; + } + /// public bool IsReadOnly => false; @@ -61,16 +92,30 @@ protected bool IsInitialized /// protected abstract void Initialize(); + /// + /// Performs any final adjustments to the collection after all initial items were added to the underlying list. + /// + /// + /// Upon calling this method, the has already been set to true, but the + /// initialization lock has not been released yet. This means that any element in the list is guaranteed + /// to be still in its initial state. It is therefore safe to access elements, as well as adding or removing + /// items from . + /// + protected virtual void PostInitialize() + { + } + private void EnsureIsInitialized() { if (!IsInitialized) { - lock (this) + lock (_items) { if (!IsInitialized) { Initialize(); IsInitialized = true; + PostInitialize(); } } } @@ -88,8 +133,11 @@ private void EnsureIsInitialized() /// public void Clear() { - OnClearItems(); - IsInitialized = true; + lock (_items) + { + OnClearItems(); + IsInitialized = true; + } } /// @@ -109,11 +157,15 @@ public void CopyTo(TItem[] array, int arrayIndex) /// public bool Remove(TItem item) { - EnsureIsInitialized(); - int index = Items.IndexOf(item); - if (index == -1) - return false; - OnRemoveItem(index); + lock (_items) + { + EnsureIsInitialized(); + int index = Items.IndexOf(item); + if (index == -1) + return false; + OnRemoveItem(index); + } + return true; } @@ -127,8 +179,11 @@ public int IndexOf(TItem item) /// public void Insert(int index, TItem item) { - EnsureIsInitialized(); - OnInsertItem(index, item); + lock (_items) + { + EnsureIsInitialized(); + OnInsertItem(index, item); + } } /// @@ -138,15 +193,21 @@ public void Insert(int index, TItem item) /// The items to insert. private void InsertRange(int index, IEnumerable items) { - EnsureIsInitialized(); - OnInsertRange(index, items); + lock (_items) + { + EnsureIsInitialized(); + OnInsertRange(index, items); + } } /// public void RemoveAt(int index) { - EnsureIsInitialized(); - OnRemoveItem(index); + lock (_items) + { + EnsureIsInitialized(); + OnRemoveItem(index); + } } /// diff --git a/src/AsmResolver/Collections/OneToManyRelation.cs b/src/AsmResolver/Collections/OneToManyRelation.cs index 570768c7e..ce3e55a4a 100644 --- a/src/AsmResolver/Collections/OneToManyRelation.cs +++ b/src/AsmResolver/Collections/OneToManyRelation.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections; using System.Collections.Generic; namespace AsmResolver.Collections @@ -11,8 +13,27 @@ public sealed class OneToManyRelation where TKey : notnull where TValue : notnull { - private readonly Dictionary> _memberLists = new(); - private readonly Dictionary _memberOwners = new(); + private readonly Dictionary _memberLists; + private readonly Dictionary _memberOwners; + + /// + /// Creates a new, empty one-to-many relation mapping. + /// + public OneToManyRelation() + { + _memberLists = new Dictionary(); + _memberOwners = new Dictionary(); + } + + /// + /// Creates a new, empty one-to-many relation mapping. + /// + /// The initial number of elements the relation can store. + public OneToManyRelation(int capacity) + { + _memberLists = new Dictionary(capacity); + _memberOwners = new Dictionary(capacity); + } /// /// Registers a relation between two objects. @@ -25,7 +46,7 @@ public bool Add(TKey key, TValue value) { if (!_memberOwners.ContainsKey(value)) { - GetValues(key).Add(value); + GetValues(key).Items.Add(value); _memberOwners.Add(value, key); return true; } @@ -38,11 +59,11 @@ public bool Add(TKey key, TValue value) /// /// The key. /// The values. - public ICollection GetValues(TKey key) + public ValueSet GetValues(TKey key) { if (!_memberLists.TryGetValue(key, out var items)) { - items = new List(); + items = new ValueSet(); _memberLists.Add(key, items); } @@ -60,5 +81,76 @@ public ICollection GetValues(TKey key) ? key : default; } + + /// + /// Represents a collection of values assigned to a single key in a one-to-many relation. + /// + public class ValueSet : ICollection + { + internal List Items + { + get; + } = new(); + + /// + public int Count => Items.Count; + + /// + public bool IsReadOnly => true; + + /// + public void Add(TValue item) => throw new NotSupportedException(); + + /// + public void Clear() => throw new NotSupportedException(); + + /// + public bool Contains(TValue item) => Items.Contains(item); + + /// + public void CopyTo(TValue[] array, int arrayIndex) => Items.CopyTo(array, arrayIndex); + + /// + public bool Remove(TValue item) => throw new NotSupportedException(); + + /// + /// Gets an enumerator that enumerates all values in the collection. + /// + public Enumerator GetEnumerator() => new(Items.GetEnumerator()); + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable) Items).GetEnumerator(); + + /// + /// Represents an enumerator that enumerates all items in a value collection. + /// + public struct Enumerator : IEnumerator + { + private List.Enumerator _enumerator; + + internal Enumerator(List.Enumerator enumerator) + { + _enumerator = enumerator; + } + + /// + public TValue Current => _enumerator.Current!; + + /// + object IEnumerator.Current => ((IEnumerator) _enumerator).Current!; + + /// + public bool MoveNext() => _enumerator.MoveNext(); + + /// + public void Reset() => throw new NotSupportedException(); + + /// + public void Dispose() => _enumerator.Dispose(); + } + } } } diff --git a/src/AsmResolver/Collections/OneToOneRelation.cs b/src/AsmResolver/Collections/OneToOneRelation.cs index 881915925..dbefc7bcc 100644 --- a/src/AsmResolver/Collections/OneToOneRelation.cs +++ b/src/AsmResolver/Collections/OneToOneRelation.cs @@ -11,8 +11,27 @@ public sealed class OneToOneRelation where TKey : notnull where TValue : notnull { - private readonly Dictionary _keyToValue = new(); - private readonly Dictionary _valueToKey = new(); + private readonly Dictionary _keyToValue; + private readonly Dictionary _valueToKey; + + /// + /// Creates a new, empty one-to-one mapping. + /// + public OneToOneRelation() + { + _keyToValue = new Dictionary(); + _valueToKey = new Dictionary(); + } + + /// + /// Creates a new, empty one-to-one mapping. + /// + /// The initial number of elements the relation can store. + public OneToOneRelation(int capacity) + { + _keyToValue = new Dictionary(capacity); + _valueToKey = new Dictionary(capacity); + } /// /// Registers a one-to-one relation between two objects. diff --git a/src/AsmResolver/Collections/OwnedCollection.cs b/src/AsmResolver/Collections/OwnedCollection.cs index 82a7d6b33..6facee7e8 100644 --- a/src/AsmResolver/Collections/OwnedCollection.cs +++ b/src/AsmResolver/Collections/OwnedCollection.cs @@ -26,6 +26,17 @@ public OwnedCollection(TOwner owner) Owner = owner ?? throw new ArgumentNullException(nameof(owner)); } + /// + /// Creates a new empty collection that is owned by an object. + /// + /// The owner of the collection. + /// The initial number of elements the collection can store. + public OwnedCollection(TOwner owner, int capacity) + : base(capacity) + { + Owner = owner ?? throw new ArgumentNullException(nameof(owner)); + } + /// /// Gets the owner of the collection. /// diff --git a/src/AsmResolver/Collections/RefList.cs b/src/AsmResolver/Collections/RefList.cs index f454fe6ff..4fa091140 100644 --- a/src/AsmResolver/Collections/RefList.cs +++ b/src/AsmResolver/Collections/RefList.cs @@ -46,9 +46,23 @@ public RefList(int capacity) public int Count => _count; /// - /// Gets the capacity of the underlying array. + /// Gets or sets the total number of elements that the underlying array can store. /// - public int Capacity => _items.Length; + public int Capacity + { + get => _items.Length; + set + { + if (value == _items.Length) + return; + + if (value < _count) + throw new ArgumentException("Capacity must be equal or larger than the current number of elements in the list."); + + EnsureCapacity(value); + IncrementVersion(); + } + } /// /// Gets a number indicating the current version of the list. @@ -127,7 +141,7 @@ public RefList(int capacity) /// The element. public void Add(in T item) { - EnsureEnoughCapacity(_count + 1); + EnsureCapacity(_count + 1); _items[_count] = item; _count++; IncrementVersion(); @@ -194,7 +208,7 @@ public bool Remove(in T item) /// The element to insert. public void Insert(int index, in T item) { - EnsureEnoughCapacity(_count + 1); + EnsureCapacity(_count + 1); if (index < _count) Array.Copy(_items, index, _items, index + 1, _count - index); @@ -246,7 +260,7 @@ private void AssertIsValidIndex(int index) throw new IndexOutOfRangeException(); } - private void EnsureEnoughCapacity(int requiredCount) + private void EnsureCapacity(int requiredCount) { if (_items.Length >= requiredCount) return; diff --git a/src/AsmResolver/DataSourceSegment.cs b/src/AsmResolver/DataSourceSegment.cs index a8df578f7..8b237906e 100644 --- a/src/AsmResolver/DataSourceSegment.cs +++ b/src/AsmResolver/DataSourceSegment.cs @@ -25,11 +25,11 @@ public DataSourceSegment(IDataSource dataSource, ulong offset, uint rva, uint si } /// - public override void UpdateOffsets(ulong newOffset, uint newRva) + public override void UpdateOffsets(in RelocationParameters parameters) { - base.UpdateOffsets(newOffset, newRva); + base.UpdateOffsets(parameters); - long displacement = (long) newOffset - (long) _originalOffset; + long displacement = (long) parameters.Offset - (long) _originalOffset; _displacedDataSource = displacement != 0 ? new DisplacedDataSource(_dataSource, displacement) : null; diff --git a/src/AsmResolver/IO/BinaryStreamReader.cs b/src/AsmResolver/IO/BinaryStreamReader.cs index 74088ce4b..9a924cda9 100644 --- a/src/AsmResolver/IO/BinaryStreamReader.cs +++ b/src/AsmResolver/IO/BinaryStreamReader.cs @@ -13,6 +13,24 @@ public struct BinaryStreamReader [ThreadStatic] private static int[]? _buffer; + /// + /// Creates a new binary stream reader on the provided data source. + /// + /// The data to read from. + public BinaryStreamReader(byte[] data) + : this(new ByteArrayDataSource(data)) + { + } + + /// + /// Creates a new binary stream reader on the provided data source. + /// + /// The object to get the data from. + public BinaryStreamReader(IDataSource dataSource) + : this(dataSource, 0, 0, (uint) dataSource.Length) + { + } + /// /// Creates a new binary stream reader on the provided data source. /// @@ -134,6 +152,14 @@ private void AssertCanRead(uint count) throw new EndOfStreamException(); } + /// + /// Peeks a single byte from the input stream. + /// + /// The read byte, or -1 if no byte could be read. + public int PeekByte() => CanRead(1) + ? DataSource[Offset] + : -1; + /// /// Reads a single byte from the input stream, and advances the current offset by one. /// @@ -328,37 +354,68 @@ public byte[] ReadToEnd() /// /// The delimeter byte to stop at. /// The read bytes, including the delimeter if it was found. - public byte[] ReadBytesUntil(byte delimeter) + public byte[] ReadBytesUntil(byte delimeter) => ReadBytesUntil(delimeter, true); + + /// + /// Reads bytes from the input stream until the provided delimeter byte is reached. + /// + /// The delimeter byte to stop at. + /// + /// true if the final delimeter should be included in the return value, false otherwise. + /// + /// The read bytes. + /// + /// This function always consumes the delimeter from the input stream if it is present, regardless of the value + /// of . + /// + public byte[] ReadBytesUntil(byte delimeter, bool includeDelimeterInReturn) { var lookahead = Fork(); - while (lookahead.RelativeOffset < lookahead.Length) - { - byte b = lookahead.ReadByte(); - if (b == delimeter) - break; - } + bool hasConsumedDelimeter = lookahead.AdvanceUntil(delimeter, includeDelimeterInReturn); byte[] buffer = new byte[lookahead.RelativeOffset - RelativeOffset]; ReadBytes(buffer, 0, buffer.Length); + + if (hasConsumedDelimeter) + ReadByte(); + return buffer; } /// - /// Reads a null-terminated ASCII string from the input stream. + /// Advances the reader until the provided delimeter byte is reached. /// - /// The read ASCII string, excluding the null terminator. - public string ReadAsciiString() + /// The delimeter byte to stop at. + /// + /// true if the final delimeter should be consumed if available, false otherwise. + /// + /// true if the delimeter byte was found and consumed, false otherwise. + public bool AdvanceUntil(byte delimeter, bool consumeDelimeter) { - byte[] data = ReadBytesUntil(0); - int length = data.Length; + while (RelativeOffset < Length) + { + byte b = ReadByte(); + if (b == delimeter) + { + if (!consumeDelimeter) + { + RelativeOffset--; + return true; + } - // Exclude trailing 0 byte. - if (data[data.Length - 1] == 0) - length--; + return false; + } + } - return Encoding.ASCII.GetString(data, 0, length); + return false; } + /// + /// Reads a null-terminated ASCII string from the input stream. + /// + /// The read ASCII string, excluding the null terminator. + public string ReadAsciiString() => Encoding.ASCII.GetString(ReadBytesUntil(0, false)); + /// /// Reads a zero-terminated Unicode string from the stream. /// @@ -378,6 +435,18 @@ public string ReadUnicodeString() return builder.ToString(); } + /// + /// Reads a null-terminated UTF-8 string from the input stream. + /// + /// The read UTF-8 string, excluding the null terminator. + public Utf8String ReadUtf8String() + { + byte[] data = ReadBytesUntil(0, false); + return data.Length != 0 + ? new Utf8String(data) + : Utf8String.Empty; + } + /// /// Reads either a 32-bit or a 64-bit number from the input stream. /// diff --git a/src/AsmResolver/IO/BinaryStreamWriter.cs b/src/AsmResolver/IO/BinaryStreamWriter.cs index 039d043c9..e55c7ed25 100644 --- a/src/AsmResolver/IO/BinaryStreamWriter.cs +++ b/src/AsmResolver/IO/BinaryStreamWriter.cs @@ -8,107 +8,113 @@ namespace AsmResolver.IO /// public class BinaryStreamWriter : IBinaryStreamWriter { - private readonly Stream _stream; - /// /// Creates a new binary stream writer using the provided output stream. /// /// The stream to write to. public BinaryStreamWriter(Stream stream) { - _stream = stream ?? throw new ArgumentNullException(nameof(stream)); + BaseStream = stream ?? throw new ArgumentNullException(nameof(stream)); + } + + /// + /// Gets the stream this writer writes to. + /// + public Stream BaseStream + { + get; } /// public ulong Offset { - get => (uint) _stream.Position; + get => (uint) BaseStream.Position; set { // Check if position actually changed before actually setting. If we don't do this, this can cause // performance issues on some systems. See https://github.com/Washi1337/AsmResolver/issues/232 - if (_stream.Position != (long) value) - _stream.Position = (long) value; + if (BaseStream.Position != (long) value) + BaseStream.Position = (long) value; } } /// - public uint Length => (uint) _stream.Length; + public uint Length => (uint) BaseStream.Length; /// public void WriteBytes(byte[] buffer, int startIndex, int count) { - _stream.Write(buffer, startIndex, count); + BaseStream.Write(buffer, startIndex, count); } /// public void WriteByte(byte value) { - _stream.WriteByte(value); + BaseStream.WriteByte(value); } /// public void WriteUInt16(ushort value) { - _stream.WriteByte((byte) (value & 0xFF)); - _stream.WriteByte((byte) ((value >> 8) & 0xFF)); + BaseStream.WriteByte((byte) (value & 0xFF)); + BaseStream.WriteByte((byte) ((value >> 8) & 0xFF)); } /// public void WriteUInt32(uint value) { - _stream.WriteByte((byte) (value & 0xFF)); - _stream.WriteByte((byte) ((value >> 8) & 0xFF)); - _stream.WriteByte((byte) ((value >> 16) & 0xFF)); - _stream.WriteByte((byte) ((value >> 24) & 0xFF)); + BaseStream.WriteByte((byte) (value & 0xFF)); + BaseStream.WriteByte((byte) ((value >> 8) & 0xFF)); + BaseStream.WriteByte((byte) ((value >> 16) & 0xFF)); + BaseStream.WriteByte((byte) ((value >> 24) & 0xFF)); } /// public void WriteUInt64(ulong value) { - _stream.WriteByte((byte) (value & 0xFF)); - _stream.WriteByte((byte) ((value >> 8) & 0xFF)); - _stream.WriteByte((byte) ((value >> 16) & 0xFF)); - _stream.WriteByte((byte) ((value >> 24) & 0xFF)); - _stream.WriteByte((byte) ((value >> 32) & 0xFF)); - _stream.WriteByte((byte) ((value >> 40) & 0xFF)); - _stream.WriteByte((byte) ((value >> 48) & 0xFF)); - _stream.WriteByte((byte) ((value >> 56) & 0xFF)); + BaseStream.WriteByte((byte) (value & 0xFF)); + BaseStream.WriteByte((byte) ((value >> 8) & 0xFF)); + BaseStream.WriteByte((byte) ((value >> 16) & 0xFF)); + BaseStream.WriteByte((byte) ((value >> 24) & 0xFF)); + BaseStream.WriteByte((byte) ((value >> 32) & 0xFF)); + BaseStream.WriteByte((byte) ((value >> 40) & 0xFF)); + BaseStream.WriteByte((byte) ((value >> 48) & 0xFF)); + BaseStream.WriteByte((byte) ((value >> 56) & 0xFF)); } /// public void WriteSByte(sbyte value) { - _stream.WriteByte(unchecked((byte) value)); + BaseStream.WriteByte(unchecked((byte) value)); } /// public void WriteInt16(short value) { - _stream.WriteByte((byte) (value & 0xFF)); - _stream.WriteByte((byte) ((value >> 8) & 0xFF)); + BaseStream.WriteByte((byte) (value & 0xFF)); + BaseStream.WriteByte((byte) ((value >> 8) & 0xFF)); } /// public void WriteInt32(int value) { - _stream.WriteByte((byte) (value & 0xFF)); - _stream.WriteByte((byte) ((value >> 8) & 0xFF)); - _stream.WriteByte((byte) ((value >> 16) & 0xFF)); - _stream.WriteByte((byte) ((value >> 24) & 0xFF)); + BaseStream.WriteByte((byte) (value & 0xFF)); + BaseStream.WriteByte((byte) ((value >> 8) & 0xFF)); + BaseStream.WriteByte((byte) ((value >> 16) & 0xFF)); + BaseStream.WriteByte((byte) ((value >> 24) & 0xFF)); } /// public void WriteInt64(long value) { - _stream.WriteByte((byte) (value & 0xFF)); - _stream.WriteByte((byte) ((value >> 8) & 0xFF)); - _stream.WriteByte((byte) ((value >> 16) & 0xFF)); - _stream.WriteByte((byte) ((value >> 24) & 0xFF)); - _stream.WriteByte((byte) ((value >> 32) & 0xFF)); - _stream.WriteByte((byte) ((value >> 40) & 0xFF)); - _stream.WriteByte((byte) ((value >> 48) & 0xFF)); - _stream.WriteByte((byte) ((value >> 56) & 0xFF)); + BaseStream.WriteByte((byte) (value & 0xFF)); + BaseStream.WriteByte((byte) ((value >> 8) & 0xFF)); + BaseStream.WriteByte((byte) ((value >> 16) & 0xFF)); + BaseStream.WriteByte((byte) ((value >> 24) & 0xFF)); + BaseStream.WriteByte((byte) ((value >> 32) & 0xFF)); + BaseStream.WriteByte((byte) ((value >> 40) & 0xFF)); + BaseStream.WriteByte((byte) ((value >> 48) & 0xFF)); + BaseStream.WriteByte((byte) ((value >> 56) & 0xFF)); } /// diff --git a/src/AsmResolver/IO/ByteArrayDataSource.cs b/src/AsmResolver/IO/ByteArrayDataSource.cs index 7520fc8b3..c9dc3d8dc 100644 --- a/src/AsmResolver/IO/ByteArrayDataSource.cs +++ b/src/AsmResolver/IO/ByteArrayDataSource.cs @@ -46,8 +46,8 @@ public ulong BaseAddress /// /// The byte array to read. /// The stream reader. - public static BinaryStreamReader CreateReader(byte[] data) => - new(new ByteArrayDataSource(data), 0, 0, (uint) data.Length); + [Obsolete("Use the constructor of AsmResolver.IO.BinaryStreamReader instead.")] + public static BinaryStreamReader CreateReader(byte[] data) => new(data); /// public bool IsValidAddress(ulong address) => address - BaseAddress < (ulong) _data.Length; diff --git a/src/AsmResolver/IO/DataSourceSlice.cs b/src/AsmResolver/IO/DataSourceSlice.cs new file mode 100644 index 000000000..f107316a0 --- /dev/null +++ b/src/AsmResolver/IO/DataSourceSlice.cs @@ -0,0 +1,68 @@ +using System; + +namespace AsmResolver.IO +{ + /// + /// Represents a data source that only exposes a part (slice) of another data source. + /// + public class DataSourceSlice : IDataSource + { + private readonly IDataSource _source; + + /// + /// Creates a new data source slice. + /// + /// The original data source to slice. + /// The starting address. + /// The number of bytes. + /// + /// Occurs when and/or result in addresses that are invalid + /// in the original data source. + /// + public DataSourceSlice(IDataSource source, ulong start, ulong length) + { + _source = source; + + if (!source.IsValidAddress(start)) + throw new ArgumentOutOfRangeException(nameof(start)); + if (length > 0 && !source.IsValidAddress(start + length - 1)) + throw new ArgumentOutOfRangeException(nameof(length)); + + BaseAddress = start; + Length = length; + } + + /// + public ulong BaseAddress + { + get; + } + + /// + public ulong Length + { + get; + } + + /// + public byte this[ulong address] + { + get + { + if (!IsValidAddress(address)) + throw new IndexOutOfRangeException(); + return _source[address]; + } + } + + /// + public bool IsValidAddress(ulong address) => address >= BaseAddress && address - BaseAddress < Length; + + /// + public int ReadBytes(ulong address, byte[] buffer, int index, int count) + { + int maxCount = Math.Max(0, (int) (Length - (address - BaseAddress))); + return _source.ReadBytes(address, buffer, index, Math.Min(maxCount, count)); + } + } +} diff --git a/src/AsmResolver/IO/MemoryStreamWriterPool.cs b/src/AsmResolver/IO/MemoryStreamWriterPool.cs new file mode 100644 index 000000000..14a3ef691 --- /dev/null +++ b/src/AsmResolver/IO/MemoryStreamWriterPool.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Concurrent; +using System.IO; + +namespace AsmResolver.IO +{ + /// + /// Provides a pool of reusable instances of that are meant to be used for + /// constructing byte arrays. + /// + /// + /// This class is thread-safe. All threads are allowed to rent and return writers from this pool simultaneously. + /// + public class MemoryStreamWriterPool + { + private readonly ConcurrentQueue _writers = new(); + + /// + /// Rents a single binary stream writer. + /// + /// The writer. + public RentedWriter Rent() + { + if (!_writers.TryDequeue(out var writer)) + writer = new BinaryStreamWriter(new MemoryStream()); + + writer.BaseStream.SetLength(0); + return new RentedWriter(this, writer); + } + + private void Return(BinaryStreamWriter writer) => _writers.Enqueue(writer); + + /// + /// Represents a single instance of a that is rented by a writer pool. + /// + public ref struct RentedWriter + { + private bool _isDisposed = false; + private readonly BinaryStreamWriter _writer; + + internal RentedWriter(MemoryStreamWriterPool pool, BinaryStreamWriter writer) + { + Pool = pool; + _writer = writer; + } + + /// + /// Gets the pool the writer was rented from. + /// + public MemoryStreamWriterPool Pool + { + get; + } + + /// + /// Gets the writer instance. + /// + public BinaryStreamWriter Writer + { + get + { + if (_isDisposed) + throw new ObjectDisposedException(nameof(Writer)); + return _writer; + } + } + + /// + /// Gets the data that was written to the temporary stream. + /// + /// + public byte[] GetData() => ((MemoryStream) Writer.BaseStream).ToArray(); + + /// + /// Returns the stream writer to the pool. + /// + public void Dispose() + { + if (_isDisposed) + return; + + Pool.Return(Writer); + _isDisposed = true; + } + } + } +} diff --git a/src/AsmResolver/IOffsetProvider.cs b/src/AsmResolver/IOffsetProvider.cs index 185b9c85e..5f83e16e9 100644 --- a/src/AsmResolver/IOffsetProvider.cs +++ b/src/AsmResolver/IOffsetProvider.cs @@ -20,20 +20,6 @@ uint Rva { get; } - - /// - /// Determines whether this structure can be relocated to another offset or virtual address. - /// - bool CanUpdateOffsets - { - get; - } - - /// - /// Assigns a new file and virtual offset to the segment and all its sub-components. - /// - /// The new file offset. - /// The new virtual offset. - void UpdateOffsets(ulong newOffset, uint newRva); } + } diff --git a/src/AsmResolver/ISegment.cs b/src/AsmResolver/ISegment.cs index f24ea98c1..dce4aec3c 100644 --- a/src/AsmResolver/ISegment.cs +++ b/src/AsmResolver/ISegment.cs @@ -9,12 +9,26 @@ namespace AsmResolver /// public interface ISegment : IOffsetProvider, IWritable { + /// + /// Determines whether this structure can be relocated to another offset or virtual address. + /// + bool CanUpdateOffsets + { + get; + } + /// /// Computes the number of bytes the segment will contain when it is mapped into memory. /// /// The number of bytes. uint GetVirtualSize(); + /// + /// Assigns a new file and virtual offset to the segment and all its sub-components. + /// + /// The parameters containing the new offset information for the segment. + void UpdateOffsets(in RelocationParameters parameters); + } public static partial class Extensions diff --git a/src/AsmResolver/ISegmentReferenceResolver.cs b/src/AsmResolver/ISegmentReferenceFactory.cs similarity index 86% rename from src/AsmResolver/ISegmentReferenceResolver.cs rename to src/AsmResolver/ISegmentReferenceFactory.cs index 7f9db42e9..7fff84851 100644 --- a/src/AsmResolver/ISegmentReferenceResolver.cs +++ b/src/AsmResolver/ISegmentReferenceFactory.cs @@ -1,15 +1,15 @@ -namespace AsmResolver -{ - /// - /// Provides members for resolving virtual addresses to a segment in a binary file. - /// - public interface ISegmentReferenceResolver - { - /// - /// Resolves the provided virtual address to a segment reference. - /// - /// The virtual address of the segment. - /// The reference to the segment. - ISegmentReference GetReferenceToRva(uint rva); - } -} \ No newline at end of file +namespace AsmResolver +{ + /// + /// Provides members for resolving virtual addresses to a segment in a binary file. + /// + public interface ISegmentReferenceFactory + { + /// + /// Resolves the provided virtual address to a segment reference. + /// + /// The virtual address of the segment. + /// The reference to the segment. + ISegmentReference GetReferenceToRva(uint rva); + } +} diff --git a/src/AsmResolver/LazyVariable.cs b/src/AsmResolver/LazyVariable.cs index 32d0627c6..c0f4775d7 100644 --- a/src/AsmResolver/LazyVariable.cs +++ b/src/AsmResolver/LazyVariable.cs @@ -7,11 +7,14 @@ namespace AsmResolver /// Represents a variable that can be lazily initialized and/or assigned a new value. /// /// The type of the values that the variable stores. + /// + /// For performance reasons, this class locks on itself for thread synchronization. Therefore, consumers + /// should not lock instances of this class as a lock object to avoid dead-locks. + /// public class LazyVariable { private T? _value; private readonly Func? _getValue; - private readonly object _lockObject = new(); /// /// Creates a new lazy variable and initialize it with a constant. @@ -55,7 +58,7 @@ public T Value } set { - lock (_lockObject) + lock (this) { _value = value; IsInitialized = true; @@ -65,7 +68,7 @@ public T Value private void InitializeValue() { - lock (_lockObject) + lock (this) { if (!IsInitialized) { diff --git a/src/AsmResolver/RelativeReference.cs b/src/AsmResolver/RelativeReference.cs index cc31132fd..24fa44250 100644 --- a/src/AsmResolver/RelativeReference.cs +++ b/src/AsmResolver/RelativeReference.cs @@ -41,13 +41,6 @@ public int Additive /// public uint Rva => (uint) (Base.Rva + Additive); - /// - public bool CanUpdateOffsets => Base.CanUpdateOffsets; - - /// - public void UpdateOffsets(ulong newOffset, uint newRva) => - Base.UpdateOffsets( newOffset - (ulong) Additive, (uint) (newRva - Additive)); - /// public bool CanRead => Base is ISegmentReference reference && reference.CanRead; diff --git a/src/AsmResolver/RelocationParameters.cs b/src/AsmResolver/RelocationParameters.cs new file mode 100644 index 000000000..3ef4f5ad0 --- /dev/null +++ b/src/AsmResolver/RelocationParameters.cs @@ -0,0 +1,130 @@ +namespace AsmResolver +{ + /// + /// Provides parameters for relocating a segment to a new offset-rva pair. + /// + public struct RelocationParameters + { + /// + /// Creates new relocation parameters. + /// + /// The new offset of the segment. + /// The new virtual address of the segment, relative to the image base. + public RelocationParameters(ulong offset, uint rva) + : this(0, offset, rva, true) + { + } + + /// + /// Creates new relocation parameters. + /// + /// The base address of the image the segment is located in. + /// The new offset of the segment. + /// The new virtual address of the segment, relative to the image base. + /// true if the image is targeting 32-bit images, false for 64-bit images. + public RelocationParameters(ulong imageBase, ulong offset, uint rva, bool is32Bit) + { + ImageBase = imageBase; + Offset = offset; + Rva = rva; + Is32Bit = is32Bit; + } + + /// + /// Gets the image base that is assumed when relocating the segment. + /// + public ulong ImageBase + { + get; + } + + /// + /// Gets the new physical offset of the segment. + /// + public ulong Offset + { + get; + set; + } + + /// + /// Gets the new virtual address of the segment, relative to the image base. + /// + public uint Rva + { + get; + set; + } + + /// + /// Gets a value indicating whether the image is targeting 32-bit machines. + /// + public bool Is32Bit + { + get; + } + + /// + /// Gets a value indicating whether the image is targeting 64-bit machines. + /// + public bool Is64Bit => !Is32Bit; + + /// + /// Copies the current relocation parameters, and assigns a new offset and relative virtual address. + /// + /// The new offset. + /// The new relative virtual address. + public RelocationParameters WithOffsetRva(ulong offset, uint rva) + { + return new RelocationParameters(ImageBase, offset, rva, Is32Bit); + } + + /// + /// Aligns the current offset and virtual address to the nearest multiple of the provided alignment. + /// + /// The alignment. + public void Align(uint alignment) + { + Offset = Offset.Align(alignment); + Rva = Rva.Align(alignment); + } + + /// + /// Advances the current offset and virtual address by the provided byte count. + /// + /// The number of bytes to advance with. + /// The new relocation parameters. + public readonly RelocationParameters WithAdvance(uint count) + { + return new RelocationParameters(ImageBase, Offset + count, Rva + count, Is32Bit); + } + + /// + /// Advances the current offset and virtual address by the provided byte count. + /// + /// The number of bytes to advance with. + /// The new relocation parameters. + public void Advance(uint count) + { + Offset += count; + Rva += count; + } + + /// + /// Advances the current offset and virtual address by the provided byte count. + /// + /// The number of bytes to advance the physical offset with. + /// The number of bytes to advance the virtual address with. + public void Advance(uint physicalCount, uint virtualCount) + { + Offset += physicalCount; + Rva += virtualCount; + } + + /// + public override string ToString() + { + return $"{nameof(ImageBase)}: {ImageBase:X8}, {nameof(Offset)}: {Offset:X8}, {nameof(Rva)}: {Rva:X8}"; + } + } +} diff --git a/src/AsmResolver/SegmentBase.cs b/src/AsmResolver/SegmentBase.cs index 6013990ef..264e1925a 100644 --- a/src/AsmResolver/SegmentBase.cs +++ b/src/AsmResolver/SegmentBase.cs @@ -25,10 +25,10 @@ public uint Rva public bool CanUpdateOffsets => true; /// - public virtual void UpdateOffsets(ulong newOffset, uint newRva) + public virtual void UpdateOffsets(in RelocationParameters parameters) { - Offset = newOffset; - Rva = newRva; + Offset = parameters.Offset; + Rva = parameters.Rva; } /// diff --git a/src/AsmResolver/SegmentBuilder.cs b/src/AsmResolver/SegmentBuilder.cs index 734d917d5..1bfd17eb3 100644 --- a/src/AsmResolver/SegmentBuilder.cs +++ b/src/AsmResolver/SegmentBuilder.cs @@ -55,31 +55,25 @@ public void Add(ISegment segment, uint alignment) } /// - public void UpdateOffsets(ulong newOffset, uint newRva) + public void UpdateOffsets(in RelocationParameters parameters) { - Offset = newOffset; - Rva = newRva; - _physicalSize = 0; - _virtualSize = 0; + Offset = parameters.Offset; + Rva = parameters.Rva; + var current = parameters; foreach (var item in _items) { - uint physicalPadding = (uint) (newOffset.Align(item.Alignment) - newOffset); - uint virtualPadding = newRva.Align(item.Alignment) - newRva; - - newOffset += physicalPadding; - newRva += virtualPadding; - - item.Segment.UpdateOffsets(newOffset, newRva); + current.Align(item.Alignment); + item.Segment.UpdateOffsets(current); uint physicalSize = item.Segment.GetPhysicalSize(); uint virtualSize = item.Segment.GetVirtualSize(); - newOffset += physicalSize; - newRva += virtualSize; - _physicalSize += physicalPadding + physicalSize; - _virtualSize += virtualPadding + virtualSize; + current.Advance(physicalSize, virtualSize); } + + _physicalSize = (uint) (current.Offset - parameters.Offset); + _virtualSize = current.Rva - parameters.Rva; } /// diff --git a/src/AsmResolver/SegmentReference.cs b/src/AsmResolver/SegmentReference.cs index cb9aa1e0d..ecda7b3e2 100644 --- a/src/AsmResolver/SegmentReference.cs +++ b/src/AsmResolver/SegmentReference.cs @@ -32,9 +32,6 @@ public SegmentReference(ISegment? segment) /// public uint Rva => Segment?.Rva ?? 0; - /// - public bool CanUpdateOffsets => Segment?.CanUpdateOffsets ?? false; - /// public bool IsBounded => true; @@ -50,9 +47,6 @@ public SegmentReference(ISegment? segment) get; } - /// - public void UpdateOffsets(ulong newOffset, uint newRva) => Segment?.UpdateOffsets(newOffset, newRva); - /// public BinaryStreamReader CreateReader() { diff --git a/src/AsmResolver/VirtualAddress.cs b/src/AsmResolver/VirtualAddress.cs new file mode 100644 index 000000000..7975ba7a6 --- /dev/null +++ b/src/AsmResolver/VirtualAddress.cs @@ -0,0 +1,37 @@ +using System; +using AsmResolver.IO; + +namespace AsmResolver +{ + /// + /// Represents a (relative) virtual address in a file. + /// + public sealed class VirtualAddress : ISegmentReference + { + /// + /// Wraps a relative virtual address into a object. + /// + /// + public VirtualAddress(uint rva) + { + Rva = rva; + } + + ulong IOffsetProvider.Offset => Rva; + + /// + public uint Rva + { + get; + } + + /// + public bool CanRead => false; + + bool ISegmentReference.IsBounded => false; + + BinaryStreamReader ISegmentReference.CreateReader() => throw new InvalidOperationException(); + + ISegment? ISegmentReference.GetSegment() => throw new InvalidOperationException(); + } +} diff --git a/src/AsmResolver/VirtualAddressFactory.cs b/src/AsmResolver/VirtualAddressFactory.cs new file mode 100644 index 000000000..b5af70799 --- /dev/null +++ b/src/AsmResolver/VirtualAddressFactory.cs @@ -0,0 +1,19 @@ +namespace AsmResolver +{ + /// + /// Provides an implementation of a reference factory that constructs objects. + /// + public class VirtualAddressFactory : ISegmentReferenceFactory + { + /// + /// Gets the default instance of this factory. + /// + public static VirtualAddressFactory Instance + { + get; + } = new(); + + /// + public ISegmentReference GetReferenceToRva(uint rva) => new VirtualAddress(rva); + } +} diff --git a/src/AsmResolver/VirtualSegment.cs b/src/AsmResolver/VirtualSegment.cs index 5b31c2e39..dd2b08022 100644 --- a/src/AsmResolver/VirtualSegment.cs +++ b/src/AsmResolver/VirtualSegment.cs @@ -44,15 +44,7 @@ public uint VirtualSize public ulong Offset => PhysicalContents?.Offset ?? 0; /// - public uint Rva - { - get => _rva; - set - { - _rva = value; - PhysicalContents?.UpdateOffsets(Offset, value); - } - } + public uint Rva => _rva; /// public bool CanUpdateOffsets => PhysicalContents?.CanUpdateOffsets ?? false; @@ -64,10 +56,10 @@ public uint Rva public bool IsReadable => PhysicalContents is IReadableSegment; /// - public void UpdateOffsets(ulong newOffset, uint newRva) + public void UpdateOffsets(in RelocationParameters parameters) { - _rva = newRva; - PhysicalContents?.UpdateOffsets(newOffset, newRva); + _rva = parameters.Rva; + PhysicalContents?.UpdateOffsets(parameters); } /// diff --git a/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj b/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj index 5931a82d2..bd140f317 100644 --- a/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj +++ b/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj @@ -6,7 +6,7 @@ - + diff --git a/test/AsmResolver.Benchmarks/ModuleReadWriteBenchmark.cs b/test/AsmResolver.Benchmarks/ModuleReadWriteBenchmark.cs index 206b2cb80..cd4924fe3 100644 --- a/test/AsmResolver.Benchmarks/ModuleReadWriteBenchmark.cs +++ b/test/AsmResolver.Benchmarks/ModuleReadWriteBenchmark.cs @@ -1,6 +1,8 @@ using System; using System.IO; +using System.Linq; using AsmResolver.DotNet; +using AsmResolver.IO; using BenchmarkDotNet.Attributes; using static AsmResolver.Benchmarks.Properties.Resources; @@ -12,15 +14,22 @@ public class ModuleReadWriteBenchmark private static readonly byte[] HelloWorldApp = HelloWorld; private static readonly byte[] CrackMeApp = Test; private static readonly byte[] ManyMethods = Utilities.DecompressDeflate(HelloWorld_ManyMethods); - private static readonly byte[] CoreLib; + private static readonly IInputFile SystemPrivateCoreLib; + private static readonly IInputFile SystemRuntime; + private static readonly IInputFile SystemPrivateXml; private readonly MemoryStream _outputStream = new(); static ModuleReadWriteBenchmark() { - var resolver = new DotNetCoreAssemblyResolver(new Version(3, 1, 0)); - string path = resolver.Resolve(KnownCorLibs.SystemPrivateCoreLib_v4_0_0_0)!.ManifestModule!.FilePath; - CoreLib = File.ReadAllBytes(path); + string runtimePath = DotNetCorePathProvider.Default + .GetRuntimePathCandidates("Microsoft.NETCore.App", new Version(3, 1, 0)) + .FirstOrDefault() ?? throw new InvalidOperationException(".NET Core 3.1 is not installed."); + + var fs = new ByteArrayFileService(); + SystemPrivateCoreLib = fs.OpenFile(Path.Combine(runtimePath, "System.Private.CoreLib.dll")); + SystemRuntime = fs.OpenFile(Path.Combine(runtimePath, "System.Runtime.dll")); + SystemPrivateXml = fs.OpenFile(Path.Combine(runtimePath, "System.Private.Xml.dll")); } [Benchmark] @@ -63,9 +72,23 @@ public void ManyMethods_ReadWrite() } [Benchmark] - public void CoreLib_ReadWrite() + public void SystemPrivateCoreLib_ReadWrite() + { + var module = ModuleDefinition.FromFile(SystemPrivateCoreLib); + module.Write(_outputStream); + } + + [Benchmark] + public void SystemRuntimeLib_ReadWrite() + { + var module = ModuleDefinition.FromFile(SystemRuntime); + module.Write(_outputStream); + } + + [Benchmark] + public void SystemPrivateXml_ReadWrite() { - var module = ModuleDefinition.FromBytes(CoreLib); + var module = ModuleDefinition.FromFile(SystemPrivateXml); module.Write(_outputStream); } } diff --git a/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj b/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj new file mode 100644 index 000000000..456d762fe --- /dev/null +++ b/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj @@ -0,0 +1,26 @@ + + + + net6.0 + disable + + false + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + diff --git a/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs b/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs new file mode 100644 index 000000000..c65bba058 --- /dev/null +++ b/test/AsmResolver.DotNet.Dynamic.Tests/DynamicMethodDefinitionTest.cs @@ -0,0 +1,124 @@ +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using AsmResolver.DotNet.Code.Cil; +using AsmResolver.DotNet.Signatures; +using AsmResolver.DotNet.Signatures.Types; +using AsmResolver.DotNet.TestCases.Methods; +using AsmResolver.PE.DotNet.Cil; +using Xunit; +using MethodAttributes = AsmResolver.PE.DotNet.Metadata.Tables.Rows.MethodAttributes; + +namespace AsmResolver.DotNet.Dynamic.Tests +{ + public class DynamicMethodDefinitionTest + { + [Fact] + public void ReadDynamicMethod() + { + var module = ModuleDefinition.FromFile(typeof(TDynamicMethod).Assembly.Location); + var generatedDynamicMethod = TDynamicMethod.GenerateDynamicMethod(); + var dynamicMethodDef = new DynamicMethodDefinition(module, generatedDynamicMethod); + + Assert.NotNull(dynamicMethodDef); + Assert.NotEmpty(dynamicMethodDef.CilMethodBody!.Instructions); + Assert.Equal(new[] + { + CilCode.Ldarg_0, + CilCode.Stloc_0, + CilCode.Ldloc_0, + CilCode.Call, + CilCode.Ldarg_1, + CilCode.Ret + }, dynamicMethodDef.CilMethodBody.Instructions.Select(q => q.OpCode.Code)); + Assert.Equal(new TypeSignature[] + { + module.CorLibTypeFactory.String, + module.CorLibTypeFactory.Int32 + }, dynamicMethodDef.Parameters.Select(q => q.ParameterType)); + Assert.Equal(new TypeSignature[] + { + module.CorLibTypeFactory.String, + }, dynamicMethodDef.CilMethodBody.LocalVariables.Select(v => v.VariableType)); + } + + [Fact] + public void RtDynamicMethod() + { + var module = ModuleDefinition.FromFile(typeof(TDynamicMethod).Assembly.Location); + + var generatedDynamicMethod = TDynamicMethod.GenerateDynamicMethod(); + object rtDynamicMethod = generatedDynamicMethod + .GetType() + .GetField("m_dynMethod", (BindingFlags) (-1))? + .GetValue(generatedDynamicMethod); + var dynamicMethod = new DynamicMethodDefinition(module, rtDynamicMethod!); + + Assert.NotNull(dynamicMethod); + Assert.NotEmpty(dynamicMethod.CilMethodBody!.Instructions); + Assert.Equal(new[] + { + CilCode.Ldarg_0, + CilCode.Stloc_0, + CilCode.Ldloc_0, + CilCode.Call, + CilCode.Ldarg_1, + CilCode.Ret + }, dynamicMethod.CilMethodBody.Instructions.Select(q => q.OpCode.Code)); + Assert.Equal(new TypeSignature[] + { + module.CorLibTypeFactory.String, + module.CorLibTypeFactory.Int32 + }, dynamicMethod.Parameters.Select(q => q.ParameterType)); + Assert.Equal(new TypeSignature[] + { + module.CorLibTypeFactory.String, + }, dynamicMethod.CilMethodBody.LocalVariables.Select(v => v.VariableType)); + } + + [Fact] + public void ReadDynamicMethodInitializedByDynamicILInfo() + { + var method = new DynamicMethod("Test", typeof(void), Type.EmptyTypes); + var info = method.GetDynamicILInfo(); + info.SetLocalSignature(new byte[] { 0x7, 0x0 }); + info.SetCode(new byte[] {0x2a}, 1); + + var contextModule = ModuleDefinition.FromFile(typeof(DynamicMethodDefinitionTest).Assembly.Location); + var definition = new DynamicMethodDefinition(contextModule, method); + + Assert.NotNull(definition.CilMethodBody); + var instruction = Assert.Single(definition.CilMethodBody.Instructions); + Assert.Equal(CilOpCodes.Ret, instruction.OpCode); + } + + [Fact] + public void ImportNestedType() + { + // https://github.com/Washi1337/AsmResolver/issues/363 + + var method = new DynamicMethod("Test", typeof(void), Type.EmptyTypes); + var cil = method.GetILGenerator(); + cil.Emit(OpCodes.Call, typeof(NestedClass).GetMethod(nameof(NestedClass.TestMethod))!); + cil.Emit(OpCodes.Ret); + + var contextModule = ModuleDefinition.FromFile(typeof(DynamicMethodDefinitionTest).Assembly.Location); + var definition = new DynamicMethodDefinition(contextModule, method); + + Assert.NotNull(definition.CilMethodBody); + var reference = Assert.IsAssignableFrom(definition.CilMethodBody.Instructions[0].Operand); + var declaringType = reference.DeclaringType; + Assert.NotNull(declaringType); + Assert.Equal(nameof(NestedClass), declaringType.Name); + Assert.NotNull(declaringType.DeclaringType); + Assert.Equal(nameof(DynamicMethodDefinitionTest), declaringType.DeclaringType.Name); + } + + internal static class NestedClass + { + public static void TestMethod() => Console.WriteLine("TestMethod"); + } + } +} diff --git a/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj b/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj index 5a428afc5..830749a8d 100644 --- a/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj +++ b/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj @@ -9,8 +9,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/AsmResolver.DotNet.Tests/AssemblyDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/AssemblyDefinitionTest.cs index 0059ec8a3..120fa3635 100644 --- a/test/AsmResolver.DotNet.Tests/AssemblyDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Tests/AssemblyDefinitionTest.cs @@ -13,7 +13,7 @@ private static AssemblyDefinition Rebuild(AssemblyDefinition assembly) { using var stream = new MemoryStream(); assembly.ManifestModule.Write(stream); - return AssemblyDefinition.FromReader(ByteArrayDataSource.CreateReader(stream.ToArray())); + return AssemblyDefinition.FromReader(new BinaryStreamReader(stream.ToArray())); } [Fact] diff --git a/test/AsmResolver.DotNet.Tests/AssemblyResolverTest.cs b/test/AsmResolver.DotNet.Tests/AssemblyResolverTest.cs index 540ac2f8c..8673fb4df 100644 --- a/test/AsmResolver.DotNet.Tests/AssemblyResolverTest.cs +++ b/test/AsmResolver.DotNet.Tests/AssemblyResolverTest.cs @@ -175,7 +175,7 @@ public void PreferResolveFromGac32If32BitAssembly(bool legacy) module.IsBit32Preferred = true; module.IsBit32Required = true; module.MachineType = MachineType.I386; - module.PEKind = OptionalHeaderMagic.Pe32; + module.PEKind = OptionalHeaderMagic.PE32; var resolved = module.CorLibTypeFactory.CorLibScope.GetAssembly()!.Resolve(); Assert.NotNull(resolved); @@ -196,7 +196,7 @@ public void PreferResolveFromGac64If64BitAssembly(bool legacy) module.IsBit32Preferred = false; module.IsBit32Required = false; module.MachineType = MachineType.Amd64; - module.PEKind = OptionalHeaderMagic.Pe32Plus; + module.PEKind = OptionalHeaderMagic.PE32Plus; var resolved = module.CorLibTypeFactory.CorLibScope.GetAssembly()!.Resolve(); Assert.NotNull(resolved); diff --git a/test/AsmResolver.DotNet.Tests/Builder/CilMethodBodySerializerTest.cs b/test/AsmResolver.DotNet.Tests/Builder/CilMethodBodySerializerTest.cs index 3dbf54fcf..92ea0f77b 100644 --- a/test/AsmResolver.DotNet.Tests/Builder/CilMethodBodySerializerTest.cs +++ b/test/AsmResolver.DotNet.Tests/Builder/CilMethodBodySerializerTest.cs @@ -35,7 +35,7 @@ public void ComputeMaxStackOnBuildOverride(bool computeMaxStack, bool? computeMa }; module.GetOrCreateModuleType().Methods.Add(main); - module.ManagedEntrypoint = main; + module.ManagedEntryPoint = main; var builder = new ManagedPEImageBuilder(new DotNetDirectoryFactory { @@ -48,7 +48,7 @@ public void ComputeMaxStackOnBuildOverride(bool computeMaxStack, bool? computeMa var newImage = builder.CreateImage(module).ConstructedImage; var newModule = ModuleDefinition.FromImage(newImage); - Assert.Equal(expectedMaxStack, newModule.ManagedEntrypointMethod.CilMethodBody.MaxStack); + Assert.Equal(expectedMaxStack, newModule.ManagedEntryPointMethod.CilMethodBody.MaxStack); } } } \ No newline at end of file diff --git a/test/AsmResolver.DotNet.Tests/Builder/ManagedPEImageBuilderTest.cs b/test/AsmResolver.DotNet.Tests/Builder/ManagedPEImageBuilderTest.cs index 583238a58..44daafcce 100644 --- a/test/AsmResolver.DotNet.Tests/Builder/ManagedPEImageBuilderTest.cs +++ b/test/AsmResolver.DotNet.Tests/Builder/ManagedPEImageBuilderTest.cs @@ -1,5 +1,6 @@ using System.IO; using System.Linq; +using AsmResolver.DotNet.Builder; using AsmResolver.PE; using Xunit; @@ -42,5 +43,41 @@ public void ExecutableImportDirectoryShouldContainMsCoreeCorDllMain() relocation.Location.CanRead && relocation.Location.CreateReader().ReadUInt32() == image.ImageBase + symbol.AddressTableEntry!.Rva); } + + [Fact] + public void ConstructPEImageFromNewModuleWithNoPreservation() + { + var module = new ModuleDefinition("Module"); + var result = module.ToPEImage(); + var newModule = ModuleDefinition.FromImage(result); + Assert.Equal(module.Name, newModule.Name); + } + + [Fact] + public void ConstructPEImageFromNewModuleWithPreservation() + { + var module = new ModuleDefinition("Module"); + var result = module.ToPEImage(new ManagedPEImageBuilder(MetadataBuilderFlags.PreserveAll)); + var newModule = ModuleDefinition.FromImage(result); + Assert.Equal(module.Name, newModule.Name); + } + + [Fact] + public void ConstructPEImageFromExistingModuleWithNoPreservation() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + var result = module.ToPEImage(); + var newModule = ModuleDefinition.FromImage(result); + Assert.Equal(module.Name, newModule.Name); + } + + [Fact] + public void ConstructPEImageFromExistingModuleWithPreservation() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + var result = module.ToPEImage(new ManagedPEImageBuilder(MetadataBuilderFlags.PreserveAll)); + var newModule = ModuleDefinition.FromImage(result); + Assert.Equal(module.Name, newModule.Name); + } } } diff --git a/test/AsmResolver.DotNet.Tests/Builder/TokenMappingTest.cs b/test/AsmResolver.DotNet.Tests/Builder/TokenMappingTest.cs index af428c6a4..514950b96 100644 --- a/test/AsmResolver.DotNet.Tests/Builder/TokenMappingTest.cs +++ b/test/AsmResolver.DotNet.Tests/Builder/TokenMappingTest.cs @@ -74,7 +74,7 @@ public void NewMethodDefinition() module.GetOrCreateModuleType().Methods.Add(method); // Get existing main method. - var main = module.ManagedEntrypointMethod; + var main = module.ManagedEntryPointMethod; // Rebuild. var builder = new ManagedPEImageBuilder(); @@ -134,7 +134,7 @@ public void NewMemberReference() var reference = importer.ImportMethod(typeof(MemoryStream).GetConstructor(Type.EmptyTypes)); // Ensure method reference is added to the module by referencing it in main. - var instructions = module.ManagedEntrypointMethod.CilMethodBody.Instructions; + var instructions = module.ManagedEntryPointMethod.CilMethodBody.Instructions; instructions.Insert(0, CilOpCodes.Newobj, reference); instructions.Insert(1, CilOpCodes.Pop); @@ -162,7 +162,7 @@ public void NewTypeSpecification() var specification = importer.ImportType(typeof(List)); // Ensure method reference is added to the module by referencing it in main. - var instructions = module.ManagedEntrypointMethod.CilMethodBody.Instructions; + var instructions = module.ManagedEntryPointMethod.CilMethodBody.Instructions; instructions.Insert(0, CilOpCodes.Ldtoken, specification); instructions.Insert(1, CilOpCodes.Pop); @@ -190,7 +190,7 @@ public void NewMethodSpecification() var reference = importer.ImportMethod(typeof(Array).GetMethod("Empty").MakeGenericMethod(typeof(object))); // Ensure method reference is added to the module by referencing it in main. - var instructions = module.ManagedEntrypointMethod.CilMethodBody.Instructions; + var instructions = module.ManagedEntryPointMethod.CilMethodBody.Instructions; instructions.Insert(0, CilOpCodes.Call, reference); instructions.Insert(1, CilOpCodes.Pop); @@ -219,7 +219,7 @@ public void NewStandaloneSignature() importer.ImportMethodSignature(MethodSignature.CreateStatic(module.CorLibTypeFactory.Void))); // Ensure reference is added to the module by referencing it in main. - var instructions = module.ManagedEntrypointMethod.CilMethodBody.Instructions; + var instructions = module.ManagedEntryPointMethod.CilMethodBody.Instructions; instructions.Insert(0, CilOpCodes.Ldnull); instructions.Insert(0, CilOpCodes.Calli, signature); diff --git a/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/AssemblyRefTokenPreservationTest.cs b/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/AssemblyRefTokenPreservationTest.cs index 5f9c79830..8c8d51208 100644 --- a/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/AssemblyRefTokenPreservationTest.cs +++ b/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/AssemblyRefTokenPreservationTest.cs @@ -30,7 +30,7 @@ public void PreserveAssemblyRefsWithTypeRefRemovedShouldAtLeastHaveOriginalAssem var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld_NetCore); var originalAssemblyRefs = GetMembers(module, TableIndex.AssemblyRef); - var instructions = module.ManagedEntrypointMethod!.CilMethodBody!.Instructions; + var instructions = module.ManagedEntryPointMethod!.CilMethodBody!.Instructions; instructions.Clear(); instructions.Add(CilOpCodes.Ret); @@ -49,7 +49,7 @@ public void PreserveAssemblyRefsWithExtraImportShouldAtLeastHaveOriginalAssembly var importer = new ReferenceImporter(module); var exists = importer.ImportMethod(typeof(File).GetMethod("Exists", new[] {typeof(string)})!); - var instructions = module.ManagedEntrypointMethod!.CilMethodBody!.Instructions; + var instructions = module.ManagedEntryPointMethod!.CilMethodBody!.Instructions; instructions.RemoveAt(instructions.Count - 1); instructions.Add(CilOpCodes.Ldstr, "file.txt"); instructions.Add(CilOpCodes.Call, exists); diff --git a/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/MemberRefTokenPreservationTest.cs b/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/MemberRefTokenPreservationTest.cs index 7d7665510..7eff9300e 100644 --- a/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/MemberRefTokenPreservationTest.cs +++ b/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/MemberRefTokenPreservationTest.cs @@ -30,7 +30,7 @@ public void PreserveMemberRefsWithTypeRefRemovedShouldAtLeastHaveOriginalMemberR var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld_NetCore); var originalMemberRefs = GetMembers(module, TableIndex.MemberRef); - var instructions = module.ManagedEntrypointMethod!.CilMethodBody!.Instructions; + var instructions = module.ManagedEntryPointMethod!.CilMethodBody!.Instructions; instructions.Clear(); instructions.Add(CilOpCodes.Ret); @@ -49,7 +49,7 @@ public void PreserveMemberRefsWithExtraImportShouldAtLeastHaveOriginalMemberRefs var importer = new ReferenceImporter(module); var readKey = importer.ImportMethod(typeof(Console).GetMethod("ReadKey", Type.EmptyTypes)!); - var instructions = module.ManagedEntrypointMethod!.CilMethodBody!.Instructions; + var instructions = module.ManagedEntryPointMethod!.CilMethodBody!.Instructions; instructions.RemoveAt(instructions.Count - 1); instructions.Add(CilOpCodes.Call, readKey); instructions.Add(CilOpCodes.Pop); diff --git a/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/TypeRefTokenPreservationTest.cs b/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/TypeRefTokenPreservationTest.cs index fa233b1e8..45eabbd0d 100644 --- a/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/TypeRefTokenPreservationTest.cs +++ b/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/TypeRefTokenPreservationTest.cs @@ -31,7 +31,7 @@ public void PreserveTypeRefsWithTypeRefRemovedShouldAtLeastHaveOriginalTypeRefs( var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld_NetCore); var originalTypeRefs = GetMembers(module, TableIndex.TypeRef); - var instructions = module.ManagedEntrypointMethod!.CilMethodBody!.Instructions; + var instructions = module.ManagedEntryPointMethod!.CilMethodBody!.Instructions; instructions.Clear(); instructions.Add(CilOpCodes.Ret); @@ -50,7 +50,7 @@ public void PreserveTypeRefsWithExtraImportShouldAtLeastHaveOriginalTypeRefs() var importer = new ReferenceImporter(module); var readKey = importer.ImportMethod(typeof(Console).GetMethod("ReadKey", Type.EmptyTypes)!); - var instructions = module.ManagedEntrypointMethod!.CilMethodBody!.Instructions; + var instructions = module.ManagedEntryPointMethod!.CilMethodBody!.Instructions; instructions.RemoveAt(instructions.Count - 1); instructions.Add(CilOpCodes.Call, readKey); instructions.Add(CilOpCodes.Pop); diff --git a/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs b/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs index 423509378..ffc339182 100644 --- a/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs +++ b/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs @@ -103,7 +103,7 @@ public void MarkFilesAsCompressed() using var stream = new MemoryStream(); ulong address = manifest.WriteManifest(new BinaryStreamWriter(stream), false); - var reader = ByteArrayDataSource.CreateReader(stream.ToArray()); + var reader = new BinaryStreamReader(stream.ToArray()); reader.Offset = address; var newManifest = BundleManifest.FromReader(reader); AssertBundlesAreEqual(manifest, newManifest); @@ -237,7 +237,7 @@ public void SameManifestContentsShouldResultInSameBundleID() private static string FindAppHostTemplate(string sdkVersion) { string sdkPath = Path.Combine(DotNetCorePathProvider.DefaultInstallationPath!, "sdk"); - string? sdkVersionPath = null; + string sdkVersionPath = null; foreach (string dir in Directory.GetDirectories(sdkPath)) { if (Path.GetFileName(dir).StartsWith(sdkVersion)) diff --git a/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs b/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs index fadcc2b51..050c8991a 100644 --- a/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs +++ b/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs @@ -10,6 +10,7 @@ using AsmResolver.DotNet.TestCases.Methods; using AsmResolver.DotNet.TestCases.Types; using AsmResolver.PE.DotNet.Cil; +using AsmResolver.Tests.Listeners; using AsmResolver.Tests.Runners; using Xunit; @@ -32,7 +33,7 @@ private static ModuleDefinition PrepareTempModule() assembly.Modules.Add(module); return module; } - + private static TypeDefinition CloneType(Type type, out TypeDefinition originalTypeDef) { var sourceModule = ModuleDefinition.FromFile(type.Module.Assembly.Location); @@ -54,7 +55,7 @@ private static TypeDefinition CloneType(Type type, out TypeDefinition originalTy return clonedType; } - + private static MethodDefinition CloneMethod(MethodBase methodBase, out MethodDefinition originalMethodDef) { var sourceModule = ModuleDefinition.FromFile(methodBase.Module.Assembly.Location); @@ -74,7 +75,7 @@ private static MethodDefinition CloneMethod(MethodBase methodBase, out MethodDef return clonedMethod; } - + private static FieldDefinition CloneIntializerField(FieldInfo field, out FieldDefinition originalFieldDef) { var sourceModule = ModuleDefinition.FromFile(field.Module.Assembly.Location); @@ -122,7 +123,7 @@ public void CloneHelloWorldProgramType() foreach (var type in result.ClonedTopLevelTypes) targetModule.TopLevelTypes.Add(type); - targetModule.ManagedEntrypointMethod = (MethodDefinition) result.ClonedMembers.First(m => m.Name == "Main"); + targetModule.ManagedEntryPointMethod = (MethodDefinition) result.ClonedMembers.First(m => m.Name == "Main"); _fixture .GetRunner() .RebuildAndRun(targetModule, "HelloWorld.exe", "Hello World!" + Environment.NewLine); @@ -138,7 +139,7 @@ public void CloneBranchInstructions() .Select(i => i.Operand) .OfType() .ToArray(); - + var newBranches = clonedMethod.CilMethodBody.Instructions .Where(i => i.IsBranch()) .Select(i => i.Operand) @@ -147,7 +148,7 @@ public void CloneBranchInstructions() // Assert offsets match. Assert.Equal( - originalBranches.Select(x => x.Offset), + originalBranches.Select(x => x.Offset), newBranches.Select(x => x.Offset)); // Assert all referenced instructions are instructions in the cloned method body. @@ -164,14 +165,14 @@ public void CloneSwitchInstruction() var originalBranches = (IEnumerable) method.CilMethodBody.Instructions .First(i => i.OpCode.Code == CilCode.Switch) .Operand; - + var newBranches = (IEnumerable) clonedMethod.CilMethodBody.Instructions .First(i => i.OpCode.Code == CilCode.Switch) .Operand; // Assert offsets match. Assert.Equal( - originalBranches.Select(x => x.Offset), + originalBranches.Select(x => x.Offset), newBranches.Select(x => x.Offset)); // Assert all referenced instructions are instructions in the cloned method body. @@ -185,7 +186,7 @@ public void CallToClonedMethods() { var sourceModule = ModuleDefinition.FromFile(typeof(Miscellaneous).Assembly.Location); var type = sourceModule.TopLevelTypes.First(t => t.Name == nameof(Miscellaneous)); - + var targetModule = PrepareTempModule(); var result = new MemberCloner(targetModule) @@ -211,7 +212,7 @@ public void ReferenceToNestedClass() { var sourceModule = ModuleDefinition.FromFile(typeof(Miscellaneous).Assembly.Location); var type = sourceModule.TopLevelTypes.First(t => t.Name == nameof(Miscellaneous)); - + var targetModule = PrepareTempModule(); var result = new MemberCloner(targetModule) @@ -221,7 +222,7 @@ public void ReferenceToNestedClass() var clonedMethod = (MethodDefinition) result.ClonedMembers .First(m => m.Name == nameof(Miscellaneous.NestedClassLocal)); - + var references = clonedMethod.CilMethodBody.Instructions .Where(i => i.OpCode.Code == CilCode.Callvirt || i.OpCode.Code == CilCode.Newobj) .Select(i => i.Operand) @@ -236,7 +237,7 @@ public void ReferenceToNestedClass() public void ReferencesToMethodSpecs() { // https://github.com/Washi1337/AsmResolver/issues/43 - + var clonedMethod = CloneMethod( typeof(GenericsTestClass).GetMethod(nameof(GenericsTestClass.MethodInstantiationFromExternalType)), out var method); @@ -252,7 +253,7 @@ public void ReferencesToMethodSpecs() Assert.Equal(originalSpec, newSpec, _signatureComparer); Assert.NotSame(originalSpec.Module, newSpec.Module); } - + [Fact] public void CloneImplMap() { @@ -267,7 +268,7 @@ public void CloneImplMap() public void CloneConstant() { var clonedMethod = CloneMethod(typeof(Miscellaneous).GetMethod(nameof(Miscellaneous.OptionalParameter)), out var method); - + Assert.NotEmpty(clonedMethod.ParameterDefinitions); Assert.NotNull(clonedMethod.ParameterDefinitions[0].Constant); Assert.Equal(clonedMethod.ParameterDefinitions[0].Constant.Type, method.ParameterDefinitions[0].Constant.Type); @@ -279,7 +280,7 @@ public void CloneFieldRva() { var clonedInitializerField = CloneIntializerField(typeof(InitialValues).GetField(nameof(InitialValues.ByteArray)), out var field); - + var originalData = ((IReadableSegment) field.FieldRva).ToArray(); var newData = ((IReadableSegment) clonedInitializerField.FieldRva).ToArray(); @@ -329,5 +330,66 @@ public void CloneInterfaceImplementations() originalTypeDef.Interfaces.Select(t => t.Interface.FullName), clonedType.Interfaces.Select(t => t.Interface.FullName)); } + + [Fact] + public void CloneCallbackResult() + { + var sourceModule = ModuleDefinition.FromFile(typeof(Miscellaneous).Assembly.Location); + var type = sourceModule.TopLevelTypes.First(t => t.Name == nameof(Miscellaneous)); + + var targetModule = PrepareTempModule(); + + var reverseMethodsNames = (IMemberDefinition original, IMemberDefinition cloned) => { + + if (cloned is MethodDefinition clonedDescriptor && original is MethodDefinition originalDescriptor) + clonedDescriptor.Name = new string(originalDescriptor.Name.Reverse().ToArray()); + + }; + + var result = new MemberCloner(targetModule, reverseMethodsNames) + .Include(type) + .Clone(); + + var clonedType = result.GetClonedMember(type); + + Assert.Equal( + type.Methods.Select(m => m.Name.Reverse().ToArray()), + clonedType.Methods.Select(m => m.Name.ToArray())); + } + + [Fact] + public void CloneCustomListenerResult() + { + var sourceModule = ModuleDefinition.FromFile(typeof(Miscellaneous).Assembly.Location); + var type = sourceModule.TopLevelTypes.First(t => t.Name == nameof(Miscellaneous)); + + var targetModule = PrepareTempModule(); + + var result = new MemberCloner(targetModule, new CustomMemberClonerListener()) + .Include(type) + .Clone(); + + var clonedType = result.GetClonedMember(type); + + Assert.Equal( + type.Methods.Select(m => $"Method_{m.Name}"), + clonedType.Methods.Select(m => m.Name.ToString())); + } + + [Fact] + public void CloneAndInject() + { + var sourceModule = ModuleDefinition.FromFile(typeof(Miscellaneous).Assembly.Location); + var targetModule = PrepareTempModule(); + + var type = sourceModule.TopLevelTypes.First(t => t.Name == nameof(Miscellaneous)); + + var result = new MemberCloner(targetModule, new InjectTypeClonerListener(targetModule)) + .Include(type) + .Clone(); + + Assert.All(result.ClonedTopLevelTypes, t => Assert.Contains(t, targetModule.TopLevelTypes)); + } + } -} \ No newline at end of file +} diff --git a/test/AsmResolver.DotNet.Tests/Code/Cil/CilMethodBodyTest.cs b/test/AsmResolver.DotNet.Tests/Code/Cil/CilMethodBodyTest.cs index 8dda5be9e..a3626ba04 100644 --- a/test/AsmResolver.DotNet.Tests/Code/Cil/CilMethodBodyTest.cs +++ b/test/AsmResolver.DotNet.Tests/Code/Cil/CilMethodBodyTest.cs @@ -124,33 +124,6 @@ public void ReadFatMethodWithExceptionHandler() Assert.Single(body.ExceptionHandlers); } - [Fact] - public void ReadDynamicMethod() - { - var module = ModuleDefinition.FromFile(typeof(TDynamicMethod).Assembly.Location); - - var type = module.TopLevelTypes.First(t => t.Name == nameof(TDynamicMethod)); - - var method = type.Methods.FirstOrDefault(m => m.Name == nameof(TDynamicMethod.GenerateDynamicMethod)); - - DynamicMethod generateDynamicMethod = TDynamicMethod.GenerateDynamicMethod(); - - //Dynamic method => CilMethodBody - var body = CilMethodBody.FromDynamicMethod(method, generateDynamicMethod); - - Assert.NotNull(body); - - Assert.NotEmpty(body.Instructions); - - Assert.Equal(body.Instructions.Select(q=>q.OpCode),new CilOpCode[] - { - CilOpCodes.Ldarg_0, - CilOpCodes.Call, - CilOpCodes.Ldarg_1, - CilOpCodes.Ret - }); - } - private static CilMethodBody CreateDummyBody(bool isVoid) { var module = new ModuleDefinition("DummyModule"); @@ -428,7 +401,7 @@ public void ReadInvalidMethodBodyErrorShouldAppearInDiagnostics() public void ExceptionHandlerWithHandlerEndOutsideOfMethodShouldResultInEndLabel() { var module = ModuleDefinition.FromBytes(Properties.Resources.HandlerEndAtEndOfMethodBody); - var body = module.ManagedEntrypointMethod.CilMethodBody; + var body = module.ManagedEntryPointMethod.CilMethodBody; Assert.Same(body.Instructions.EndLabel, body.ExceptionHandlers[0].HandlerEnd); body.VerifyLabels(); } @@ -624,5 +597,106 @@ public void SmallTryBlockStartingOnLargeOffsetShouldResultInFatFormat() Assert.True(handler.IsFat); } + + [Fact] + public void ReadUserStringFromNormalMetadata() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld_DoubleUserStringsStream); + var instruction = module.ManagedEntryPointMethod!.CilMethodBody!.Instructions + .First(i => i.OpCode.Code == CilCode.Ldstr); + + Assert.Equal("Hello Mars!!", instruction.Operand); + } + + [Fact] + public void ReadUserStringFromEnCMetadata() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld_DoubleUserStringsStream_EnC); + var instruction = module.ManagedEntryPointMethod!.CilMethodBody!.Instructions + .First(i => i.OpCode.Code == CilCode.Ldstr); + + Assert.Equal("Hello World!", instruction.Operand); + } + + private CilMethodBody CreateAndReadPatchedBody(IErrorListener listener, Action patch) + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + + // Add new dummy method to type. + var method = new MethodDefinition("Dummy", MethodAttributes.Static, + MethodSignature.CreateStatic(module.CorLibTypeFactory.Void)); + module.GetOrCreateModuleType().Methods.Add(method); + + // Give it a method body. + var body = new CilMethodBody(method); + method.MethodBody = body; + + // Add some random local variables. + for (int i = 0; i < 10; i++) + body.LocalVariables.Add(new CilLocalVariable(module.CorLibTypeFactory.Object)); + + // Add some random instructions. + for (int i = 0; i < 100; i++) + body.Instructions.Add(CilOpCodes.Nop); + body.Instructions.Add(CilOpCodes.Ret); + + // Construct PE image. + var result = new ManagedPEImageBuilder().CreateImage(module); + + // Look up raw method body. + var token = result.TokenMapping[method]; + var metadata = result.ConstructedImage!.DotNetDirectory!.Metadata!; + var rawBody = (CilRawFatMethodBody) metadata + .GetStream() + .GetTable() + .GetByRid(token.Rid) + .Body.GetSegment(); + + Assert.NotNull(rawBody); + + // Patch it. + patch(rawBody); + + // Read back module definition and look up interpreted method body. + module = ModuleDefinition.FromImage(result.ConstructedImage, new ModuleReaderParameters(listener)); + return ((MethodDefinition) module.LookupMember(token)).CilMethodBody; + } + + [Fact] + public void ReadLocalsFromBodyWithInvalidCodeStream() + { + var body = CreateAndReadPatchedBody(EmptyErrorListener.Instance, raw => + { + raw.Code = new DataSegment(new byte[] + { + 0xFE // 2-byte prefix opcode + }); + }); + + Assert.NotEmpty(body.LocalVariables); + } + + [Fact] + public void ReadCodeStreamFromBodyWithInvalidLocalVariablesSignature() + { + var body = CreateAndReadPatchedBody(EmptyErrorListener.Instance, raw => + { + raw.LocalVarSigToken = new MetadataToken(TableIndex.StandAloneSig, 0x123456); + }); + + Assert.NotEmpty(body.Instructions); + } + + [Fact] + public void ReadInvalidBody() + { + var body = CreateAndReadPatchedBody(EmptyErrorListener.Instance, raw => + { + raw.Code = new DataSegment(new byte[] { 0xFE }); + raw.LocalVarSigToken = new MetadataToken(TableIndex.StandAloneSig, 0x123456); + }); + + Assert.NotNull(body); + } } } diff --git a/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs b/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs index a41eae6e8..356163a4d 100644 --- a/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs +++ b/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs @@ -36,13 +36,13 @@ private static NativeMethodBody CreateDummyBody(bool isVoid, bool is32Bit) module.Attributes &= ~DotNetDirectoryFlags.ILOnly; if (is32Bit) { - module.PEKind = OptionalHeaderMagic.Pe32; + module.PEKind = OptionalHeaderMagic.PE32; module.MachineType = MachineType.I386; module.Attributes |= DotNetDirectoryFlags.Bit32Required; } else { - module.PEKind = OptionalHeaderMagic.Pe32Plus; + module.PEKind = OptionalHeaderMagic.PE32Plus; module.MachineType = MachineType.Amd64; } @@ -310,7 +310,7 @@ public void NativeBodyWithLocalSymbols(bool is32Bit, byte[] movInstruction, uint module.CorLibTypeFactory.String) ); - var instructions = module.ManagedEntrypointMethod!.CilMethodBody!.Instructions; + var instructions = module.ManagedEntryPointMethod!.CilMethodBody!.Instructions; instructions.Clear(); instructions.Add(CilOpCodes.Call, body.Owner); instructions.Add(CilOpCodes.Newobj, stringConstructor); diff --git a/test/AsmResolver.DotNet.Tests/CustomAttributeTest.cs b/test/AsmResolver.DotNet.Tests/CustomAttributeTest.cs index 3c7da128b..b05032774 100644 --- a/test/AsmResolver.DotNet.Tests/CustomAttributeTest.cs +++ b/test/AsmResolver.DotNet.Tests/CustomAttributeTest.cs @@ -1,15 +1,12 @@ using System; using System.IO; using System.Linq; -using AsmResolver.DotNet.Builder; -using AsmResolver.DotNet.Code.Cil; using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.Signatures.Types; using AsmResolver.DotNet.TestCases.CustomAttributes; using AsmResolver.DotNet.TestCases.Properties; using AsmResolver.IO; using AsmResolver.PE; -using AsmResolver.PE.DotNet.Cil; using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; using Xunit; @@ -18,7 +15,7 @@ namespace AsmResolver.DotNet.Tests { public class CustomAttributeTest { - private readonly SignatureComparer _comparer = new SignatureComparer(); + private readonly SignatureComparer _comparer = new(); [Fact] public void ReadConstructor() @@ -27,7 +24,7 @@ public void ReadConstructor() var type = module.TopLevelTypes.First(t => t.Name == nameof(CustomAttributesTestClass)); Assert.All(type.CustomAttributes, a => - Assert.Equal(nameof(TestCaseAttribute), a.Constructor.DeclaringType.Name)); + Assert.Equal(nameof(TestCaseAttribute), a.Constructor!.DeclaringType!.Name)); } [Fact] @@ -37,11 +34,11 @@ public void PersistentConstructor() using var stream = new MemoryStream(); module.Write(stream); - module = ModuleDefinition.FromReader(ByteArrayDataSource.CreateReader(stream.ToArray())); + module = ModuleDefinition.FromReader(new BinaryStreamReader(stream.ToArray())); var type = module.TopLevelTypes.First(t => t.Name == nameof(CustomAttributesTestClass)); Assert.All(type.CustomAttributes, a => - Assert.Equal(nameof(TestCaseAttribute), a.Constructor.DeclaringType.Name)); + Assert.Equal(nameof(TestCaseAttribute), a.Constructor!.DeclaringType!.Name)); } [Fact] @@ -51,7 +48,7 @@ public void ReadParent() string filePath = typeof(CustomAttributesTestClass).Assembly.Location; var image = PEImage.FromFile(filePath); - var tablesStream = image.DotNetDirectory.Metadata.GetStream(); + var tablesStream = image.DotNetDirectory!.Metadata!.GetStream(); var encoder = tablesStream.GetIndexEncoder(CodedIndex.HasCustomAttribute); var attributeTable = tablesStream.GetTable(TableIndex.CustomAttribute); @@ -73,13 +70,19 @@ public void ReadParent() Assert.Equal(parentToken, attribute.Parent.MetadataToken); } - private static CustomAttribute GetCustomAttributeTestCase(string methodName, bool rebuild = false) + private static CustomAttribute GetCustomAttributeTestCase(string methodName, bool rebuild = false, bool access = false) { var module = ModuleDefinition.FromFile(typeof(CustomAttributesTestClass).Assembly.Location); var type = module.TopLevelTypes.First(t => t.Name == nameof(CustomAttributesTestClass)); var method = type.Methods.First(m => m.Name == methodName); var attribute = method.CustomAttributes - .First(c => c.Constructor.DeclaringType.Name == nameof(TestCaseAttribute)); + .First(c => c.Constructor!.DeclaringType!.Name == nameof(TestCaseAttribute)); + + if (access) + { + _ = attribute.Signature!.FixedArguments; + _ = attribute.Signature.NamedArguments; + } if (rebuild) attribute = RebuildAndLookup(attribute); @@ -89,24 +92,28 @@ private static CustomAttribute GetCustomAttributeTestCase(string methodName, boo private static CustomAttribute RebuildAndLookup(CustomAttribute attribute) { var stream = new MemoryStream(); - var method = (MethodDefinition) attribute.Parent; - method.Module.Write(stream); + var method = (MethodDefinition) attribute.Parent!; + method.Module!.Write(stream); var newModule = ModuleDefinition.FromBytes(stream.ToArray()); return newModule - .TopLevelTypes.First(t => t.FullName == method.DeclaringType.FullName) + .TopLevelTypes.First(t => t.FullName == method.DeclaringType!.FullName) .Methods.First(f => f.Name == method.Name) .CustomAttributes[0]; } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedInt32Argument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedInt32Argument(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32Argument), rebuild); + var attribute = GetCustomAttributeTestCase( + nameof(CustomAttributesTestClass.FixedInt32Argument), + rebuild, + access); - Assert.Single(attribute.Signature.FixedArguments); + Assert.Single(attribute.Signature!.FixedArguments); Assert.Empty(attribute.Signature.NamedArguments); var argument = attribute.Signature.FixedArguments[0]; @@ -114,12 +121,13 @@ public void FixedInt32Argument(bool rebuild) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedStringArgument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedStringArgument(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedStringArgument), rebuild); - Assert.Single(attribute.Signature.FixedArguments); + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedStringArgument),rebuild, access); + Assert.Single(attribute.Signature!.FixedArguments); Assert.Empty(attribute.Signature.NamedArguments); var argument = attribute.Signature.FixedArguments[0]; @@ -127,12 +135,13 @@ public void FixedStringArgument(bool rebuild) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedEnumArgument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedEnumArgument(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedEnumArgument), rebuild); - Assert.Single(attribute.Signature.FixedArguments); + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedEnumArgument),rebuild, access); + Assert.Single(attribute.Signature!.FixedArguments); Assert.Empty(attribute.Signature.NamedArguments); var argument = attribute.Signature.FixedArguments[0]; @@ -140,41 +149,44 @@ public void FixedEnumArgument(bool rebuild) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedNullTypeArgument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedNullTypeArgument(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedNullTypeArgument), rebuild); - var fixedArg = Assert.Single(attribute.Signature.FixedArguments); + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedNullTypeArgument),rebuild, access); + var fixedArg = Assert.Single(attribute.Signature!.FixedArguments); Assert.Null(fixedArg.Element); } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedTypeArgument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedTypeArgument(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedTypeArgument), rebuild); - Assert.Single(attribute.Signature.FixedArguments); + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedTypeArgument),rebuild, access); + Assert.Single(attribute.Signature!.FixedArguments); Assert.Empty(attribute.Signature.NamedArguments); var argument = attribute.Signature.FixedArguments[0]; Assert.Equal( - attribute.Constructor.Module.CorLibTypeFactory.String, + attribute.Constructor!.Module!.CorLibTypeFactory.String, argument.Element as TypeSignature, _comparer); } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedComplexTypeArgument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedComplexTypeArgument(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedComplexTypeArgument), rebuild); - Assert.Single(attribute.Signature.FixedArguments); + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedComplexTypeArgument),rebuild, access); + Assert.Single(attribute.Signature!.FixedArguments); Assert.Empty(attribute.Signature.NamedArguments); var argument = attribute.Signature.FixedArguments[0]; - var factory = attribute.Constructor.Module.CorLibTypeFactory; + var factory = attribute.Constructor!.Module!.CorLibTypeFactory; var listRef = new TypeReference(factory.CorLibScope, "System.Collections.Generic", "KeyValuePair`2"); var instance = new GenericInstanceTypeSignature(listRef, false, @@ -185,12 +197,13 @@ public void FixedComplexTypeArgument(bool rebuild) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void NamedInt32Argument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void NamedInt32Argument(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.NamedInt32Argument), rebuild); - Assert.Empty(attribute.Signature.FixedArguments); + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.NamedInt32Argument),rebuild, access); + Assert.Empty(attribute.Signature!.FixedArguments); Assert.Single(attribute.Signature.NamedArguments); var argument = attribute.Signature.NamedArguments[0]; @@ -199,12 +212,13 @@ public void NamedInt32Argument(bool rebuild) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void NamedStringArgument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void NamedStringArgument(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.NamedStringArgument), rebuild); - Assert.Empty(attribute.Signature.FixedArguments); + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.NamedStringArgument),rebuild, access); + Assert.Empty(attribute.Signature!.FixedArguments); Assert.Single(attribute.Signature.NamedArguments); var argument = attribute.Signature.NamedArguments[0]; @@ -213,12 +227,13 @@ public void NamedStringArgument(bool rebuild) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void NamedEnumArgument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void NamedEnumArgument(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.NamedEnumArgument), rebuild); - Assert.Empty(attribute.Signature.FixedArguments); + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.NamedEnumArgument),rebuild, access); + Assert.Empty(attribute.Signature!.FixedArguments); Assert.Single(attribute.Signature.NamedArguments); var argument = attribute.Signature.NamedArguments[0]; @@ -227,16 +242,17 @@ public void NamedEnumArgument(bool rebuild) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void NamedTypeArgument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void NamedTypeArgument(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.NamedTypeArgument), rebuild); - Assert.Empty(attribute.Signature.FixedArguments); + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.NamedTypeArgument),rebuild, access); + Assert.Empty(attribute.Signature!.FixedArguments); Assert.Single(attribute.Signature.NamedArguments); var expected = new TypeReference( - attribute.Constructor.Module.CorLibTypeFactory.CorLibScope, + attribute.Constructor!.Module!.CorLibTypeFactory.CorLibScope, "System", "Int32"); var argument = attribute.Signature.NamedArguments[0]; @@ -250,22 +266,23 @@ public void IsCompilerGeneratedMember() var module = ModuleDefinition.FromFile(typeof(SingleProperty).Assembly.Location); var type = module.TopLevelTypes.First(t => t.Name == nameof(SingleProperty)); var property = type.Properties.First(); - var setMethod = property.SetMethod; + var setMethod = property.SetMethod!; Assert.True(setMethod.IsCompilerGenerated()); } [Theory] - [InlineData(false)] - [InlineData(true)] - public void GenericTypeArgument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void GenericTypeArgument(bool rebuild, bool access) { // https://github.com/Washi1337/AsmResolver/issues/92 - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.GenericType), rebuild); - var argument = attribute.Signature.FixedArguments[0]; + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.GenericType),rebuild, access); + var argument = attribute.Signature!.FixedArguments[0]; - var module = attribute.Constructor.Module; + var module = attribute.Constructor!.Module!; var nestedClass = (TypeDefinition) module.LookupMember(typeof(TestGenericType<>).MetadataToken); var expected = new GenericInstanceTypeSignature(nestedClass, false, module.CorLibTypeFactory.Object); @@ -274,16 +291,17 @@ public void GenericTypeArgument(bool rebuild) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void ArrayGenericTypeArgument(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void ArrayGenericTypeArgument(bool rebuild, bool access) { // https://github.com/Washi1337/AsmResolver/issues/92 - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.GenericTypeArray), rebuild); - var argument = attribute.Signature.FixedArguments[0]; + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.GenericTypeArray),rebuild, access); + var argument = attribute.Signature!.FixedArguments[0]; - var module = attribute.Constructor.Module; + var module = attribute.Constructor!.Module!; var nestedClass = (TypeDefinition) module.LookupMember(typeof(TestGenericType<>).MetadataToken); var expected = new SzArrayTypeSignature( new GenericInstanceTypeSignature(nestedClass, false, module.CorLibTypeFactory.Object) @@ -294,64 +312,69 @@ public void ArrayGenericTypeArgument(bool rebuild) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void IntPassedOnAsObject(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void IntPassedOnAsObject(bool rebuild, bool access) { // https://github.com/Washi1337/AsmResolver/issues/92 - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.Int32PassedAsObject), rebuild); - var argument = attribute.Signature.FixedArguments[0]; + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.Int32PassedAsObject),rebuild, access); + var argument = attribute.Signature!.FixedArguments[0]; Assert.IsAssignableFrom(argument.Element); Assert.Equal(123, ((BoxedArgument) argument.Element).Value); } [Theory] - [InlineData(false)] - [InlineData(true)] - public void TypePassedOnAsObject(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void TypePassedOnAsObject(bool rebuild, bool access) { // https://github.com/Washi1337/AsmResolver/issues/92 - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.TypePassedAsObject), rebuild); - var argument = attribute.Signature.FixedArguments[0]; + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.TypePassedAsObject),rebuild, access); + var argument = attribute.Signature!.FixedArguments[0]; - var module = attribute.Constructor.Module; + var module = attribute.Constructor!.Module!; Assert.IsAssignableFrom(argument.Element); Assert.Equal(module.CorLibTypeFactory.Int32, (ITypeDescriptor) ((BoxedArgument) argument.Element).Value, _comparer); } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedInt32NullArray(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedInt32NullArray(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayNullArgument), rebuild); - var argument = attribute.Signature.FixedArguments[0]; + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayNullArgument),rebuild, access); + var argument = attribute.Signature!.FixedArguments[0]; Assert.True(argument.IsNullArray); } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedInt32EmptyArray(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedInt32EmptyArray(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayEmptyArgument), rebuild); - var argument = attribute.Signature.FixedArguments[0]; + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayEmptyArgument),rebuild, access); + var argument = attribute.Signature!.FixedArguments[0]; Assert.False(argument.IsNullArray); Assert.Empty(argument.Elements); } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedInt32Array(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedInt32Array(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayArgument), rebuild); - var argument = attribute.Signature.FixedArguments[0]; + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayArgument),rebuild, access); + var argument = attribute.Signature!.FixedArguments[0]; Assert.Equal(new[] { @@ -360,12 +383,13 @@ public void FixedInt32Array(bool rebuild) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedInt32ArrayNullAsObject(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedInt32ArrayNullAsObject(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayAsObjectNullArgument), rebuild); - var argument = attribute.Signature.FixedArguments[0]; + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayAsObjectNullArgument),rebuild, access); + var argument = attribute.Signature!.FixedArguments[0]; Assert.IsAssignableFrom(argument.Element); var boxedArgument = (BoxedArgument) argument.Element; @@ -373,25 +397,27 @@ public void FixedInt32ArrayNullAsObject(bool rebuild) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedInt32EmptyArrayAsObject(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedInt32EmptyArrayAsObject(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayAsObjectEmptyArgument), rebuild); - var argument = attribute.Signature.FixedArguments[0]; + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayAsObjectEmptyArgument),rebuild, access); + var argument = attribute.Signature!.FixedArguments[0]; Assert.IsAssignableFrom(argument.Element); var boxedArgument = (BoxedArgument) argument.Element; - Assert.Equal(new object[0], boxedArgument.Value); + Assert.Equal(Array.Empty(), boxedArgument.Value); } [Theory] - [InlineData(false)] - [InlineData(true)] - public void FixedInt32ArrayAsObject(bool rebuild) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(true, true)] + public void FixedInt32ArrayAsObject(bool rebuild, bool access) { - var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayAsObjectArgument), rebuild); - var argument = attribute.Signature.FixedArguments[0]; + var attribute = GetCustomAttributeTestCase(nameof(CustomAttributesTestClass.FixedInt32ArrayAsObjectArgument),rebuild, access); + var argument = attribute.Signature!.FixedArguments[0]; Assert.IsAssignableFrom(argument.Element); var boxedArgument = (BoxedArgument) argument.Element; @@ -400,5 +426,43 @@ public void FixedInt32ArrayAsObject(bool rebuild) 1, 2, 3, 4 }, boxedArgument.Value); } + + [Fact] + public void CreateNewWithFixedArgumentsViaConstructor() + { + var module = new ModuleDefinition("Module.exe"); + + var attribute = new CustomAttribute(module.CorLibTypeFactory.CorLibScope + .CreateTypeReference("System", "ObsoleteAttribute") + .CreateMemberReference(".ctor", MethodSignature.CreateInstance( + module.CorLibTypeFactory.Void, + module.CorLibTypeFactory.String)), + new CustomAttributeSignature(new CustomAttributeArgument( + module.CorLibTypeFactory.String, + "My Message"))); + + Assert.NotNull(attribute.Signature); + var argument = Assert.Single(attribute.Signature.FixedArguments); + Assert.Equal("My Message", argument.Element); + } + + [Fact] + public void CreateNewWithFixedArgumentsViaProperty() + { + var module = new ModuleDefinition("Module.exe"); + + var attribute = new CustomAttribute(module.CorLibTypeFactory.CorLibScope + .CreateTypeReference("System", "ObsoleteAttribute") + .CreateMemberReference(".ctor", MethodSignature.CreateInstance( + module.CorLibTypeFactory.Void, + module.CorLibTypeFactory.String))); + attribute.Signature!.FixedArguments.Add(new CustomAttributeArgument( + module.CorLibTypeFactory.String, + "My Message")); + + Assert.NotNull(attribute.Signature); + var argument = Assert.Single(attribute.Signature.FixedArguments); + Assert.Equal("My Message", argument.Element); + } } } diff --git a/test/AsmResolver.DotNet.Tests/DynamicMethodDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/DynamicMethodDefinitionTest.cs deleted file mode 100644 index f8f203513..000000000 --- a/test/AsmResolver.DotNet.Tests/DynamicMethodDefinitionTest.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; -using AsmResolver.DotNet.Signatures.Types; -using AsmResolver.DotNet.TestCases.Methods; -using AsmResolver.PE.DotNet.Cil; -using Xunit; - -namespace AsmResolver.DotNet.Tests -{ - public class DynamicMethodDefinitionTest - { - [Fact] - public void ReadDynamicMethod() - { - var module = ModuleDefinition.FromFile(typeof(TDynamicMethod).Assembly.Location); - - DynamicMethod generateDynamicMethod = TDynamicMethod.GenerateDynamicMethod(); - - var dynamicMethodDefinition = new DynamicMethodDefinition(module, generateDynamicMethod); - - Assert.NotNull(dynamicMethodDefinition); - - Assert.NotEmpty(dynamicMethodDefinition.CilMethodBody.Instructions); - - Assert.Equal(dynamicMethodDefinition.CilMethodBody.Instructions.Select(q=>q.OpCode),new [] - { - CilOpCodes.Ldarg_0, - CilOpCodes.Call, - CilOpCodes.Ldarg_1, - CilOpCodes.Ret - }); - Assert.Equal(dynamicMethodDefinition.Parameters.Select(q=>q.ParameterType),new TypeSignature[] - { - module.CorLibTypeFactory.String, - module.CorLibTypeFactory.Int32 - }); - } - - [Fact] - public void RtDynamicMethod() - { - var module = ModuleDefinition.FromFile(typeof(TDynamicMethod).Assembly.Location); - - DynamicMethod generateDynamicMethod = TDynamicMethod.GenerateDynamicMethod(); - - var rtDynamicMethod = generateDynamicMethod.GetType().GetField("m_dynMethod", (BindingFlags) (-1))?.GetValue(generateDynamicMethod); - - var dynamicMethodDefinition = new DynamicMethodDefinition(module, rtDynamicMethod); - - Assert.NotNull(dynamicMethodDefinition); - - Assert.NotEmpty(dynamicMethodDefinition.CilMethodBody.Instructions); - - Assert.Equal(dynamicMethodDefinition.CilMethodBody.Instructions.Select(q=>q.OpCode),new [] - { - CilOpCodes.Ldarg_0, - CilOpCodes.Call, - CilOpCodes.Ldarg_1, - CilOpCodes.Ret - }); - Assert.Equal(dynamicMethodDefinition.Parameters.Select(q=>q.ParameterType),new TypeSignature[] - { - module.CorLibTypeFactory.String, - module.CorLibTypeFactory.Int32 - }); - } - } -} \ No newline at end of file diff --git a/test/AsmResolver.DotNet.Tests/ImplementationMapTest.cs b/test/AsmResolver.DotNet.Tests/ImplementationMapTest.cs index 38308f37f..d51d3aee7 100644 --- a/test/AsmResolver.DotNet.Tests/ImplementationMapTest.cs +++ b/test/AsmResolver.DotNet.Tests/ImplementationMapTest.cs @@ -28,17 +28,17 @@ private ImplementationMap RebuildAndLookup(ImplementationMap implementationMap) { using var stream = new MemoryStream(); implementationMap.MemberForwarded.Module.Write(stream); - + var newModule = ModuleDefinition.FromBytes(stream.ToArray()); var t = newModule.TopLevelTypes.First(t => t.Name == nameof(PlatformInvoke)); return t.Methods.First(m => m.Name == implementationMap.MemberForwarded.Name).ImplementationMap; } - + [Fact] public void ReadName() { var map = Lookup(nameof(PlatformInvoke.ExternalMethod)); - Assert.Equal("SomeEntrypoint", map.Name); + Assert.Equal("SomeEntryPoint", map.Name); } [Fact] @@ -49,27 +49,27 @@ public void PersistentName() var newMap = RebuildAndLookup(map); Assert.Equal(map.Name, newMap.Name); } - + [Fact] public void ReadScope() { var map = Lookup(nameof(PlatformInvoke.ExternalMethod)); Assert.Equal("SomeDll.dll", map.Scope.Name); } - + [Fact] public void PersistentScope() { var map = Lookup(nameof(PlatformInvoke.ExternalMethod)); - + var newModule = new ModuleReference("SomeOtherDll.dll"); map.MemberForwarded.Module.ModuleReferences.Add(newModule); map.Scope = newModule; - + var newMap = RebuildAndLookup(map); Assert.Equal(newModule.Name, newMap.Scope.Name); } - + [Fact] public void ReadMemberForwarded() { @@ -100,11 +100,11 @@ public void AddingAlreadyAddedMapToAnotherMemberShouldThrow() public void PersistentMemberForwarded() { var map = Lookup(nameof(PlatformInvoke.ExternalMethod)); - + var declaringType = (TypeDefinition) map.MemberForwarded.DeclaringType; var otherMethod = declaringType.Methods.First(m => m.Name == nameof(PlatformInvoke.NonImplementationMapMethod)); - + map.MemberForwarded.ImplementationMap = null; otherMethod.ImplementationMap = map; @@ -113,4 +113,4 @@ public void PersistentMemberForwarded() } } -} \ No newline at end of file +} diff --git a/test/AsmResolver.DotNet.Tests/ManifestResourceTest.cs b/test/AsmResolver.DotNet.Tests/ManifestResourceTest.cs index a685375e6..074e4f4b8 100644 --- a/test/AsmResolver.DotNet.Tests/ManifestResourceTest.cs +++ b/test/AsmResolver.DotNet.Tests/ManifestResourceTest.cs @@ -64,8 +64,10 @@ public void PersistentDataReader() module.Write(stream); var newModule = ModuleDefinition.FromBytes(stream.ToArray()); - Assert.Equal(contents - , newModule.Resources.First(r => r.Name == resourceName).GetReader()?.ReadToEnd()); + var resource = newModule.Resources.First(r => r.Name == resourceName); + + Assert.True(resource.TryGetReader(out var reader)); + Assert.Equal(contents, reader.ReadToEnd()); } } } diff --git a/test/AsmResolver.DotNet.Tests/MetadataResolverTest.cs b/test/AsmResolver.DotNet.Tests/MetadataResolverTest.cs index 94298169e..06d4d5ca7 100644 --- a/test/AsmResolver.DotNet.Tests/MetadataResolverTest.cs +++ b/test/AsmResolver.DotNet.Tests/MetadataResolverTest.cs @@ -192,7 +192,7 @@ public void ResolveExportedMemberReference() resolver.AddToCache(assembly2, assembly2); // Resolve - var instructions = module.ManagedEntrypointMethod.CilMethodBody.Instructions; + var instructions = module.ManagedEntryPointMethod.CilMethodBody.Instructions; Assert.NotNull(((IMethodDescriptor) instructions[0].Operand).Resolve()); } @@ -238,7 +238,7 @@ public void ResolveToOlderNetVersion() .TopLevelTypes.First(t => t.Name == "MyClass") .Methods.First(m => m.Name == "ThrowMe"); - var reference = (IMethodDescriptor) mainApp.ManagedEntrypointMethod!.CilMethodBody!.Instructions.First( + var reference = (IMethodDescriptor) mainApp.ManagedEntryPointMethod!.CilMethodBody!.Instructions.First( i => i.OpCode == CilOpCodes.Callvirt && ((IMethodDescriptor) i.Operand)?.Name == "ThrowMe") .Operand!; @@ -246,7 +246,8 @@ public void ResolveToOlderNetVersion() Assert.NotNull(resolved); Assert.Equal(definition, resolved); } - + + [Fact] public void ResolveMethodWithoutHideBySig() { // https://github.com/Washi1337/AsmResolver/issues/241 @@ -259,7 +260,7 @@ public void ResolveMethodWithoutHideBySig() .ToArray(); var helloWorld = ModuleDefinition.FromFile(typeof(HelloWorldVB.Program).Assembly.Location); - var resolved = helloWorld.ManagedEntrypointMethod!.CilMethodBody!.Instructions + var resolved = helloWorld.ManagedEntryPointMethod!.CilMethodBody!.Instructions .Where(x => x.OpCode == CilOpCodes.Call) .Select(x => ((IMethodDescriptor) x.Operand!).Resolve()) .ToArray(); diff --git a/test/AsmResolver.DotNet.Tests/MethodDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/MethodDefinitionTest.cs index cb3e996f2..1e2a6fc8f 100644 --- a/test/AsmResolver.DotNet.Tests/MethodDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Tests/MethodDefinitionTest.cs @@ -216,13 +216,13 @@ private static AssemblyDefinition CreateDummyLibraryWithExport(int methodCount, { module.Attributes |= DotNetDirectoryFlags.Bit32Required; module.FileCharacteristics |= Characteristics.Dll | Characteristics.Machine32Bit; - module.PEKind = OptionalHeaderMagic.Pe32; + module.PEKind = OptionalHeaderMagic.PE32; module.MachineType = MachineType.I386; } else { module.FileCharacteristics |= Characteristics.Dll; - module.PEKind = OptionalHeaderMagic.Pe32Plus; + module.PEKind = OptionalHeaderMagic.PE32Plus; module.MachineType = MachineType.Amd64; } diff --git a/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs index d085b63cc..7e2e1ba10 100644 --- a/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs @@ -4,14 +4,9 @@ using System.Linq; using System.Reflection; using System.Runtime.InteropServices; -using AsmResolver.DotNet.Builder; -using AsmResolver.DotNet.Cloning; -using AsmResolver.DotNet.Serialized; using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.TestCases.NestedClasses; using AsmResolver.IO; -using AsmResolver.PE.DotNet.Builder; -using AsmResolver.PE.DotNet.Metadata.Strings; using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.Win32Resources; using Xunit; @@ -29,7 +24,7 @@ private static ModuleDefinition Rebuild(ModuleDefinition module) { using var stream = new MemoryStream(); module.Write(stream); - return ModuleDefinition.FromReader(ByteArrayDataSource.CreateReader(stream.ToArray())); + return ModuleDefinition.FromReader(new BinaryStreamReader(stream.ToArray())); } [SkippableFact] @@ -59,6 +54,28 @@ public void ReadNameTest() Assert.Equal("HelloWorld.exe", module.Name); } + [Fact] + public void ReadMvidFromNormalMetadata() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld_DoubleGuidStream); + Assert.Equal( + new Guid(new byte[] + { + 0x94, 0xe3, 0x75, 0xe2, 0x82, 0x8b, 0xac, 0x4c, 0xa3, 0x8c, 0xb3, 0x72, 0x4b, 0x81, 0xea, 0x05 + }), module.Mvid); + } + + [Fact] + public void ReadMvidFromEnCMetadata() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld_DoubleGuidStream_EnC); + Assert.Equal( + new Guid(new byte[] + { + 0x8F, 0x6C, 0x77, 0x06, 0xEE, 0x44, 0x65, 0x41, 0xB0, 0xF7, 0x2D, 0xBD, 0x12, 0x7F, 0xE2, 0x1B + }), module.Mvid); + } + [Fact] public void NameIsPersistentAfterRebuild() { @@ -268,7 +285,7 @@ public void PersistentResources() // Write and rebuild. using var stream = new MemoryStream(); module.Write(stream); - var newModule = ModuleDefinition.FromReader(ByteArrayDataSource.CreateReader(stream.ToArray())); + var newModule = ModuleDefinition.FromReader(new BinaryStreamReader(stream.ToArray())); // Assert contents. var newDirectory = (IResourceDirectory)newModule.NativeResourceDirectory.Entries @@ -327,6 +344,7 @@ public void PersistentFileReferences() public void DetectTargetNetFramework40() { var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + Assert.True(module.OriginalTargetRuntime.IsNetFramework); Assert.Contains(DotNetRuntimeInfo.NetFramework, module.OriginalTargetRuntime.Name); Assert.Equal(4, module.OriginalTargetRuntime.Version.Major); Assert.Equal(0, module.OriginalTargetRuntime.Version.Minor); @@ -336,6 +354,7 @@ public void DetectTargetNetFramework40() public void DetectTargetNetCore() { var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld_NetCore); + Assert.True(module.OriginalTargetRuntime.IsNetCoreApp); Assert.Contains(DotNetRuntimeInfo.NetCoreApp, module.OriginalTargetRuntime.Name); Assert.Equal(2, module.OriginalTargetRuntime.Version.Major); Assert.Equal(2, module.OriginalTargetRuntime.Version.Minor); @@ -345,6 +364,7 @@ public void DetectTargetNetCore() public void DetectTargetStandard() { var module = ModuleDefinition.FromFile(typeof(TestCases.Types.Class).Assembly.Location); + Assert.True(module.OriginalTargetRuntime.IsNetStandard); Assert.Contains(DotNetRuntimeInfo.NetStandard, module.OriginalTargetRuntime.Name); Assert.Equal(2, module.OriginalTargetRuntime.Version.Major); } @@ -356,5 +376,41 @@ public void NewModuleShouldContainSingleReferenceToCorLib() var reference = Assert.Single(module.AssemblyReferences); Assert.Equal(KnownCorLibs.NetStandard_v2_0_0_0, reference, Comparer); } + + [Fact] + public void RewriteSystemPrivateCoreLib() + { + string runtimePath = DotNetCorePathProvider.Default + .GetRuntimePathCandidates("Microsoft.NETCore.App", new Version(3, 1, 0)) + .FirstOrDefault() ?? throw new InvalidOperationException(".NET Core 3.1 is not installed."); + var module = ModuleDefinition.FromFile(Path.Combine(runtimePath, "System.Private.CoreLib.dll")); + + using var stream = new MemoryStream(); + module.Write(stream); + } + + [Fact] + public void RewriteSystemRuntime() + { + string runtimePath = DotNetCorePathProvider.Default + .GetRuntimePathCandidates("Microsoft.NETCore.App", new Version(3, 1, 0)) + .FirstOrDefault() ?? throw new InvalidOperationException(".NET Core 3.1 is not installed."); + var module = ModuleDefinition.FromFile(Path.Combine(runtimePath, "System.Runtime.dll")); + + using var stream = new MemoryStream(); + module.Write(stream); + } + + [Fact] + public void RewriteSystemPrivateXml() + { + string runtimePath = DotNetCorePathProvider.Default + .GetRuntimePathCandidates("Microsoft.NETCore.App", new Version(3, 1, 0)) + .FirstOrDefault() ?? throw new InvalidOperationException(".NET Core 3.1 is not installed."); + var module = ModuleDefinition.FromFile(Path.Combine(runtimePath, "System.Private.Xml.dll")); + + using var stream = new MemoryStream(); + module.Write(stream); + } } } diff --git a/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs index fbf27fbcb..fffb016d9 100644 --- a/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs @@ -171,6 +171,62 @@ public class Resources { } } + public static byte[] HelloWorld_DoubleBlobStream { + get { + object obj = ResourceManager.GetObject("HelloWorld_DoubleBlobStream", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] HelloWorld_DoubleBlobStream_EnC { + get { + object obj = ResourceManager.GetObject("HelloWorld_DoubleBlobStream_EnC", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] HelloWorld_DoubleGuidStream { + get { + object obj = ResourceManager.GetObject("HelloWorld_DoubleGuidStream", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] HelloWorld_DoubleGuidStream_EnC { + get { + object obj = ResourceManager.GetObject("HelloWorld_DoubleGuidStream_EnC", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] HelloWorld_DoubleStringsStream { + get { + object obj = ResourceManager.GetObject("HelloWorld_DoubleStringsStream", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] HelloWorld_DoubleStringsStream_EnC { + get { + object obj = ResourceManager.GetObject("HelloWorld_DoubleStringsStream_EnC", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] HelloWorld_DoubleUserStringsStream { + get { + object obj = ResourceManager.GetObject("HelloWorld_DoubleUserStringsStream", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] HelloWorld_DoubleUserStringsStream_EnC { + get { + object obj = ResourceManager.GetObject("HelloWorld_DoubleUserStringsStream_EnC", resourceCulture); + return ((byte[])(obj)); + } + } + public static byte[] Assembly1_Forwarder { get { object obj = ResourceManager.GetObject("Assembly1_Forwarder", resourceCulture); diff --git a/test/AsmResolver.DotNet.Tests/Properties/Resources.resx b/test/AsmResolver.DotNet.Tests/Properties/Resources.resx index e919e0c39..37af630d8 100644 --- a/test/AsmResolver.DotNet.Tests/Properties/Resources.resx +++ b/test/AsmResolver.DotNet.Tests/Properties/Resources.resx @@ -72,6 +72,30 @@ ..\Resources\HelloWorld.UnusualNestedTypeRefOrder.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\HelloWorld.DoubleBlobStream.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.DoubleBlobStream.EnC.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.DoubleGuidStream.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.DoubleGuidStream.EnC.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.DoubleStringsStream.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.DoubleStringsStream.EnC.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.DoubleUserStringsStream.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.DoubleUserStringsStream.EnC.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\Resources\Assembly1_Forwarder.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 diff --git a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs index d7c2cdc0d..8112872b0 100644 --- a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs +++ b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs @@ -5,6 +5,7 @@ using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.Signatures.Types; using AsmResolver.DotNet.TestCases.Fields; +using AsmResolver.DotNet.TestCases.NestedClasses; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; using Xunit; @@ -137,6 +138,20 @@ public void ImportNestedTypeDefinitionShouldImportParentType() Assert.Equal(_module, reference.DeclaringType.Module); } + [Fact] + public void ImportNestedTypeViaReflectionShouldImportParentType() + { + var module = ModuleDefinition.FromFile(typeof(TopLevelClass1).Assembly.Location); + var declaringType = module.TopLevelTypes.First(t => t.Name == nameof(TopLevelClass1)); + var nested = declaringType.NestedTypes.First(t => t.Name == nameof(TopLevelClass1.Nested1)); + + var result = _importer.ImportType(typeof(TopLevelClass1.Nested1)); + + Assert.Equal(nested, result, Comparer); + Assert.Equal(_module, result.Module); + Assert.Equal(_module, result.DeclaringType?.Module); + } + [Fact] public void ImportSimpleTypeFromReflectionShouldResultInTypeRef() { diff --git a/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleBlobStream.EnC.dll b/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleBlobStream.EnC.dll new file mode 100644 index 000000000..1d8ca99cd Binary files /dev/null and b/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleBlobStream.EnC.dll differ diff --git a/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleBlobStream.dll b/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleBlobStream.dll new file mode 100644 index 000000000..3b01c9015 Binary files /dev/null and b/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleBlobStream.dll differ diff --git a/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleGuidStream.EnC.dll b/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleGuidStream.EnC.dll new file mode 100644 index 000000000..3d7446cb4 Binary files /dev/null and b/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleGuidStream.EnC.dll differ diff --git a/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleGuidStream.dll b/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleGuidStream.dll new file mode 100644 index 000000000..91b35664f Binary files /dev/null and b/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleGuidStream.dll differ diff --git a/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleStringsStream.EnC.dll b/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleStringsStream.EnC.dll new file mode 100644 index 000000000..3be9c2a48 Binary files /dev/null and b/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleStringsStream.EnC.dll differ diff --git a/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleStringsStream.dll b/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleStringsStream.dll new file mode 100644 index 000000000..205a7311a Binary files /dev/null and b/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleStringsStream.dll differ diff --git a/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleUserStringsStream.EnC.dll b/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleUserStringsStream.EnC.dll new file mode 100644 index 000000000..dd1d0cd6a Binary files /dev/null and b/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleUserStringsStream.EnC.dll differ diff --git a/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleUserStringsStream.dll b/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleUserStringsStream.dll new file mode 100644 index 000000000..ff6955484 Binary files /dev/null and b/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.DoubleUserStringsStream.dll differ diff --git a/test/AsmResolver.DotNet.Tests/Resources/ResourceSetTest.cs b/test/AsmResolver.DotNet.Tests/Resources/ResourceSetTest.cs index 356c2113b..1d01b5fa5 100644 --- a/test/AsmResolver.DotNet.Tests/Resources/ResourceSetTest.cs +++ b/test/AsmResolver.DotNet.Tests/Resources/ResourceSetTest.cs @@ -87,7 +87,7 @@ public void PersistentIntrinsicElement(string key, ResourceTypeCode type, object using var stream = new MemoryStream(); set.Write(new BinaryStreamWriter(stream)); - var actualSet = ResourceSet.FromReader(ByteArrayDataSource.CreateReader(stream.ToArray())); + var actualSet = ResourceSet.FromReader(new BinaryStreamReader(stream.ToArray())); var actualEntry = actualSet.First(e => e.Name == key); Assert.Equal(entry.Type, actualEntry.Type); Assert.Equal(entry.Data, actualEntry.Data); @@ -110,7 +110,7 @@ public void PersistentUserDefinedTypeElement() using var stream = new MemoryStream(); set.Write(new BinaryStreamWriter(stream)); - var actualSet = ResourceSet.FromReader(ByteArrayDataSource.CreateReader(stream.ToArray())); + var actualSet = ResourceSet.FromReader(new BinaryStreamReader(stream.ToArray())); var actualEntry = actualSet.First(e => e.Name == "Point"); Assert.Equal(entry.Type.FullName, actualEntry.Type.FullName); Assert.Equal(entry.Data, actualEntry.Data); @@ -134,7 +134,7 @@ public void PersistentSetMultipleEntries() var resourceReader = new ResourceReader(stream); - var actualSet = ResourceSet.FromReader(ByteArrayDataSource.CreateReader(stream.ToArray())); + var actualSet = ResourceSet.FromReader(new BinaryStreamReader(stream.ToArray())); Assert.Equal(set.Count, actualSet.Count); for (int i = 0; i < set.Count; i++) { diff --git a/test/AsmResolver.DotNet.Tests/SecurityDeclarationTest.cs b/test/AsmResolver.DotNet.Tests/SecurityDeclarationTest.cs index c37960f59..36edb87c7 100644 --- a/test/AsmResolver.DotNet.Tests/SecurityDeclarationTest.cs +++ b/test/AsmResolver.DotNet.Tests/SecurityDeclarationTest.cs @@ -16,7 +16,7 @@ private static MethodDefinition LookupMethod(string methodName, bool rebuild) { var stream = new MemoryStream(); module.Write(stream); - module = ModuleDefinition.FromReader(ByteArrayDataSource.CreateReader(stream.ToArray())); + module = ModuleDefinition.FromReader(new BinaryStreamReader(stream.ToArray())); } var type = module.TopLevelTypes.First(t => t.Name == nameof(SecurityAttributes)); diff --git a/test/AsmResolver.DotNet.Tests/Signatures/SignatureComparerTest.cs b/test/AsmResolver.DotNet.Tests/Signatures/SignatureComparerTest.cs index 5ae715869..bbe5cd367 100644 --- a/test/AsmResolver.DotNet.Tests/Signatures/SignatureComparerTest.cs +++ b/test/AsmResolver.DotNet.Tests/Signatures/SignatureComparerTest.cs @@ -145,7 +145,7 @@ public void MatchForwardedNestedTypes() module.MetadataResolver.AssemblyResolver.AddToCache(library, library); forwarder.ManifestModule!.MetadataResolver.AssemblyResolver.AddToCache(library, library); - var referencedTypes = module.ManagedEntrypointMethod!.CilMethodBody!.Instructions + var referencedTypes = module.ManagedEntryPointMethod!.CilMethodBody!.Instructions .Where(i => i.OpCode.Code == CilCode.Call) .Select(i => ((IMethodDefOrRef) i.Operand!).DeclaringType) .Where(t => t.Name == "MyNestedClass") diff --git a/test/AsmResolver.DotNet.Tests/TypeDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/TypeDefinitionTest.cs index ac2834c36..a30204992 100644 --- a/test/AsmResolver.DotNet.Tests/TypeDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Tests/TypeDefinitionTest.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -53,6 +54,20 @@ public void ReadName() Assert.Equal("Program", module.TopLevelTypes[1].Name); } + [Fact] + public void ReadNameFromNormalMetadata() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld_DoubleStringsStream); + Assert.Equal("Class_2", module.TopLevelTypes[1].Name); + } + + [Fact] + public void ReadNameFromEnCMetadata() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld_DoubleStringsStream_EnC); + Assert.Equal("Class_1", module.TopLevelTypes[1].Name); + } + [Fact] public void NameIsPersistentAfterRebuild() { @@ -571,5 +586,18 @@ public void AddTypeWithCorLibBaseTypeToAssemblyWithCorLibTypeReferenceInAttribut var reference = Assert.IsAssignableFrom(corlib.Object.Scope!.GetAssembly()); Assert.Same(module, reference.Module); } + + [Fact] + public void ReadIsByRefLike() + { + var resolver = new DotNetCoreAssemblyResolver(new Version(5, 0)); + var corLib = resolver.Resolve(KnownCorLibs.SystemPrivateCoreLib_v5_0_0_0); + + var intType = corLib.ManifestModule.TopLevelTypes.First(t => t.Name == "Int32"); + var spanType = corLib.ManifestModule.TopLevelTypes.First(t => t.Name == "Span`1"); + + Assert.False(intType.IsByRefLike); + Assert.True(spanType.IsByRefLike); + } } } diff --git a/test/AsmResolver.DotNet.Tests/TypeSpecificationTest.cs b/test/AsmResolver.DotNet.Tests/TypeSpecificationTest.cs index d763cc1c4..ee2e8c2e9 100644 --- a/test/AsmResolver.DotNet.Tests/TypeSpecificationTest.cs +++ b/test/AsmResolver.DotNet.Tests/TypeSpecificationTest.cs @@ -12,7 +12,7 @@ namespace AsmResolver.DotNet.Tests { public class TypeSpecificationTest { - + [Fact] public void ReadGenericTypeInstantiation() { @@ -33,17 +33,17 @@ public void ReadGenericTypeInstantiation() [Fact] public void PersistentGenericTypeInstantiation() - { + { var module = ModuleDefinition.FromFile(typeof(GenericsTestClass).Assembly.Location); - + using var tempStream = new MemoryStream(); module.Write(tempStream); - + module = ModuleDefinition.FromBytes(tempStream.ToArray()); var fieldType = module - .TopLevelTypes.First(t => t.Name == nameof(GenericsTestClass)) - .Fields.First(f => f.Name == nameof(GenericsTestClass.GenericField)) - .Signature.FieldType; + .TopLevelTypes.First(t => t.Name == nameof(GenericsTestClass))! + .Fields.First(f => f.Name == nameof(GenericsTestClass.GenericField))! + .Signature!.FieldType; Assert.IsAssignableFrom(fieldType); var genericType = (GenericInstanceTypeSignature) fieldType; @@ -53,7 +53,7 @@ public void PersistentGenericTypeInstantiation() "System.String", "System.Int32", "System.Object" }, genericType.TypeArguments.Select(a => a.FullName)); } - + [Fact] public void IllegalTypeSpecInTypeDefOrRef() { @@ -61,7 +61,7 @@ public void IllegalTypeSpecInTypeDefOrRef() var typeSpec = (TypeSpecification) module.LookupMember(new MetadataToken(TableIndex.TypeSpec, 1)); Assert.NotNull(typeSpec); } - + [Fact] public void MaliciousTypeSpecLoop() { @@ -70,6 +70,6 @@ public void MaliciousTypeSpecLoop() var typeSpec = (TypeSpecification) module.LookupMember(new MetadataToken(TableIndex.TypeSpec, 1)); Assert.NotNull(typeSpec.Signature); } - + } -} \ No newline at end of file +} diff --git a/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj b/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj index ba56b3821..6b532fdfc 100644 --- a/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj +++ b/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj @@ -9,8 +9,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj b/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj index 2e5602e90..3e76c6ef0 100644 --- a/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj +++ b/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj @@ -16,9 +16,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/AsmResolver.PE.Tests/DotNet/Builder/ManagedPEFileBuilderTest.cs b/test/AsmResolver.PE.Tests/DotNet/Builder/ManagedPEFileBuilderTest.cs index 18d2e859c..d9678ea24 100644 --- a/test/AsmResolver.PE.Tests/DotNet/Builder/ManagedPEFileBuilderTest.cs +++ b/test/AsmResolver.PE.Tests/DotNet/Builder/ManagedPEFileBuilderTest.cs @@ -62,7 +62,7 @@ public void HelloWorld32BitTo64Bit() // Change machine type and pe kind to 64-bit image.MachineType = MachineType.Amd64; - image.PEKind = OptionalHeaderMagic.Pe32Plus; + image.PEKind = OptionalHeaderMagic.PE32Plus; // Rebuild var builder = new ManagedPEFileBuilder(); @@ -82,7 +82,7 @@ public void HelloWorld64BitTo32Bit() // Change machine type and pe kind to 32-bit image.MachineType = MachineType.I386; - image.PEKind = OptionalHeaderMagic.Pe32; + image.PEKind = OptionalHeaderMagic.PE32; // Rebuild var builder = new ManagedPEFileBuilder(); diff --git a/test/AsmResolver.PE.Tests/DotNet/Builder/MixedModeAssemblyTest.cs b/test/AsmResolver.PE.Tests/DotNet/Builder/MixedModeAssemblyTest.cs index dfc390f0a..b5e6988d6 100644 --- a/test/AsmResolver.PE.Tests/DotNet/Builder/MixedModeAssemblyTest.cs +++ b/test/AsmResolver.PE.Tests/DotNet/Builder/MixedModeAssemblyTest.cs @@ -34,13 +34,13 @@ private static void ReplaceBodyWithNativeCode(IPEImage image, CodeSegment body, if (is32bit) { image.MachineType = MachineType.I386; - image.PEKind = OptionalHeaderMagic.Pe32; + image.PEKind = OptionalHeaderMagic.PE32; image.DotNetDirectory.Flags |= DotNetDirectoryFlags.Bit32Required; } else { image.MachineType = MachineType.Amd64; - image.PEKind = OptionalHeaderMagic.Pe32Plus; + image.PEKind = OptionalHeaderMagic.PE32Plus; } // Access metadata. @@ -89,7 +89,7 @@ public void NativeBodyWithNoCalls() // Read image var image = PEImage.FromBytes(Properties.Resources.TheAnswer_NetFx); - ReplaceBodyWithNativeCode(image, new CodeSegment(image.ImageBase, new byte[] + ReplaceBodyWithNativeCode(image, new CodeSegment(new byte[] { 0xb8, 0x39, 0x05, 0x00, 0x00, // mov rax, 1337 0xc3 // ret @@ -120,7 +120,7 @@ public void NativeBodyWithCall() var function = new ImportedSymbol(0x4fc, "puts"); module.Symbols.Add(function); - var body = new CodeSegment(image.ImageBase, new byte[] + var body = new CodeSegment(new byte[] { /* 00: */ 0x48, 0x83, 0xEC, 0x28, // sub rsp, 0x28 /* 04: */ 0x48, 0x8D, 0x0D, 0x10, 0x00, 0x00, 0x00, // lea rcx, qword [rel str] @@ -170,7 +170,7 @@ public void NativeBodyWithCallX86() var function = new ImportedSymbol(0x4fc, "puts"); module.Symbols.Add(function); - var body = new CodeSegment(image.ImageBase, new byte[] + var body = new CodeSegment(new byte[] { /* 00: */ 0x55, // push ebp /* 01: */ 0x89, 0xE5, // mov ebp,esp diff --git a/test/AsmResolver.PE.Tests/DotNet/Metadata/MetadataTest.cs b/test/AsmResolver.PE.Tests/DotNet/Metadata/MetadataTest.cs index ed16611f3..dc4a0740d 100644 --- a/test/AsmResolver.PE.Tests/DotNet/Metadata/MetadataTest.cs +++ b/test/AsmResolver.PE.Tests/DotNet/Metadata/MetadataTest.cs @@ -103,8 +103,9 @@ public void PreserveMetadataNoChange() using var tempStream = new MemoryStream(); metadata.Write(new BinaryStreamWriter(tempStream)); - var reader = ByteArrayDataSource.CreateReader(tempStream.ToArray()); - var newMetadata = new SerializedMetadata(new PEReaderContext(peFile), ref reader); + var reader = new BinaryStreamReader(tempStream.ToArray()); + var context = MetadataReaderContext.FromReaderContext(new PEReaderContext(peFile)); + var newMetadata = new SerializedMetadata(context, ref reader); Assert.Equal(metadata.MajorVersion, newMetadata.MajorVersion); Assert.Equal(metadata.MinorVersion, newMetadata.MinorVersion); @@ -113,7 +114,7 @@ public void PreserveMetadataNoChange() Assert.Equal(metadata.Flags, newMetadata.Flags); Assert.Equal(metadata.Streams.Count, newMetadata.Streams.Count); - for (int i = 0; i < metadata.Streams.Count; i++) + Assert.All(Enumerable.Range(0, metadata.Streams.Count), i => { var oldStream = metadata.Streams[i]; var newStream = newMetadata.Streams[i]; @@ -122,10 +123,71 @@ public void PreserveMetadataNoChange() var oldData = oldStream.CreateReader().ReadToEnd(); var newData = newStream.CreateReader().ReadToEnd(); Assert.Equal(oldData, newData); + }); + } + + private void AssertCorrectStreamIsSelected(byte[] assembly, bool isEnC) + where TStream : class, IMetadataStream + { + var peImage = PEImage.FromBytes(assembly); + var metadata = peImage.DotNetDirectory!.Metadata!; + + var allStreams = metadata.Streams + .OfType() + .ToArray(); + + var dominantStream = metadata.GetStream(); + int expectedIndex = isEnC ? 0 : allStreams.Length - 1; + Assert.Equal(allStreams[expectedIndex], dominantStream); + } + + [Fact] + public void SelectLastBlobStreamInNormalMetadata() + { + AssertCorrectStreamIsSelected(Properties.Resources.HelloWorld_DoubleBlobStream, false); + } + + [Fact] + public void SelectLastGuidStreamInNormalMetadata() + { + AssertCorrectStreamIsSelected(Properties.Resources.HelloWorld_DoubleGuidStream, false); + } + + [Fact] + public void SelectLastStringsStreamInNormalMetadata() + { + AssertCorrectStreamIsSelected(Properties.Resources.HelloWorld_DoubleStringsStream, false); + } + + [Fact] + public void SelectLastUserStringsStreamInNormalMetadata() + { + AssertCorrectStreamIsSelected(Properties.Resources.HelloWorld_DoubleUserStringsStream, false); + } + + [Fact] + public void SelectFirstBlobStreamInEnCMetadata() + { + AssertCorrectStreamIsSelected(Properties.Resources.HelloWorld_DoubleBlobStream_EnC, true); + } - } + [Fact] + public void SelectFirstGuidStreamInEnCMetadata() + { + AssertCorrectStreamIsSelected(Properties.Resources.HelloWorld_DoubleGuidStream_EnC, true); } + [Fact] + public void SelectFirstStringsStreamInEnCMetadata() + { + AssertCorrectStreamIsSelected(Properties.Resources.HelloWorld_DoubleStringsStream_EnC, true); + } + + [Fact] + public void SelectFirstUserStringsStreamInEnCMetadata() + { + AssertCorrectStreamIsSelected(Properties.Resources.HelloWorld_DoubleUserStringsStream_EnC, true); + } } } diff --git a/test/AsmResolver.PE.Tests/DotNet/Metadata/PdbStreamTest.cs b/test/AsmResolver.PE.Tests/DotNet/Metadata/PdbStreamTest.cs new file mode 100644 index 000000000..6927f3554 --- /dev/null +++ b/test/AsmResolver.PE.Tests/DotNet/Metadata/PdbStreamTest.cs @@ -0,0 +1,60 @@ +using System.IO; +using System.Linq; +using AsmResolver.IO; +using AsmResolver.PE.DotNet.Metadata; +using AsmResolver.PE.DotNet.Metadata.Pdb; +using AsmResolver.PE.DotNet.Metadata.Tables; +using Xunit; + +namespace AsmResolver.PE.Tests.DotNet.Metadata +{ + public class PdbStreamTest + { + private static IMetadata GetMetadata(bool rebuild) + { + var metadata = PE.DotNet.Metadata.Metadata.FromBytes(Properties.Resources.TheAnswerPortablePdb); + if (rebuild) + { + using var stream = new MemoryStream(); + metadata.Write(new BinaryStreamWriter(stream)); + metadata = PE.DotNet.Metadata.Metadata.FromBytes(stream.ToArray()); + } + + return metadata; + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Id(bool rebuild) + { + var metadata = GetMetadata(rebuild); + Assert.Equal(new byte[] + { + 0x95, 0x26, 0xB5, 0xAC, 0xA7, 0xB, 0xB1, 0x4D, 0x9B, 0xF3, + 0xCD, 0x31, 0x73, 0xB, 0xE9, 0x64, 0xBE, 0xFE, 0x11, 0xFC + }, metadata.GetStream().Id); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void TypeSystemRowCounts(bool rebuild) + { + var metadata = GetMetadata(rebuild); + var pdbStream = metadata.GetStream(); + var tablesStream = metadata.GetStream(); + + Assert.Equal(new uint[] + { + 1, 17, 2, 0, 0, 0, 5, 0, 3, 0, 16, 0, 12, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 0, 0, 1, + 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, pdbStream.TypeSystemRowCounts); + + Assert.True(tablesStream.HasExternalRowCounts); + Assert.Equal( + tablesStream.ExternalRowCounts.Take((int) TableIndex.MaxTypeSystemTableIndex), + pdbStream.TypeSystemRowCounts.Take((int) TableIndex.MaxTypeSystemTableIndex)); + } + } +} diff --git a/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/MetadataRangeTest.cs b/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/MetadataRangeTest.cs index 5b1b4ca65..6c8e6ed55 100644 --- a/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/MetadataRangeTest.cs +++ b/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/MetadataRangeTest.cs @@ -10,7 +10,7 @@ public class MetadataRangeTest [Fact] public void ContinuousRangeEmpty() { - var range = new ContinuousMetadataRange(TableIndex.Method, 3, 3); + var range = new MetadataRange(TableIndex.Method, 3, 3); Assert.Equal(0, range.Count); Assert.Empty(range); } @@ -18,7 +18,7 @@ public void ContinuousRangeEmpty() [Fact] public void ContinuousRangeSingleItem() { - var range = new ContinuousMetadataRange(TableIndex.Method, 3, 4); + var range = new MetadataRange(TableIndex.Method, 3, 4); Assert.Equal(1, range.Count); Assert.Single(range); Assert.Equal(new MetadataToken(TableIndex.Method, 3), range.First()); @@ -27,7 +27,7 @@ public void ContinuousRangeSingleItem() [Fact] public void ContinuousRangeMultipleItems() { - var range = new ContinuousMetadataRange(TableIndex.Method, 3, 10); + var range = new MetadataRange(TableIndex.Method, 3, 10); Assert.Equal(7, range.Count); Assert.Equal(new[] { @@ -46,12 +46,12 @@ public void RedirectedRangeEmpty() { var stream = new TablesStream(); var redirectTable = stream.GetTable(); - - var range = new RedirectedMetadataRange(redirectTable, TableIndex.Method, 3, 3); + + var range = new MetadataRange(redirectTable, TableIndex.Method, 3, 3); Assert.Equal(0, range.Count); Assert.Empty(range); } - + [Fact] public void RedirectedRangeSingleItem() { @@ -62,13 +62,13 @@ public void RedirectedRangeSingleItem() redirectTable.Add(new MethodPointerRow(5)); redirectTable.Add(new MethodPointerRow(4)); redirectTable.Add(new MethodPointerRow(3)); - - var range = new RedirectedMetadataRange(redirectTable, TableIndex.Method, 3, 4); + + var range = new MetadataRange(redirectTable, TableIndex.Method, 3, 4); Assert.Equal(1, range.Count); Assert.Single(range); Assert.Equal(new MetadataToken(TableIndex.Method, 5), range.First()); } - + [Fact] public void RedirectedRangeMultipleItems() { @@ -82,8 +82,8 @@ public void RedirectedRangeMultipleItems() redirectTable.Add(new MethodPointerRow(9)); redirectTable.Add(new MethodPointerRow(8)); redirectTable.Add(new MethodPointerRow(10)); - - var range = new RedirectedMetadataRange(redirectTable, TableIndex.Method, 3, 8); + + var range = new MetadataRange(redirectTable, TableIndex.Method, 3, 8); Assert.Equal(5, range.Count); Assert.Equal(new[] { @@ -96,4 +96,4 @@ public void RedirectedRangeMultipleItems() } } -} \ No newline at end of file +} diff --git a/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/Rows/RowTestUtils.cs b/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/Rows/RowTestUtils.cs index 7566e52bd..99cf719bf 100644 --- a/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/Rows/RowTestUtils.cs +++ b/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/Rows/RowTestUtils.cs @@ -2,6 +2,7 @@ using System.IO; using System.Linq; using AsmResolver.IO; +using AsmResolver.PE.DotNet.Metadata; using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; using AsmResolver.PE.File; @@ -20,7 +21,7 @@ internal static class RowTestUtils using var tempStream = new MemoryStream(); expected.Write(new BinaryStreamWriter(tempStream), table.Layout); - var reader = ByteArrayDataSource.CreateReader(tempStream.ToArray()); + var reader = new BinaryStreamReader(tempStream.ToArray()); var newRow = readRow(ref reader, table.Layout); Assert.Equal(expected, newRow); @@ -35,8 +36,8 @@ internal static class RowTestUtils using var tempStream = new MemoryStream(); expected.Write(new BinaryStreamWriter(tempStream), table.Layout); - var reader = ByteArrayDataSource.CreateReader(tempStream.ToArray()); - var newRow = readRow(new PEReaderContext(new PEFile()), ref reader, table.Layout); + var reader = new BinaryStreamReader(tempStream.ToArray()); + var newRow = readRow(new MetadataReaderContext(VirtualAddressFactory.Instance), ref reader, table.Layout); Assert.Equal(expected, newRow); } diff --git a/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/TablesStreamTest.cs b/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/TablesStreamTest.cs index d35b8fd43..e207837f3 100644 --- a/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/TablesStreamTest.cs +++ b/test/AsmResolver.PE.Tests/DotNet/Metadata/Tables/TablesStreamTest.cs @@ -1,5 +1,8 @@ using System.IO; +using System.Linq; using AsmResolver.IO; +using AsmResolver.PE.DotNet.Metadata; +using AsmResolver.PE.DotNet.Metadata.Pdb; using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.File; using Xunit; @@ -34,10 +37,72 @@ public void PreserveTableStreamNoChange() var peImage = PEImage.FromFile(peFile); var tablesStream = peImage.DotNetDirectory!.Metadata!.GetStream(); + AssertEquivalentAfterRebuild(tablesStream); + } + + [Fact] + public void SmallExternalIndicesShouldHaveSmallIndicesInTablesStream() + { + var pdbMetadata = PE.DotNet.Metadata.Metadata.FromBytes(Properties.Resources.TheAnswerPortablePdb); + var stream = pdbMetadata.GetStream(); + Assert.Equal(IndexSize.Short, stream.GetIndexEncoder(CodedIndex.HasCustomAttribute).IndexSize); + } + + [Fact] + public void LargeExternalIndicesShouldHaveLargeIndicesInTablesStream() + { + var pdbMetadata = PE.DotNet.Metadata.Metadata.FromBytes(Properties.Resources.LargeIndicesPdb); + var stream = pdbMetadata.GetStream(); + Assert.Equal(IndexSize.Long, stream.GetIndexEncoder(CodedIndex.HasCustomAttribute).IndexSize); + } + + [Fact] + public void PreservePdbTableStreamWithSmallExternalIndicesNoChange() + { + var pdbMetadata = PE.DotNet.Metadata.Metadata.FromBytes(Properties.Resources.TheAnswerPortablePdb); + AssertEquivalentAfterRebuild(pdbMetadata.GetStream()); + } + + [Fact] + public void PreservePdbTableStreamWithLargeExternalIndicesNoChange() + { + var pdbMetadata = PE.DotNet.Metadata.Metadata.FromBytes(Properties.Resources.LargeIndicesPdb); + AssertEquivalentAfterRebuild(pdbMetadata.GetStream()); + } + + [Fact] + public void GetImpliedTableRowCountFromNonPdbMetadataShouldGetLocalRowCount() + { + var peImage = PEImage.FromBytes(Properties.Resources.HelloWorld); + var stream = peImage.DotNetDirectory!.Metadata!.GetStream(); + Assert.Equal((uint) stream.GetTable(TableIndex.TypeDef).Count, stream.GetTableRowCount(TableIndex.TypeDef)); + } + + [Fact] + public void GetImpliedTableRowCountFromPdbMetadataShouldGetExternalRowCount() + { + var pdbMetadata = PE.DotNet.Metadata.Metadata.FromBytes(Properties.Resources.TheAnswerPortablePdb); + var stream = pdbMetadata.GetStream(); + Assert.Equal(2u, stream.GetTableRowCount(TableIndex.TypeDef)); + Assert.Equal(0u ,(uint) stream.GetTable(TableIndex.TypeDef).Count); + } + + private static void AssertEquivalentAfterRebuild(TablesStream tablesStream) + { using var tempStream = new MemoryStream(); tablesStream.Write(new BinaryStreamWriter(tempStream)); - var newTablesStream = new SerializedTableStream(new PEReaderContext(new PEFile()), tablesStream.Name, tempStream.ToArray()); + var context = new MetadataReaderContext(VirtualAddressFactory.Instance); + var newTablesStream = new SerializedTableStream(context, tablesStream.Name, tempStream.ToArray()); + + var metadata = new PE.DotNet.Metadata.Metadata(); + if (tablesStream.HasExternalRowCounts) + { + var pdbStream = new PdbStream(); + pdbStream.UpdateRowCounts(tablesStream.ExternalRowCounts); + metadata.Streams.Add(pdbStream); + } + newTablesStream.Initialize(metadata); Assert.Equal(tablesStream.Reserved, newTablesStream.Reserved); Assert.Equal(tablesStream.MajorVersion, newTablesStream.MajorVersion); @@ -46,15 +111,19 @@ public void PreserveTableStreamNoChange() Assert.Equal(tablesStream.Log2LargestRid, newTablesStream.Log2LargestRid); Assert.Equal(tablesStream.ExtraData, newTablesStream.ExtraData); - for (TableIndex i = 0; i <= TableIndex.GenericParamConstraint; i++) + Assert.All(Enumerable.Range(0, (int) TableIndex.Max), i => { - var oldTable = tablesStream.GetTable(i); - var newTable = newTablesStream.GetTable(i); + var tableIndex = (TableIndex) i; + if (!tableIndex.IsValidTableIndex()) + return; + var oldTable = tablesStream.GetTable(tableIndex); + var newTable = newTablesStream.GetTable(tableIndex); + + Assert.Equal(oldTable.IsSorted, newTable.IsSorted); Assert.Equal(oldTable.Count, newTable.Count); - for (int j = 0; j < oldTable.Count; j++) - Assert.Equal(oldTable[j], newTable[j]); - } + Assert.All(Enumerable.Range(0, oldTable.Count), j => Assert.Equal(oldTable[j], newTable[j])); + }); } } } diff --git a/test/AsmResolver.PE.Tests/DotNet/StrongName/StrongNamePublicKeyTest.cs b/test/AsmResolver.PE.Tests/DotNet/StrongName/StrongNamePublicKeyTest.cs index 82ef37be1..ab8525f94 100644 --- a/test/AsmResolver.PE.Tests/DotNet/StrongName/StrongNamePublicKeyTest.cs +++ b/test/AsmResolver.PE.Tests/DotNet/StrongName/StrongNamePublicKeyTest.cs @@ -18,7 +18,7 @@ public void PersistentStrongNamePublicKey() using var tempStream = new MemoryStream(); publicKey.Write(new BinaryStreamWriter(tempStream)); - var reader = ByteArrayDataSource.CreateReader(tempStream.ToArray()); + var reader = new BinaryStreamReader(tempStream.ToArray()); var newPublicKey = StrongNamePublicKey.FromReader(ref reader); Assert.Equal(publicKey.Modulus, newPublicKey.Modulus); diff --git a/test/AsmResolver.PE.Tests/Exports/ExportDirectoryTest.cs b/test/AsmResolver.PE.Tests/Exports/ExportDirectoryTest.cs index 6426cb5b4..b60f1ed9d 100644 --- a/test/AsmResolver.PE.Tests/Exports/ExportDirectoryTest.cs +++ b/test/AsmResolver.PE.Tests/Exports/ExportDirectoryTest.cs @@ -1,3 +1,4 @@ +using System; using System.IO; using System.Linq; using AsmResolver.IO; @@ -24,7 +25,7 @@ public void ReadExportNames() { "NamedExport1", "NamedExport2", - }, image.Exports?.Entries.Select(e => e.Name)); + }, image.Exports?.Entries.Select(e => e.Name) ?? Array.Empty()); } [Fact] diff --git a/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs index 388c1419d..86d6f305d 100644 --- a/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs @@ -94,6 +94,69 @@ public class Resources { } } + public static byte[] HelloWorld_DoubleBlobStream { + get { + object obj = ResourceManager.GetObject("HelloWorld_DoubleBlobStream", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] HelloWorld_DoubleBlobStream_EnC { + get { + object obj = ResourceManager.GetObject("HelloWorld_DoubleBlobStream_EnC", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] HelloWorld_DoubleGuidStream { + get { + object obj = ResourceManager.GetObject("HelloWorld_DoubleGuidStream", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] HelloWorld_DoubleGuidStream_EnC { + get { + object obj = ResourceManager.GetObject("HelloWorld_DoubleGuidStream_EnC", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] HelloWorld_DoubleStringsStream { + get { + object obj = ResourceManager.GetObject("HelloWorld_DoubleStringsStream", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] HelloWorld_DoubleStringsStream_EnC { + get { + object obj = ResourceManager.GetObject("HelloWorld_DoubleStringsStream_EnC", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] HelloWorld_DoubleUserStringsStream { + get { + object obj = ResourceManager.GetObject("HelloWorld_DoubleUserStringsStream", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] HelloWorld_DoubleUserStringsStream_EnC { + get { + object obj = ResourceManager.GetObject("HelloWorld_DoubleUserStringsStream_EnC", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] HelloWorldPortablePdb { + get { + object obj = ResourceManager.GetObject("HelloWorldPortablePdb", resourceCulture); + return ((byte[])(obj)); + } + } + public static byte[] SimpleDll { get { object obj = ResourceManager.GetObject("SimpleDll", resourceCulture); @@ -122,6 +185,13 @@ public class Resources { } } + public static byte[] TheAnswerPortablePdb { + get { + object obj = ResourceManager.GetObject("TheAnswerPortablePdb", resourceCulture); + return ((byte[])(obj)); + } + } + public static byte[] SEHSamples { get { object obj = ResourceManager.GetObject("SEHSamples", resourceCulture); @@ -156,5 +226,12 @@ public class Resources { return ((byte[])(obj)); } } + + public static byte[] LargeIndicesPdb { + get { + object obj = ResourceManager.GetObject("LargeIndicesPdb", resourceCulture); + return ((byte[])(obj)); + } + } } } diff --git a/test/AsmResolver.PE.Tests/Properties/Resources.resx b/test/AsmResolver.PE.Tests/Properties/Resources.resx index e9a93ecd7..93af0f6bb 100644 --- a/test/AsmResolver.PE.Tests/Properties/Resources.resx +++ b/test/AsmResolver.PE.Tests/Properties/Resources.resx @@ -39,6 +39,33 @@ ..\Resources\HelloWorld.TablesStream.ExtraData.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\HelloWorld.DoubleBlobStream.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.DoubleBlobStream.EnC.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.DoubleGuidStream.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.DoubleGuidStream.EnC.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.DoubleStringsStream.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.DoubleStringsStream.EnC.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.DoubleUserStringsStream.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.DoubleUserStringsStream.EnC.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.pdb;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\Resources\SimpleDll.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 @@ -51,6 +78,9 @@ ..\Resources\TheAnswer.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\TheAnswer.pdb;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\Resources\SEHSamples.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 @@ -66,4 +96,7 @@ ..\Resources\TlsTest.x64.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\LargeIndicesPdb.pdb;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + diff --git a/test/AsmResolver.PE.Tests/Resources/.gitignore b/test/AsmResolver.PE.Tests/Resources/.gitignore new file mode 100644 index 000000000..bd46a47b5 --- /dev/null +++ b/test/AsmResolver.PE.Tests/Resources/.gitignore @@ -0,0 +1 @@ +!*.pdb diff --git a/test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleBlobStream.EnC.dll b/test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleBlobStream.EnC.dll new file mode 100644 index 000000000..1d8ca99cd Binary files /dev/null and b/test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleBlobStream.EnC.dll differ diff --git a/test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleBlobStream.dll b/test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleBlobStream.dll new file mode 100644 index 000000000..3b01c9015 Binary files /dev/null and b/test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleBlobStream.dll differ diff --git a/test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleGuidStream.EnC.dll b/test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleGuidStream.EnC.dll new file mode 100644 index 000000000..3d7446cb4 Binary files /dev/null and b/test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleGuidStream.EnC.dll differ diff --git a/test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleGuidStream.dll b/test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleGuidStream.dll new file mode 100644 index 000000000..91b35664f Binary files /dev/null and b/test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleGuidStream.dll differ diff --git a/test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleStringsStream.EnC.dll b/test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleStringsStream.EnC.dll new file mode 100644 index 000000000..3be9c2a48 Binary files /dev/null and b/test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleStringsStream.EnC.dll differ diff --git a/test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleStringsStream.dll b/test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleStringsStream.dll new file mode 100644 index 000000000..205a7311a Binary files /dev/null and b/test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleStringsStream.dll differ diff --git a/test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleUserStringsStream.EnC.dll b/test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleUserStringsStream.EnC.dll new file mode 100644 index 000000000..dd1d0cd6a Binary files /dev/null and b/test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleUserStringsStream.EnC.dll differ diff --git a/test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleUserStringsStream.dll b/test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleUserStringsStream.dll new file mode 100644 index 000000000..ff6955484 Binary files /dev/null and b/test/AsmResolver.PE.Tests/Resources/HelloWorld.DoubleUserStringsStream.dll differ diff --git a/test/AsmResolver.PE.Tests/Resources/HelloWorld.pdb b/test/AsmResolver.PE.Tests/Resources/HelloWorld.pdb new file mode 100644 index 000000000..aba7e1f78 Binary files /dev/null and b/test/AsmResolver.PE.Tests/Resources/HelloWorld.pdb differ diff --git a/test/AsmResolver.PE.Tests/Resources/LargeIndicesPdb.pdb b/test/AsmResolver.PE.Tests/Resources/LargeIndicesPdb.pdb new file mode 100644 index 000000000..d3107bf70 Binary files /dev/null and b/test/AsmResolver.PE.Tests/Resources/LargeIndicesPdb.pdb differ diff --git a/test/AsmResolver.PE.Tests/Resources/TheAnswer.pdb b/test/AsmResolver.PE.Tests/Resources/TheAnswer.pdb new file mode 100644 index 000000000..479dfee18 Binary files /dev/null and b/test/AsmResolver.PE.Tests/Resources/TheAnswer.pdb differ diff --git a/test/AsmResolver.PE.Tests/Tls/TlsDirectoryTest.cs b/test/AsmResolver.PE.Tests/Tls/TlsDirectoryTest.cs index 72e200b43..be18565ff 100644 --- a/test/AsmResolver.PE.Tests/Tls/TlsDirectoryTest.cs +++ b/test/AsmResolver.PE.Tests/Tls/TlsDirectoryTest.cs @@ -143,7 +143,7 @@ public void Persistent(bool is32Bit) }, OptionalHeader = { - Magic = is32Bit ? OptionalHeaderMagic.Pe32 : OptionalHeaderMagic.Pe32Plus + Magic = is32Bit ? OptionalHeaderMagic.PE32 : OptionalHeaderMagic.PE32Plus }, Sections = { @@ -162,8 +162,6 @@ public void Persistent(bool is32Bit) var directory = new TlsDirectory { - ImageBase = file.OptionalHeader.ImageBase, - Is32Bit = file.OptionalHeader.Magic == OptionalHeaderMagic.Pe32, TemplateData = templateData, Index = indexSegment.ToReference(), Characteristics = TlsCharacteristics.Align4Bytes, diff --git a/test/AsmResolver.PE.Tests/VirtualAddress.cs b/test/AsmResolver.PE.Tests/VirtualAddress.cs deleted file mode 100644 index b9424e496..000000000 --- a/test/AsmResolver.PE.Tests/VirtualAddress.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using AsmResolver.IO; - -namespace AsmResolver.PE.Tests -{ - public readonly struct VirtualAddress : ISegmentReference - { - public VirtualAddress(uint rva) - { - Rva = rva; - } - - ulong IOffsetProvider.Offset => Rva; - - public uint Rva - { - get; - } - - bool IOffsetProvider.CanUpdateOffsets => false; - - void IOffsetProvider.UpdateOffsets(ulong newOffset, uint newRva) => throw new InvalidOperationException(); - - public bool CanRead => false; - - bool ISegmentReference.IsBounded => false; - - BinaryStreamReader ISegmentReference.CreateReader() => throw new InvalidOperationException(); - - public ISegment? GetSegment() => throw new InvalidOperationException(); - } -} diff --git a/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj b/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj index c6ae10b54..cec83bfea 100644 --- a/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj +++ b/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj @@ -8,8 +8,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/AsmResolver.PE.Win32Resources.Tests/Version/VersionInfoResourceTest.cs b/test/AsmResolver.PE.Win32Resources.Tests/Version/VersionInfoResourceTest.cs index 06d769b89..a9699d22b 100644 --- a/test/AsmResolver.PE.Win32Resources.Tests/Version/VersionInfoResourceTest.cs +++ b/test/AsmResolver.PE.Win32Resources.Tests/Version/VersionInfoResourceTest.cs @@ -45,7 +45,7 @@ public void PersistentFixedVersionInfo() versionInfo.Write(new BinaryStreamWriter(tempStream)); // Reload. - var infoReader = ByteArrayDataSource.CreateReader(tempStream.ToArray()); + var infoReader = new BinaryStreamReader(tempStream.ToArray()); var newVersionInfo = VersionInfoResource.FromReader(ref infoReader); var newFixedVersionInfo = newVersionInfo.FixedVersionInfo; @@ -117,7 +117,7 @@ public void PersistentVarFileInfo() versionInfo.Write(new BinaryStreamWriter(tempStream)); // Reload. - var infoReader = ByteArrayDataSource.CreateReader(tempStream.ToArray()); + var infoReader = new BinaryStreamReader(tempStream.ToArray()); var newVersionInfo = VersionInfoResource.FromReader(ref infoReader); // Verify. @@ -152,7 +152,7 @@ public void PersistentStringFileInfo() versionInfo.Write(new BinaryStreamWriter(tempStream)); // Reload. - var infoReader = ByteArrayDataSource.CreateReader(tempStream.ToArray()); + var infoReader = new BinaryStreamReader(tempStream.ToArray()); var newVersionInfo = VersionInfoResource.FromReader(ref infoReader); // Verify. diff --git a/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj b/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj new file mode 100644 index 000000000..582e228ed --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj @@ -0,0 +1,42 @@ + + + + net6.0 + enable + + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + True + True + Resources.resx + + + + + + + + diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArgumentListLeafTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArgumentListLeafTest.cs new file mode 100644 index 000000000..a059b14b4 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArgumentListLeafTest.cs @@ -0,0 +1,23 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class ArgumentListLeafTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public ArgumentListLeafTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void ReadMultipleTypes() + { + var list = (ArgumentListLeaf) _fixture.SimplePdb.GetLeafRecord(0x2391); + Assert.IsAssignableFrom(list.Types[0]); + Assert.IsAssignableFrom(list.Types[1]); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArrayTypeRecordTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArrayTypeRecordTest.cs new file mode 100644 index 000000000..b33c90b37 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ArrayTypeRecordTest.cs @@ -0,0 +1,42 @@ +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class ArrayTypeRecordTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public ArrayTypeRecordTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void ReadElementType() + { + var type = (ArrayTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1905); + Assert.Equal(SimpleTypeKind.Void, Assert.IsAssignableFrom(type.ElementType).Kind); + } + + [Fact] + public void ReadIndexType() + { + var type = (ArrayTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1905); + Assert.Equal(SimpleTypeKind.UInt32Long, Assert.IsAssignableFrom(type.IndexType).Kind); + } + + [Fact] + public void ReadLength() + { + var type = (ArrayTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1905); + Assert.Equal(4u, type.Length); + } + + [Fact] + public void ReadEmptyName() + { + var type = (ArrayTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1905); + Assert.True(Utf8String.IsNullOrEmpty(type.Name)); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/BitFieldTypeRecordTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/BitFieldTypeRecordTest.cs new file mode 100644 index 000000000..34902397f --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/BitFieldTypeRecordTest.cs @@ -0,0 +1,35 @@ +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class BitFieldTypeRecordTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public BitFieldTypeRecordTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void ReadBaseType() + { + var type = (BitFieldTypeRecord) _fixture.MyTestApplication.GetLeafRecord(0x1060); + Assert.Equal(SimpleTypeKind.UInt32Long, Assert.IsAssignableFrom(type.Type).Kind); + } + + [Fact] + public void ReadPosition() + { + var type = (BitFieldTypeRecord) _fixture.MyTestApplication.GetLeafRecord(0x1060); + Assert.Equal(2, type.Position); + } + + [Fact] + public void ReadLength() + { + var type = (BitFieldTypeRecord) _fixture.MyTestApplication.GetLeafRecord(0x1060); + Assert.Equal(30, type.Length); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ClassTypeRecordTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ClassTypeRecordTest.cs new file mode 100644 index 000000000..e51e80d1e --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ClassTypeRecordTest.cs @@ -0,0 +1,61 @@ +using System; +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class ClassTypeRecordTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public ClassTypeRecordTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Theory] + [InlineData(CodeViewLeafKind.Class)] + [InlineData(CodeViewLeafKind.Structure)] + [InlineData(CodeViewLeafKind.Interface)] + public void CreateNewValidType(CodeViewLeafKind kind) + { + var type = new ClassTypeRecord(kind, "MyType", "MyUniqueType", 4, StructureAttributes.FwdRef, null); + Assert.Equal(kind, type.LeafKind); + Assert.Equal("MyType", type.Name); + Assert.Equal("MyUniqueType", type.UniqueName); + Assert.Equal(4u, type.Size); + Assert.Equal(StructureAttributes.FwdRef, type.StructureAttributes); + Assert.Null(type.BaseType); + } + + [Fact] + public void CreateNonValidType() + { + Assert.Throws(() => new ClassTypeRecord( + CodeViewLeafKind.Char, + "Invalid", + "Invalid", + 4, + StructureAttributes.FwdRef, + null)); + } + + [Fact] + public void ReadFieldList() + { + var type = (ClassTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x101b); + Assert.NotNull(type.Fields); + } + + [Fact] + public void ReadVTableShape() + { + var type = (ClassTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x239f); + Assert.NotNull(type.VTableShape); + Assert.Equal(new[] + { + VTableShapeEntry.Near32, + VTableShapeEntry.Near32, + }, type.VTableShape.Entries); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/EnumTypeRecordTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/EnumTypeRecordTest.cs new file mode 100644 index 000000000..5911ceb5e --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/EnumTypeRecordTest.cs @@ -0,0 +1,37 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class EnumTypeRecordTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public EnumTypeRecordTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void FieldList() + { + var leaf = _fixture.SimplePdb.GetLeafRecord(0x1009); + var fields = Assert.IsAssignableFrom(leaf).Fields!.Entries.Cast().ToArray(); + var names = new[] + { + "DISPLAYCONFIG_SCANLINE_ORDERING_UNSPECIFIED", + "DISPLAYCONFIG_SCANLINE_ORDERING_PROGRESSIVE", + "DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED", + "DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED_UPPERFIELDFIRST", + "DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED_LOWERFIELDFIRST", + "DISPLAYCONFIG_SCANLINE_ORDERING_FORCE_UINT32", + }; + Assert.Equal(names[0], fields[0].Name); + Assert.Equal(names[1], fields[1].Name); + Assert.Equal(names[2], fields[2].Name); + Assert.Equal(names[3], fields[3].Name); + Assert.Equal(names[4], fields[4].Name); + Assert.Equal(names[5], fields[5].Name); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListLeafTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListLeafTest.cs new file mode 100644 index 000000000..cd067940b --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/FieldListLeafTest.cs @@ -0,0 +1,130 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; +using static AsmResolver.Symbols.Pdb.Leaves.CodeViewFieldAttributes; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class FieldListLeafTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public FieldListLeafTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void ReadEnumerateList() + { + var list = (FieldListLeaf) _fixture.SimplePdb.GetLeafRecord(0x1008); + var enumerates = list.Entries + .Cast() + .Select(f => (f.Attributes, f.Name.Value, f.Value)) + .ToArray(); + + Assert.Equal((Public, "DISPLAYCONFIG_SCANLINE_ORDERING_UNSPECIFIED", 0u), enumerates[0]); + Assert.Equal((Public, "DISPLAYCONFIG_SCANLINE_ORDERING_PROGRESSIVE", 1u), enumerates[1]); + Assert.Equal((Public, "DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED", 2u), enumerates[2]); + Assert.Equal((Public, "DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED_UPPERFIELDFIRST", 2u), enumerates[3]); + Assert.Equal((Public, "DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED_LOWERFIELDFIRST", 3u), enumerates[4]); + Assert.Equal((Public, "DISPLAYCONFIG_SCANLINE_ORDERING_FORCE_UINT32", '\xff'), enumerates[5]); + } + + [Fact] + public void ReadInstanceDataMemberList() + { + var list = (FieldListLeaf) _fixture.SimplePdb.GetLeafRecord(0x1017); + var enumerates = list.Entries + .Cast() + .Select(f => (f.Attributes, f.Name.Value, f.Offset)) + .ToArray(); + + Assert.Equal((Public, "cbSize", 0ul), enumerates[0]); + Assert.Equal((Public, "fMask", 4ul), enumerates[1]); + Assert.Equal((Public, "fType", 8ul), enumerates[2]); + Assert.Equal((Public, "fState", 12ul), enumerates[3]); + Assert.Equal((Public, "wID", 16ul), enumerates[4]); + Assert.Equal((Public, "hSubMenu", 20ul), enumerates[5]); + Assert.Equal((Public, "hbmpChecked", 24ul), enumerates[6]); + Assert.Equal((Public, "hbmpUnchecked", 28ul), enumerates[7]); + Assert.Equal((Public, "dwItemData", 32ul), enumerates[8]); + Assert.Equal((Public, "dwTypeData", 36ul), enumerates[9]); + Assert.Equal((Public, "cch", 40ul), enumerates[10]); + Assert.Equal((Public, "hbmpItem", 44ul), enumerates[11]); + } + + [Fact] + public void ReadMethodsAndBaseClass() + { + var list = (FieldListLeaf) _fixture.SimplePdb.GetLeafRecord(0x239d); + + Assert.Equal("std::exception", Assert.IsAssignableFrom( + Assert.IsAssignableFrom(list.Entries[0]).Type).Name); + Assert.Equal("bad_cast", Assert.IsAssignableFrom(list.Entries[1]).Name); + Assert.Equal("__construct_from_string_literal", Assert.IsAssignableFrom(list.Entries[2]).Name); + Assert.Equal("~bad_cast", Assert.IsAssignableFrom(list.Entries[3]).Name); + Assert.Equal("operator=", Assert.IsAssignableFrom(list.Entries[4]).Name); + Assert.Equal("__local_vftable_ctor_closure", Assert.IsAssignableFrom(list.Entries[5]).Name); + Assert.Equal("__vecDelDtor", Assert.IsAssignableFrom(list.Entries[6]).Name); + } + + [Fact] + public void ReadNestedTypes() + { + var list = (FieldListLeaf) _fixture.SimplePdb.GetLeafRecord(0x1854); + + Assert.Equal("_LDT_ENTRY::::", + Assert.IsAssignableFrom(Assert.IsAssignableFrom(list.Entries[0]).Type).Name); + Assert.Equal("_LDT_ENTRY::::", + Assert.IsAssignableFrom(Assert.IsAssignableFrom(list.Entries[2]).Type).Name); + } + + [Fact] + public void ReadVirtualBaseClass() + { + var list = (FieldListLeaf) _fixture.MyTestApplication.GetLeafRecord(0x1347); + var baseClass = Assert.IsAssignableFrom(list.Entries[0]); + + Assert.Equal("std::basic_ios >", + Assert.IsAssignableFrom(baseClass.Type).Name); + Assert.True(Assert.IsAssignableFrom(baseClass.PointerType).IsNear64); + Assert.False(baseClass.IsIndirect); + Assert.Equal(0ul, baseClass.PointerOffset); + Assert.Equal(1ul, baseClass.TableOffset); + } + + [Fact] + public void ReadIndirectVirtualBaseClass() + { + var list = (FieldListLeaf) _fixture.MyTestApplication.GetLeafRecord(0x1e97); + var baseClass = Assert.IsAssignableFrom(list.Entries[2]); + + Assert.Equal("std::basic_ios >", + Assert.IsAssignableFrom(baseClass.Type).Name); + Assert.True(Assert.IsAssignableFrom(baseClass.PointerType).IsNear64); + Assert.True(baseClass.IsIndirect); + Assert.Equal(0ul, baseClass.PointerOffset); + Assert.Equal(1ul, baseClass.TableOffset); + } + + [Fact] + public void ReadStaticFields() + { + var list = (FieldListLeaf) _fixture.MyTestApplication.GetLeafRecord(0x1423); + + Assert.Equal("is_bounded", Assert.IsAssignableFrom(list.Entries[1]).Name); + Assert.Equal("is_exact", Assert.IsAssignableFrom(list.Entries[2]).Name); + Assert.Equal("is_integer", Assert.IsAssignableFrom(list.Entries[3]).Name); + Assert.Equal("is_specialized", Assert.IsAssignableFrom(list.Entries[4]).Name); + Assert.Equal("radix", Assert.IsAssignableFrom(list.Entries[5]).Name); + } + + [Fact] + public void ReadVTableField() + { + var list = (FieldListLeaf) _fixture.MyTestApplication.GetLeafRecord(0x1215); + Assert.IsAssignableFrom(Assert.IsAssignableFrom(list.Entries[0]).PointerType); + Assert.IsAssignableFrom(list.Entries[1]); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/MemberFunctionLeafTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/MemberFunctionLeafTest.cs new file mode 100644 index 000000000..465f27203 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/MemberFunctionLeafTest.cs @@ -0,0 +1,44 @@ +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class MemberFunctionLeafTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public MemberFunctionLeafTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void ReadReturnType() + { + var function = (MemberFunctionLeaf) _fixture.SimplePdb.GetLeafRecord(0x2392); + Assert.Equal(SimpleTypeKind.Void, Assert.IsAssignableFrom(function.ReturnType).Kind); + } + + [Fact] + public void ReadDeclaringType() + { + var function = (MemberFunctionLeaf) _fixture.SimplePdb.GetLeafRecord(0x2392); + Assert.Equal("std::bad_cast", Assert.IsAssignableFrom(function.DeclaringType).Name); + } + + [Fact] + public void ReadNonNullThisType() + { + var function = (MemberFunctionLeaf) _fixture.SimplePdb.GetLeafRecord(0x2392); + Assert.IsAssignableFrom(function.ThisType); + } + + [Fact] + public void ReadArgumentList() + { + var function = (MemberFunctionLeaf) _fixture.SimplePdb.GetLeafRecord(0x2392); + Assert.NotNull(function.Arguments); + Assert.IsAssignableFrom(function.Arguments!.Types[0]); + Assert.IsAssignableFrom(function.Arguments.Types[1]); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/MethodListLeafTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/MethodListLeafTest.cs new file mode 100644 index 000000000..53941c44f --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/MethodListLeafTest.cs @@ -0,0 +1,33 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; +using static AsmResolver.Symbols.Pdb.Leaves.CodeViewFieldAttributes; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class MethodListLeafTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public MethodListLeafTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void ReadNonIntroVirtualEntries() + { + var list = (MethodListLeaf) _fixture.SimplePdb.GetLeafRecord(0x2394); + var entries = list.Entries; + + Assert.Equal(new[] + { + Public | CompilerGenerated, + Public | CompilerGenerated, + Private, + Public, + }, entries.Select(e => e.Attributes)); + + Assert.All(entries, Assert.NotNull); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ModifierTypeRecordTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ModifierTypeRecordTest.cs new file mode 100644 index 000000000..e9a03fc40 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ModifierTypeRecordTest.cs @@ -0,0 +1,35 @@ +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class ModifierTypeRecordTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public ModifierTypeRecordTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void CreateNewType() + { + var type = new ModifierTypeRecord(new SimpleTypeRecord(SimpleTypeKind.Character8), ModifierAttributes.Const); + Assert.True(type.IsConst); + } + + [Fact] + public void ReadAttributes() + { + var type = (ModifierTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1011); + Assert.True(type.IsConst); + } + + [Fact] + public void ReadBaseType() + { + var type = (ModifierTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1011); + Assert.Equal(CodeViewLeafKind.Structure, type.BaseType.LeafKind); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/PointerTypeRecordTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/PointerTypeRecordTest.cs new file mode 100644 index 000000000..854c560b6 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/PointerTypeRecordTest.cs @@ -0,0 +1,60 @@ +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class PointerTypeRecordTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public PointerTypeRecordTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void CreateNewType() + { + var type = new PointerTypeRecord(new SimpleTypeRecord(SimpleTypeKind.Character8), PointerAttributes.Const, 4); + Assert.True(type.IsConst); + Assert.Equal(4, type.Size); + } + + [Fact] + public void UpdateKind() + { + var type = new PointerTypeRecord(new SimpleTypeRecord(SimpleTypeKind.Character8), PointerAttributes.Const, 4); + type.Kind = PointerAttributes.Near32; + Assert.Equal(PointerAttributes.Near32, type.Kind); + } + + [Fact] + public void UpdateMode() + { + var type = new PointerTypeRecord(new SimpleTypeRecord(SimpleTypeKind.Character8), PointerAttributes.Const, 4); + type.Mode = PointerAttributes.LValueReference; + Assert.Equal(PointerAttributes.LValueReference, type.Mode); + } + + [Fact] + public void UpdateSize() + { + var type = new PointerTypeRecord(new SimpleTypeRecord(SimpleTypeKind.Character8), PointerAttributes.Const, 4); + type.Size = 8; + Assert.Equal(8, type.Size); + } + + [Fact] + public void ReadAttributes() + { + var type = (PointerTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1012); + Assert.True(type.IsNear32); + } + + [Fact] + public void ReadBaseType() + { + var type = (PointerTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1012); + Assert.Equal(CodeViewLeafKind.Modifier, type.BaseType.LeafKind); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ProcedureTypeRecordTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ProcedureTypeRecordTest.cs new file mode 100644 index 000000000..4067d6f7d --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/ProcedureTypeRecordTest.cs @@ -0,0 +1,36 @@ +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class ProcedureTypeRecordTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public ProcedureTypeRecordTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void ReadReturnType() + { + var procedure = (ProcedureTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x18f7); + Assert.Equal(SimpleTypeKind.Void, Assert.IsAssignableFrom(procedure.ReturnType).Kind); + } + + [Fact] + public void ReadCallingConvention() + { + var procedure = (ProcedureTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x18f7); + Assert.Equal(CodeViewCallingConvention.NearStd, procedure.CallingConvention); + } + + [Fact] + public void ReadArguments() + { + var procedure = (ProcedureTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x18f7); + Assert.NotNull(procedure.Arguments); + Assert.Equal(2, procedure.Arguments!.Types.Count); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/SimpleTypeRecordTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/SimpleTypeRecordTest.cs new file mode 100644 index 000000000..ac2a784c7 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/SimpleTypeRecordTest.cs @@ -0,0 +1,17 @@ +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class SimpleTypeRecordTest +{ + [Theory] + [InlineData(0x00_75, SimpleTypeKind.UInt32, SimpleTypeMode.Direct)] + [InlineData(0x04_03, SimpleTypeKind.Void, SimpleTypeMode.NearPointer32)] + public void TypeIndexParsing(uint typeIndex, SimpleTypeKind kind, SimpleTypeMode mode) + { + var type = new SimpleTypeRecord(typeIndex); + Assert.Equal(kind, type.Kind); + Assert.Equal(mode, type.Mode); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/UnionTypeRecordTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/UnionTypeRecordTest.cs new file mode 100644 index 000000000..75f7c747c --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/UnionTypeRecordTest.cs @@ -0,0 +1,47 @@ +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class UnionTypeRecordTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public UnionTypeRecordTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void ReadSize() + { + var type = (UnionTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1855); + Assert.Equal(4ul, type.Size); + } + + [Fact] + public void ReadName() + { + var type = (UnionTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1855); + Assert.Equal("_LDT_ENTRY::", type.Name); + } + + [Fact] + public void ReadUniqueName() + { + var type = (UnionTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1855); + Assert.Equal(".?AT@_LDT_ENTRY@@", type.UniqueName); + } + + [Fact] + public void ReadFieldList() + { + var type = (UnionTypeRecord) _fixture.SimplePdb.GetLeafRecord(0x1855); + var fields = type.Fields!; + Assert.NotNull(fields); + Assert.IsAssignableFrom(fields.Entries[0]); + Assert.IsAssignableFrom(fields.Entries[1]); + Assert.IsAssignableFrom(fields.Entries[2]); + Assert.IsAssignableFrom(fields.Entries[3]); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Leaves/VTableShapeLeafTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/VTableShapeLeafTest.cs new file mode 100644 index 000000000..929183c76 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Leaves/VTableShapeLeafTest.cs @@ -0,0 +1,23 @@ +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Leaves; + +public class VTableShapeLeafTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public VTableShapeLeafTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Theory] + [InlineData(0x2416, new[] { VTableShapeEntry.Near })] + [InlineData(0x239e, new[] { VTableShapeEntry.Near32,VTableShapeEntry.Near32 })] + public void ReadEntries(uint typeIndex, VTableShapeEntry[] expectedEntries) + { + var shape = (VTableShapeLeaf) _fixture.SimplePdb.GetLeafRecord(typeIndex); + Assert.Equal(expectedEntries, shape.Entries); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs new file mode 100644 index 000000000..32a23f448 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs @@ -0,0 +1,213 @@ +using System; +using System.IO; +using System.Linq; +using AsmResolver.IO; +using AsmResolver.PE.File.Headers; +using AsmResolver.Symbols.Pdb.Metadata.Dbi; +using AsmResolver.Symbols.Pdb.Msf; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Metadata.Dbi; + +public class DbiStreamTest +{ + private DbiStream GetDbiStream(bool rebuild) + { + var file = MsfFile.FromBytes(Properties.Resources.SimpleDllPdb); + var dbiStream = DbiStream.FromReader(file.Streams[DbiStream.StreamIndex].CreateReader()); + + if (rebuild) + { + using var stream = new MemoryStream(); + dbiStream.Write(new BinaryStreamWriter(stream)); + dbiStream = DbiStream.FromReader(new BinaryStreamReader(stream.ToArray())); + } + + return dbiStream; + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Header(bool rebuild) + { + var dbiStream = GetDbiStream(rebuild); + + Assert.Equal(1u, dbiStream.Age); + Assert.Equal(DbiAttributes.None, dbiStream.Attributes); + Assert.Equal(MachineType.I386, dbiStream.Machine); + Assert.Equal(14, dbiStream.BuildMajorVersion); + Assert.Equal(29, dbiStream.BuildMinorVersion); + Assert.True(dbiStream.IsNewVersionFormat); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ModuleNames(bool rebuild) + { + var dbiStream = GetDbiStream(rebuild); + + Assert.Equal(new[] + { + "* CIL *", + "C:\\Users\\Admin\\source\\repos\\AsmResolver\\test\\TestBinaries\\Native\\SimpleDll\\Release\\dllmain.obj", + "C:\\Users\\Admin\\source\\repos\\AsmResolver\\test\\TestBinaries\\Native\\SimpleDll\\Release\\pch.obj", + "* Linker Generated Manifest RES *", + "Import:KERNEL32.dll", + "KERNEL32.dll", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\sehprolg4.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\gs_cookie.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\gs_report.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\gs_support.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\guard_support.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\loadcfg.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\dyn_tls_init.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\ucrt_detection.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\cpu_disp.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\chandler4gs.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\secchk.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\argv_mode.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\default_local_stdio_options.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\tncleanup.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\dll_dllmain.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\initializers.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\utility.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\ucrt_stubs.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\utility_desktop.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\initsect.obj", + "D:\\a\\_work\\1\\s\\Intermediate\\vctools\\msvcrt.nativeproj_110336922\\objr\\x86\\x86_exception_filter.obj", + "VCRUNTIME140.dll", + "Import:VCRUNTIME140.dll", + "Import:api-ms-win-crt-runtime-l1-1-0.dll", + "api-ms-win-crt-runtime-l1-1-0.dll", + "* Linker *", + }, dbiStream.Modules.Select(m => m.ModuleName?.Value)); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void SectionContributions(bool rebuild) + { + var dbiStream = GetDbiStream(rebuild); + + Assert.Equal(new (ushort, uint)[] + { + (1, 1669053862), (16, 2162654757), (20, 1635644926), (20, 3159649454), (20, 1649652954), (20, 3877379438), + (20, 4262788820), (20, 199934614), (8, 4235719287), (8, 1374843914), (9, 4241735292), (9, 2170796787), + (19, 1300950661), (19, 3968158929), (18, 3928463356), (18, 3928463356), (18, 2109213706), (22, 1457516325), + (22, 3939645857), (22, 1393694582), (22, 546064581), (22, 1976627334), (22, 513172946), (22, 25744891), + (22, 1989765812), (22, 2066266302), (22, 3810887196), (22, 206965504), (22, 647717352), (22, 3911072265), + (22, 3290064241), (12, 3928463356), (24, 2717331243), (24, 3687876222), (25, 2318145338), (25, 2318145338), + (6, 542071654), (15, 1810708069), (10, 3974941622), (14, 1150179208), (17, 2709606169), (13, 2361171624), + (28, 0), (28, 0), (28, 0), (29, 0), (29, 0), (29, 0), (29, 0), (29, 0), (29, 0), (29, 0), (29, 0), + (23, 3467414241), (23, 4079273803), (26, 1282639619), (4, 0), (4, 0), (4, 0), (4, 0), (4, 0), (4, 0), + (4, 0), (4, 0), (4, 0), (4, 0), (4, 0), (5, 0), (28, 0), (28, 0), (28, 0), (27, 0), (29, 0), (29, 0), + (29, 0), (29, 0), (29, 0), (29, 0), (29, 0), (29, 0), (30, 0), (10, 2556510175), (21, 2556510175), + (21, 2556510175), (21, 2556510175), (21, 2556510175), (21, 2556510175), (21, 2556510175), (21, 2556510175), + (21, 2556510175), (20, 2556510175), (8, 4117779887), (31, 0), (11, 525614319), (31, 0), (31, 0), (31, 0), + (31, 0), (31, 0), (25, 2556510175), (25, 2556510175), (25, 2556510175), (25, 2556510175), (20, 3906165615), + (20, 1185345766), (20, 407658226), (22, 2869884627), (27, 0), (30, 0), (5, 0), (27, 0), (4, 0), (4, 0), + (4, 0), (4, 0), (4, 0), (4, 0), (4, 0), (4, 0), (4, 0), (4, 0), (4, 0), (5, 0), (28, 0), (28, 0), (28, 0), + (27, 0), (29, 0), (29, 0), (29, 0), (29, 0), (29, 0), (29, 0), (29, 0), (29, 0), (30, 0), (28, 0), (28, 0), + (28, 0), (27, 0), (29, 0), (29, 0), (29, 0), (29, 0), (29, 0), (29, 0), (29, 0), (29, 0), (30, 0), (4, 0), + (4, 0), (4, 0), (4, 0), (4, 0), (4, 0), (4, 0), (4, 0), (4, 0), (4, 0), (4, 0), (5, 0), (7, 4096381681), + (22, 454268333), (14, 1927129959), (23, 1927129959), (20, 0), (8, 0), (19, 0), (18, 0), (18, 0), (22, 0), + (24, 0), (10, 0), (14, 0), (2, 0), (31, 0), (3, 0), (3, 0) + }, dbiStream.SectionContributions.Select(x => (x.ModuleIndex, x.DataCrc))); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void SectionMaps(bool rebuild) + { + var dbiStream = GetDbiStream(rebuild); + + Assert.Equal(new (ushort, ushort, ushort, ushort, ushort, ushort, uint, uint)[] + { + (0x010d, 0x0000, 0x0000, 0x0001, 0xffff, 0xffff, 0x00000000, 0x00000ce8), + (0x0109, 0x0000, 0x0000, 0x0002, 0xffff, 0xffff, 0x00000000, 0x00000834), + (0x010b, 0x0000, 0x0000, 0x0003, 0xffff, 0xffff, 0x00000000, 0x00000394), + (0x0109, 0x0000, 0x0000, 0x0004, 0xffff, 0xffff, 0x00000000, 0x000000f8), + (0x0109, 0x0000, 0x0000, 0x0005, 0xffff, 0xffff, 0x00000000, 0x0000013c), + (0x0208, 0x0000, 0x0000, 0x0000, 0xffff, 0xffff, 0x00000000, 0xffffffff), + }, + dbiStream.SectionMaps.Select(m => ((ushort) + m.Attributes, m.LogicalOverlayNumber, m.Group, m.Frame, + m.SectionName, m.ClassName, m.Offset, m.SectionLength))); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void SourceFiles(bool rebuild) + { + var dbiStream = GetDbiStream(rebuild); + + string[][] firstThreeActualFileLists = dbiStream.SourceFiles + .Take(3) + .Select(x => x + .Select(y => y.ToString()) + .ToArray() + ).ToArray(); + + Assert.Equal(new[] + { + Array.Empty(), + new[] + { + @"C:\Users\Admin\source\repos\AsmResolver\test\TestBinaries\Native\SimpleDll\pch.h", + @"C:\Users\Admin\source\repos\AsmResolver\test\TestBinaries\Native\SimpleDll\dllmain.cpp", + @"C:\Users\Admin\source\repos\AsmResolver\test\TestBinaries\Native\SimpleDll\Release\SimpleDll.pch", + }, + new[] + { + @"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\um\winuser.h", + @"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\shared\basetsd.h", + @"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\um\winbase.h", + @"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\shared\stralign.h", + @"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\shared\guiddef.h", + @"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\shared\winerror.h", + @"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\ucrt\corecrt_wstring.h", + @"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\um\processthreadsapi.h", + @"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\um\winnt.h", + @"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\ucrt\ctype.h", + @"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\ucrt\string.h", + @"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\ucrt\corecrt_memory.h", + @"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\um\memoryapi.h", + @"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\ucrt\corecrt_memcpy_s.h", + @"C:\Users\Admin\source\repos\AsmResolver\test\TestBinaries\Native\SimpleDll\Release\SimpleDll.pch", + } + }, + firstThreeActualFileLists); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ExtraDebugIndices(bool rebuild) + { + var dbiStream = GetDbiStream(rebuild); + + Assert.Equal(new ushort[] + { + 0x7, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xB, 0xFFFF, 0xFFFF, 0xFFFF, 0xD, 0xFFFF + }, dbiStream.ExtraStreamIndices); + } + + [Fact] + public void SizeCalculation() + { + var file = MsfFile.FromBytes(Properties.Resources.SimpleDllPdb); + var infoStream = DbiStream.FromReader(file.Streams[DbiStream.StreamIndex].CreateReader()); + + uint calculatedSize = infoStream.GetPhysicalSize(); + + using var stream = new MemoryStream(); + infoStream.Write(new BinaryStreamWriter(stream)); + + Assert.Equal(stream.Length, calculatedSize); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Info/InfoStreamTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Info/InfoStreamTest.cs new file mode 100644 index 000000000..31f17c82e --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Info/InfoStreamTest.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.IO; +using AsmResolver.IO; +using AsmResolver.Symbols.Pdb.Metadata.Info; +using AsmResolver.Symbols.Pdb.Msf; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Metadata.Info; + +public class InfoStreamTest +{ + private static InfoStream GetInfoStream(bool rebuild) + { + var file = MsfFile.FromBytes(Properties.Resources.SimpleDllPdb); + var infoStream = InfoStream.FromReader(file.Streams[InfoStream.StreamIndex].CreateReader()); + + if (rebuild) + { + using var stream = new MemoryStream(); + infoStream.Write(new BinaryStreamWriter(stream)); + infoStream = InfoStream.FromReader(new BinaryStreamReader(stream.ToArray())); + } + + return infoStream; + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Header(bool rebuild) + { + var infoStream = GetInfoStream(rebuild); + + Assert.Equal(InfoStreamVersion.VC70, infoStream.Version); + Assert.Equal(1u, infoStream.Age); + Assert.Equal(Guid.Parse("205dc366-d8f8-4175-8e06-26dd76722df5"), infoStream.UniqueId); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void NameTable(bool rebuild) + { + var infoStream = GetInfoStream(rebuild); + + Assert.Equal(new Dictionary + { + ["/UDTSRCLINEUNDONE"] = 48, + ["/src/headerblock"] = 46, + ["/LinkInfo"] = 5, + ["/TMCache"] = 6, + ["/names"] = 12 + }, infoStream.StreamIndices); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void FeatureCodes(bool rebuild) + { + var infoStream = GetInfoStream(rebuild); + + Assert.Equal(new[] {PdbFeature.VC140}, infoStream.Features); + } + + [Fact] + public void SizeCalculation() + { + var file = MsfFile.FromBytes(Properties.Resources.SimpleDllPdb); + var infoStream = InfoStream.FromReader(file.Streams[InfoStream.StreamIndex].CreateReader()); + + uint calculatedSize = infoStream.GetPhysicalSize(); + + using var stream = new MemoryStream(); + infoStream.Write(new BinaryStreamWriter(stream)); + + Assert.Equal(stream.Length, calculatedSize); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Metadata/PdbHashTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/PdbHashTest.cs new file mode 100644 index 000000000..5ca3e791f --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/PdbHashTest.cs @@ -0,0 +1,18 @@ +using AsmResolver.Symbols.Pdb.Metadata; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Metadata; + +public class PdbHashTest +{ + [Theory] + [InlineData("/UDTSRCLINEUNDONE", 0x23296bb2)] + [InlineData("/src/headerblock", 0x2b237ecd)] + [InlineData("/LinkInfo", 0x282209ed)] + [InlineData("/TMCache", 0x2621d5e9)] + [InlineData("/names", 0x6d6cfc21)] + public void HashV1(string value, uint expected) + { + Assert.Equal(expected, PdbHash.ComputeV1(value)); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/MockPdbFixture.cs b/test/AsmResolver.Symbols.Pdb.Tests/MockPdbFixture.cs new file mode 100644 index 000000000..36218c414 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/MockPdbFixture.cs @@ -0,0 +1,14 @@ +namespace AsmResolver.Symbols.Pdb.Tests; + +public class MockPdbFixture +{ + public PdbImage SimplePdb + { + get; + } = PdbImage.FromBytes(Properties.Resources.SimpleDllPdb); + + public PdbImage MyTestApplication + { + get; + } = PdbImage.FromBytes(Properties.Resources.MyTestApplication); +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Msf/MsfFileTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Msf/MsfFileTest.cs new file mode 100644 index 000000000..b17cd4b36 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Msf/MsfFileTest.cs @@ -0,0 +1,27 @@ +using System.IO; +using System.Linq; +using AsmResolver.Symbols.Pdb.Msf; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Msf; + +public class MsfFileTest +{ + [Fact] + public void RoundTrip() + { + var file = MsfFile.FromBytes(Properties.Resources.SimpleDllPdb); + + using var stream = new MemoryStream(); + file.Write(stream); + + var newFile = MsfFile.FromBytes(stream.ToArray()); + + Assert.Equal(file.BlockSize, newFile.BlockSize); + Assert.Equal(file.Streams.Count, newFile.Streams.Count); + Assert.All(Enumerable.Range(0, file.Streams.Count), i => + { + Assert.Equal(file.Streams[i].CreateReader().ReadToEnd(), newFile.Streams[i].CreateReader().ReadToEnd());; + }); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Msf/MsfStreamDataSourceTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Msf/MsfStreamDataSourceTest.cs new file mode 100644 index 000000000..c61fa8dc1 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Msf/MsfStreamDataSourceTest.cs @@ -0,0 +1,79 @@ +using System; +using System.Linq; +using AsmResolver.IO; +using AsmResolver.Symbols.Pdb.Msf; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Msf; + +public class MsfStreamDataSourceTest +{ + [Fact] + public void EmptyStream() + { + var source = new MsfStreamDataSource(0, 0x200, Array.Empty()); + + byte[] buffer = new byte[0x1000]; + int readCount = source.ReadBytes(0, buffer, 0, buffer.Length); + Assert.Equal(0, readCount); + Assert.All(buffer, b => Assert.Equal(0, b)); + } + + [Theory] + [InlineData(0x200, 0x200)] + [InlineData(0x200, 0x100)] + public void StreamWithOneBlock(int blockSize, int actualSize) + { + byte[] block = new byte[blockSize]; + for (int i = 0; i < blockSize; i++) + block[i] = (byte) (i & 0xFF); + + var source = new MsfStreamDataSource((ulong) actualSize, (uint) blockSize, new[] {block}); + + byte[] buffer = new byte[0x1000]; + int readCount = source.ReadBytes(0, buffer, 0, buffer.Length); + Assert.Equal(actualSize, readCount); + Assert.Equal(block.Take(actualSize), buffer.Take(actualSize)); + } + + [Theory] + [InlineData(0x200, 0x400)] + [InlineData(0x200, 0x300)] + public void StreamWithTwoBlocks(int blockSize, int actualSize) + { + byte[] block1 = new byte[blockSize]; + for (int i = 0; i < blockSize; i++) + block1[i] = (byte) 'A'; + + byte[] block2 = new byte[blockSize]; + for (int i = 0; i < blockSize; i++) + block2[i] = (byte) 'B'; + + var source = new MsfStreamDataSource((ulong) actualSize, (uint) blockSize, new[] {block1, block2}); + + byte[] buffer = new byte[0x1000]; + int readCount = source.ReadBytes(0, buffer, 0, buffer.Length); + Assert.Equal(actualSize, readCount); + Assert.Equal(block1.Concat(block2).Take(actualSize), buffer.Take(actualSize)); + } + + [Theory] + [InlineData(0x200, 0x400)] + public void ReadInMiddleOfBlock(int blockSize, int actualSize) + { + byte[] block1 = new byte[blockSize]; + for (int i = 0; i < blockSize; i++) + block1[i] = (byte) ((i*2) & 0xFF); + + byte[] block2 = new byte[blockSize]; + for (int i = 0; i < blockSize; i++) + block2[i] = (byte) ((i * 2 + 1) & 0xFF); + + var source = new MsfStreamDataSource((ulong) actualSize, (uint) blockSize, new[] {block1, block2}); + + byte[] buffer = new byte[blockSize]; + int readCount = source.ReadBytes((ulong) blockSize / 4, buffer, 0, blockSize); + Assert.Equal(blockSize, readCount); + Assert.Equal(block1.Skip(blockSize / 4).Concat(block2).Take(blockSize), buffer); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/PdbImageTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/PdbImageTest.cs new file mode 100644 index 000000000..b1e8cfc2d --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/PdbImageTest.cs @@ -0,0 +1,35 @@ +using AsmResolver.Symbols.Pdb.Leaves; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests; + +public class PdbImageTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public PdbImageTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Theory] + [InlineData(0x00_75, SimpleTypeKind.UInt32, SimpleTypeMode.Direct)] + [InlineData(0x04_03, SimpleTypeKind.Void, SimpleTypeMode.NearPointer32)] + public void SimpleTypeLookup(uint typeIndex, SimpleTypeKind kind, SimpleTypeMode mode) + { + var type = Assert.IsAssignableFrom(_fixture.SimplePdb.GetLeafRecord(typeIndex)); + Assert.Equal(kind, type.Kind); + Assert.Equal(mode, type.Mode); + } + + [Fact] + public void SimpleTypeLookupTwiceShouldCache() + { + var image = _fixture.SimplePdb; + + var type = image.GetLeafRecord(0x00_75); + var type2 = image.GetLeafRecord(0x00_75); + + Assert.Same(type, type2); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.Symbols.Pdb.Tests/Properties/Resources.Designer.cs new file mode 100644 index 000000000..8321cc22d --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Properties/Resources.Designer.cs @@ -0,0 +1,62 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AsmResolver.Symbols.Pdb.Tests.Properties { + using System; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static System.Resources.ResourceManager resourceMan; + + private static System.Globalization.CultureInfo resourceCulture; + + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Resources.ResourceManager ResourceManager { + get { + if (object.Equals(null, resourceMan)) { + System.Resources.ResourceManager temp = new System.Resources.ResourceManager("AsmResolver.Symbols.Pdb.Tests.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + internal static byte[] SimpleDllPdb { + get { + object obj = ResourceManager.GetObject("SimpleDllPdb", resourceCulture); + return ((byte[])(obj)); + } + } + + internal static byte[] MyTestApplication { + get { + object obj = ResourceManager.GetObject("MyTestApplication", resourceCulture); + return ((byte[])(obj)); + } + } + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Properties/Resources.resx b/test/AsmResolver.Symbols.Pdb.Tests/Properties/Resources.resx new file mode 100644 index 000000000..406c6d277 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Properties/Resources.resx @@ -0,0 +1,27 @@ + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\SimpleDll.pdb;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\MyTestApplication.pdb;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/ConstantTypeTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/ConstantTypeTest.cs new file mode 100644 index 000000000..a9cfdfbf6 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/ConstantTypeTest.cs @@ -0,0 +1,34 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Leaves; +using AsmResolver.Symbols.Pdb.Records; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Records; + +public class ConstantTypeTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public ConstantTypeTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void Name() + { + Assert.Equal("JOB_OBJECT_IO_RATE_CONTROL_STANDALONE_VOLUME", _fixture.SimplePdb.Symbols.OfType().First().Name); + } + + [Fact] + public void Type() + { + Assert.Equal(CodeViewLeafKind.Enum, _fixture.SimplePdb.Symbols.OfType().First().Type.LeafKind); + } + + [Fact] + public void Value() + { + Assert.Equal(2, _fixture.SimplePdb.Symbols.OfType().First().Value); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/PublicSymbolTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/PublicSymbolTest.cs new file mode 100644 index 000000000..dc16222e6 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/PublicSymbolTest.cs @@ -0,0 +1,21 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Records; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Records; + +public class PublicSymbolTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public PublicSymbolTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void Name() + { + Assert.Equal("___enclave_config", _fixture.SimplePdb.Symbols.OfType().First().Name); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Records/UserDefinedTypeTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Records/UserDefinedTypeTest.cs new file mode 100644 index 000000000..bc0e8614e --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Records/UserDefinedTypeTest.cs @@ -0,0 +1,38 @@ +using System.Linq; +using AsmResolver.Symbols.Pdb.Leaves; +using AsmResolver.Symbols.Pdb.Records; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Records; + +public class UserDefinedTypeTest : IClassFixture +{ + private readonly MockPdbFixture _fixture; + + public UserDefinedTypeTest(MockPdbFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void Name() + { + var udt = _fixture.SimplePdb.Symbols.OfType().First(); + Assert.Equal("UINT", udt.Name); + } + + [Fact] + public void Type() + { + var udt = _fixture.SimplePdb.Symbols.OfType().First(); + var type = Assert.IsAssignableFrom(udt.Type); + Assert.Equal(SimpleTypeKind.UInt32, type.Kind); + } + + [Fact] + public void Type2() + { + var udt = _fixture.SimplePdb.Symbols.OfType().ElementAt(1); + Assert.Equal(CodeViewLeafKind.Pointer, udt.Type.LeafKind); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Resources/.gitignore b/test/AsmResolver.Symbols.Pdb.Tests/Resources/.gitignore new file mode 100644 index 000000000..bd46a47b5 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Resources/.gitignore @@ -0,0 +1 @@ +!*.pdb diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Resources/MyTestApplication.pdb b/test/AsmResolver.Symbols.Pdb.Tests/Resources/MyTestApplication.pdb new file mode 100644 index 000000000..abb3d2833 Binary files /dev/null and b/test/AsmResolver.Symbols.Pdb.Tests/Resources/MyTestApplication.pdb differ diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Resources/SimpleDll.pdb b/test/AsmResolver.Symbols.Pdb.Tests/Resources/SimpleDll.pdb new file mode 100644 index 000000000..2a5f3d4ad Binary files /dev/null and b/test/AsmResolver.Symbols.Pdb.Tests/Resources/SimpleDll.pdb differ diff --git a/test/AsmResolver.Tests/AsmResolver.Tests.csproj b/test/AsmResolver.Tests/AsmResolver.Tests.csproj index 82f9cb9c6..6d9327ffb 100644 --- a/test/AsmResolver.Tests/AsmResolver.Tests.csproj +++ b/test/AsmResolver.Tests/AsmResolver.Tests.csproj @@ -8,8 +8,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -17,6 +17,7 @@ + diff --git a/test/AsmResolver.Tests/Collections/RefListTest.cs b/test/AsmResolver.Tests/Collections/RefListTest.cs index 8c7b5f201..5f7c0adae 100644 --- a/test/AsmResolver.Tests/Collections/RefListTest.cs +++ b/test/AsmResolver.Tests/Collections/RefListTest.cs @@ -1,3 +1,4 @@ +using System; using AsmResolver.Collections; using Xunit; @@ -11,6 +12,47 @@ public void EmptyList() Assert.Empty(new RefList()); } + [Fact] + public void SetCapacityToCount() + { + var list = new RefList + { + 1, + 2, + 3 + }; + + list.Capacity = 3; + Assert.True(list.Capacity >= 3); + } + + [Fact] + public void SetCapacityToLargerThanCount() + { + var list = new RefList + { + 1, + 2, + 3 + }; + + list.Capacity = 6; + Assert.True(list.Capacity >= 6); + } + + [Fact] + public void SetCapacityToSmallerThanCountShouldThrow() + { + var list = new RefList + { + 1, + 2, + 3 + }; + + Assert.Throws(() => list.Capacity = 2); + } + [Fact] public void AddItemShouldUpdateVersion() { diff --git a/test/AsmResolver.Tests/IO/BinaryStreamReaderTest.cs b/test/AsmResolver.Tests/IO/BinaryStreamReaderTest.cs index 0a3757d56..2c4c201c4 100644 --- a/test/AsmResolver.Tests/IO/BinaryStreamReaderTest.cs +++ b/test/AsmResolver.Tests/IO/BinaryStreamReaderTest.cs @@ -11,7 +11,7 @@ public class BinaryStreamReaderTest [Fact] public void EmptyArray() { - var reader = ByteArrayDataSource.CreateReader(new byte[0]); + var reader = new BinaryStreamReader(new byte[0]); Assert.Equal(0u, reader.Length); Assert.Throws(() => reader.ReadByte()); @@ -21,7 +21,7 @@ public void EmptyArray() [Fact] public void ReadByte() { - var reader = ByteArrayDataSource.CreateReader(new byte[] + var reader = new BinaryStreamReader(new byte[] { 0x80, 0x80 @@ -37,7 +37,7 @@ public void ReadByte() [Fact] public void ReadInt16() { - var reader = ByteArrayDataSource.CreateReader(new byte[] + var reader = new BinaryStreamReader(new byte[] { 0x01, 0x80, 0x02, 0x80 @@ -52,7 +52,7 @@ public void ReadInt16() [Fact] public void ReadInt32() { - var reader = ByteArrayDataSource.CreateReader(new byte[] + var reader = new BinaryStreamReader(new byte[] { 0x04, 0x03, 0x02, 0x81, 0x08, 0x07, 0x06, 0x85 @@ -68,7 +68,7 @@ public void ReadInt32() [Fact] public void ReadInt64() { - var reader = ByteArrayDataSource.CreateReader(new byte[] + var reader = new BinaryStreamReader(new byte[] { 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x80, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x88, @@ -90,14 +90,14 @@ public void ReadBinaryFormatterString(string value) var writer = new BinaryWriter(stream, Encoding.UTF8); writer.Write(value); - var reader = ByteArrayDataSource.CreateReader(stream.ToArray()); + var reader = new BinaryStreamReader(stream.ToArray()); Assert.Equal(value, reader.ReadBinaryFormatterString()); } [Fact] public void NewForkSubRange() { - var reader = ByteArrayDataSource.CreateReader(new byte[] + var reader = new BinaryStreamReader(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }); @@ -112,7 +112,7 @@ public void NewForkSubRange() [Fact] public void NewForkInvalidStart() { - var reader = ByteArrayDataSource.CreateReader(new byte[] + var reader = new BinaryStreamReader(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }); @@ -123,7 +123,7 @@ public void NewForkInvalidStart() [Fact] public void NewForkTooLong() { - var reader = ByteArrayDataSource.CreateReader(new byte[] + var reader = new BinaryStreamReader(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }); @@ -134,7 +134,7 @@ public void NewForkTooLong() [Fact] public void ForkReadsSameData() { - var reader = ByteArrayDataSource.CreateReader(new byte[] + var reader = new BinaryStreamReader(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }); @@ -146,7 +146,7 @@ public void ForkReadsSameData() [Fact] public void ForkMovesIndependentOfOriginal() { - var reader = ByteArrayDataSource.CreateReader(new byte[] + var reader = new BinaryStreamReader(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }); @@ -161,7 +161,7 @@ public void ForkMovesIndependentOfOriginal() [Fact] public void ForkStartAtMiddle() { - var reader = ByteArrayDataSource.CreateReader(new byte[] + var reader = new BinaryStreamReader(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }); @@ -173,7 +173,7 @@ public void ForkStartAtMiddle() [Fact] public void ForkOfFork() { - var reader = ByteArrayDataSource.CreateReader(new byte[] + var reader = new BinaryStreamReader(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }); diff --git a/test/AsmResolver.Tests/IO/BinaryStreamWriterTest.cs b/test/AsmResolver.Tests/IO/BinaryStreamWriterTest.cs index adc93d2d1..2c646ed23 100644 --- a/test/AsmResolver.Tests/IO/BinaryStreamWriterTest.cs +++ b/test/AsmResolver.Tests/IO/BinaryStreamWriterTest.cs @@ -84,7 +84,7 @@ public void Write7BitEncodedInt32(int value) writer.Write7BitEncodedInt32(value); - var reader = ByteArrayDataSource.CreateReader(stream.ToArray()); + var reader = new BinaryStreamReader(stream.ToArray()); Assert.Equal(value, reader.Read7BitEncodedInt32()); } diff --git a/test/AsmResolver.Tests/IO/DataSourceSliceTest.cs b/test/AsmResolver.Tests/IO/DataSourceSliceTest.cs new file mode 100644 index 000000000..937e79993 --- /dev/null +++ b/test/AsmResolver.Tests/IO/DataSourceSliceTest.cs @@ -0,0 +1,70 @@ +using System; +using System.Linq; +using AsmResolver.IO; +using Xunit; + +namespace AsmResolver.Tests.IO +{ + public class DataSourceSliceTest + { + private readonly IDataSource _source = new ByteArrayDataSource(new byte[] + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + }); + + [Fact] + public void EmptySlice() + { + var slice = new DataSourceSlice(_source, 0, 0); + Assert.Equal(0ul, slice.Length); + } + + [Fact] + public void SliceStart() + { + var slice = new DataSourceSlice(_source, 0, 5); + Assert.Equal(5ul, slice.Length); + Assert.All(Enumerable.Range(0, 5), i => Assert.Equal(slice[(ulong) i], _source[(ulong) i])); + Assert.Throws(() => slice[5]); + } + + [Fact] + public void SliceMiddle() + { + var slice = new DataSourceSlice(_source, 3, 5); + Assert.Equal(5ul, slice.Length); + Assert.All(Enumerable.Range(3, 5), i => Assert.Equal(slice[(ulong) i], _source[(ulong) i])); + Assert.Throws(() => slice[3 - 1]); + Assert.Throws(() => slice[3 + 5]); + } + + [Fact] + public void SliceEnd() + { + var slice = new DataSourceSlice(_source, 5, 5); + Assert.Equal(5ul, slice.Length); + Assert.All(Enumerable.Range(5, 5), i => Assert.Equal(slice[(ulong) i], _source[(ulong) i])); + Assert.Throws(() => slice[5 - 1]); + } + + [Fact] + public void ReadSlicedShouldReadUpToSliceAmountOfBytes() + { + var slice = new DataSourceSlice(_source, 3, 5); + + byte[] data1 = new byte[7]; + int originalCount = _source.ReadBytes(3, data1, 0, data1.Length); + Assert.Equal(7, originalCount); + + byte[] data2 = new byte[3]; + int newCount = slice.ReadBytes(3, data2, 0, data2.Length); + Assert.Equal(3, newCount); + Assert.Equal(data1.Take(3), data2.Take(3)); + + byte[] data3 = new byte[7]; + int newCount2 = slice.ReadBytes(3, data3, 0, data3.Length); + Assert.Equal(5, newCount2); + Assert.Equal(data1.Take(5), data3.Take(5)); + } + } +} diff --git a/test/AsmResolver.Tests/IO/MemoryStreamReaderPoolTest.cs b/test/AsmResolver.Tests/IO/MemoryStreamReaderPoolTest.cs new file mode 100644 index 000000000..b59f492d6 --- /dev/null +++ b/test/AsmResolver.Tests/IO/MemoryStreamReaderPoolTest.cs @@ -0,0 +1,136 @@ +using System; +using System.IO; +using System.Linq; +using AsmResolver.IO; +using Xunit; + +namespace AsmResolver.Tests.IO +{ + public class MemoryStreamReaderPoolTest + { + private readonly MemoryStreamWriterPool _pool = new(); + + [Fact] + public void RentShouldStartWithEmptyStream() + { + using (var rent = _pool.Rent()) + { + Assert.Equal(0u, rent.Writer.Length); + rent.Writer.WriteInt64(0x0123456789abcdef); + Assert.Equal(8u, rent.Writer.Length); + } + + using (var rent = _pool.Rent()) + { + Assert.Equal(0u, rent.Writer.Length); + } + } + + [Fact] + public void RentBeforeDisposeShouldUseNewBackendStream() + { + using var rent1 = _pool.Rent(); + using var rent2 = _pool.Rent(); + Assert.NotSame(rent1.Writer.BaseStream, rent2.Writer.BaseStream); + } + + [Fact] + public void RentAfterDisposeShouldReuseBackendStream() + { + Stream stream; + using (var rent = _pool.Rent()) + { + stream = rent.Writer.BaseStream; + } + + using (var rent = _pool.Rent()) + { + Assert.Same(stream, rent.Writer.BaseStream); + } + } + + [Fact] + public void RentAfterDisposeShouldReuseBackendStream2() + { + var rent1 = _pool.Rent(); + var rent2 = _pool.Rent(); + var stream2 = rent2.Writer.BaseStream; + var rent3 = _pool.Rent(); + + rent2.Dispose(); + var rent4 = _pool.Rent(); + + Assert.Same(stream2, rent4.Writer.BaseStream); + } + + [Fact] + public void GetFinalData() + { + byte[] value = Enumerable.Range(0, 255) + .Select(x => (byte) x) + .ToArray(); + + using var rent = _pool.Rent(); + rent.Writer.WriteBytes(value); + rent.Writer.WriteBytes(value); + Assert.Equal(value.Concat(value), rent.GetData()); + } + + [Fact] + public void UseAfterDisposeShouldThrow() + { + Assert.Throws(() => + { + var rent = _pool.Rent(); + rent.Dispose(); + rent.Writer.WriteInt64(0); + }); + + Assert.Throws(() => + { + var rent = _pool.Rent(); + rent.Dispose(); + return rent.GetData(); + }); + } + + [Fact] + public void DisposeTwiceShouldNotReturnTwice() + { + var rent1 = _pool.Rent(); + var stream = rent1.Writer.BaseStream; + + rent1.Dispose(); + rent1.Dispose(); + + var rent2 = _pool.Rent(); + var rent3 = _pool.Rent(); + + Assert.Same(stream, rent2.Writer.BaseStream); + Assert.NotSame(stream, rent3.Writer.BaseStream); + } + + [Fact] + public void GetDataShouldNotResultInSameInstance() + { + byte[] data1; + + using (var rent1 = _pool.Rent()) + { + rent1.Writer.WriteInt64(0x0123456789abcdef); + data1 = rent1.GetData(); + byte[] data2 = rent1.GetData(); + Assert.Equal(data1, data2); + Assert.NotSame(data1, data2); + } + + using (var rent2 = _pool.Rent()) + { + rent2.Writer.WriteInt64(0x0123456789abcdef); + byte[] data3 = rent2.GetData(); + Assert.Equal(data1, data3); + Assert.NotSame(data1, data3); + } + } + } +} diff --git a/test/AsmResolver.Tests/Listeners/CustomMemberClonerListener.cs b/test/AsmResolver.Tests/Listeners/CustomMemberClonerListener.cs new file mode 100644 index 000000000..0afb61f6d --- /dev/null +++ b/test/AsmResolver.Tests/Listeners/CustomMemberClonerListener.cs @@ -0,0 +1,12 @@ +using AsmResolver.DotNet; +using AsmResolver.DotNet.Cloning; +using System; + +namespace AsmResolver.Tests.Listeners +{ + public class CustomMemberClonerListener : MemberClonerListener + { + public override void OnClonedMethod(MethodDefinition original, MethodDefinition cloned) => + cloned.Name = $"Method_{original.Name}"; + } +} diff --git a/test/AsmResolver.Tests/SegmentBuilderTest.cs b/test/AsmResolver.Tests/SegmentBuilderTest.cs index d64aa1c78..be2aff523 100644 --- a/test/AsmResolver.Tests/SegmentBuilderTest.cs +++ b/test/AsmResolver.Tests/SegmentBuilderTest.cs @@ -8,8 +8,6 @@ public class SegmentBuilderTest { private static byte[] ToBytes(ISegment segment) { - segment.UpdateOffsets(0, 0); - using var stream = new MemoryStream(); var writer = new BinaryStreamWriter(stream); @@ -22,7 +20,7 @@ public void EmptyNoAlignment() { var collection = new SegmentBuilder(); - collection.UpdateOffsets(0x400, 0x1000); + collection.UpdateOffsets(new RelocationParameters(0x400000, 0x400, 0x1000, false)); Assert.Equal(0x400u, collection.Offset); Assert.Equal(0x1000u, collection.Rva); @@ -39,7 +37,7 @@ public void SingleItemNoAlignment() var collection = new SegmentBuilder {segment}; - collection.UpdateOffsets(0x400, 0x1000); + collection.UpdateOffsets(new RelocationParameters(0x400000, 0x400, 0x1000, false)); Assert.Equal(0x400u, segment.Offset); Assert.Equal(0x1000u, segment.Rva); @@ -61,7 +59,7 @@ public void MultipleItemsNoAlignment() var collection = new SegmentBuilder {segment1, segment2, segment3}; - collection.UpdateOffsets(0x400, 0x1000); + collection.UpdateOffsets(new RelocationParameters(0x400000, 0x400, 0x1000, false)); Assert.Equal(0x400u, segment1.Offset); Assert.Equal(0x1000u, segment1.Rva); @@ -88,7 +86,7 @@ public void SingleItemAlignment() var builder = new SegmentBuilder {segment}; - builder.UpdateOffsets(0x400, 0x1000); + builder.UpdateOffsets(new RelocationParameters(0x400000, 0x400, 0x1000, false)); Assert.Equal(0x400u, segment.Offset); @@ -115,7 +113,7 @@ public void MultipleItemsAlignment() {segment3, 8} }; - builder.UpdateOffsets(0x400, 0x1000); + builder.UpdateOffsets(new RelocationParameters(0x400000, 0x400, 0x1000, false)); Assert.Equal(0x400u, segment1.Offset); Assert.Equal(0x1000u, segment1.Rva); diff --git a/test/AsmResolver.Tests/Utf8StringTest.cs b/test/AsmResolver.Tests/Utf8StringTest.cs index 9de95a07e..46a9d8f36 100644 --- a/test/AsmResolver.Tests/Utf8StringTest.cs +++ b/test/AsmResolver.Tests/Utf8StringTest.cs @@ -81,7 +81,7 @@ public void ByteConcat(byte[]? a, byte[]? b) { Utf8String? s1 = a; Utf8String? s2 = b; - Assert.Equal((a ?? Array.Empty()).Concat(b ?? Array.Empty()), (s1 + s2)?.GetBytes()); + Assert.Equal((a ?? Array.Empty()).Concat(b ?? Array.Empty()), (s1 + s2).GetBytes()); } [Theory] diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/PlatformInvoke.cs b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/PlatformInvoke.cs index 46641b2aa..755903da3 100644 --- a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/PlatformInvoke.cs +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/PlatformInvoke.cs @@ -5,21 +5,21 @@ namespace AsmResolver.DotNet.TestCases.Methods { public class PlatformInvoke { - [DllImport("SomeDll.dll", EntryPoint = "SomeEntrypoint")] + [DllImport("SomeDll.dll", EntryPoint = "SomeEntryPoint")] public static extern void ExternalMethod(); - + [DllImport("SomeDll.dll")] public static extern void SimpleMarshaller([MarshalAs(UnmanagedType.Bool)] bool b); - + [DllImport("SomeDll.dll")] public static extern void LPArrayFixedSizeMarshaller([MarshalAs(UnmanagedType.LPArray, SizeConst = 10)] byte[] array); - + [DllImport("SomeDll.dll")] public static extern void LPArrayVariableSizeMarshaller([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] array, int count); - + [DllImport("SomeDll.dll")] public static extern void SafeArrayMarshaller([MarshalAs(UnmanagedType.SafeArray)] byte[] array); - + [DllImport("SomeDll.dll")] public static extern void SafeArrayMarshallerWithSubType([MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_UI1)] byte[] array); @@ -44,4 +44,4 @@ public static void NonImplementationMapMethod() { } } -} \ No newline at end of file +} diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/TDynamicMethod.cs b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/TDynamicMethod.cs index 3cd849478..9890d227b 100644 --- a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/TDynamicMethod.cs +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/TDynamicMethod.cs @@ -14,7 +14,7 @@ public static DynamicMethod GenerateDynamicMethod() // of Integer, and two parameters whose types are specified by // the array helloArgs. Create the method in the module that // defines the String class. - DynamicMethod hello = new DynamicMethod("Hello", + var hello = new DynamicMethod("Hello", typeof(int), helloArgs, typeof(string).Module); @@ -24,15 +24,18 @@ public static DynamicMethod GenerateDynamicMethod() Type[] writeStringArgs = {typeof(string)}; // Get the overload of Console.WriteLine that has one // String parameter. - MethodInfo writeString = typeof(Console).GetMethod("WriteLine", + var writeString = typeof(Console).GetMethod("WriteLine", writeStringArgs); // Get an ILGenerator and emit a body for the dynamic method, // using a stream size larger than the IL that will be // emitted. - ILGenerator il = hello.GetILGenerator(256); + var il = hello.GetILGenerator(256); + il.DeclareLocal(typeof(string)); // Load the first argument, which is a string, onto the stack. il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Stloc_0); + il.Emit(OpCodes.Ldloc_0); // Call the overload of Console.WriteLine that prints a string. il.EmitCall(OpCodes.Call, writeString, null); // The Hello method returns the value of the second argument; @@ -50,4 +53,4 @@ public static DynamicMethod GenerateDynamicMethod() } } -} \ No newline at end of file +} diff --git a/test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj b/test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj index 21604ba0d..329c82d3e 100644 --- a/test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj +++ b/test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj @@ -10,6 +10,6 @@ AnyCPU - + diff --git a/test/TestBinaries/DotNet/TheAnswer/TheAnswer.csproj b/test/TestBinaries/DotNet/TheAnswer/TheAnswer.csproj index 264964033..02d35264b 100644 --- a/test/TestBinaries/DotNet/TheAnswer/TheAnswer.csproj +++ b/test/TestBinaries/DotNet/TheAnswer/TheAnswer.csproj @@ -5,7 +5,7 @@ net47;netcoreapp3.1 - +