From 2239aa62d10ecd8ee5433aee0c8b205622aa99ca Mon Sep 17 00:00:00 2001 From: Jeremy Pritts Date: Mon, 27 Dec 2021 02:24:09 -0500 Subject: [PATCH 001/184] add documentation for generic instance methods --- docs/dotnet/generic-methods.rst | 34 +++++++++++++++++++++++++++++++++ docs/index.rst | 3 ++- 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 docs/dotnet/generic-methods.rst diff --git a/docs/dotnet/generic-methods.rst b/docs/dotnet/generic-methods.rst new file mode 100644 index 000000000..759aa09b2 --- /dev/null +++ b/docs/dotnet/generic-methods.rst @@ -0,0 +1,34 @@ +Generic Methods +=============== + +Non-Generic Methods on Generic Types +------------------------------------------ + +This section covers referencing methods such as ``System.Collections.Generic.List`1.Add``. They can be referenced with the ``MemberReference`` class. + +.. code-block:: csharp + + var listTypeRef = new TypeReference(corlibScope, "System.Collections.Generic", "List`1"); + + var listOfInt32 = new GenericInstanceTypeSignature(listTypeRef, + isValueType: false, + typeArguments: new[] { module.CorLibTypeFactory.Int32 }); + + var addMethodDefinition = listTypeRef.Resolve().Methods.Single(m => m.Name == "Add" && m.Parameters.Count == 1); + + var reference = new MemberReference(listOfInt32.ToTypeDefOrRef(), addMethodDefinition.Name, addMethodDefinition.Signature); + +Generic Methods on Non-Generic Types +------------------------------------------ + +This section covers referencing methods such as ``System.Array.Empty``. They can be referenced with the ``MethodSpecification`` class. + +.. code-block:: csharp + + var arrayRef = new TypeReference(corlibScope, "System", "Array"); + + var emptyMethodDefinition = arrayRef.Resolve().Methods.Single(m => m.Name == "Empty" && m.Parameters.Count == 0); + + var genericInstanceMethodSignature = new GenericInstanceMethodSignature(module.CorLibTypeFactory.Int32); + + var reference = new MethodSpecification(emptyMethodDefinition, genericInstanceMethodSignature); diff --git a/docs/index.rst b/docs/index.rst index b3fee3b95..6cdce53d9 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -55,10 +55,11 @@ Table of Contents: dotnet/member-tree dotnet/type-signatures dotnet/importing + dotnet/generic-methods dotnet/managed-method-bodies dotnet/unmanaged-method-bodies dotnet/managed-resources dotnet/cloning dotnet/token-allocation dotnet/type-memory-layout - dotnet/advanced-pe-image-building.rst \ No newline at end of file + dotnet/advanced-pe-image-building.rst From b36899ece69e6fea377d5bbeadcbffec11faabbf Mon Sep 17 00:00:00 2001 From: Jeremy Pritts Date: Mon, 27 Dec 2021 12:42:05 -0500 Subject: [PATCH 002/184] rename file to methods --- docs/dotnet/{generic-methods.rst => methods.rst} | 2 +- docs/index.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename docs/dotnet/{generic-methods.rst => methods.rst} (98%) diff --git a/docs/dotnet/generic-methods.rst b/docs/dotnet/methods.rst similarity index 98% rename from docs/dotnet/generic-methods.rst rename to docs/dotnet/methods.rst index 759aa09b2..8f65c1490 100644 --- a/docs/dotnet/generic-methods.rst +++ b/docs/dotnet/methods.rst @@ -1,4 +1,4 @@ -Generic Methods +Methods =============== Non-Generic Methods on Generic Types diff --git a/docs/index.rst b/docs/index.rst index 6cdce53d9..6e56f82d4 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -55,7 +55,7 @@ Table of Contents: dotnet/member-tree dotnet/type-signatures dotnet/importing - dotnet/generic-methods + dotnet/methods dotnet/managed-method-bodies dotnet/unmanaged-method-bodies dotnet/managed-resources From 5c98a9a2d0561f9950aa6e74a045f6054a01f796 Mon Sep 17 00:00:00 2001 From: JPaja Date: Fri, 11 Feb 2022 23:15:37 +0100 Subject: [PATCH 003/184] Add: Workspace Profiles --- .../DotNetWorkspace.cs | 36 ++----------- .../Profiles/DotNetTraversalProfile.cs | 50 +++++++++++++++++++ src/AsmResolver.Workspaces/AnalysisContext.cs | 3 +- src/AsmResolver.Workspaces/Workspace.cs | 36 +++++++++---- .../WorkspaceProfile.cs | 17 +++++++ 5 files changed, 99 insertions(+), 43 deletions(-) create mode 100644 src/AsmResolver.Workspaces.DotNet/Profiles/DotNetTraversalProfile.cs create mode 100644 src/AsmResolver.Workspaces/WorkspaceProfile.cs diff --git a/src/AsmResolver.Workspaces.DotNet/DotNetWorkspace.cs b/src/AsmResolver.Workspaces.DotNet/DotNetWorkspace.cs index 6706b0113..312a59ffe 100644 --- a/src/AsmResolver.Workspaces.DotNet/DotNetWorkspace.cs +++ b/src/AsmResolver.Workspaces.DotNet/DotNetWorkspace.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using AsmResolver.DotNet; using AsmResolver.DotNet.Code.Cil; using AsmResolver.DotNet.Collections; @@ -8,6 +8,7 @@ using AsmResolver.Workspaces.DotNet.Analyzers.Definition; using AsmResolver.Workspaces.DotNet.Analyzers.Reference; using AsmResolver.Workspaces.DotNet.Analyzers.Signature; +using AsmResolver.Workspaces.DotNet.Profiles; namespace AsmResolver.Workspaces.DotNet { @@ -21,38 +22,7 @@ public class DotNetWorkspace : Workspace /// public DotNetWorkspace() { - Analyzers.Register(typeof(AssemblyDefinition), new AssemblyAnalyzer()); - Analyzers.Register(typeof(ModuleDefinition), new ModuleAnalyzer()); - Analyzers.Register(typeof(TypeDefinition), new TypeAnalyzer()); - Analyzers.Register(typeof(MethodDefinition), new MethodAnalyzer()); - Analyzers.Register(typeof(MethodImplementation), new MethodImplementationAnalyzer()); - Analyzers.Register(typeof(IHasSemantics), new SemanticsImplementationAnalyzer()); - Analyzers.Register(typeof(TypeReference), new TypeReferenceAnalyzer()); - Analyzers.Register(typeof(MemberReference), new MemberReferenceAnalyzer()); - Analyzers.Register(typeof(IHasCustomAttribute), new HasCustomAttributeAnalyzer()); - Analyzers.Register(typeof(CustomAttribute), new CustomAttributeAnalyzer()); - Analyzers.Register(typeof(TypeSignature), new TypeSignatureAnalyzer()); - Analyzers.Register(typeof(MethodSignatureBase), new MethodSignatureBaseAnalyzer()); - Analyzers.Register(typeof(FieldSignature), new FieldSignatureAnalyzer()); - Analyzers.Register(typeof(FieldDefinition), new FieldAnalyzer()); - Analyzers.Register(typeof(PropertyDefinition), new PropertyAnalyzer()); - Analyzers.Register(typeof(EventDefinition), new EventAnalyzer()); - Analyzers.Register(typeof(IHasGenericParameters), new HasGenericParameterAnalyzer()); - Analyzers.Register(typeof(LocalVariablesSignature), new LocalVariablesSignatureAnalyzer()); - Analyzers.Register(typeof(IGenericArgumentsProvider), new GenericArgumentAnalyzer()); - Analyzers.Register(typeof(CilMethodBody), new CilMethodBodyAnalyzer()); - Analyzers.Register(typeof(CustomAttributeArgument), new CustomAttributeArgumentAnalyzer()); - Analyzers.Register(typeof(CustomAttributeNamedArgument), new CustomAttributeNamedArgumentAnalyzer()); - Analyzers.Register(typeof(AssemblyReference), new AssemblyReferenceAnalyzer()); - Analyzers.Register(typeof(TypeSpecification), new TypeSpecificationAnalyzer()); - Analyzers.Register(typeof(ExportedType), new ExportedTypeAnalyzer()); - Analyzers.Register(typeof(IHasSecurityDeclaration), new HasSecurityDeclarationAnalyzer()); - Analyzers.Register(typeof(SecurityDeclaration), new SecurityDeclarationAnalyzer()); - Analyzers.Register(typeof(CilExceptionHandler), new ExceptionHandlerAnalyzer()); - Analyzers.Register(typeof(CilLocalVariable), new CilLocalVariableAnalyzer()); - Analyzers.Register(typeof(StandAloneSignature), new StandaloneSignatureAnalyzer()); - Analyzers.Register(typeof(InterfaceImplementation), new InterfaceImplementationAnalyzer()); - Analyzers.Register(typeof(MethodSpecification), new MethodSpecificationAnalyzer()); + Profiles.Add(new DotNetTraversalProfile()); } /// diff --git a/src/AsmResolver.Workspaces.DotNet/Profiles/DotNetTraversalProfile.cs b/src/AsmResolver.Workspaces.DotNet/Profiles/DotNetTraversalProfile.cs new file mode 100644 index 000000000..f918b66b7 --- /dev/null +++ b/src/AsmResolver.Workspaces.DotNet/Profiles/DotNetTraversalProfile.cs @@ -0,0 +1,50 @@ +using AsmResolver.DotNet; +using AsmResolver.DotNet.Code.Cil; +using AsmResolver.DotNet.Signatures; +using AsmResolver.DotNet.Signatures.Types; +using AsmResolver.Workspaces.DotNet.Analyzers.Definition; +using AsmResolver.Workspaces.DotNet.Analyzers.Reference; +using AsmResolver.Workspaces.DotNet.Analyzers.Signature; + +namespace AsmResolver.Workspaces.DotNet.Profiles +{ + internal class DotNetTraversalProfile : WorksapceProfile + { + public DotNetTraversalProfile() + { + Analyzers.Register(typeof(AssemblyDefinition), new AssemblyAnalyzer()); + Analyzers.Register(typeof(ModuleDefinition), new ModuleAnalyzer()); + Analyzers.Register(typeof(TypeDefinition), new TypeAnalyzer()); + Analyzers.Register(typeof(MethodDefinition), new MethodAnalyzer()); + Analyzers.Register(typeof(MethodImplementation), new MethodImplementationAnalyzer()); + Analyzers.Register(typeof(IHasSemantics), new SemanticsImplementationAnalyzer()); + Analyzers.Register(typeof(TypeReference), new TypeReferenceAnalyzer()); + Analyzers.Register(typeof(MemberReference), new MemberReferenceAnalyzer()); + Analyzers.Register(typeof(IHasCustomAttribute), new HasCustomAttributeAnalyzer()); + Analyzers.Register(typeof(CustomAttribute), new CustomAttributeAnalyzer()); + Analyzers.Register(typeof(TypeSignature), new TypeSignatureAnalyzer()); + Analyzers.Register(typeof(MethodSignatureBase), new MethodSignatureBaseAnalyzer()); + Analyzers.Register(typeof(FieldSignature), new FieldSignatureAnalyzer()); + Analyzers.Register(typeof(FieldDefinition), new FieldAnalyzer()); + Analyzers.Register(typeof(PropertyDefinition), new PropertyAnalyzer()); + Analyzers.Register(typeof(EventDefinition), new EventAnalyzer()); + Analyzers.Register(typeof(IHasGenericParameters), new HasGenericParameterAnalyzer()); + Analyzers.Register(typeof(LocalVariablesSignature), new LocalVariablesSignatureAnalyzer()); + Analyzers.Register(typeof(IGenericArgumentsProvider), new GenericArgumentAnalyzer()); + Analyzers.Register(typeof(CilMethodBody), new CilMethodBodyAnalyzer()); + Analyzers.Register(typeof(CustomAttributeArgument), new CustomAttributeArgumentAnalyzer()); + Analyzers.Register(typeof(CustomAttributeNamedArgument), new CustomAttributeNamedArgumentAnalyzer()); + Analyzers.Register(typeof(AssemblyReference), new AssemblyReferenceAnalyzer()); + Analyzers.Register(typeof(TypeSpecification), new TypeSpecificationAnalyzer()); + Analyzers.Register(typeof(ExportedType), new ExportedTypeAnalyzer()); + Analyzers.Register(typeof(IHasSecurityDeclaration), new HasSecurityDeclarationAnalyzer()); + Analyzers.Register(typeof(SecurityDeclaration), new SecurityDeclarationAnalyzer()); + Analyzers.Register(typeof(CilExceptionHandler), new ExceptionHandlerAnalyzer()); + Analyzers.Register(typeof(CilLocalVariable), new CilLocalVariableAnalyzer()); + Analyzers.Register(typeof(StandAloneSignature), new StandaloneSignatureAnalyzer()); + Analyzers.Register(typeof(InterfaceImplementation), new InterfaceImplementationAnalyzer()); + Analyzers.Register(typeof(MethodSpecification), new MethodSpecificationAnalyzer()); + + } + } +} diff --git a/src/AsmResolver.Workspaces/AnalysisContext.cs b/src/AsmResolver.Workspaces/AnalysisContext.cs index 754e034cd..d14e83bcf 100644 --- a/src/AsmResolver.Workspaces/AnalysisContext.cs +++ b/src/AsmResolver.Workspaces/AnalysisContext.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; namespace AsmResolver.Workspaces { @@ -49,7 +50,7 @@ public ISet TraversedObjects /// true if there exists at least one analyzer that can analyze objects of the provided type, /// false otherwise. /// - public bool HasAnalyzers(Type type) => Workspace.Analyzers.HasAnalyzers(type); + public bool HasAnalyzers(Type type) => Workspace.Profiles.Any(profile => profile.Analyzers.HasAnalyzers(type)); /// /// Schedules the provided object if it was not scheduled before. diff --git a/src/AsmResolver.Workspaces/Workspace.cs b/src/AsmResolver.Workspaces/Workspace.cs index 723ccbe98..934fbbf75 100644 --- a/src/AsmResolver.Workspaces/Workspace.cs +++ b/src/AsmResolver.Workspaces/Workspace.cs @@ -1,4 +1,6 @@ -namespace AsmResolver.Workspaces +using System.Collections.Generic; + +namespace AsmResolver.Workspaces { /// /// Provides a base mechanism for indexing assemblies and their components. @@ -6,9 +8,9 @@ public abstract class Workspace { /// - /// Gets a collection of object analyzers that are used in this workspace. + /// Gets a ordered list of profiles for workspace analyzing. /// - public AnalyzerRepository Analyzers + public List Profiles { get; } = new(); @@ -27,16 +29,32 @@ public WorkspaceIndex Index /// The analysis context. protected void Analyze(AnalysisContext context) { - while (context.Agenda.Count > 0) + var traversedObjects = new HashSet(context.Agenda); + foreach (var profile in Profiles) { - var nextSubject = context.Agenda.Dequeue(); - var analyzers = Analyzers.GetAnalyzers(nextSubject.GetType()); - foreach (var analyzer in analyzers) + context.Agenda.Clear(); + foreach (object agenda in traversedObjects) + context.Agenda.Enqueue(agenda); + context.TraversedObjects.Clear(); + + while (context.Agenda.Count > 0) { - if (analyzer.CanAnalyze(context, nextSubject)) - analyzer.Analyze(context, nextSubject); + object nextSubject = context.Agenda.Dequeue(); + var analyzers = profile.Analyzers.GetAnalyzers(nextSubject.GetType()); + foreach (var analyzer in analyzers) + { + if (analyzer.CanAnalyze(context, nextSubject)) + analyzer.Analyze(context, nextSubject); + } } + + foreach (object traversedObject in context.TraversedObjects) + traversedObjects.Add(traversedObject); } + + context.TraversedObjects.Clear(); + foreach (object traversedObject in traversedObjects) + context.TraversedObjects.Add(traversedObject); } } } diff --git a/src/AsmResolver.Workspaces/WorkspaceProfile.cs b/src/AsmResolver.Workspaces/WorkspaceProfile.cs new file mode 100644 index 000000000..ede46922b --- /dev/null +++ b/src/AsmResolver.Workspaces/WorkspaceProfile.cs @@ -0,0 +1,17 @@ +namespace AsmResolver.Workspaces +{ + /// + /// Provides a base mechanism for storing and scheduling analyzers. + /// + public class WorksapceProfile + { + /// + /// Gets a collection of object analyzers. + /// + public AnalyzerRepository Analyzers + { + get; + } = new(); + + } +} From 760ec32ba3899fb3334c2044ca14a05ac9a0f12c Mon Sep 17 00:00:00 2001 From: JPaja Date: Fri, 11 Feb 2022 23:41:51 +0100 Subject: [PATCH 004/184] Add: Implementation profile --- .../Analyzers/Definition/MethodAnalyzer.cs | 21 +------------ .../ImplementationMethodAnalyzer.cs | 31 +++++++++++++++++++ .../ImplementationSemanticsAnalyzer.cs} | 4 +-- .../ImplementationUtilities.cs} | 4 +-- .../DotNetWorkspace.cs | 1 + .../Profiles/DotNetImplementationProfile.cs | 21 +++++++++++++ .../Profiles/DotNetTraversalProfile.cs | 9 ++++-- 7 files changed, 65 insertions(+), 26 deletions(-) create mode 100644 src/AsmResolver.Workspaces.DotNet/Analyzers/Implementation/ImplementationMethodAnalyzer.cs rename src/AsmResolver.Workspaces.DotNet/Analyzers/{Definition/SemanticsImplementationAnalyzer.cs => Implementation/ImplementationSemanticsAnalyzer.cs} (94%) rename src/AsmResolver.Workspaces.DotNet/Analyzers/{AnalyzerUtilities.cs => Implementation/ImplementationUtilities.cs} (95%) create mode 100644 src/AsmResolver.Workspaces.DotNet/Profiles/DotNetImplementationProfile.cs diff --git a/src/AsmResolver.Workspaces.DotNet/Analyzers/Definition/MethodAnalyzer.cs b/src/AsmResolver.Workspaces.DotNet/Analyzers/Definition/MethodAnalyzer.cs index 8e27f6de6..e3a687317 100644 --- a/src/AsmResolver.Workspaces.DotNet/Analyzers/Definition/MethodAnalyzer.cs +++ b/src/AsmResolver.Workspaces.DotNet/Analyzers/Definition/MethodAnalyzer.cs @@ -7,30 +7,12 @@ namespace AsmResolver.Workspaces.DotNet.Analyzers.Definition { /// - /// Analyzes a for implicit base definitions, such as abstract methods in - /// base classes or methods in implemented interfaces. + /// Provides a default implementation for an analyzer. /// public class MethodAnalyzer : ObjectAnalyzer { /// protected override void Analyze(AnalysisContext context, MethodDefinition subject) - { - ScheduleMembersForAnalysis(context, subject); - - if (!subject.IsVirtual) - return; - - var index = context.Workspace.Index; - var node = index.GetOrCreateNode(subject); - - foreach (var baseMethod in subject.FindBaseMethods(context.Workspace.Index)) - { - var candidateNode = index.GetOrCreateNode(baseMethod); - node.ForwardRelations.Add(DotNetRelations.ImplementationMethod, candidateNode); - } - } - - private static void ScheduleMembersForAnalysis(AnalysisContext context, MethodDefinition subject) { // Schedule parameters for analysis. if (context.HasAnalyzers(typeof(ParameterDefinition))) @@ -50,7 +32,6 @@ private static void ScheduleMembersForAnalysis(AnalysisContext context, MethodDe { context.ScheduleForAnalysis(subject.CilMethodBody); } - } } } diff --git a/src/AsmResolver.Workspaces.DotNet/Analyzers/Implementation/ImplementationMethodAnalyzer.cs b/src/AsmResolver.Workspaces.DotNet/Analyzers/Implementation/ImplementationMethodAnalyzer.cs new file mode 100644 index 000000000..5e1cec1bf --- /dev/null +++ b/src/AsmResolver.Workspaces.DotNet/Analyzers/Implementation/ImplementationMethodAnalyzer.cs @@ -0,0 +1,31 @@ +using System.Linq; +using AsmResolver.DotNet; +using AsmResolver.DotNet.Code.Cil; +using AsmResolver.DotNet.Collections; +using AsmResolver.DotNet.Signatures; + +namespace AsmResolver.Workspaces.DotNet.Analyzers.Implementation +{ + /// + /// Analyzes a for implicit base definitions, such as abstract methods in + /// base classes or methods in implemented interfaces. + /// + public class ImplementationMethodAnalyzer : ObjectAnalyzer + { + /// + protected override void Analyze(AnalysisContext context, MethodDefinition subject) + { + if (!subject.IsVirtual) + return; + + var index = context.Workspace.Index; + var node = index.GetOrCreateNode(subject); + + foreach (var baseMethod in subject.FindBaseMethods(context.Workspace.Index)) + { + var candidateNode = index.GetOrCreateNode(baseMethod); + node.ForwardRelations.Add(DotNetRelations.ImplementationMethod, candidateNode); + } + } + } +} diff --git a/src/AsmResolver.Workspaces.DotNet/Analyzers/Definition/SemanticsImplementationAnalyzer.cs b/src/AsmResolver.Workspaces.DotNet/Analyzers/Implementation/ImplementationSemanticsAnalyzer.cs similarity index 94% rename from src/AsmResolver.Workspaces.DotNet/Analyzers/Definition/SemanticsImplementationAnalyzer.cs rename to src/AsmResolver.Workspaces.DotNet/Analyzers/Implementation/ImplementationSemanticsAnalyzer.cs index 544e6138a..1f64b23a3 100644 --- a/src/AsmResolver.Workspaces.DotNet/Analyzers/Definition/SemanticsImplementationAnalyzer.cs +++ b/src/AsmResolver.Workspaces.DotNet/Analyzers/Implementation/ImplementationSemanticsAnalyzer.cs @@ -1,13 +1,13 @@ using AsmResolver.DotNet; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; -namespace AsmResolver.Workspaces.DotNet.Analyzers.Definition +namespace AsmResolver.Workspaces.DotNet.Analyzers.Implementation { /// /// Analyzes a for implicit base definitions, such as abstract events or properties in /// base classes or members in implemented interfaces. /// - public class SemanticsImplementationAnalyzer : ObjectAnalyzer + public class ImplementationSemanticsAnalyzer : ObjectAnalyzer { /// protected override void Analyze(AnalysisContext context, IHasSemantics subject) diff --git a/src/AsmResolver.Workspaces.DotNet/Analyzers/AnalyzerUtilities.cs b/src/AsmResolver.Workspaces.DotNet/Analyzers/Implementation/ImplementationUtilities.cs similarity index 95% rename from src/AsmResolver.Workspaces.DotNet/Analyzers/AnalyzerUtilities.cs rename to src/AsmResolver.Workspaces.DotNet/Analyzers/Implementation/ImplementationUtilities.cs index 52fa73a17..46a6dce76 100644 --- a/src/AsmResolver.Workspaces.DotNet/Analyzers/AnalyzerUtilities.cs +++ b/src/AsmResolver.Workspaces.DotNet/Analyzers/Implementation/ImplementationUtilities.cs @@ -4,9 +4,9 @@ using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.Signatures.Types; -namespace AsmResolver.Workspaces.DotNet.Analyzers +namespace AsmResolver.Workspaces.DotNet.Analyzers.Implementation { - internal static class AnalyzerUtilities + internal static class ImplementationUtilities { private static readonly SignatureComparer _comparer = new (); diff --git a/src/AsmResolver.Workspaces.DotNet/DotNetWorkspace.cs b/src/AsmResolver.Workspaces.DotNet/DotNetWorkspace.cs index 312a59ffe..8098894ce 100644 --- a/src/AsmResolver.Workspaces.DotNet/DotNetWorkspace.cs +++ b/src/AsmResolver.Workspaces.DotNet/DotNetWorkspace.cs @@ -23,6 +23,7 @@ public class DotNetWorkspace : Workspace public DotNetWorkspace() { Profiles.Add(new DotNetTraversalProfile()); + Profiles.Add(new DotNetImplementationProfile()); } /// diff --git a/src/AsmResolver.Workspaces.DotNet/Profiles/DotNetImplementationProfile.cs b/src/AsmResolver.Workspaces.DotNet/Profiles/DotNetImplementationProfile.cs new file mode 100644 index 000000000..0beb1f591 --- /dev/null +++ b/src/AsmResolver.Workspaces.DotNet/Profiles/DotNetImplementationProfile.cs @@ -0,0 +1,21 @@ +using AsmResolver.DotNet; +using AsmResolver.Workspaces.DotNet.Analyzers.Implementation; + +namespace AsmResolver.Workspaces.DotNet.Profiles +{ + /// + /// Provides a default implementation of profile to connect all abstract, virtual and interface members. + /// + public class DotNetImplementationProfile : WorksapceProfile + { + /// + /// Provides a default implementation of profile to connect all abstract, virtual and interface members. + /// + public DotNetImplementationProfile() + { + Analyzers.Register(typeof(MethodDefinition), new ImplementationMethodAnalyzer()); + Analyzers.Register(typeof(IHasSemantics), new ImplementationSemanticsAnalyzer()); + + } + } +} diff --git a/src/AsmResolver.Workspaces.DotNet/Profiles/DotNetTraversalProfile.cs b/src/AsmResolver.Workspaces.DotNet/Profiles/DotNetTraversalProfile.cs index f918b66b7..3d0299781 100644 --- a/src/AsmResolver.Workspaces.DotNet/Profiles/DotNetTraversalProfile.cs +++ b/src/AsmResolver.Workspaces.DotNet/Profiles/DotNetTraversalProfile.cs @@ -8,8 +8,14 @@ namespace AsmResolver.Workspaces.DotNet.Profiles { - internal class DotNetTraversalProfile : WorksapceProfile + /// + /// Provides a default implementation of profile to traverse all .net members. + /// + public class DotNetTraversalProfile : WorksapceProfile { + /// + /// Provides a default implementation of profile to traverse all .net members. + /// public DotNetTraversalProfile() { Analyzers.Register(typeof(AssemblyDefinition), new AssemblyAnalyzer()); @@ -17,7 +23,6 @@ public DotNetTraversalProfile() Analyzers.Register(typeof(TypeDefinition), new TypeAnalyzer()); Analyzers.Register(typeof(MethodDefinition), new MethodAnalyzer()); Analyzers.Register(typeof(MethodImplementation), new MethodImplementationAnalyzer()); - Analyzers.Register(typeof(IHasSemantics), new SemanticsImplementationAnalyzer()); Analyzers.Register(typeof(TypeReference), new TypeReferenceAnalyzer()); Analyzers.Register(typeof(MemberReference), new MemberReferenceAnalyzer()); Analyzers.Register(typeof(IHasCustomAttribute), new HasCustomAttributeAnalyzer()); From fc62a45f62b1ae765d5e120c8632955f7461ffbf Mon Sep 17 00:00:00 2001 From: JPaja Date: Fri, 11 Feb 2022 23:43:53 +0100 Subject: [PATCH 005/184] Fix: Xmldoc --- .../Profiles/DotNetImplementationProfile.cs | 2 +- .../Profiles/DotNetTraversalProfile.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AsmResolver.Workspaces.DotNet/Profiles/DotNetImplementationProfile.cs b/src/AsmResolver.Workspaces.DotNet/Profiles/DotNetImplementationProfile.cs index 0beb1f591..709e466bd 100644 --- a/src/AsmResolver.Workspaces.DotNet/Profiles/DotNetImplementationProfile.cs +++ b/src/AsmResolver.Workspaces.DotNet/Profiles/DotNetImplementationProfile.cs @@ -9,7 +9,7 @@ namespace AsmResolver.Workspaces.DotNet.Profiles public class DotNetImplementationProfile : WorksapceProfile { /// - /// Provides a default implementation of profile to connect all abstract, virtual and interface members. + /// Creates a new instance of the class. /// public DotNetImplementationProfile() { diff --git a/src/AsmResolver.Workspaces.DotNet/Profiles/DotNetTraversalProfile.cs b/src/AsmResolver.Workspaces.DotNet/Profiles/DotNetTraversalProfile.cs index 3d0299781..7d43b3435 100644 --- a/src/AsmResolver.Workspaces.DotNet/Profiles/DotNetTraversalProfile.cs +++ b/src/AsmResolver.Workspaces.DotNet/Profiles/DotNetTraversalProfile.cs @@ -14,7 +14,7 @@ namespace AsmResolver.Workspaces.DotNet.Profiles public class DotNetTraversalProfile : WorksapceProfile { /// - /// Provides a default implementation of profile to traverse all .net members. + /// Creates a new instance of the class. /// public DotNetTraversalProfile() { From 398b4d7ecd30621339a770644965315be68719f9 Mon Sep 17 00:00:00 2001 From: JPaja Date: Fri, 11 Feb 2022 23:55:51 +0100 Subject: [PATCH 006/184] Add: issue/198 test --- .../AbstractInheritanceTest.cs | 46 +++++++++++++++++++ .../MyAboveAbstractClass.cs | 7 +++ .../MyClassGeneric.cs | 2 +- .../MyDerivedAboveClass.cs | 7 +++ .../MyDerivedClassGeneric.cs | 4 +- .../MyDerivedInbetweenClass.cs | 7 +++ .../MyInbetweenAbstractClass.cs | 6 +++ 7 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 test/AsmResolver.Workspaces.DotNet.Tests/AbstractInheritanceTest.cs create mode 100644 test/TestBinaries/DotNet/AsmResolver.Workspaces.DotNet.TestCases/MyAboveAbstractClass.cs create mode 100644 test/TestBinaries/DotNet/AsmResolver.Workspaces.DotNet.TestCases/MyDerivedAboveClass.cs create mode 100644 test/TestBinaries/DotNet/AsmResolver.Workspaces.DotNet.TestCases/MyDerivedInbetweenClass.cs create mode 100644 test/TestBinaries/DotNet/AsmResolver.Workspaces.DotNet.TestCases/MyInbetweenAbstractClass.cs diff --git a/test/AsmResolver.Workspaces.DotNet.Tests/AbstractInheritanceTest.cs b/test/AsmResolver.Workspaces.DotNet.Tests/AbstractInheritanceTest.cs new file mode 100644 index 000000000..d79ae442f --- /dev/null +++ b/test/AsmResolver.Workspaces.DotNet.Tests/AbstractInheritanceTest.cs @@ -0,0 +1,46 @@ +using AsmResolver.DotNet; +using AsmResolver.Workspaces.DotNet.TestCases; +using System.Linq; +using Xunit; + +namespace AsmResolver.Workspaces.DotNet.Tests +{ + public class AbstractInheritanceTest : IClassFixture + { + private readonly TestCasesFixture _fixture; + + public AbstractInheritanceTest(TestCasesFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void InterfaceImplementation() + { + var module = _fixture.WorkspacesAssembly.ManifestModule; + var abstractType = (TypeDefinition)module.LookupMember(typeof(MyAboveAbstractClass).MetadataToken); + var abstractMethod = abstractType.Methods.First(m => m.Name == nameof(MyAboveAbstractClass.TestAboveAbstract)); + + var implType1 = (TypeDefinition)module.LookupMember(typeof(MyDerivedAboveClass).MetadataToken); + var implMethod1 = implType1.Methods.First(m => m.Name == nameof(MyAboveAbstractClass.TestAboveAbstract)); + + var implType2 = (TypeDefinition)module.LookupMember(typeof(MyDerivedInbetweenClass).MetadataToken); + var implMethod2 = implType2.Methods.First(m => m.Name == nameof(MyAboveAbstractClass.TestAboveAbstract)); + + var implType3 = (TypeDefinition)module.LookupMember(typeof(MyDerivedClassGeneric).MetadataToken); + var implMethod3 = implType3.Methods.First(m => m.Name == nameof(MyAboveAbstractClass.TestAboveAbstract)); + + + var workspace = new DotNetWorkspace(); + workspace.Assemblies.Add(_fixture.WorkspacesAssembly); + workspace.Analyze(); + + var node = workspace.Index.GetOrCreateNode(abstractMethod); + var implMethods = node.BackwardRelations.GetObjects(DotNetRelations.ImplementationMethod); + + Assert.Contains(implMethod1, implMethods); + Assert.Contains(implMethod2, implMethods); + Assert.Contains(implMethod3, implMethods); + } + } +} diff --git a/test/TestBinaries/DotNet/AsmResolver.Workspaces.DotNet.TestCases/MyAboveAbstractClass.cs b/test/TestBinaries/DotNet/AsmResolver.Workspaces.DotNet.TestCases/MyAboveAbstractClass.cs new file mode 100644 index 000000000..c8cba7945 --- /dev/null +++ b/test/TestBinaries/DotNet/AsmResolver.Workspaces.DotNet.TestCases/MyAboveAbstractClass.cs @@ -0,0 +1,7 @@ +namespace AsmResolver.Workspaces.DotNet.TestCases +{ + public abstract class MyAboveAbstractClass + { + public abstract int TestAboveAbstract(); + } +} diff --git a/test/TestBinaries/DotNet/AsmResolver.Workspaces.DotNet.TestCases/MyClassGeneric.cs b/test/TestBinaries/DotNet/AsmResolver.Workspaces.DotNet.TestCases/MyClassGeneric.cs index 90bc72e68..5a1e5406d 100644 --- a/test/TestBinaries/DotNet/AsmResolver.Workspaces.DotNet.TestCases/MyClassGeneric.cs +++ b/test/TestBinaries/DotNet/AsmResolver.Workspaces.DotNet.TestCases/MyClassGeneric.cs @@ -1,6 +1,6 @@ namespace AsmResolver.Workspaces.DotNet.TestCases { - public abstract class MyClassGeneric : IMyInterfaceGeneric + public abstract class MyClassGeneric : MyInbetweenAbstractClass, IMyInterfaceGeneric { /// void IMyInterfaceGeneric.Explicit() diff --git a/test/TestBinaries/DotNet/AsmResolver.Workspaces.DotNet.TestCases/MyDerivedAboveClass.cs b/test/TestBinaries/DotNet/AsmResolver.Workspaces.DotNet.TestCases/MyDerivedAboveClass.cs new file mode 100644 index 000000000..cb86cb86e --- /dev/null +++ b/test/TestBinaries/DotNet/AsmResolver.Workspaces.DotNet.TestCases/MyDerivedAboveClass.cs @@ -0,0 +1,7 @@ +namespace AsmResolver.Workspaces.DotNet.TestCases +{ + public class MyDerivedAboveClass : MyAboveAbstractClass + { + public override int TestAboveAbstract() => 1; + } +} diff --git a/test/TestBinaries/DotNet/AsmResolver.Workspaces.DotNet.TestCases/MyDerivedClassGeneric.cs b/test/TestBinaries/DotNet/AsmResolver.Workspaces.DotNet.TestCases/MyDerivedClassGeneric.cs index f870c5574..dc67bc6e3 100644 --- a/test/TestBinaries/DotNet/AsmResolver.Workspaces.DotNet.TestCases/MyDerivedClassGeneric.cs +++ b/test/TestBinaries/DotNet/AsmResolver.Workspaces.DotNet.TestCases/MyDerivedClassGeneric.cs @@ -11,7 +11,9 @@ public override int GenericMethod(float x) public override bool Implicit() => true; public new virtual int Shadowed(int x) => 1; - + + public override int TestAboveAbstract() => 3; + /// public override int ImplicitP { get; set; } = 1; diff --git a/test/TestBinaries/DotNet/AsmResolver.Workspaces.DotNet.TestCases/MyDerivedInbetweenClass.cs b/test/TestBinaries/DotNet/AsmResolver.Workspaces.DotNet.TestCases/MyDerivedInbetweenClass.cs new file mode 100644 index 000000000..f669d19df --- /dev/null +++ b/test/TestBinaries/DotNet/AsmResolver.Workspaces.DotNet.TestCases/MyDerivedInbetweenClass.cs @@ -0,0 +1,7 @@ +namespace AsmResolver.Workspaces.DotNet.TestCases +{ + public class MyDerivedInbetweenClass : MyInbetweenAbstractClass + { + public override int TestAboveAbstract() => 2; + } +} diff --git a/test/TestBinaries/DotNet/AsmResolver.Workspaces.DotNet.TestCases/MyInbetweenAbstractClass.cs b/test/TestBinaries/DotNet/AsmResolver.Workspaces.DotNet.TestCases/MyInbetweenAbstractClass.cs new file mode 100644 index 000000000..4ad9bc4d4 --- /dev/null +++ b/test/TestBinaries/DotNet/AsmResolver.Workspaces.DotNet.TestCases/MyInbetweenAbstractClass.cs @@ -0,0 +1,6 @@ +namespace AsmResolver.Workspaces.DotNet.TestCases +{ + public abstract class MyInbetweenAbstractClass : MyAboveAbstractClass + { + } +} From 13ea26d260c96cdf000ffc129d2ddcea3c98cfb8 Mon Sep 17 00:00:00 2001 From: JPaja Date: Fri, 11 Feb 2022 23:59:55 +0100 Subject: [PATCH 007/184] Fix: Get correct base types --- .../Implementation/ImplementationUtilities.cs | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/AsmResolver.Workspaces.DotNet/Analyzers/Implementation/ImplementationUtilities.cs b/src/AsmResolver.Workspaces.DotNet/Analyzers/Implementation/ImplementationUtilities.cs index 46a6dce76..28833e82a 100644 --- a/src/AsmResolver.Workspaces.DotNet/Analyzers/Implementation/ImplementationUtilities.cs +++ b/src/AsmResolver.Workspaces.DotNet/Analyzers/Implementation/ImplementationUtilities.cs @@ -16,10 +16,8 @@ internal static class ImplementationUtilities if (subject.DeclaringType is not { } declaringType) yield break; - var baseTypes = index - .GetOrCreateNode(declaringType) // Get indexed declaring type. - .ForwardRelations.GetObjects(DotNetRelations.BaseType) // Get types that this declaring type is implementing. - .ToArray(); + var declaringTypeNode = index.GetOrCreateNode(declaringType); + var baseTypes = GetBaseTypes(declaringTypeNode); foreach (var baseType in baseTypes) { @@ -55,5 +53,29 @@ internal static class ImplementationUtilities } } } + + internal static IEnumerable GetBaseTypes(WorkspaceIndexNode baseNode) + { + var visited = new HashSet(); + var agenda = new Queue(); + agenda.Enqueue(baseNode); + while (agenda.Count != 0) + { + var node = agenda.Dequeue(); + if (!visited.Add(node)) + continue; + var baseTypeNodes = node.ForwardRelations.GetNodes(DotNetRelations.BaseType); + foreach (var baseTypeNode in baseTypeNodes) + { + var baseType = (ITypeDefOrRef)baseTypeNode.Subject; + agenda.Enqueue(baseTypeNode); + var baseTypeDefinitions = baseTypeNode.BackwardRelations.GetNodes(DotNetRelations.ReferenceType + , DotNetRelations.ReferenceTypeSpecification); + foreach (var baseTypeDefinition in baseTypeDefinitions) + agenda.Enqueue(baseTypeDefinition); + yield return baseType; + } + } + } } } From 8bc6458d1f121b476364c24d32c1787546716988 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 19 Feb 2022 12:36:31 +0100 Subject: [PATCH 008/184] Add duped typeref test. --- .../TypeRefTokenPreservationTest.cs | 59 ++++++++++++++++--- 1 file changed, 50 insertions(+), 9 deletions(-) diff --git a/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/TypeRefTokenPreservationTest.cs b/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/TypeRefTokenPreservationTest.cs index b2fbbc1b6..dd6a8073e 100644 --- a/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/TypeRefTokenPreservationTest.cs +++ b/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/TypeRefTokenPreservationTest.cs @@ -1,8 +1,12 @@ using System; +using System.Collections.Generic; using System.Linq; using AsmResolver.DotNet.Builder; +using AsmResolver.PE; using AsmResolver.PE.DotNet.Cil; +using AsmResolver.PE.DotNet.Metadata.Strings; using AsmResolver.PE.DotNet.Metadata.Tables; +using AsmResolver.PE.DotNet.Metadata.Tables.Rows; using Xunit; namespace AsmResolver.DotNet.Tests.Builder.TokenPreservation @@ -14,10 +18,10 @@ public void PreserveTypeRefsNoChangeShouldAtLeastHaveOriginalTypeRefs() { var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld_NetCore); var originalTypeRefs = GetMembers(module, TableIndex.TypeRef); - + var newModule = RebuildAndReloadModule(module, MetadataBuilderFlags.PreserveTypeReferenceIndices); var newTypeRefs = GetMembers(newModule, TableIndex.TypeRef); - + Assert.Equal(originalTypeRefs, newTypeRefs.Take(originalTypeRefs.Count), Comparer); } @@ -26,14 +30,14 @@ public void PreserveTypeRefsWithTypeRefRemovedShouldAtLeastHaveOriginalTypeRefs( { var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld_NetCore); var originalTypeRefs = GetMembers(module, TableIndex.TypeRef); - + var instructions = module.ManagedEntrypointMethod.CilMethodBody.Instructions; instructions.Clear(); instructions.Add(CilOpCodes.Ret); - + var newModule = RebuildAndReloadModule(module, MetadataBuilderFlags.PreserveTypeReferenceIndices); var newTypeRefs = GetMembers(newModule, TableIndex.TypeRef); - + Assert.Equal(originalTypeRefs, newTypeRefs.Take(originalTypeRefs.Count), Comparer); } @@ -42,7 +46,7 @@ public void PreserveTypeRefsWithExtraImportShouldAtLeastHaveOriginalTypeRefs() { var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld_NetCore); var originalTypeRefs = GetMembers(module, TableIndex.TypeRef); - + var importer = new ReferenceImporter(module); var readKey = importer.ImportMethod(typeof(Console).GetMethod("ReadKey", Type.EmptyTypes)); @@ -51,12 +55,49 @@ public void PreserveTypeRefsWithExtraImportShouldAtLeastHaveOriginalTypeRefs() instructions.Add(CilOpCodes.Call, readKey); instructions.Add(CilOpCodes.Pop); instructions.Add(CilOpCodes.Ret); - + var newModule = RebuildAndReloadModule(module, MetadataBuilderFlags.PreserveTypeReferenceIndices); var newTypeRefs = GetMembers(newModule, TableIndex.TypeRef); - + Assert.Equal(originalTypeRefs, newTypeRefs.Take(originalTypeRefs.Count), Comparer); } + [Fact] + public void PreserveDuplicatedTypeRefs() + { + var image = PEImage.FromBytes(Properties.Resources.HelloWorld_NetCore); + var metadata = image.DotNetDirectory!.Metadata!; + var strings = metadata.GetStream(); + var table = metadata + .GetStream() + .GetTable(); + + // Duplicate Object row. + var objectRow = table.First(t => strings.GetStringByIndex(t.Name) == "Object"); + table.Add(objectRow); + + // Open module from modified image. + var module = ModuleDefinition.FromImage(image); + + // Obtain references to Object. + var objectReferences = module + .GetImportedTypeReferences() + .Where(t => t.Name == "Object") + .ToArray(); + + Assert.Equal(2, objectReferences.Length); + + // Rebuild with preservation. + var newModule = RebuildAndReloadModule(module, MetadataBuilderFlags.PreserveTypeReferenceIndices); + + var newObjectReferences = newModule + .GetImportedTypeReferences() + .Where(t => t.Name == "Object") + .ToArray(); + + Assert.Equal( + objectReferences.Select(r => r.MetadataToken).ToHashSet(), + newObjectReferences.Select(r => r.MetadataToken).ToHashSet()); + } } -} \ No newline at end of file +} From 9b8ab64e48c7dfece327010ecbac78ae5999b7ef Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 19 Feb 2022 13:19:52 +0100 Subject: [PATCH 009/184] Add DistinctMetadataTableBuffer.Add(TRow, bool) allowing for duplicated entries to be added. --- .../DotNetDirectoryBuffer.TokenProvider.cs | 125 +++++++++++++++--- .../Builder/DotNetDirectoryFactory.cs | 45 +++++-- .../Tables/DistinctMetadataTableBuffer.cs | 16 ++- .../Metadata/Tables/TablesStreamBuffer.cs | 12 ++ .../AssemblyRefTokenPreservationTest.cs | 63 +++++++-- .../MemberRefTokenPreservationTest.cs | 47 ++++++- .../TokenPreservationTestBase.cs | 5 +- .../TypeRefTokenPreservationTest.cs | 14 +- .../TokenAllocatorTest.cs | 29 +++- 9 files changed, 303 insertions(+), 53 deletions(-) diff --git a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.TokenProvider.cs b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.TokenProvider.cs index 4615f1005..1abba1f59 100644 --- a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.TokenProvider.cs +++ b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.TokenProvider.cs @@ -10,18 +10,29 @@ public partial class DotNetDirectoryBuffer : IMetadataTokenProvider public uint GetUserStringIndex(string value) => Metadata.UserStringsStream.GetStringIndex(value); /// - public MetadataToken GetTypeReferenceToken(TypeReference? type) + public MetadataToken GetTypeReferenceToken(TypeReference? type) => AddTypeReference(type, false); + + /// + /// Adds a type reference to the buffer. + /// + /// The reference to add. + /// + /// true if the row is always to be added to the end of the buffer, false if a duplicated row + /// is supposed to be removed and the token of the original should be returned instead. + /// + /// The newly assigned metadata token. + public MetadataToken AddTypeReference(TypeReference? type, bool allowDuplicates) { if (!AssertIsImported(type)) return MetadataToken.Zero; - var table = Metadata.TablesStream.GetTable(TableIndex.TypeRef); + var table = Metadata.TablesStream.GetDistinctTable(TableIndex.TypeRef); var row = new TypeReferenceRow( AddResolutionScope(type.Scope), Metadata.StringsStream.GetStringIndex(type.Name), Metadata.StringsStream.GetStringIndex(type.Namespace)); - var token = table.Add(row); + var token = table.Add(row, allowDuplicates); _tokenMapping.Register(type, token); AddCustomAttributes(token, type); return token; @@ -90,17 +101,31 @@ public MetadataToken GetEventDefinitionToken(EventDefinition? @event) /// public MetadataToken GetMemberReferenceToken(MemberReference? member) + { + return AddMemberReference(member, false); + } + + /// + /// Adds a member reference to the buffer. + /// + /// The reference to add. + /// + /// true if the row is always to be added to the end of the buffer, false if a duplicated row + /// is supposed to be removed and the token of the original should be returned instead. + /// + /// The newly assigned metadata token. + public MetadataToken AddMemberReference(MemberReference? member, bool allowDuplicates) { if (!AssertIsImported(member)) return MetadataToken.Zero; - var table = Metadata.TablesStream.GetTable(TableIndex.MemberRef); + var table = Metadata.TablesStream.GetDistinctTable(TableIndex.MemberRef); var row = new MemberReferenceRow( AddMemberRefParent(member.Parent), Metadata.StringsStream.GetStringIndex(member.Name), Metadata.BlobStream.GetBlobIndex(this, member.Signature, ErrorListener)); - var token = table.Add(row); + var token = table.Add(row, allowDuplicates); _tokenMapping.Register(member, token); AddCustomAttributes(token, member); return token; @@ -108,15 +133,29 @@ public MetadataToken GetMemberReferenceToken(MemberReference? member) /// public MetadataToken GetStandAloneSignatureToken(StandAloneSignature? signature) + { + return AddStandAloneSignature(signature, false); + } + + /// + /// Adds a stand-alone signature to the buffer. + /// + /// The signature to add. + /// + /// true if the row is always to be added to the end of the buffer, false if a duplicated row + /// is supposed to be removed and the token of the original should be returned instead. + /// + /// The newly assigned metadata token. + public MetadataToken AddStandAloneSignature(StandAloneSignature? signature, bool allowDuplicates) { if (signature is null) return MetadataToken.Zero; - var table = Metadata.TablesStream.GetTable(TableIndex.StandAloneSig); + var table = Metadata.TablesStream.GetDistinctTable(TableIndex.StandAloneSig); var row = new StandAloneSignatureRow( Metadata.BlobStream.GetBlobIndex(this, signature.Signature, ErrorListener)); - var token = table.Add(row); + var token = table.Add(row, allowDuplicates); _tokenMapping.Register(signature, token); AddCustomAttributes(token, signature); return token; @@ -124,11 +163,25 @@ public MetadataToken GetStandAloneSignatureToken(StandAloneSignature? signature) /// public MetadataToken GetAssemblyReferenceToken(AssemblyReference? assembly) + { + return AddAssemblyReference(assembly, false); + } + + /// + /// Adds an assembly reference to the buffer. + /// + /// The reference to add. + /// + /// true if the row is always to be added to the end of the buffer, false if a duplicated row + /// is supposed to be removed and the token of the original should be returned instead. + /// + /// The newly assigned metadata token. + public MetadataToken AddAssemblyReference(AssemblyReference? assembly, bool allowDuplicates) { if (assembly is null || !AssertIsImported(assembly)) return MetadataToken.Zero; - var table = Metadata.TablesStream.GetTable(TableIndex.AssemblyRef); + var table = Metadata.TablesStream.GetDistinctTable(TableIndex.AssemblyRef); var row = new AssemblyReferenceRow((ushort) assembly.Version.Major, (ushort) assembly.Version.Minor, @@ -140,7 +193,7 @@ public MetadataToken GetAssemblyReferenceToken(AssemblyReference? assembly) Metadata.StringsStream.GetStringIndex(assembly.Culture), Metadata.BlobStream.GetBlobIndex(assembly.HashValue)); - var token = table.Add(row); + var token = table.Add(row, allowDuplicates); AddCustomAttributes(token, assembly); return token; } @@ -151,28 +204,56 @@ public MetadataToken GetAssemblyReferenceToken(AssemblyReference? assembly) /// The reference to add. /// The new metadata token assigned to the module reference. public MetadataToken GetModuleReferenceToken(ModuleReference? reference) + { + return AddModuleReference(reference, false); + } + + /// + /// Adds a module reference to the buffer. + /// + /// The reference to add. + /// + /// true if the row is always to be added to the end of the buffer, false if a duplicated row + /// is supposed to be removed and the token of the original should be returned instead. + /// + /// The newly assigned metadata token. + public MetadataToken AddModuleReference(ModuleReference? reference, bool allowDuplicates) { if (!AssertIsImported(reference)) return MetadataToken.Zero; - var table = Metadata.TablesStream.GetTable(TableIndex.ModuleRef); + var table = Metadata.TablesStream.GetDistinctTable(TableIndex.ModuleRef); var row = new ModuleReferenceRow(Metadata.StringsStream.GetStringIndex(reference.Name)); - var token = table.Add(row); + var token = table.Add(row, allowDuplicates); AddCustomAttributes(token, reference); return token; } /// public MetadataToken GetTypeSpecificationToken(TypeSpecification? type) + { + return AddTypeSpecification(type, false); + } + + /// + /// Adds a type specification to the buffer. + /// + /// The specification to add. + /// + /// true if the row is always to be added to the end of the buffer, false if a duplicated row + /// is supposed to be removed and the token of the original should be returned instead. + /// + /// The newly assigned metadata token. + public MetadataToken AddTypeSpecification(TypeSpecification? type, bool allowDuplicates) { if (!AssertIsImported(type)) return MetadataToken.Zero; - var table = Metadata.TablesStream.GetTable(TableIndex.TypeSpec); + var table = Metadata.TablesStream.GetDistinctTable(TableIndex.TypeSpec); var row = new TypeSpecificationRow(Metadata.BlobStream.GetBlobIndex(this, type.Signature, ErrorListener)); - var token = table.Add(row); + var token = table.Add(row, allowDuplicates); _tokenMapping.Register(type, token); AddCustomAttributes(token, type); return token; @@ -180,16 +261,30 @@ public MetadataToken GetTypeSpecificationToken(TypeSpecification? type) /// public MetadataToken GetMethodSpecificationToken(MethodSpecification? method) + { + return AddMethodSpecification(method, false); + } + + /// + /// Adds a method specification to the buffer. + /// + /// The specification to add. + /// + /// true if the row is always to be added to the end of the buffer, false if a duplicated row + /// is supposed to be removed and the token of the original should be returned instead. + /// + /// The newly assigned metadata token. + public MetadataToken AddMethodSpecification(MethodSpecification? method, bool allowDuplicates) { if (!AssertIsImported(method)) return MetadataToken.Zero; - var table = Metadata.TablesStream.GetTable(TableIndex.MethodSpec); + var table = Metadata.TablesStream.GetDistinctTable(TableIndex.MethodSpec); var row = new MethodSpecificationRow( AddMethodDefOrRef(method.Method), Metadata.BlobStream.GetBlobIndex(this, method.Signature, ErrorListener)); - var token = table.Add(row); + var token = table.Add(row, allowDuplicates); _tokenMapping.Register(method, token); AddCustomAttributes(token, method); return token; diff --git a/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs b/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs index 8def5c196..705fda930 100644 --- a/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs +++ b/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs @@ -84,7 +84,7 @@ public IMethodBodySerializer MethodBodySerializer // When specified, import existing AssemblyRef, ModuleRef, TypeRef and MemberRef prior to adding any other // member reference or definition, to ensure that they are assigned their original RIDs. - ImportBasicTablesIntoTableBuffersIfSpecified(module, buffer); + ImportBasicTablesIfSpecified(module, buffer); // Define all types defined in the module. buffer.DefineTypes(discoveryResult.Types); @@ -102,7 +102,7 @@ public IMethodBodySerializer MethodBodySerializer // Import remaining preservable tables (Type specs, method specs, signatures etc). // We do this before finalizing any member to ensure that they are assigned their original RIDs. - ImportRemainingTablesIntoTableBuffersIfSpecified(module, buffer); + ImportRemainingTablesIfSpecified(module, buffer); // Finalize member definitions. buffer.FinalizeTypes(); @@ -197,7 +197,7 @@ private IMetadataBuffer CreateMetadataBuffer(ModuleDefinition module) return metadataBuffer; } - private void ImportBasicTablesIntoTableBuffersIfSpecified(ModuleDefinition module, DotNetDirectoryBuffer buffer) + private void ImportBasicTablesIfSpecified(ModuleDefinition module, DotNetDirectoryBuffer buffer) { if (module.DotNetDirectory is null) return; @@ -210,37 +210,58 @@ private void ImportBasicTablesIntoTableBuffersIfSpecified(ModuleDefinition modul // and type reference tokens are still preserved, we need to prioritize these. if ((MetadataBuilderFlags & MetadataBuilderFlags.PreserveAssemblyReferenceIndices) != 0) - ImportTableIntoTableBuffers(module, TableIndex.AssemblyRef, buffer.GetAssemblyReferenceToken); + { + ImportTables(module, TableIndex.AssemblyRef, + r => buffer.AddAssemblyReference(r, true)); + } if ((MetadataBuilderFlags & MetadataBuilderFlags.PreserveModuleReferenceIndices) != 0) - ImportTableIntoTableBuffers(module, TableIndex.ModuleRef, buffer.GetModuleReferenceToken); + { + ImportTables(module, TableIndex.ModuleRef, + r => buffer.AddModuleReference(r, true)); + } if ((MetadataBuilderFlags & MetadataBuilderFlags.PreserveTypeReferenceIndices) != 0) - ImportTableIntoTableBuffers(module, TableIndex.TypeRef, buffer.GetTypeReferenceToken); + { + ImportTables(module, TableIndex.TypeRef, + r => buffer.AddTypeReference(r, true)); + } } private void ImportTypeSpecsAndMemberRefsIfSpecified(ModuleDefinition module, DotNetDirectoryBuffer buffer) { if ((MetadataBuilderFlags & MetadataBuilderFlags.PreserveTypeSpecificationIndices) != 0) - ImportTableIntoTableBuffers(module, TableIndex.TypeSpec, buffer.GetTypeSpecificationToken); + { + ImportTables(module, TableIndex.TypeSpec, + s => buffer.AddTypeSpecification(s, true)); + } if ((MetadataBuilderFlags & MetadataBuilderFlags.PreserveMemberReferenceIndices) != 0) - ImportTableIntoTableBuffers(module, TableIndex.MemberRef, buffer.GetMemberReferenceToken); + { + ImportTables(module, TableIndex.MemberRef, + r => buffer.AddMemberReference(r, true)); + } } - private void ImportRemainingTablesIntoTableBuffersIfSpecified(ModuleDefinition module, DotNetDirectoryBuffer buffer) + private void ImportRemainingTablesIfSpecified(ModuleDefinition module, DotNetDirectoryBuffer buffer) { if (module.DotNetDirectory is null) return; if ((MetadataBuilderFlags & MetadataBuilderFlags.PreserveStandAloneSignatureIndices) != 0) - ImportTableIntoTableBuffers(module, TableIndex.StandAloneSig, buffer.GetStandAloneSignatureToken); + { + ImportTables(module, TableIndex.StandAloneSig, + s => buffer.AddStandAloneSignature(s, true)); + } if ((MetadataBuilderFlags & MetadataBuilderFlags.PreserveMethodSpecificationIndices) != 0) - ImportTableIntoTableBuffers(module, TableIndex.MethodSpec, buffer.GetMethodSpecificationToken); + { + ImportTables(module, TableIndex.MethodSpec, + s => buffer.AddMethodSpecification(s, true)); + } } - private static void ImportTableIntoTableBuffers(ModuleDefinition module, TableIndex tableIndex, + private static void ImportTables(ModuleDefinition module, TableIndex tableIndex, Func importAction) { int count = module.DotNetDirectory!.Metadata diff --git a/src/AsmResolver.DotNet/Builder/Metadata/Tables/DistinctMetadataTableBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Tables/DistinctMetadataTableBuffer.cs index 1c1f7f796..2fba58233 100644 --- a/src/AsmResolver.DotNet/Builder/Metadata/Tables/DistinctMetadataTableBuffer.cs +++ b/src/AsmResolver.DotNet/Builder/Metadata/Tables/DistinctMetadataTableBuffer.cs @@ -48,13 +48,27 @@ public DistinctMetadataTableBuffer(IMetadataTableBuffer underlyingBuffer) public ref TRow GetRowRef(uint rid) => ref _underlyingBuffer.GetRowRef(rid); /// - public MetadataToken Add(in TRow row) + public MetadataToken Add(in TRow row) => Add(row, false); + + /// + /// Adds a row to the metadata table buffer. + /// + /// The row to add. + /// + /// true if the row is always to be added to the end of the buffer, false if a duplicated row + /// is supposed to be removed and the token of the original should be returned instead. + /// The metadata token that this row was assigned to. + public MetadataToken Add(in TRow row, bool allowDuplicates) { if (!_entries.TryGetValue(row, out var token)) { token = _underlyingBuffer.Add(in row); _entries.Add(row, token); } + else if (allowDuplicates) + { + token = _underlyingBuffer.Add(in row); + } return token; } diff --git a/src/AsmResolver.DotNet/Builder/Metadata/Tables/TablesStreamBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Tables/TablesStreamBuffer.cs index e379139e1..47fab20d9 100644 --- a/src/AsmResolver.DotNet/Builder/Metadata/Tables/TablesStreamBuffer.cs +++ b/src/AsmResolver.DotNet/Builder/Metadata/Tables/TablesStreamBuffer.cs @@ -120,6 +120,18 @@ public IMetadataTableBuffer GetTable(TableIndex table) return (IMetadataTableBuffer) _tableBuffers[(int) table]; } + /// + /// Gets a table buffer by its table index. + /// + /// The index of the table to get. + /// The type of rows the table stores. + /// The metadata table. + public DistinctMetadataTableBuffer GetDistinctTable(TableIndex table) + where TRow : struct, IMetadataRow + { + return (DistinctMetadataTableBuffer) _tableBuffers[(int) table]; + } + /// /// Gets a table buffer by its table index. /// diff --git a/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/AssemblyRefTokenPreservationTest.cs b/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/AssemblyRefTokenPreservationTest.cs index f9cda7e70..5f9c79830 100644 --- a/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/AssemblyRefTokenPreservationTest.cs +++ b/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/AssemblyRefTokenPreservationTest.cs @@ -1,8 +1,11 @@ using System.IO; using System.Linq; using AsmResolver.DotNet.Builder; +using AsmResolver.PE; using AsmResolver.PE.DotNet.Cil; +using AsmResolver.PE.DotNet.Metadata.Strings; using AsmResolver.PE.DotNet.Metadata.Tables; +using AsmResolver.PE.DotNet.Metadata.Tables.Rows; using Xunit; namespace AsmResolver.DotNet.Tests.Builder.TokenPreservation @@ -14,10 +17,10 @@ public void PreserveAssemblyRefsNoChangeShouldAtLeastHaveOriginalAssemblyRefs() { var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld_NetCore); var originalAssemblyRefs = GetMembers(module, TableIndex.AssemblyRef); - + var newModule = RebuildAndReloadModule(module, MetadataBuilderFlags.PreserveAssemblyReferenceIndices); var newAssemblyRefs = GetMembers(newModule, TableIndex.AssemblyRef); - + Assert.Equal(originalAssemblyRefs, newAssemblyRefs.Take(originalAssemblyRefs.Count), Comparer); } @@ -26,14 +29,14 @@ 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); - + var newModule = RebuildAndReloadModule(module, MetadataBuilderFlags.PreserveAssemblyReferenceIndices); var newAssemblyRefs = GetMembers(newModule, TableIndex.AssemblyRef); - + Assert.Equal(originalAssemblyRefs, newAssemblyRefs.Take(originalAssemblyRefs.Count), Comparer); } @@ -42,22 +45,58 @@ public void PreserveAssemblyRefsWithExtraImportShouldAtLeastHaveOriginalAssembly { var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld_NetCore); var originalAssemblyRefs = GetMembers(module, TableIndex.AssemblyRef); - + var importer = new ReferenceImporter(module); - var exists = importer.ImportMethod(typeof(File).GetMethod("Exists", new[] {typeof(string)})); + 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); instructions.Add(CilOpCodes.Pop); instructions.Add(CilOpCodes.Ret); - + var newModule = RebuildAndReloadModule(module, MetadataBuilderFlags.PreserveAssemblyReferenceIndices); var newAssemblyRefs = GetMembers(newModule, TableIndex.AssemblyRef); - + Assert.Equal(originalAssemblyRefs, newAssemblyRefs.Take(originalAssemblyRefs.Count), Comparer); } + [Fact] + public void PreserveDuplicatedAssemblyRefs() + { + var image = PEImage.FromBytes(Properties.Resources.HelloWorld); + var metadata = image.DotNetDirectory!.Metadata!; + var strings = metadata.GetStream(); + var table = metadata + .GetStream() + .GetTable(); + + // Duplicate mscorlib row. + var corlibRow = table.First(a => strings.GetStringByIndex(a.Name) == "mscorlib"); + table.Add(corlibRow); + + // Open module from modified image. + var module = ModuleDefinition.FromImage(image); + + // Obtain references to mscorlib. + var references = module.AssemblyReferences + .Where(t => t.Name == "mscorlib") + .ToArray(); + + Assert.Equal(2, references.Length); + + // Rebuild with preservation. + var newModule = RebuildAndReloadModule(module, MetadataBuilderFlags.PreserveAssemblyReferenceIndices); + + var newReferences = newModule + .AssemblyReferences + .Where(t => t.Name == "mscorlib") + .ToArray(); + + Assert.Equal( + references.Select(r => r.MetadataToken).ToHashSet(), + newReferences.Select(r => r.MetadataToken).ToHashSet()); + } } -} \ No newline at end of file +} diff --git a/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/MemberRefTokenPreservationTest.cs b/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/MemberRefTokenPreservationTest.cs index b801ea2f2..7d7665510 100644 --- a/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/MemberRefTokenPreservationTest.cs +++ b/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/MemberRefTokenPreservationTest.cs @@ -1,8 +1,11 @@ using System; using System.Linq; using AsmResolver.DotNet.Builder; +using AsmResolver.PE; using AsmResolver.PE.DotNet.Cil; +using AsmResolver.PE.DotNet.Metadata.Strings; using AsmResolver.PE.DotNet.Metadata.Tables; +using AsmResolver.PE.DotNet.Metadata.Tables.Rows; using Xunit; namespace AsmResolver.DotNet.Tests.Builder.TokenPreservation @@ -27,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); @@ -44,9 +47,9 @@ public void PreserveMemberRefsWithExtraImportShouldAtLeastHaveOriginalMemberRefs var originalMemberRefs = GetMembers(module, TableIndex.MemberRef); var importer = new ReferenceImporter(module); - var readKey = importer.ImportMethod(typeof(Console).GetMethod("ReadKey", Type.EmptyTypes)); + 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); @@ -58,5 +61,43 @@ public void PreserveMemberRefsWithExtraImportShouldAtLeastHaveOriginalMemberRefs Assert.Equal(originalMemberRefs, newMemberRefs.Take(originalMemberRefs.Count), Comparer); } + [Fact] + public void PreserveDuplicatedTypeRefs() + { + var image = PEImage.FromBytes(Properties.Resources.HelloWorld); + var metadata = image.DotNetDirectory!.Metadata!; + var strings = metadata.GetStream(); + var table = metadata + .GetStream() + .GetTable(); + + // Duplicate WriteLine row. + var writeLineRow = table.First(m => strings.GetStringByIndex(m.Name) == "WriteLine"); + table.Add(writeLineRow); + + // Open module from modified image. + var module = ModuleDefinition.FromImage(image); + + // Obtain references to Object. + var references = module + .GetImportedMemberReferences() + .Where(t => t.Name == "WriteLine") + .ToArray(); + + Assert.Equal(2, references.Length); + + // Rebuild with preservation. + var newModule = RebuildAndReloadModule(module, MetadataBuilderFlags.PreserveMemberReferenceIndices); + + var newReferences = newModule + .GetImportedMemberReferences() + .Where(m => m.Name == "WriteLine") + .ToArray(); + + Assert.Equal( + references.Select(r => r.MetadataToken).ToHashSet(), + newReferences.Select(r => r.MetadataToken).ToHashSet()); + } + } } diff --git a/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/TokenPreservationTestBase.cs b/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/TokenPreservationTestBase.cs index 452e4483c..3adf1bd7e 100644 --- a/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/TokenPreservationTestBase.cs +++ b/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/TokenPreservationTestBase.cs @@ -19,7 +19,7 @@ protected SignatureComparer Comparer protected static List GetMembers(ModuleDefinition module, TableIndex tableIndex) { - int count = module.DotNetDirectory.Metadata + int count = module.DotNetDirectory!.Metadata! .GetStream() .GetTable(tableIndex) .Count; @@ -38,10 +38,11 @@ protected static ModuleDefinition RebuildAndReloadModule(ModuleDefinition module }; var result = builder.CreateImage(module); - if (result.DiagnosticBag.HasErrors) + if (result.HasFailed) throw new AggregateException(result.DiagnosticBag.Exceptions); var newImage = result.ConstructedImage; + return ModuleDefinition.FromImage(newImage); } diff --git a/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/TypeRefTokenPreservationTest.cs b/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/TypeRefTokenPreservationTest.cs index dd6a8073e..ebb5bc237 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); @@ -48,9 +48,9 @@ public void PreserveTypeRefsWithExtraImportShouldAtLeastHaveOriginalTypeRefs() var originalTypeRefs = GetMembers(module, TableIndex.TypeRef); var importer = new ReferenceImporter(module); - var readKey = importer.ImportMethod(typeof(Console).GetMethod("ReadKey", Type.EmptyTypes)); + 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); @@ -65,7 +65,7 @@ public void PreserveTypeRefsWithExtraImportShouldAtLeastHaveOriginalTypeRefs() [Fact] public void PreserveDuplicatedTypeRefs() { - var image = PEImage.FromBytes(Properties.Resources.HelloWorld_NetCore); + var image = PEImage.FromBytes(Properties.Resources.HelloWorld); var metadata = image.DotNetDirectory!.Metadata!; var strings = metadata.GetStream(); var table = metadata @@ -80,12 +80,12 @@ public void PreserveDuplicatedTypeRefs() var module = ModuleDefinition.FromImage(image); // Obtain references to Object. - var objectReferences = module + var references = module .GetImportedTypeReferences() .Where(t => t.Name == "Object") .ToArray(); - Assert.Equal(2, objectReferences.Length); + Assert.Equal(2, references.Length); // Rebuild with preservation. var newModule = RebuildAndReloadModule(module, MetadataBuilderFlags.PreserveTypeReferenceIndices); @@ -96,7 +96,7 @@ public void PreserveDuplicatedTypeRefs() .ToArray(); Assert.Equal( - objectReferences.Select(r => r.MetadataToken).ToHashSet(), + references.Select(r => r.MetadataToken).ToHashSet(), newObjectReferences.Select(r => r.MetadataToken).ToHashSet()); } } diff --git a/test/AsmResolver.DotNet.Tests/TokenAllocatorTest.cs b/test/AsmResolver.DotNet.Tests/TokenAllocatorTest.cs index 91a297084..aa241f50e 100644 --- a/test/AsmResolver.DotNet.Tests/TokenAllocatorTest.cs +++ b/test/AsmResolver.DotNet.Tests/TokenAllocatorTest.cs @@ -141,7 +141,34 @@ public void Issue187() allocator.AssignNextAvailableToken(method); targetModule.GetOrCreateModuleConstructor(); - var image = targetModule.ToPEImage(new ManagedPEImageBuilder(MetadataBuilderFlags.PreserveAll)); + _ = targetModule.ToPEImage(new ManagedPEImageBuilder(MetadataBuilderFlags.PreserveAll)); } + + [Fact] + public void Issue252() + { + // https://github.com/Washi1337/AsmResolver/issues/252 + + var module = ModuleDefinition.FromFile(typeof(TokenAllocatorTest).Assembly.Location); + var asmResRef = module.AssemblyReferences.First(a => a.Name == "AsmResolver.DotNet"); + + var reference = new TypeReference(module, asmResRef, "AsmResolver.DotNet", "MethodDefinition"); + var reference2 = new TypeReference(module, asmResRef, "AsmResolver.DotNet", "ModuleDefinition"); + + module.TokenAllocator.AssignNextAvailableToken(reference); + module.TokenAllocator.AssignNextAvailableToken(reference2); + + var image = module.ToPEImage(new ManagedPEImageBuilder(MetadataBuilderFlags.PreserveAll)); + var newModule = ModuleDefinition.FromImage(image); + + var newReference = (TypeReference) newModule.LookupMember(reference.MetadataToken); + var newReference2 = (TypeReference) newModule.LookupMember(reference2.MetadataToken); + + Assert.Equal(reference.Namespace, newReference.Namespace); + Assert.Equal(reference.Name, newReference.Name); + Assert.Equal(reference2.Namespace, newReference2.Namespace); + Assert.Equal(reference2.Name, newReference2.Name); + } + } } From 858f0f284397b90468c07e01334c01a9f4953032 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 19 Feb 2022 13:33:16 +0100 Subject: [PATCH 010/184] Deprecate FieldSignature.CreateXXX methods. --- src/AsmResolver.DotNet/FieldDefinition.cs | 22 ++++++++++++++----- .../Signatures/FieldSignature.cs | 10 +++++++-- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/AsmResolver.DotNet/FieldDefinition.cs b/src/AsmResolver.DotNet/FieldDefinition.cs index 1961a4db1..35b9343ae 100644 --- a/src/AsmResolver.DotNet/FieldDefinition.cs +++ b/src/AsmResolver.DotNet/FieldDefinition.cs @@ -4,6 +4,7 @@ using AsmResolver.Collections; using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.Signatures.Marshal; +using AsmResolver.DotNet.Signatures.Types; using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; @@ -56,11 +57,6 @@ protected FieldDefinition(MetadataToken token) /// The name of the field. /// The attributes. /// The signature of the field. - /// - /// For a valid .NET image, if of the signature referenced by - /// is set, the bit should be unset in - /// and vice versa. - /// public FieldDefinition(string? name, FieldAttributes attributes, FieldSignature? signature) : this(new MetadataToken(TableIndex.Field, 0)) { @@ -69,6 +65,22 @@ public FieldDefinition(string? name, FieldAttributes attributes, FieldSignature? Signature = signature; } + /// + /// Creates a new field definition. + /// + /// The name of the field. + /// The attributes. + /// The type of values the field contains. + public FieldDefinition(string? name, FieldAttributes attributes, TypeSignature? signature) + : this(new MetadataToken(TableIndex.Field, 0)) + { + Name = name; + Attributes = attributes; + Signature = signature is not null + ? new FieldSignature(signature) + : null; + } + /// /// Gets or sets the name of the field. /// diff --git a/src/AsmResolver.DotNet/Signatures/FieldSignature.cs b/src/AsmResolver.DotNet/Signatures/FieldSignature.cs index d1f14c7a7..d5b1bf8ab 100644 --- a/src/AsmResolver.DotNet/Signatures/FieldSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/FieldSignature.cs @@ -1,6 +1,6 @@ +using System; using AsmResolver.DotNet.Signatures.Types; using AsmResolver.IO; -using AsmResolver.PE.DotNet.Metadata.Tables.Rows; namespace AsmResolver.DotNet.Signatures { @@ -14,6 +14,9 @@ public class FieldSignature : MemberSignature /// /// The value type of the field. /// The signature. + [Obsolete("The HasThis bit in field signatures is ignored by the CLR. Use the constructor instead, or" + + " when this call is used in an argument of a FieldDefinition constructor, use the overload taking " + + "a TypeSignature instead.")] public static FieldSignature CreateStatic(TypeSignature fieldType) => new(CallingConventionAttributes.Field, fieldType); @@ -22,6 +25,9 @@ public static FieldSignature CreateStatic(TypeSignature fieldType) /// /// The value type of the field. /// The signature. + [Obsolete("The HasThis bit in field signatures is ignored by the CLR. Use the constructor instead, or" + + " when this call is used in an argument of a FieldDefinition constructor, use the overload taking " + + "a TypeSignature instead.")] public static FieldSignature CreateInstance(TypeSignature fieldType) => new(CallingConventionAttributes.Field | CallingConventionAttributes.HasThis, fieldType); @@ -61,7 +67,7 @@ public FieldSignature(CallingConventionAttributes attributes, TypeSignature fiel } /// - /// Gets the type of the object that the field stores. + /// Gets the type of the value that the field contains. /// public TypeSignature FieldType { From 0d1d1740153657a3531bc995bf6ba19eebae007f Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 19 Feb 2022 13:38:30 +0100 Subject: [PATCH 011/184] Remove usages of deprecated FieldSignature.CreateXXX methods. --- .../Builder/Discovery/MemberDiscoverer.cs | 2 +- .../Signatures/FieldSignature.cs | 12 +++---- .../Builder/TokenMappingTest.cs | 4 +-- .../FieldTokenPreservationTest.cs | 30 ++++++++--------- .../FieldDefinitionTest.cs | 2 +- .../MetadataResolverTest.cs | 6 ++-- .../ReferenceImporterTest.cs | 32 +++++++++---------- .../Signatures/GenericContextTest.cs | 6 ++-- .../TokenAllocatorTest.cs | 12 ++++--- 9 files changed, 54 insertions(+), 52 deletions(-) diff --git a/src/AsmResolver.DotNet/Builder/Discovery/MemberDiscoverer.cs b/src/AsmResolver.DotNet/Builder/Discovery/MemberDiscoverer.cs index d6f233046..d6eeac3c6 100644 --- a/src/AsmResolver.DotNet/Builder/Discovery/MemberDiscoverer.cs +++ b/src/AsmResolver.DotNet/Builder/Discovery/MemberDiscoverer.cs @@ -343,7 +343,7 @@ private FieldDefinition AddPlaceHolderField(TypeDefinition placeHolderType, Meta var placeHolderField = new FieldDefinition( $"PlaceHolderField_{token.Rid.ToString()}", FieldPlaceHolderAttributes, - FieldSignature.CreateStatic(_module.CorLibTypeFactory.Object)); + _module.CorLibTypeFactory.Object); // Add the field to the type. placeHolderType.Fields.Add(placeHolderField); diff --git a/src/AsmResolver.DotNet/Signatures/FieldSignature.cs b/src/AsmResolver.DotNet/Signatures/FieldSignature.cs index d5b1bf8ab..9b742200a 100644 --- a/src/AsmResolver.DotNet/Signatures/FieldSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/FieldSignature.cs @@ -14,9 +14,9 @@ public class FieldSignature : MemberSignature /// /// The value type of the field. /// The signature. - [Obsolete("The HasThis bit in field signatures is ignored by the CLR. Use the constructor instead, or" - + " when this call is used in an argument of a FieldDefinition constructor, use the overload taking " - + "a TypeSignature instead.")] + [Obsolete("The HasThis bit in field signatures is ignored by the CLR. Use the constructor instead," + + " or when this call is used in an argument of a FieldDefinition constructor, use the overload" + + " taking TypeSignature instead.")] public static FieldSignature CreateStatic(TypeSignature fieldType) => new(CallingConventionAttributes.Field, fieldType); @@ -25,9 +25,9 @@ public static FieldSignature CreateStatic(TypeSignature fieldType) /// /// The value type of the field. /// The signature. - [Obsolete("The HasThis bit in field signatures is ignored by the CLR. Use the constructor instead, or" - + " when this call is used in an argument of a FieldDefinition constructor, use the overload taking " - + "a TypeSignature instead.")] + [Obsolete("The HasThis bit in field signatures is ignored by the CLR. Use the constructor instead," + + " or when this call is used in an argument of a FieldDefinition constructor, use the overload" + + " taking TypeSignature instead.")] public static FieldSignature CreateInstance(TypeSignature fieldType) => new(CallingConventionAttributes.Field | CallingConventionAttributes.HasThis, fieldType); diff --git a/test/AsmResolver.DotNet.Tests/Builder/TokenMappingTest.cs b/test/AsmResolver.DotNet.Tests/Builder/TokenMappingTest.cs index c62e752ca..af428c6a4 100644 --- a/test/AsmResolver.DotNet.Tests/Builder/TokenMappingTest.cs +++ b/test/AsmResolver.DotNet.Tests/Builder/TokenMappingTest.cs @@ -44,7 +44,7 @@ public void NewFieldDefinition() var field = new FieldDefinition( "MyField", FieldAttributes.Public | FieldAttributes.Static, - FieldSignature.CreateStatic(module.CorLibTypeFactory.Object)); + module.CorLibTypeFactory.Object); module.GetOrCreateModuleType().Fields.Add(field); // Rebuild. @@ -107,7 +107,7 @@ public void NewTypeReference() module.GetOrCreateModuleType().Fields.Add(new FieldDefinition( "MyField", FieldAttributes.Public | FieldAttributes.Static, - FieldSignature.CreateStatic(reference.ToTypeSignature()))); + reference.ToTypeSignature())); // Rebuild. var builder = new ManagedPEImageBuilder(); diff --git a/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/FieldTokenPreservationTest.cs b/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/FieldTokenPreservationTest.cs index b80344617..845cc7eae 100644 --- a/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/FieldTokenPreservationTest.cs +++ b/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/FieldTokenPreservationTest.cs @@ -14,12 +14,12 @@ public class FieldTokenPreservationTest : TokenPreservationTestBase private static ModuleDefinition CreateSampleFieldDefsModule(int typeCount, int fieldsPerType) { var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld_NetCore); - + for (int i = 0; i < typeCount; i++) { var dummyType = new TypeDefinition("Namespace", $"Type{i.ToString()}", TypeAttributes.Public | TypeAttributes.Abstract | TypeAttributes.Sealed); - + module.TopLevelTypes.Add(dummyType); for (int j = 0; j < fieldsPerType; j++) dummyType.Fields.Add(CreateDummyField(module, $"Field{j}")); @@ -28,18 +28,16 @@ private static ModuleDefinition CreateSampleFieldDefsModule(int typeCount, int f return RebuildAndReloadModule(module, MetadataBuilderFlags.None); } - private static FieldDefinition CreateDummyField(ModuleDefinition module, string name) - { - return new FieldDefinition(name, - FieldAttributes.Public | FieldAttributes.Static, - FieldSignature.CreateStatic(module.CorLibTypeFactory.Int32)); - } - + private static FieldDefinition CreateDummyField(ModuleDefinition module, string name) => new( + name, + FieldAttributes.Public | FieldAttributes.Static, + module.CorLibTypeFactory.Int32); + [Fact] public void PreserveFieldDefsNoChange() { var module = CreateSampleFieldDefsModule(10, 10); - + var newModule = RebuildAndReloadModule(module,MetadataBuilderFlags.PreserveFieldDefinitionIndices); AssertSameTokens(module, newModule, t => t.Fields); @@ -49,12 +47,12 @@ public void PreserveFieldDefsNoChange() public void PreserveFieldDefsChangeOrderOfTypes() { var module = CreateSampleFieldDefsModule(10, 10); - + const int swapIndex = 3; var type = module.TopLevelTypes[swapIndex]; module.TopLevelTypes.RemoveAt(swapIndex); module.TopLevelTypes.Insert(swapIndex + 1, type); - + var newModule = RebuildAndReloadModule(module,MetadataBuilderFlags.PreserveFieldDefinitionIndices); AssertSameTokens(module, newModule, t => t.Fields); @@ -70,7 +68,7 @@ public void PreserveFieldDefsChangeOrderOfFieldsInType() var field = type.Fields[swapIndex]; type.Fields.RemoveAt(swapIndex); type.Fields.Insert(swapIndex + 1, field); - + var newModule = RebuildAndReloadModule(module,MetadataBuilderFlags.PreserveFieldDefinitionIndices); AssertSameTokens(module, newModule, t => t.Fields); @@ -84,7 +82,7 @@ public void PreserveFieldDefsAddExtraField() var type = module.TopLevelTypes[2]; var field = CreateDummyField(module, "ExtraField"); type.Fields.Insert(3, field); - + var newModule = RebuildAndReloadModule(module,MetadataBuilderFlags.PreserveFieldDefinitionIndices); AssertSameTokens(module, newModule, t => t.Fields); @@ -99,10 +97,10 @@ public void PreserveFieldDefsRemoveField() const int indexToRemove = 3; var field = type.Fields[indexToRemove]; type.Fields.RemoveAt(indexToRemove); - + var newModule = RebuildAndReloadModule(module,MetadataBuilderFlags.PreserveFieldDefinitionIndices); AssertSameTokens(module, newModule, t => t.Fields, field.MetadataToken); } } -} \ No newline at end of file +} diff --git a/test/AsmResolver.DotNet.Tests/FieldDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/FieldDefinitionTest.cs index 355ef7fb9..28737ee06 100644 --- a/test/AsmResolver.DotNet.Tests/FieldDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Tests/FieldDefinitionTest.cs @@ -73,7 +73,7 @@ public void PersistentFieldSignature() var field = (FieldDefinition) module.LookupMember( typeof(SingleField).GetField(nameof(SingleField.IntField)).MetadataToken); - field.Signature = FieldSignature.CreateInstance(module.CorLibTypeFactory.Byte); + field.Signature = new FieldSignature(module.CorLibTypeFactory.Byte); var newField = RebuildAndLookup(field); diff --git a/test/AsmResolver.DotNet.Tests/MetadataResolverTest.cs b/test/AsmResolver.DotNet.Tests/MetadataResolverTest.cs index f4053192f..d3bc6ecac 100644 --- a/test/AsmResolver.DotNet.Tests/MetadataResolverTest.cs +++ b/test/AsmResolver.DotNet.Tests/MetadataResolverTest.cs @@ -161,8 +161,10 @@ public void ResolveStringEmptyField() var module = new ModuleDefinition("SomeModule.dll"); var stringType = new TypeReference(module.CorLibTypeFactory.CorLibScope, "System", "String"); - var emptyField = new MemberReference(stringType, "Empty", - FieldSignature.CreateStatic(module.CorLibTypeFactory.String)); + var emptyField = new MemberReference( + stringType, + "Empty", + new FieldSignature(module.CorLibTypeFactory.String)); var definition = _fwResolver.ResolveField(emptyField); diff --git a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs index f4c8ef7c9..4fec02d8f 100644 --- a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs +++ b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs @@ -16,18 +16,18 @@ public class ReferenceImporterTest private readonly AssemblyReference _dummyAssembly = new AssemblyReference("SomeAssembly", new Version(1, 2, 3, 4)); private readonly ModuleDefinition _module; private readonly ReferenceImporter _importer; - + public ReferenceImporterTest() { _module = new ModuleDefinition("SomeModule.dll"); _importer = new ReferenceImporter(_module); } - + [Fact] public void ImportNewAssemblyShouldAddToModule() { var result = _importer.ImportScope(_dummyAssembly); - + Assert.Equal(_dummyAssembly, result, _comparer); Assert.Contains(result, _module.AssemblyReferences); } @@ -38,10 +38,10 @@ public void ImportExistingAssemblyShouldUseExistingAssembly() _module.AssemblyReferences.Add(_dummyAssembly); int count = _module.AssemblyReferences.Count; - + var copy = new AssemblyReference(_dummyAssembly); var result = _importer.ImportScope(copy); - + Assert.Same(_dummyAssembly, result); Assert.Equal(count, _module.AssemblyReferences.Count); } @@ -61,7 +61,7 @@ public void ImportAlreadyImportedTypeShouldUseSameInstance() { var type = new TypeReference(_dummyAssembly, "SomeNamespace", "SomeName"); var importedType = _importer.ImportType(type); - + var result = _importer.ImportType(importedType); Assert.Same(importedType, result); @@ -80,13 +80,13 @@ public void ImportTypeDefFromDifferentModuleShouldReturnTypeRef() Assert.IsAssignableFrom(result); Assert.Equal(definition, result, _comparer); } - + [Fact] public void ImportTypeDefInSameModuleShouldReturnSameInstance() { var definition = new TypeDefinition("SomeNamespace", "SomeName", TypeAttributes.Public); _module.TopLevelTypes.Add(definition); - + var importedType = _importer.ImportType(definition); Assert.Same(definition, importedType); @@ -127,7 +127,7 @@ public void ImportArrayTypeShouldResultInTypeSpecWithSzArray() Assert.IsAssignableFrom(result); Assert.IsAssignableFrom(((TypeSpecification) result).Signature); } - + [Fact] public void ImportCorLibTypeAsSignatureShouldResultInCorLibTypeSignature() { @@ -179,7 +179,7 @@ public void ImportMethodFromSameModuleShouldResultInSameInstance() { var type = new TypeDefinition(null, "Type", TypeAttributes.Public); _module.TopLevelTypes.Add(type); - + var method = new MethodDefinition("Method", MethodAttributes.Public | MethodAttributes.Static, MethodSignature.CreateStatic(_module.CorLibTypeFactory.Void)); type.Methods.Add(method); @@ -225,7 +225,7 @@ public void ImportFieldFromExternalModuleShouldResultInMemberRef() { var type = new TypeReference(_dummyAssembly, null, "Type"); var field = new MemberReference(type, "Field", - FieldSignature.CreateStatic(_module.CorLibTypeFactory.String)); + new FieldSignature(_module.CorLibTypeFactory.String)); var result = _importer.ImportField(field); @@ -238,9 +238,9 @@ public void ImportFieldFromSameModuleShouldResultInSameInstance() { var type = new TypeDefinition(null, "Type", TypeAttributes.Public); _module.TopLevelTypes.Add(type); - - var field = new FieldDefinition("Method", FieldAttributes.Public | FieldAttributes.Static, - FieldSignature.CreateStatic(_module.CorLibTypeFactory.Void)); + + var field = new FieldDefinition("Field", FieldAttributes.Public | FieldAttributes.Static, + new FieldSignature(_module.CorLibTypeFactory.Int32)); type.Fields.Add(field); var result = _importer.ImportField(field); @@ -254,10 +254,10 @@ public void ImportFieldFromReflectionShouldResultInMemberRef() var field = typeof(string).GetField("Empty"); var result = _importer.ImportField(field); - + Assert.Equal(field.Name, result.Name); Assert.Equal(field.DeclaringType.FullName, result.DeclaringType.FullName); Assert.Equal(field.FieldType.FullName, ((FieldSignature) result.Signature).FieldType.FullName); } } -} \ No newline at end of file +} diff --git a/test/AsmResolver.DotNet.Tests/Signatures/GenericContextTest.cs b/test/AsmResolver.DotNet.Tests/Signatures/GenericContextTest.cs index aa9a55beb..2554c775c 100644 --- a/test/AsmResolver.DotNet.Tests/Signatures/GenericContextTest.cs +++ b/test/AsmResolver.DotNet.Tests/Signatures/GenericContextTest.cs @@ -272,8 +272,7 @@ public void ParseGenericFromField() var genericParameter = new GenericParameterSignature(GenericParameterType.Type, 0); - var field = new FieldDefinition("Field", FieldAttributes.Private, - FieldSignature.CreateStatic(genericParameter)); + var field = new FieldDefinition("Field", FieldAttributes.Private, genericParameter); var member = new MemberReference(typeSpecification, field.Name, field.Signature); @@ -292,8 +291,7 @@ public void ParseGenericFromNotGenericField() var type = new TypeDefinition("", "Test type", TypeAttributes.Public); var notGenericSignature = new TypeDefOrRefSignature(type); - var field = new FieldDefinition("Field", FieldAttributes.Private, - FieldSignature.CreateStatic(notGenericSignature)); + var field = new FieldDefinition("Field", FieldAttributes.Private, notGenericSignature); var member = new MemberReference(type, field.Name, field.Signature); diff --git a/test/AsmResolver.DotNet.Tests/TokenAllocatorTest.cs b/test/AsmResolver.DotNet.Tests/TokenAllocatorTest.cs index 91a297084..6f7df22b3 100644 --- a/test/AsmResolver.DotNet.Tests/TokenAllocatorTest.cs +++ b/test/AsmResolver.DotNet.Tests/TokenAllocatorTest.cs @@ -98,10 +98,14 @@ public void AssignTokenOfNextMemberShouldPreserve() // Create two dummy fields. var fieldType = module.CorLibTypeFactory.Object; - var field1 = new FieldDefinition("NonAssignedField", FieldAttributes.Static, - FieldSignature.CreateStatic(fieldType)); - var field2 = new FieldDefinition("AssignedField", FieldAttributes.Static, - FieldSignature.CreateStatic(fieldType)); + var field1 = new FieldDefinition( + "NonAssignedField", + FieldAttributes.Static, + new FieldSignature(fieldType)); + var field2 = new FieldDefinition( + "AssignedField", + FieldAttributes.Static, + new FieldSignature(fieldType)); // Add both. var moduleType = module.GetOrCreateModuleType(); From bff8e08c934829d30fda64722bad2db24da7b03d Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 19 Feb 2022 13:39:26 +0100 Subject: [PATCH 012/184] Rename signature to field type in field constructor. --- src/AsmResolver.DotNet/FieldDefinition.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/AsmResolver.DotNet/FieldDefinition.cs b/src/AsmResolver.DotNet/FieldDefinition.cs index 35b9343ae..08dc3832d 100644 --- a/src/AsmResolver.DotNet/FieldDefinition.cs +++ b/src/AsmResolver.DotNet/FieldDefinition.cs @@ -70,14 +70,14 @@ public FieldDefinition(string? name, FieldAttributes attributes, FieldSignature? /// /// The name of the field. /// The attributes. - /// The type of values the field contains. - public FieldDefinition(string? name, FieldAttributes attributes, TypeSignature? signature) + /// The type of values the field contains. + public FieldDefinition(string? name, FieldAttributes attributes, TypeSignature? fieldType) : this(new MetadataToken(TableIndex.Field, 0)) { Name = name; Attributes = attributes; - Signature = signature is not null - ? new FieldSignature(signature) + Signature = fieldType is not null + ? new FieldSignature(fieldType) : null; } From 8b9e5ed8225c585e20d6f92ffc52804d7405e379 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 19 Feb 2022 13:43:48 +0100 Subject: [PATCH 013/184] Don't use field constructor taking fieldsig when not necessary. --- .../AsmResolver.DotNet.Tests/ReferenceImporterTest.cs | 11 ++++++++--- test/AsmResolver.DotNet.Tests/TokenAllocatorTest.cs | 10 ++-------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs index 4fec02d8f..be9f40fc4 100644 --- a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs +++ b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs @@ -224,7 +224,9 @@ public void ImportGenericMethodFromReflectionShouldResultInMethodSpec() public void ImportFieldFromExternalModuleShouldResultInMemberRef() { var type = new TypeReference(_dummyAssembly, null, "Type"); - var field = new MemberReference(type, "Field", + var field = new MemberReference( + type, + "Field", new FieldSignature(_module.CorLibTypeFactory.String)); var result = _importer.ImportField(field); @@ -239,8 +241,11 @@ public void ImportFieldFromSameModuleShouldResultInSameInstance() var type = new TypeDefinition(null, "Type", TypeAttributes.Public); _module.TopLevelTypes.Add(type); - var field = new FieldDefinition("Field", FieldAttributes.Public | FieldAttributes.Static, - new FieldSignature(_module.CorLibTypeFactory.Int32)); + var field = new FieldDefinition( + "Field", + FieldAttributes.Public | FieldAttributes.Static, + _module.CorLibTypeFactory.Int32); + type.Fields.Add(field); var result = _importer.ImportField(field); diff --git a/test/AsmResolver.DotNet.Tests/TokenAllocatorTest.cs b/test/AsmResolver.DotNet.Tests/TokenAllocatorTest.cs index 6f7df22b3..3c18ff7c6 100644 --- a/test/AsmResolver.DotNet.Tests/TokenAllocatorTest.cs +++ b/test/AsmResolver.DotNet.Tests/TokenAllocatorTest.cs @@ -98,14 +98,8 @@ public void AssignTokenOfNextMemberShouldPreserve() // Create two dummy fields. var fieldType = module.CorLibTypeFactory.Object; - var field1 = new FieldDefinition( - "NonAssignedField", - FieldAttributes.Static, - new FieldSignature(fieldType)); - var field2 = new FieldDefinition( - "AssignedField", - FieldAttributes.Static, - new FieldSignature(fieldType)); + var field1 = new FieldDefinition("NonAssignedField", FieldAttributes.Static, fieldType); + var field2 = new FieldDefinition("AssignedField", FieldAttributes.Static, fieldType); // Add both. var moduleType = module.GetOrCreateModuleType(); From 42554a858833fb9f23027643c93c3d26a7c877d9 Mon Sep 17 00:00:00 2001 From: Washi Date: Tue, 22 Feb 2022 12:18:20 +0100 Subject: [PATCH 014/184] Add UnresolvedMethodBody class. --- src/AsmResolver.DotNet/Code/MethodBody.cs | 64 +++++++++++-------- .../Code/UnresolvedMethodBody.cs | 19 ++++++ .../Serialized/DefaultMethodBodyReader.cs | 39 +++++------ 3 files changed, 77 insertions(+), 45 deletions(-) create mode 100644 src/AsmResolver.DotNet/Code/UnresolvedMethodBody.cs diff --git a/src/AsmResolver.DotNet/Code/MethodBody.cs b/src/AsmResolver.DotNet/Code/MethodBody.cs index ad3b4828a..b9837850d 100644 --- a/src/AsmResolver.DotNet/Code/MethodBody.cs +++ b/src/AsmResolver.DotNet/Code/MethodBody.cs @@ -1,27 +1,37 @@ -using System; - -namespace AsmResolver.DotNet.Code -{ - /// - /// Represents a body of a method defined in a .NET assembly. - /// - public abstract class MethodBody - { - /// - /// Initializes a new empty method body. - /// - /// The owner of the method body. - protected MethodBody(MethodDefinition owner) - { - Owner = owner ?? throw new ArgumentNullException(nameof(owner)); - } - - /// - /// Gets the method that owns the method body. - /// - public MethodDefinition Owner - { - get; - } - } -} \ No newline at end of file +using System; + +namespace AsmResolver.DotNet.Code +{ + /// + /// Represents a body of a method defined in a .NET assembly. + /// + public abstract class MethodBody + { + /// + /// Initializes a new empty method body. + /// + /// The owner of the method body. + protected MethodBody(MethodDefinition owner) + { + Owner = owner ?? throw new ArgumentNullException(nameof(owner)); + } + + /// + /// Gets the method that owns the method body. + /// + public MethodDefinition Owner + { + get; + } + + /// + /// When this method is stored in a serialized module, gets or sets the reference to the beginning of the + /// raw contents of the body. + /// + public ISegmentReference? Address + { + get; + set; + } + } +} diff --git a/src/AsmResolver.DotNet/Code/UnresolvedMethodBody.cs b/src/AsmResolver.DotNet/Code/UnresolvedMethodBody.cs new file mode 100644 index 000000000..b9fed1135 --- /dev/null +++ b/src/AsmResolver.DotNet/Code/UnresolvedMethodBody.cs @@ -0,0 +1,19 @@ +namespace AsmResolver.DotNet.Code +{ + /// + /// Represents a method body that was not resolved into + /// + public class UnresolvedMethodBody : MethodBody + { + /// + /// Creates a new unresolved method body stub. + /// + /// The owner of the method body. + /// The reference to the start of the method body. + public UnresolvedMethodBody(MethodDefinition owner, ISegmentReference address) + : base(owner) + { + Address = address; + } + } +} diff --git a/src/AsmResolver.DotNet/Serialized/DefaultMethodBodyReader.cs b/src/AsmResolver.DotNet/Serialized/DefaultMethodBodyReader.cs index 0ff094b92..8ebd40d4c 100644 --- a/src/AsmResolver.DotNet/Serialized/DefaultMethodBodyReader.cs +++ b/src/AsmResolver.DotNet/Serialized/DefaultMethodBodyReader.cs @@ -2,7 +2,6 @@ using AsmResolver.DotNet.Code; using AsmResolver.DotNet.Code.Cil; using AsmResolver.PE.DotNet.Cil; -using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; namespace AsmResolver.DotNet.Serialized @@ -16,27 +15,25 @@ public class DefaultMethodBodyReader : IMethodBodyReader /// public virtual MethodBody? ReadMethodBody(ModuleReaderContext context, MethodDefinition owner, in MethodDefinitionRow row) { + var bodyReference = row.Body; + if (bodyReference == SegmentReference.Null) + return null; + + MethodBody? result = null; + try { - if (row.Body.CanRead) + if (bodyReference.IsBounded) { - if (owner.IsIL) - { - var reader = row.Body.CreateReader(); - var rawBody = CilRawMethodBody.FromReader(context, ref reader); - return rawBody is not null - ? CilMethodBody.FromRawMethodBody(context, owner, rawBody) - : null; - } - else - { - context.NotSupported($"Body of method {owner.MetadataToken} is native and unbounded which is not supported."); - // TODO: handle native method bodies. - } + if (bodyReference.GetSegment() is CilRawMethodBody rawMethodBody) + result = CilMethodBody.FromRawMethodBody(context, owner, rawMethodBody); } - else if (row.Body.IsBounded && row.Body.GetSegment() is CilRawMethodBody rawMethodBody) + else if (bodyReference.CanRead && owner.IsIL) { - return CilMethodBody.FromRawMethodBody(context, owner, rawMethodBody); + var reader = bodyReference.CreateReader(); + var rawBody = CilRawMethodBody.FromReader(context, ref reader); + if (rawBody is not null) + result = CilMethodBody.FromRawMethodBody(context, owner, rawBody); } } catch (Exception ex) @@ -44,7 +41,13 @@ public class DefaultMethodBodyReader : IMethodBodyReader context.RegisterException(new BadImageFormatException($"Failed to parse the method body of {owner.MetadataToken}.", ex)); } - return null; + if (result is not null) + { + result.Address = bodyReference; + return result; + } + + return new UnresolvedMethodBody(owner, bodyReference); } } } From 7e464ca02336bfdc12b3723399b17b9368bbc3ad Mon Sep 17 00:00:00 2001 From: Washi Date: Tue, 22 Feb 2022 12:29:56 +0100 Subject: [PATCH 015/184] Add consistency test. --- .../Code/Native/NativeMethodBodyTest.cs | 59 ++++++++++++++----- 1 file changed, 45 insertions(+), 14 deletions(-) diff --git a/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs b/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs index 3d6515007..8248382d7 100644 --- a/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs +++ b/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Linq; using AsmResolver.DotNet.Code.Native; using AsmResolver.DotNet.Signatures; @@ -18,7 +19,7 @@ public class NativeMethodBodyTest private static NativeMethodBody CreateDummyBody(bool isVoid, bool is32Bit) { var module = ModuleDefinition.FromBytes(Properties.Resources.TheAnswer_NetFx); - + module.Attributes &= DotNetDirectoryFlags.ILOnly; if (is32Bit) { @@ -41,13 +42,13 @@ private static NativeMethodBody CreateDummyBody(bool isVoid, bool is32Bit) | MethodImplAttributes.PreserveSig; method.DeclaringType.Methods.Remove(method); module.GetOrCreateModuleType().Methods.Add(method); - + return method.NativeMethodBody = new NativeMethodBody(method); } private static CodeSegment GetNewCodeSegment(IPEImage image) { - var methodTable = image.DotNetDirectory.Metadata + var methodTable = image.DotNetDirectory!.Metadata! .GetStream() .GetTable(TableIndex.Method); var row = methodTable.First(r => (r.ImplAttributes & MethodImplAttributes.Native) != 0); @@ -99,18 +100,18 @@ public void NativeMethodBodyImportedSymbolShouldEndUpInImportsDirectory() 0x67, 0x65, 0x64, 0x20, 0x77, 0x6f, 0x72, // "ed worl" 0x6c, 0x64, 0x21, 0x00 // "d!" }; - + // Fix up reference to ucrtbased.dll!puts var ucrtbased = new ImportedModule("ucrtbased.dll"); var puts = new ImportedSymbol(0x4fc, "puts"); ucrtbased.Symbols.Add(puts); - + body.AddressFixups.Add(new AddressFixup( 0xD, AddressFixupType.Relative32BitAddress, puts )); // Serialize module to PE image. - var module = body.Owner.Module; + var module = body.Owner.Module!; var image = module.ToPEImage(); // Verify import is added to PE image. @@ -136,24 +137,24 @@ public void Native32BitMethodShouldResultInBaseRelocation() /* 19: */ 0x5D, // pop ebp /* 1A: */ 0xC3, // ret }; - + // Fix up reference to ucrtbased.dll!puts var ucrtbased = new ImportedModule("ucrtbased.dll"); var puts = new ImportedSymbol(0x4fc, "puts"); ucrtbased.Symbols.Add(puts); - + body.AddressFixups.Add(new AddressFixup( 0xD, AddressFixupType.Absolute32BitAddress, puts )); // Serialize module to PE image. - var module = body.Owner.Module; + var module = body.Owner.Module!; var image = module.ToPEImage(); // Verify import is added to PE image. Assert.Contains(image.Imports, m => m.Name == ucrtbased.Name && m.Symbols.Any(s => s.Name == puts.Name)); - + // Verify relocation is added. var segment = GetNewCodeSegment(image); Assert.Contains(image.Relocations, r => @@ -183,7 +184,7 @@ public void DuplicateImportedSymbolsShouldResultInSameImportInImage() 0x67, 0x65, 0x64, 0x20, 0x77, 0x6f, 0x72, // "ed worl" 0x6c, 0x64, 0x21, 0x00 // "d!" }; - + // Add reference to ucrtbased.dll!puts at offset 0xD. var ucrtbased1 = new ImportedModule("ucrtbased.dll"); var puts1 = new ImportedSymbol(0x4fc, "puts"); @@ -191,7 +192,7 @@ public void DuplicateImportedSymbolsShouldResultInSameImportInImage() body.AddressFixups.Add(new AddressFixup( 0xD, AddressFixupType.Relative32BitAddress, puts1 )); - + // Add second (duplicated) reference to ucrtbased.dll!puts at offset 0x20. var ucrtbased2 = new ImportedModule("ucrtbased.dll"); var puts2 = new ImportedSymbol(0x4fc, "puts"); @@ -201,7 +202,7 @@ public void DuplicateImportedSymbolsShouldResultInSameImportInImage() )); // Serialize module to PE image. - var module = body.Owner.Module; + var module = body.Owner.Module!; var image = module.ToPEImage(); // Verify import is added to PE image. @@ -212,5 +213,35 @@ public void DuplicateImportedSymbolsShouldResultInSameImportInImage() Assert.NotNull(importedSymbol); Assert.Equal(puts1.Name, importedSymbol.Name); } + + [Fact] + public void ReadNativeMethodShouldResultInReferenceWithRightContents() + { + // Create native body. + var body = CreateDummyBody(false, false); + body.Code = new byte[] + { + 0xb8, 0x39, 0x05, 0x00, 0x00, // mov rax, 1337 + 0xc3 // ret + }; + + // Serialize module. + var module = body.Owner.Module!; + using var stream = new MemoryStream(); + module.Write(stream); + + // Reload and look up native method. + var newModule = ModuleDefinition.FromBytes(stream.ToArray()); + var method = newModule.GetAllTypes().SelectMany(t => t.Methods).First(m => m.IsNative); + + // Verify if code behind the entry address is consistent. + var reference = method.MethodBody?.Address; + Assert.NotNull(reference); + Assert.True(reference.CanRead); + + byte[] newBuffer = new byte[body.Code.Length]; + reference.CreateReader().ReadBytes(newBuffer, 0, newBuffer.Length); + Assert.Equal(body.Code, newBuffer); + } } -} \ No newline at end of file +} From ab76788dc220595370bdfa6096b05395de5667f0 Mon Sep 17 00:00:00 2001 From: Jeremy Pritts Date: Wed, 23 Feb 2022 13:28:58 -0500 Subject: [PATCH 016/184] use more consistent naming and relevant extension methods --- docs/dotnet/methods.rst | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/dotnet/methods.rst b/docs/dotnet/methods.rst index 8f65c1490..30f8dec4e 100644 --- a/docs/dotnet/methods.rst +++ b/docs/dotnet/methods.rst @@ -8,27 +8,27 @@ This section covers referencing methods such as ``System.Collections.Generic.Lis .. code-block:: csharp - var listTypeRef = new TypeReference(corlibScope, "System.Collections.Generic", "List`1"); + var corlibScope = moduleDefinition.CorLibTypeFactory.CorLibScope; + + var listTypeReference = new TypeReference(corlibScope, "System.Collections.Generic", "List`1"); - var listOfInt32 = new GenericInstanceTypeSignature(listTypeRef, - isValueType: false, - typeArguments: new[] { module.CorLibTypeFactory.Int32 }); + var listOfInt32 = listTypeReference.MakeGenericInstanceType(moduleDefinition.CorLibTypeFactory.Int32); - var addMethodDefinition = listTypeRef.Resolve().Methods.Single(m => m.Name == "Add" && m.Parameters.Count == 1); + var addMethodDefinition = listTypeReference.Resolve().Methods.Single(m => m.Name == "Add" && m.Parameters.Count == 1); var reference = new MemberReference(listOfInt32.ToTypeDefOrRef(), addMethodDefinition.Name, addMethodDefinition.Signature); Generic Methods on Non-Generic Types ------------------------------------------ -This section covers referencing methods such as ``System.Array.Empty``. They can be referenced with the ``MethodSpecification`` class. +This section covers referencing methods such as ``System.Array.Empty``. They can be referenced with the ``MethodSpecification`` class via the ``MakeGenericInstanceMethod`` extension method on ``IMethodDefOrRef``. .. code-block:: csharp - var arrayRef = new TypeReference(corlibScope, "System", "Array"); + var corlibScope = moduleDefinition.CorLibTypeFactory.CorLibScope; - var emptyMethodDefinition = arrayRef.Resolve().Methods.Single(m => m.Name == "Empty" && m.Parameters.Count == 0); + var arrayTypeReference = new TypeReference(corlibScope, "System", "Array"); - var genericInstanceMethodSignature = new GenericInstanceMethodSignature(module.CorLibTypeFactory.Int32); + var emptyMethodDefinition = arrayTypeReference.Resolve().Methods.Single(m => m.Name == "Empty" && m.Parameters.Count == 0); - var reference = new MethodSpecification(emptyMethodDefinition, genericInstanceMethodSignature); + var reference = emptyMethodDefinition.MakeGenericInstanceMethod(moduleDefinition.CorLibTypeFactory.Int32); From 8ecdcd4590618d9248fd6af46cb774128da707d9 Mon Sep 17 00:00:00 2001 From: Jeremy Pritts Date: Wed, 23 Feb 2022 13:34:21 -0500 Subject: [PATCH 017/184] add net 7 to known cor libs --- src/AsmResolver.DotNet/KnownCorLibs.cs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/AsmResolver.DotNet/KnownCorLibs.cs b/src/AsmResolver.DotNet/KnownCorLibs.cs index 106d21c03..951f7be5e 100644 --- a/src/AsmResolver.DotNet/KnownCorLibs.cs +++ b/src/AsmResolver.DotNet/KnownCorLibs.cs @@ -70,6 +70,16 @@ public static class KnownCorLibs 0x7C, 0xEC, 0x85, 0xD7, 0xBE, 0xA7, 0x79, 0x8E }); + /// + /// References System.Private.CoreLib.dll, Version=7.0.0.0, PublicKeyToken=7CEC85D7BEA7798E. This is used by .NET + /// assemblies targeting .NET 7.0. + /// + public static readonly AssemblyReference SystemPrivateCoreLib_v7_0_0_0 = new AssemblyReference("System.Private.CoreLib", + new Version(7, 0, 0, 0), false, new byte[] + { + 0x7C, 0xEC, 0x85, 0xD7, 0xBE, 0xA7, 0x79, 0x8E + }); + /// /// References System.Runtime.dll, Version=4.0.20.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET /// assemblies targeting .NET standard 1.3 and 1.4. @@ -130,6 +140,16 @@ public static class KnownCorLibs 0xB0, 0x3F, 0x5F, 0x7F, 0x11, 0xD5, 0x0A, 0x3A }); + /// + /// References System.Runtime.dll, Version=7.0.0.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET + /// assemblies targeting .NET 7.0. + /// + public static readonly AssemblyReference SystemRuntime_v7_0_0_0 = new AssemblyReference("System.Runtime", + new Version(7, 0, 0, 0), false, new byte[] + { + 0xB0, 0x3F, 0x5F, 0x7F, 0x11, 0xD5, 0x0A, 0x3A + }); + /// /// References netstandard.dll, Version=2.0.0.0, PublicKeyToken=CC7B13FFCD2DDD51. This is used by .NET /// assemblies targeting .NET standard 2.0. @@ -164,9 +184,11 @@ static KnownCorLibs() SystemRuntime_v4_2_2_0, SystemRuntime_v5_0_0_0, SystemRuntime_v6_0_0_0, + SystemRuntime_v7_0_0_0, SystemPrivateCoreLib_v4_0_0_0, SystemPrivateCoreLib_v5_0_0_0, - SystemPrivateCoreLib_v6_0_0_0 + SystemPrivateCoreLib_v6_0_0_0, + SystemPrivateCoreLib_v7_0_0_0 }; KnownCorLibNames = new HashSet(KnownCorLibReferences.Select(r => r.Name!.Value)); From cf199d235214255d3657a75d1c84ae1614795855 Mon Sep 17 00:00:00 2001 From: Jeremy Pritts Date: Wed, 23 Feb 2022 13:52:23 -0500 Subject: [PATCH 018/184] set all projects to use C# language version 10 --- src/AsmResolver.DotNet/AsmResolver.DotNet.csproj | 4 ++-- src/AsmResolver.PE.File/AsmResolver.PE.File.csproj | 3 ++- .../AsmResolver.PE.Win32Resources.csproj | 1 + src/AsmResolver.PE/AsmResolver.PE.csproj | 4 ++-- src/AsmResolver/AsmResolver.csproj | 3 ++- test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj | 1 + test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj | 3 ++- .../AsmResolver.PE.File.Tests.csproj | 3 ++- test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj | 4 +++- .../AsmResolver.PE.Win32Resources.Tests.csproj | 2 +- test/AsmResolver.Tests/AsmResolver.Tests.csproj | 4 +++- .../AsmResolver.DotNet.TestCases.CustomAttributes.csproj | 3 ++- .../AsmResolver.DotNet.TestCases.Events.csproj | 3 ++- .../AsmResolver.DotNet.TestCases.Fields.csproj | 3 ++- .../AsmResolver.DotNet.TestCases.Generics.csproj | 3 ++- .../AsmResolver.DotNet.TestCases.Methods.csproj | 3 ++- .../AsmResolver.DotNet.TestCases.MultiModules.csproj | 1 + .../AsmResolver.DotNet.TestCases.NestedClasses.csproj | 3 ++- .../AsmResolver.DotNet.TestCases.Properties.csproj | 3 ++- .../AsmResolver.DotNet.TestCases.Resources.csproj | 2 +- .../AsmResolver.DotNet.TestCases.Types.csproj | 3 ++- test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj | 1 + test/TestBinaries/DotNet/TheAnswer/TheAnswer.csproj | 1 + .../AsmResolver.PE.Exports.OrdinalMapper.csproj | 1 + 24 files changed, 42 insertions(+), 20 deletions(-) diff --git a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj index 46d0b6a32..da8f80ae9 100644 --- a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj +++ b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj @@ -1,4 +1,4 @@ - + AsmResolver.DotNet @@ -7,7 +7,7 @@ netstandard2.0 true 1701;1702;NU5105 - 9 + 10 enable diff --git a/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj b/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj index 15999d2f1..97588083c 100644 --- a/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj +++ b/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj @@ -1,4 +1,4 @@ - + AsmResolver.PE.File @@ -9,6 +9,7 @@ 1701;1702;NU5105 true enable + 10 diff --git a/src/AsmResolver.PE.Win32Resources/AsmResolver.PE.Win32Resources.csproj b/src/AsmResolver.PE.Win32Resources/AsmResolver.PE.Win32Resources.csproj index d16e37894..62df025a3 100644 --- a/src/AsmResolver.PE.Win32Resources/AsmResolver.PE.Win32Resources.csproj +++ b/src/AsmResolver.PE.Win32Resources/AsmResolver.PE.Win32Resources.csproj @@ -7,6 +7,7 @@ true 1701;1702;NU5105 enable + 10 diff --git a/src/AsmResolver.PE/AsmResolver.PE.csproj b/src/AsmResolver.PE/AsmResolver.PE.csproj index 5c3dd46fe..3881ad3bc 100644 --- a/src/AsmResolver.PE/AsmResolver.PE.csproj +++ b/src/AsmResolver.PE/AsmResolver.PE.csproj @@ -1,4 +1,4 @@ - + AsmResolver.PE @@ -7,7 +7,7 @@ netstandard2.0 true 1701;1702;NU5105 - 9 + 10 enable diff --git a/src/AsmResolver/AsmResolver.csproj b/src/AsmResolver/AsmResolver.csproj index 3e731b8aa..75cea2584 100644 --- a/src/AsmResolver/AsmResolver.csproj +++ b/src/AsmResolver/AsmResolver.csproj @@ -1,4 +1,4 @@ - + AsmResolver @@ -8,6 +8,7 @@ true 1701;1702;NU5105 enable + 10 diff --git a/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj b/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj index 645f5f42d..fb6f4004e 100644 --- a/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj +++ b/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj @@ -3,6 +3,7 @@ Exe netcoreapp3.1 + 10 diff --git a/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj b/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj index 108836e48..cfeb0164d 100644 --- a/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj +++ b/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj @@ -1,7 +1,8 @@ - + net5.0 + 10 false 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 600b80c66..e3c2c7781 100644 --- a/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj +++ b/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj @@ -1,7 +1,8 @@ - + netcoreapp3.1 + 10 false diff --git a/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj b/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj index e2f4044ef..e08b436b3 100644 --- a/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj +++ b/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.1 @@ -7,6 +7,8 @@ true + 10 + enable 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 233b2df5b..cfe41b2cb 100644 --- a/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj +++ b/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj @@ -7,7 +7,7 @@ AsmResolver.PE.Win32Resources.Tests - 8 + 10 warnings diff --git a/test/AsmResolver.Tests/AsmResolver.Tests.csproj b/test/AsmResolver.Tests/AsmResolver.Tests.csproj index e333759d2..29bfeacd8 100644 --- a/test/AsmResolver.Tests/AsmResolver.Tests.csproj +++ b/test/AsmResolver.Tests/AsmResolver.Tests.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.1 @@ -7,6 +7,8 @@ true + 10 + enable diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.CustomAttributes/AsmResolver.DotNet.TestCases.CustomAttributes.csproj b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.CustomAttributes/AsmResolver.DotNet.TestCases.CustomAttributes.csproj index 261ad5bf0..5fa483f12 100644 --- a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.CustomAttributes/AsmResolver.DotNet.TestCases.CustomAttributes.csproj +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.CustomAttributes/AsmResolver.DotNet.TestCases.CustomAttributes.csproj @@ -1,7 +1,8 @@ - + netstandard2.0 + 10 diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Events/AsmResolver.DotNet.TestCases.Events.csproj b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Events/AsmResolver.DotNet.TestCases.Events.csproj index 261ad5bf0..5fa483f12 100644 --- a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Events/AsmResolver.DotNet.TestCases.Events.csproj +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Events/AsmResolver.DotNet.TestCases.Events.csproj @@ -1,7 +1,8 @@ - + netstandard2.0 + 10 diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Fields/AsmResolver.DotNet.TestCases.Fields.csproj b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Fields/AsmResolver.DotNet.TestCases.Fields.csproj index d2a210cda..5840e70fd 100644 --- a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Fields/AsmResolver.DotNet.TestCases.Fields.csproj +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Fields/AsmResolver.DotNet.TestCases.Fields.csproj @@ -1,7 +1,8 @@ - + netstandard2.0 + 10 diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/AsmResolver.DotNet.TestCases.Generics.csproj b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/AsmResolver.DotNet.TestCases.Generics.csproj index d2a210cda..5840e70fd 100644 --- a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/AsmResolver.DotNet.TestCases.Generics.csproj +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/AsmResolver.DotNet.TestCases.Generics.csproj @@ -1,7 +1,8 @@ - + netstandard2.0 + 10 diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/AsmResolver.DotNet.TestCases.Methods.csproj b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/AsmResolver.DotNet.TestCases.Methods.csproj index 08f7f347e..320097974 100644 --- a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/AsmResolver.DotNet.TestCases.Methods.csproj +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/AsmResolver.DotNet.TestCases.Methods.csproj @@ -1,7 +1,8 @@ - + netstandard2.0 + 10 diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.MultiModules/AsmResolver.DotNet.TestCases.MultiModules.csproj b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.MultiModules/AsmResolver.DotNet.TestCases.MultiModules.csproj index 94607d638..d387b2437 100644 --- a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.MultiModules/AsmResolver.DotNet.TestCases.MultiModules.csproj +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.MultiModules/AsmResolver.DotNet.TestCases.MultiModules.csproj @@ -2,6 +2,7 @@ netstandard2.0 + 10 AsmResolver.DotNet.TestCases.MultiModules diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.NestedClasses/AsmResolver.DotNet.TestCases.NestedClasses.csproj b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.NestedClasses/AsmResolver.DotNet.TestCases.NestedClasses.csproj index d2a210cda..5840e70fd 100644 --- a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.NestedClasses/AsmResolver.DotNet.TestCases.NestedClasses.csproj +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.NestedClasses/AsmResolver.DotNet.TestCases.NestedClasses.csproj @@ -1,7 +1,8 @@ - + netstandard2.0 + 10 diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Properties/AsmResolver.DotNet.TestCases.Properties.csproj b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Properties/AsmResolver.DotNet.TestCases.Properties.csproj index 261ad5bf0..5fa483f12 100644 --- a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Properties/AsmResolver.DotNet.TestCases.Properties.csproj +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Properties/AsmResolver.DotNet.TestCases.Properties.csproj @@ -1,7 +1,8 @@ - + netstandard2.0 + 10 diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Resources/AsmResolver.DotNet.TestCases.Resources.csproj b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Resources/AsmResolver.DotNet.TestCases.Resources.csproj index d22f09dba..5b50ab772 100644 --- a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Resources/AsmResolver.DotNet.TestCases.Resources.csproj +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Resources/AsmResolver.DotNet.TestCases.Resources.csproj @@ -2,7 +2,7 @@ netstandard2.0 - 8 + 10 true diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Types/AsmResolver.DotNet.TestCases.Types.csproj b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Types/AsmResolver.DotNet.TestCases.Types.csproj index 261ad5bf0..5fa483f12 100644 --- a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Types/AsmResolver.DotNet.TestCases.Types.csproj +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Types/AsmResolver.DotNet.TestCases.Types.csproj @@ -1,7 +1,8 @@ - + netstandard2.0 + 10 diff --git a/test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj b/test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj index a2a134785..8401ca8b4 100644 --- a/test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj +++ b/test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj @@ -3,6 +3,7 @@ Exe net47;netcoreapp3.1 + 10 Resources\Icon.ico diff --git a/test/TestBinaries/DotNet/TheAnswer/TheAnswer.csproj b/test/TestBinaries/DotNet/TheAnswer/TheAnswer.csproj index d67905bb3..799dc02bc 100644 --- a/test/TestBinaries/DotNet/TheAnswer/TheAnswer.csproj +++ b/test/TestBinaries/DotNet/TheAnswer/TheAnswer.csproj @@ -3,6 +3,7 @@ Exe net47;netcoreapp3.1 + 10 diff --git a/tools/AsmResolver.PE.Exports.OrdinalMapper/AsmResolver.PE.Exports.OrdinalMapper.csproj b/tools/AsmResolver.PE.Exports.OrdinalMapper/AsmResolver.PE.Exports.OrdinalMapper.csproj index 399605bab..fc63a13a3 100644 --- a/tools/AsmResolver.PE.Exports.OrdinalMapper/AsmResolver.PE.Exports.OrdinalMapper.csproj +++ b/tools/AsmResolver.PE.Exports.OrdinalMapper/AsmResolver.PE.Exports.OrdinalMapper.csproj @@ -3,6 +3,7 @@ Exe net472 + 10 enable From 510f24ea7891f8c3ca4ff7229eb6872d6f1ce32b Mon Sep 17 00:00:00 2001 From: Jeremy Pritts Date: Wed, 23 Feb 2022 14:01:32 -0500 Subject: [PATCH 019/184] bump appveyor to VS 2022 --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 9b205fb48..ce0a1f25a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,7 +3,7 @@ only: - master - image: Visual Studio 2019 + image: Visual Studio 2022 version: 4.8.0-master-build.{build} configuration: Release @@ -32,7 +32,7 @@ only: - development - image: Visual Studio 2019 + image: Visual Studio 2022 version: 4.8.0-dev-build.{build} configuration: Release From 6901e0b6a6d64bf6833408f7bccfb3df73bf4854 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 24 Feb 2022 18:57:04 +0100 Subject: [PATCH 020/184] Prefer reading the method body segment over casting to CilRawMethodBody if possible. --- src/AsmResolver.DotNet/Code/UnresolvedMethodBody.cs | 3 ++- .../Serialized/DefaultMethodBodyReader.cs | 13 +++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/AsmResolver.DotNet/Code/UnresolvedMethodBody.cs b/src/AsmResolver.DotNet/Code/UnresolvedMethodBody.cs index b9fed1135..52123b1f3 100644 --- a/src/AsmResolver.DotNet/Code/UnresolvedMethodBody.cs +++ b/src/AsmResolver.DotNet/Code/UnresolvedMethodBody.cs @@ -1,7 +1,8 @@ namespace AsmResolver.DotNet.Code { /// - /// Represents a method body that was not resolved into + /// Provides a wrapper around a , pointing to the beginning of a method body. + /// The interpretation of the data behind the pointer was left to the user. /// public class UnresolvedMethodBody : MethodBody { diff --git a/src/AsmResolver.DotNet/Serialized/DefaultMethodBodyReader.cs b/src/AsmResolver.DotNet/Serialized/DefaultMethodBodyReader.cs index 8ebd40d4c..07dbb19ea 100644 --- a/src/AsmResolver.DotNet/Serialized/DefaultMethodBodyReader.cs +++ b/src/AsmResolver.DotNet/Serialized/DefaultMethodBodyReader.cs @@ -23,18 +23,19 @@ public class DefaultMethodBodyReader : IMethodBodyReader try { - if (bodyReference.IsBounded) - { - if (bodyReference.GetSegment() is CilRawMethodBody rawMethodBody) - result = CilMethodBody.FromRawMethodBody(context, owner, rawMethodBody); - } - else if (bodyReference.CanRead && owner.IsIL) + if (bodyReference.CanRead && owner.IsIL) { var reader = bodyReference.CreateReader(); var rawBody = CilRawMethodBody.FromReader(context, ref reader); if (rawBody is not null) result = CilMethodBody.FromRawMethodBody(context, owner, rawBody); } + + if (result is null && bodyReference.IsBounded) + { + if (bodyReference.GetSegment() is CilRawMethodBody rawMethodBody) + result = CilMethodBody.FromRawMethodBody(context, owner, rawMethodBody); + } } catch (Exception ex) { From 9568a5445aafbd99685a5ce14e57d77a512265d0 Mon Sep 17 00:00:00 2001 From: Jeremy Pritts Date: Thu, 24 Feb 2022 15:21:39 -0500 Subject: [PATCH 021/184] bump nuget dependency versions --- src/AsmResolver.DotNet/AsmResolver.DotNet.csproj | 2 +- test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj | 2 +- test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj | 2 +- .../AsmResolver.PE.File.Tests.csproj | 2 +- test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj | 2 +- .../AsmResolver.PE.Win32Resources.Tests.csproj | 4 ++-- test/AsmResolver.Tests/AsmResolver.Tests.csproj | 2 +- .../AsmResolver.DotNet.TestCases.Resources.csproj | 4 ++-- test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj | 2 +- test/TestBinaries/DotNet/TheAnswer/TheAnswer.csproj | 2 +- 10 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj index da8f80ae9..87725f3f4 100644 --- a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj +++ b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj @@ -28,7 +28,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj b/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj index fb6f4004e..e3eb947fb 100644 --- a/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj +++ b/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj @@ -7,7 +7,7 @@ - + diff --git a/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj b/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj index cfeb0164d..aa4054fa5 100644 --- a/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj +++ b/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj @@ -10,7 +10,7 @@ - + all 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 e3c2c7781..1c8b905d7 100644 --- a/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj +++ b/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj @@ -10,7 +10,7 @@ - + all diff --git a/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj b/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj index e08b436b3..bdd463044 100644 --- a/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj +++ b/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj @@ -21,7 +21,7 @@ - + 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 cfe41b2cb..3e2ef16ff 100644 --- a/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj +++ b/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj @@ -13,13 +13,13 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/AsmResolver.Tests/AsmResolver.Tests.csproj b/test/AsmResolver.Tests/AsmResolver.Tests.csproj index 29bfeacd8..30e201029 100644 --- a/test/AsmResolver.Tests/AsmResolver.Tests.csproj +++ b/test/AsmResolver.Tests/AsmResolver.Tests.csproj @@ -13,7 +13,7 @@ - + all diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Resources/AsmResolver.DotNet.TestCases.Resources.csproj b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Resources/AsmResolver.DotNet.TestCases.Resources.csproj index 5b50ab772..37d68115c 100644 --- a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Resources/AsmResolver.DotNet.TestCases.Resources.csproj +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Resources/AsmResolver.DotNet.TestCases.Resources.csproj @@ -25,8 +25,8 @@ - - + + diff --git a/test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj b/test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj index 8401ca8b4..e12456cfc 100644 --- a/test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj +++ b/test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj @@ -11,6 +11,6 @@ AnyCPU - + diff --git a/test/TestBinaries/DotNet/TheAnswer/TheAnswer.csproj b/test/TestBinaries/DotNet/TheAnswer/TheAnswer.csproj index 799dc02bc..cdfdf3186 100644 --- a/test/TestBinaries/DotNet/TheAnswer/TheAnswer.csproj +++ b/test/TestBinaries/DotNet/TheAnswer/TheAnswer.csproj @@ -6,7 +6,7 @@ 10 - + From 36bd036ab71f1017d5e365c65b30a492e6c6b31a Mon Sep 17 00:00:00 2001 From: Jeremy Pritts Date: Thu, 24 Feb 2022 15:22:11 -0500 Subject: [PATCH 022/184] add dependabot script --- .github/dependabot.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..0006a8e24 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,20 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "nuget" # See documentation for possible values + target-branch: "development" + directory: "/" # Location of package manifests + schedule: + interval: "daily" + + - package-ecosystem: "github-actions" + target-branch: "development" + # Workflow files stored in the + # default location of `.github/workflows` + directory: "/" + schedule: + interval: "daily" From b730af5c8ce24380c9c29c8f9f0e7beb7e7e417a Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 4 Mar 2022 14:46:39 +0100 Subject: [PATCH 023/184] Add CilInstruction.ReplaceWith helper functions. --- .../DotNet/Cil/CilInstruction.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/AsmResolver.PE/DotNet/Cil/CilInstruction.cs b/src/AsmResolver.PE/DotNet/Cil/CilInstruction.cs index 1f2dc0fb5..59a0cda47 100644 --- a/src/AsmResolver.PE/DotNet/Cil/CilInstruction.cs +++ b/src/AsmResolver.PE/DotNet/Cil/CilInstruction.cs @@ -373,5 +373,39 @@ public bool IsLdcI4() CilCode.Ldc_I4_M1 => -1, _ => throw new ArgumentOutOfRangeException() }; + + /// + /// Replaces the operation code used by the instruction with a new one, and clears the operand. + /// + /// The new operation code. + /// + /// This method may be useful when patching a method body, where reusing the instruction object is favourable. + /// This can prevent breaking any references to the instruction (e.g. branch or exception handler targets). + /// + public void ReplaceWith(CilOpCode opCode) => ReplaceWith(opCode, null); + + /// + /// Replaces the operation code and operand used by the instruction with new ones. + /// + /// The new operation code. + /// The new operand. + /// + /// This method may be useful when patching a method body, where reusing the instruction object is favourable. + /// This can prevent breaking any references to the instruction (e.g. branch or exception handler targets). + /// + public void ReplaceWith(CilOpCode opCode, object? operand) + { + OpCode = opCode; + Operand = operand; + } + + /// + /// Clears the operand and replaces the operation code with a (No-Operation). + /// + /// + /// This method may be useful when patching a method body, where reusing the instruction object is favourable. + /// This can prevent breaking any references to the instruction (e.g. branch or exception handler targets). + /// + public void ReplaceWithNop() => ReplaceWith(CilOpCodes.Nop); } } From b1f248eeecc6f908281f72ab2f64fc3d6fd0eff6 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 4 Mar 2022 14:50:51 +0100 Subject: [PATCH 024/184] Add CIL instruction tests. --- .../DotNet/Cil/CilInstructionTest.cs | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 test/AsmResolver.PE.Tests/DotNet/Cil/CilInstructionTest.cs diff --git a/test/AsmResolver.PE.Tests/DotNet/Cil/CilInstructionTest.cs b/test/AsmResolver.PE.Tests/DotNet/Cil/CilInstructionTest.cs new file mode 100644 index 000000000..68573748b --- /dev/null +++ b/test/AsmResolver.PE.Tests/DotNet/Cil/CilInstructionTest.cs @@ -0,0 +1,64 @@ +using AsmResolver.PE.DotNet.Cil; +using Xunit; + +namespace AsmResolver.PE.Tests.DotNet.Cil +{ + public class CilInstructionTest + { + [Theory] + [InlineData(CilCode.Ldc_I4, 1234, 1234)] + [InlineData(CilCode.Ldc_I4_M1, null, -1)] + [InlineData(CilCode.Ldc_I4_0, null, 0)] + [InlineData(CilCode.Ldc_I4_1, null, 1)] + [InlineData(CilCode.Ldc_I4_2, null, 2)] + [InlineData(CilCode.Ldc_I4_3, null, 3)] + [InlineData(CilCode.Ldc_I4_4, null, 4)] + [InlineData(CilCode.Ldc_I4_5, null, 5)] + [InlineData(CilCode.Ldc_I4_6, null, 6)] + [InlineData(CilCode.Ldc_I4_7, null, 7)] + [InlineData(CilCode.Ldc_I4_8, null, 8)] + [InlineData(CilCode.Ldc_I4_S, (sbyte) -10, -10)] + public void GetLdcI4ConstantTest(CilCode code, object operand, int expectedValue) + { + var instruction = new CilInstruction(code.ToOpCode(), operand); + Assert.Equal(expectedValue, instruction.GetLdcI4Constant()); + } + + [Fact] + public void ReplaceWithOperand() + { + var instruction = new CilInstruction(CilOpCodes.Ldstr, "Hello, world!"); + + Assert.NotNull(instruction.Operand); + + instruction.ReplaceWith(CilOpCodes.Ldc_I4, 1337); + + Assert.Equal(CilOpCodes.Ldc_I4, instruction.OpCode); + Assert.Equal(1337, instruction.Operand); + } + + [Fact] + public void ReplaceWithoutOperandShouldRemoveOperand() + { + var instruction = new CilInstruction(CilOpCodes.Ldstr, "Hello, world!"); + + Assert.NotNull(instruction.Operand); + + instruction.ReplaceWith(CilOpCodes.Ldnull); + + Assert.Equal(CilOpCodes.Ldnull, instruction.OpCode); + Assert.Null(instruction.Operand); + } + + [Fact] + public void ReplaceWithNop() + { + var instruction = new CilInstruction(CilOpCodes.Ldstr, "Hello, world!"); + + instruction.ReplaceWithNop(); + + Assert.Equal(CilOpCodes.Nop, instruction.OpCode); + Assert.Null(instruction.Operand); + } + } +} From 8e624bd782007cd5fc3c3e6606c52206d302ba95 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 4 Mar 2022 15:09:18 +0100 Subject: [PATCH 025/184] Update docs on new ReplaceWith APIs. --- docs/dotnet/managed-method-bodies.rst | 71 ++++++++++++++++++++------- 1 file changed, 54 insertions(+), 17 deletions(-) diff --git a/docs/dotnet/managed-method-bodies.rst b/docs/dotnet/managed-method-bodies.rst index db72bba4b..281be6f31 100644 --- a/docs/dotnet/managed-method-bodies.rst +++ b/docs/dotnet/managed-method-bodies.rst @@ -90,14 +90,14 @@ By default, depending on the value of ``OpCode.OperandType``, ``Operand`` contai +----------------------------------------+----------------------------------------------+ .. warning:: - + Providing an incorrect operand type will result in the CIL assembler to fail assembling the method body upon writing the module to the disk. Creating a new instruction can be done using one of the constructors, together with the ``CilOpCodes`` static class: -.. code-block:: csharp +.. code-block:: csharp - body.Instructions.AddRange(new[] + body.Instructions.AddRange(new[] { new CilInstruction(CilOpCodes.Ldstr, "Hello, World!"), new CilInstruction(CilOpCodes.Ret), @@ -105,7 +105,7 @@ Creating a new instruction can be done using one of the constructors, together w However, the preferred way of adding instructions to add or insert new instructions is to use one of the ``Add`` or ``Insert`` overloads that directly take an opcode and operand. This is because it avoids an allocation of an array, and the overloads perform immediate validation on the created instruction. -.. code-block:: csharp +.. code-block:: csharp var instructions = body.Instructions; instructions.Add(CilOpCodes.Ldstr, "Hello, World!"); @@ -115,16 +115,16 @@ However, the preferred way of adding instructions to add or insert new instructi Pushing 32-bit integer constants onto the stack ----------------------------------------------- -In CIL, pushing integer constants onto the stack is done using one of the ``ldc.i4`` instruction variants. +In CIL, pushing integer constants onto the stack is done using one of the ``ldc.i4`` instruction variants. The recommended way to create such an instruction is not to use the constructor, but instead use the ``CilInstruction.CreateLdcI4(int)`` method instead. This automatically selects the smallest possible opcode possible and sets the operand accordingly: -.. code-block:: csharp +.. code-block:: csharp CilInstruction push1 = CilInstruction.CreateLdcI4(1); // Returns "ldc.i4.1" macro CilInstruction pushShort = CilInstruction.CreateLdcI4(123); // Returns "ldc.i4.s 123" macro CilInstruction pushLarge = CilInstruction.CreateLdcI4(12345678); // Returns "ldc.i4 12345678" - + If we want to get the pushed value, we can use the ``CilInstruction.GetLdcI4Constant()`` method. This method works on any of the ``ldc.i4`` variants, including all the macro opcodes that do not explicitly define an operand such as ``ldc.i4.1``. @@ -133,7 +133,7 @@ Branching Instructions Branch instructions are instructions that (might) transfer control to another part of the method body. To reference the instruction to jump to (the branch target), ``ICilLabel`` is used. The easiest way to create such a label is to use the ``CreateLabel()`` function on the instruction to reference: -.. code-block:: csharp +.. code-block:: csharp CilInstruction targetInstruction = ... ICilLabel label = targetInstruction.CreateLabel(); @@ -142,7 +142,7 @@ Branch instructions are instructions that (might) transfer control to another pa Alternatively, when using the ``Add`` or ``Insert`` overloads, it is possible to use the return value of these overloads. -.. code-block:: csharp +.. code-block:: csharp var instructions = body.Instructions; var label = new CilInstructionLabel(); @@ -150,8 +150,8 @@ Alternatively, when using the ``Add`` or ``Insert`` overloads, it is possible to instructions.Add(CilOpCodes.Br, label); /* ... */ label.Instruction = instruction.Add(CilOpCodes.Ret); - - + + The ``switch`` operation uses a ``IList`` instead. .. note:: @@ -159,10 +159,10 @@ The ``switch`` operation uses a ``IList`` instead. When a branching instruction contains a ``null`` label or a label that references an instruction that is not present in the method body, AsmResolver will by default report an exception upon serializing the code stream. This can be disabled by setting ``VerifyLabelsOnBuild`` to ``false``. -Finding instructions by offset +Finding instructions by offset ------------------------------ -Instructions stored in a method body are indexed not by offset, but by order of occurrence. If it is required to find an instruction by offset, it is possible to use the ``Instructions.GetByOffset(int)`` method, which performs a binary search (O(log(n))) and is faster than a linear search (O(n)) such as a for loop or using a construction like ``.First(i => i.Offset == offset)`` provided by ``System.Linq``. +Instructions stored in a method body are indexed not by offset, but by order of occurrence. If it is required to find an instruction by offset, it is possible to use the ``Instructions.GetByOffset(int)`` method, which performs a binary search (O(log(n))) and is faster than a linear search (O(n)) such as a for loop or using a construction like ``.First(i => i.Offset == offset)`` provided by ``System.Linq``. For ``GetByOffset`` to work, it is required that all offsets in the instruction collection are up to date. Recalculating all offsets within an instruction collection can be done through ``Instructions.CalculateOffsets()``. @@ -180,12 +180,12 @@ For ``GetByOffset`` to work, it is required that all offsets in the instruction instruction1 = body.Instructions[index]; -Referencing members +Referencing members ------------------- As specified by the table above, operations such as a ``call`` require a member as operand. -It is important that the member referenced in the operand of such an instruction is imported in the module. This can be done using the ``ReferenceImporter`` class. +It is important that the member referenced in the operand of such an instruction is imported in the module. This can be done using the ``ReferenceImporter`` class. Below an example on how to use the ``ReferenceImporter`` to emit a call to ``Console::WriteLine(string)`` using reflection: @@ -238,7 +238,44 @@ Instructions can be formatted using e.g. an instance of the ``CilInstructionForm Console.WriteLine(formatter.FormatInstruction(instruction)); -Exception handlers +Patching CIL instructions +------------------------- + +Instructions can be added or removed using the ``Add``, ``Insert``, ``Remove`` and ``RemoveAt`` methods: + +.. code-block:: csharp + + body.Instructions.Add(CilOpCodes.Ldstr, "Hello, world!"); + body.Instructions.Insert(i, CilOpCodes.Ldc_I4, 1234); + body.Instructions.RemoveAt(i); + +... or by using the indexer to replace existing instructions: + +.. code-block:: csharp + + body.Instructions[i] = new CilInstruction(CilOpCodes.Ret); + +Removing or replacing instructions may not always be favourable. The original ``CilInstruction`` object might be used as a reference for a branch target or exception handler boundary. Removing or replacing these ``CilInstruction`` objects would therefore break these kinds of references, rendering the body invalid. Rather than updating all references manually, it may therefore be wiser to reuse the ``CilInstruction`` object and simply modify the ``OpCode`` and ``Operand`` properties instead: + +.. code-block:: csharp + + body.Instructions[i].OpCode = CilOpCodes.Ldc_I4; + body.Instructions[i].Operand = 1234; + +AsmResolver provides a helper function ``ReplaceWith`` that shortens the code into a single line: + +.. code-block:: csharp + + body.Instructions[i].ReplaceWith(CilOpCodes.Ldc_I4, 1234); + +Since it is very common to replace instructions with a `nop`, AsmResolver also defines a special ``ReplaceWithNop`` helper function: + +.. code-block:: csharp + + body.Instructions[i].ReplaceWithNop(); + + +Exception handlers ------------------ Exception handlers are regions in the method body that are protected from exceptions. In AsmResolver, they are represented by the ``CilExceptionHandler`` class, and define the following properties: @@ -267,4 +304,4 @@ The max stack can be computed by using the ``ComputeMaxStack`` method. By defaul .. note:: - If a ``StackImbalanceException`` is thrown upon writing the module to the disk, or upon calling ``ComputeMaxStack``, it means that not all execution paths in the provided CIL code push or pop the expected amount of values. It is a good indication that the provided CIL code is invalid. \ No newline at end of file + If a ``StackImbalanceException`` is thrown upon writing the module to the disk, or upon calling ``ComputeMaxStack``, it means that not all execution paths in the provided CIL code push or pop the expected amount of values. It is a good indication that the provided CIL code is invalid. From 86ab83c5820c780a78298f48517f1e1116a4123b Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 5 Mar 2022 00:42:01 +0100 Subject: [PATCH 026/184] Add HelloWorld test app with 10k dummy methods. Split benchmarks into separate classes, each for every layer of abstraction. --- .../LargeMethodBodiesBenchmark.cs | 20 +--- .../ModuleReadWriteBenchmark.cs | 56 +++++++++++ .../PEFileReadWriteBenchmark.cs | 54 ++++++++++ .../PEImageReadWriteBenchmark.cs | 57 +++++++++++ .../PEReadWriteBenchmark.cs | 94 ------------------ .../Properties/Resources.Designer.cs | 10 ++ .../Properties/Resources.resx | 3 + .../Resources/HelloWorld.ManyMethods.deflate | Bin 0 -> 63924 bytes test/AsmResolver.Benchmarks/Utilities.cs | 20 ++++ 9 files changed, 201 insertions(+), 113 deletions(-) create mode 100644 test/AsmResolver.Benchmarks/ModuleReadWriteBenchmark.cs create mode 100644 test/AsmResolver.Benchmarks/PEFileReadWriteBenchmark.cs create mode 100644 test/AsmResolver.Benchmarks/PEImageReadWriteBenchmark.cs delete mode 100644 test/AsmResolver.Benchmarks/PEReadWriteBenchmark.cs create mode 100644 test/AsmResolver.Benchmarks/Resources/HelloWorld.ManyMethods.deflate create mode 100644 test/AsmResolver.Benchmarks/Utilities.cs diff --git a/test/AsmResolver.Benchmarks/LargeMethodBodiesBenchmark.cs b/test/AsmResolver.Benchmarks/LargeMethodBodiesBenchmark.cs index aabd4bb70..e8f389ae1 100644 --- a/test/AsmResolver.Benchmarks/LargeMethodBodiesBenchmark.cs +++ b/test/AsmResolver.Benchmarks/LargeMethodBodiesBenchmark.cs @@ -1,5 +1,4 @@ using System.IO; -using System.IO.Compression; using AsmResolver.DotNet; using BenchmarkDotNet.Attributes; @@ -8,26 +7,9 @@ namespace AsmResolver.Benchmarks [MemoryDiagnoser] public class LargeMethodBodiesBenchmark { - private static readonly byte[] HelloWorldPumped; + private static readonly byte[] HelloWorldPumped = Utilities.DecompressDeflate(Properties.Resources.HelloWorld_Pumped); private readonly MemoryStream _outputStream = new(); - static LargeMethodBodiesBenchmark() - { - HelloWorldPumped = DecompressDeflate(Properties.Resources.HelloWorld_Pumped); - } - - private static byte[] DecompressDeflate(byte[] compressedData) - { - using var decompressed = new MemoryStream(); - using (var compressed = new MemoryStream(compressedData)) - { - using var deflate = new DeflateStream(compressed, CompressionMode.Decompress); - deflate.CopyTo(decompressed); - } - - return decompressed.ToArray(); - } - [Benchmark] public void HelloWorldPumped_ModuleDefinition_Read() { diff --git a/test/AsmResolver.Benchmarks/ModuleReadWriteBenchmark.cs b/test/AsmResolver.Benchmarks/ModuleReadWriteBenchmark.cs new file mode 100644 index 000000000..46e64fdec --- /dev/null +++ b/test/AsmResolver.Benchmarks/ModuleReadWriteBenchmark.cs @@ -0,0 +1,56 @@ +using System.IO; +using AsmResolver.DotNet; +using BenchmarkDotNet.Attributes; +using static AsmResolver.Benchmarks.Properties.Resources; + +namespace AsmResolver.Benchmarks +{ + [MemoryDiagnoser] + 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 readonly MemoryStream _outputStream = new(); + + [Benchmark] + public void HelloWorld_Read() + { + var file = ModuleDefinition.FromBytes(HelloWorldApp); + } + + [Benchmark] + public void HelloWorld_ReadWrite() + { + var file = ModuleDefinition.FromBytes(HelloWorldApp); + file.Write(_outputStream); + } + + [Benchmark] + public void CrackMe_Read() + { + var file = ModuleDefinition.FromBytes(CrackMeApp); + } + + [Benchmark] + public void CrackMe_ReadWrite() + { + var file = ModuleDefinition.FromBytes(CrackMeApp); + file.Write(_outputStream); + } + + [Benchmark] + public void ManyMethods_Read() + { + var file = ModuleDefinition.FromBytes(ManyMethods); + } + + [Benchmark] + public void ManyMethods_ReadWrite() + { + var file = ModuleDefinition.FromBytes(ManyMethods); + file.Write(_outputStream); + } + } +} diff --git a/test/AsmResolver.Benchmarks/PEFileReadWriteBenchmark.cs b/test/AsmResolver.Benchmarks/PEFileReadWriteBenchmark.cs new file mode 100644 index 000000000..2ebbeb913 --- /dev/null +++ b/test/AsmResolver.Benchmarks/PEFileReadWriteBenchmark.cs @@ -0,0 +1,54 @@ +using System.IO; +using BenchmarkDotNet.Attributes; +using static AsmResolver.Benchmarks.Properties.Resources; + +namespace AsmResolver.Benchmarks +{ + public class PEFileReadWriteBenchmark + { + private static readonly byte[] HelloWorldApp = HelloWorld; + private static readonly byte[] CrackMeApp = Test; + private static readonly byte[] ManyMethods = Utilities.DecompressDeflate(HelloWorld_ManyMethods); + + private readonly MemoryStream _outputStream = new(); + + [Benchmark] + public void HelloWorld_Read() + { + var file = PE.File.PEFile.FromBytes(HelloWorldApp); + } + + [Benchmark] + public void HelloWorld_ReadWrite() + { + var file = PE.File.PEFile.FromBytes(HelloWorldApp); + file.Write(_outputStream); + } + + [Benchmark] + public void CrackMe_Read() + { + var file = PE.File.PEFile.FromBytes(CrackMeApp); + } + + [Benchmark] + public void CrackMe_ReadWrite() + { + var file = PE.File.PEFile.FromBytes(CrackMeApp); + file.Write(_outputStream); + } + + [Benchmark] + public void ManyMethods_Read() + { + var file = PE.File.PEFile.FromBytes(ManyMethods); + } + + [Benchmark] + public void ManyMethods_ReadWrite() + { + var file = PE.File.PEFile.FromBytes(ManyMethods); + file.Write(_outputStream); + } + } +} diff --git a/test/AsmResolver.Benchmarks/PEImageReadWriteBenchmark.cs b/test/AsmResolver.Benchmarks/PEImageReadWriteBenchmark.cs new file mode 100644 index 000000000..be20c7030 --- /dev/null +++ b/test/AsmResolver.Benchmarks/PEImageReadWriteBenchmark.cs @@ -0,0 +1,57 @@ +using System.IO; +using AsmResolver.PE; +using AsmResolver.PE.DotNet.Builder; +using BenchmarkDotNet.Attributes; +using static AsmResolver.Benchmarks.Properties.Resources; + +namespace AsmResolver.Benchmarks +{ + [MemoryDiagnoser] + public class PEImageReadWriteBenchmark + { + private static readonly byte[] HelloWorldApp = HelloWorld; + private static readonly byte[] CrackMeApp = Test; + private static readonly byte[] ManyMethods = Utilities.DecompressDeflate(HelloWorld_ManyMethods); + + private readonly MemoryStream _outputStream = new(); + + [Benchmark] + public void HelloWorld_Read() + { + var file = PEImage.FromBytes(HelloWorldApp); + } + + [Benchmark] + public void HelloWorld_ReadWrite() + { + var image = PEImage.FromBytes(HelloWorldApp); + new ManagedPEFileBuilder().CreateFile(image).Write(_outputStream); + } + + [Benchmark] + public void CrackMe_Read() + { + var file = PEImage.FromBytes(HelloWorldApp); + } + + [Benchmark] + public void CrackMe_ReadWrite() + { + var image = PEImage.FromBytes(CrackMeApp); + new ManagedPEFileBuilder().CreateFile(image).Write(_outputStream); + } + + [Benchmark] + public void ManyMethods_Read() + { + var file = PEImage.FromBytes(ManyMethods); + } + + [Benchmark] + public void ManyMethods_ReadWrite() + { + var image = PEImage.FromBytes(ManyMethods); + new ManagedPEFileBuilder().CreateFile(image).Write(_outputStream); + } + } +} diff --git a/test/AsmResolver.Benchmarks/PEReadWriteBenchmark.cs b/test/AsmResolver.Benchmarks/PEReadWriteBenchmark.cs deleted file mode 100644 index 4674b494f..000000000 --- a/test/AsmResolver.Benchmarks/PEReadWriteBenchmark.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System.IO; -using AsmResolver.DotNet; -using AsmResolver.PE; -using AsmResolver.PE.DotNet.Builder; -using BenchmarkDotNet.Attributes; - -namespace AsmResolver.Benchmarks -{ - [MemoryDiagnoser] - public class PEReadWriteBenchmark - { - private static readonly byte[] HelloWorldApp = Properties.Resources.HelloWorld; - private static readonly byte[] CrackMeApp = Properties.Resources.Test; - private readonly MemoryStream _outputStream = new(); - - [Benchmark] - public void HelloWorld_PEFile_Read() - { - var file = PE.File.PEFile.FromBytes(HelloWorldApp); - } - - [Benchmark] - public void HelloWorld_PEFile_ReadWrite() - { - var file = PE.File.PEFile.FromBytes(HelloWorldApp); - file.Write(_outputStream); - } - - [Benchmark] - public void HelloWorld_PEImage_Read() - { - var file = PEImage.FromBytes(HelloWorldApp); - } - - [Benchmark] - public void HelloWorld_PEImage_ReadWrite() - { - var image = PEImage.FromBytes(HelloWorldApp); - new ManagedPEFileBuilder().CreateFile(image).Write(_outputStream); - } - - [Benchmark] - public void HelloWorld_ModuleDefinition_Read() - { - var file = ModuleDefinition.FromBytes(HelloWorldApp); - } - - [Benchmark] - public void HelloWorld_ModuleDefinition_ReadWrite() - { - var file = ModuleDefinition.FromBytes(HelloWorldApp); - file.Write(_outputStream); - } - - [Benchmark] - public void CrackMe_PEFile_Read() - { - var file = PE.File.PEFile.FromBytes(CrackMeApp); - } - - [Benchmark] - public void CrackMe_PEFile_ReadWrite() - { - var file = PE.File.PEFile.FromBytes(CrackMeApp); - file.Write(_outputStream); - } - - [Benchmark] - public void CrackMe_PEImage_Read() - { - var file = PEImage.FromBytes(HelloWorldApp); - } - - [Benchmark] - public void CrackMe_PEImage_ReadWrite() - { - var image = PEImage.FromBytes(CrackMeApp); - new ManagedPEFileBuilder().CreateFile(image).Write(_outputStream); - } - - [Benchmark] - public void CrackMe_ModuleDefinition_Read() - { - var file = ModuleDefinition.FromBytes(CrackMeApp); - } - - [Benchmark] - public void CrackMe_ModuleDefinition_ReadWrite() - { - var file = ModuleDefinition.FromBytes(CrackMeApp); - file.Write(_outputStream); - } - } -} diff --git a/test/AsmResolver.Benchmarks/Properties/Resources.Designer.cs b/test/AsmResolver.Benchmarks/Properties/Resources.Designer.cs index 6d44032e3..86ed39706 100644 --- a/test/AsmResolver.Benchmarks/Properties/Resources.Designer.cs +++ b/test/AsmResolver.Benchmarks/Properties/Resources.Designer.cs @@ -89,5 +89,15 @@ public class Resources { return ((byte[])(obj)); } } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + public static byte[] HelloWorld_ManyMethods { + get { + object obj = ResourceManager.GetObject("HelloWorld_ManyMethods", resourceCulture); + return ((byte[])(obj)); + } + } } } diff --git a/test/AsmResolver.Benchmarks/Properties/Resources.resx b/test/AsmResolver.Benchmarks/Properties/Resources.resx index a2a96ebb5..83f78bc25 100644 --- a/test/AsmResolver.Benchmarks/Properties/Resources.resx +++ b/test/AsmResolver.Benchmarks/Properties/Resources.resx @@ -127,4 +127,7 @@ ..\Resources\HelloWorld.pumped.deflate;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\HelloWorld.ManyMethods.deflate;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + diff --git a/test/AsmResolver.Benchmarks/Resources/HelloWorld.ManyMethods.deflate b/test/AsmResolver.Benchmarks/Resources/HelloWorld.ManyMethods.deflate new file mode 100644 index 0000000000000000000000000000000000000000..f62ac4be199d965cc04c5d9c3c5f3405e85fc054 GIT binary patch literal 63924 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 Z3xaUo+FPAeieedDmI1>8xR?&x{2v`PmRJA) literal 0 HcmV?d00001 diff --git a/test/AsmResolver.Benchmarks/Utilities.cs b/test/AsmResolver.Benchmarks/Utilities.cs new file mode 100644 index 000000000..1075aabcf --- /dev/null +++ b/test/AsmResolver.Benchmarks/Utilities.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.IO.Compression; + +namespace AsmResolver.Benchmarks +{ + internal static class Utilities + { + public static byte[] DecompressDeflate(byte[] compressedData) + { + using var decompressed = new MemoryStream(); + using (var compressed = new MemoryStream(compressedData)) + { + using var deflate = new DeflateStream(compressed, CompressionMode.Decompress); + deflate.CopyTo(decompressed); + } + + return decompressed.ToArray(); + } + } +} From f5df8feb67983320c639058a5a92980d50197135 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 5 Mar 2022 00:42:25 +0100 Subject: [PATCH 027/184] Lazy init diagnostic prefix in CilAssembler. --- .../Code/Cil/CilMethodBodySerializer.cs | 2 +- src/AsmResolver.PE/DotNet/Cil/CilAssembler.cs | 49 ++++++++++++++++--- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/src/AsmResolver.DotNet/Code/Cil/CilMethodBodySerializer.cs b/src/AsmResolver.DotNet/Code/Cil/CilMethodBodySerializer.cs index 5f920444a..023e04f9e 100644 --- a/src/AsmResolver.DotNet/Code/Cil/CilMethodBodySerializer.cs +++ b/src/AsmResolver.DotNet/Code/Cil/CilMethodBodySerializer.cs @@ -146,7 +146,7 @@ private static byte[] BuildRawCodeStream(MethodBodySerializationContext context, var assembler = new CilAssembler( writer, new CilOperandBuilder(context.TokenProvider, bag), - body.Owner.SafeToString(), + body.Owner.SafeToString, bag); assembler.WriteInstructions(body.Instructions); diff --git a/src/AsmResolver.PE/DotNet/Cil/CilAssembler.cs b/src/AsmResolver.PE/DotNet/Cil/CilAssembler.cs index a22248110..6ef1fecef 100644 --- a/src/AsmResolver.PE/DotNet/Cil/CilAssembler.cs +++ b/src/AsmResolver.PE/DotNet/Cil/CilAssembler.cs @@ -12,8 +12,9 @@ public class CilAssembler { private readonly IBinaryStreamWriter _writer; private readonly ICilOperandBuilder _operandBuilder; - private readonly string? _diagnosticPrefix; + private readonly Func? _getMethodBodyName; private readonly IErrorListener _errorListener; + private string? _diagnosticPrefix; /// /// Creates a new CIL instruction encoder. @@ -21,7 +22,7 @@ public class CilAssembler /// The output stream to write the encoded instructions to. /// The object to use for creating raw operands. public CilAssembler(IBinaryStreamWriter writer, ICilOperandBuilder operandBuilder) - : this(writer, operandBuilder, null, ThrowErrorListener.Instance) + : this(writer, operandBuilder, default(string), ThrowErrorListener.Instance) { } @@ -46,6 +47,40 @@ public CilAssembler(IBinaryStreamWriter writer, ICilOperandBuilder operandBuilde : null; } + /// + /// Creates a new CIL instruction encoder. + /// + /// The output stream to write the encoded instructions to. + /// The object to use for creating raw operands. + /// A delegate that is used for lazily obtaining the name of the method body. + /// The object used for recording error listener. + public CilAssembler( + IBinaryStreamWriter writer, + ICilOperandBuilder operandBuilder, + Func? getMethodBodyName, + IErrorListener errorListener) + { + _writer = writer ?? throw new ArgumentNullException(nameof(writer)); + _errorListener = errorListener ?? throw new ArgumentNullException(nameof(errorListener)); + _operandBuilder = operandBuilder ?? throw new ArgumentNullException(nameof(operandBuilder)); + _getMethodBodyName = getMethodBodyName; + } + + private string? DiagnosticPrefix + { + get + { + if (_diagnosticPrefix is null && _getMethodBodyName is not null) + { + string? name = _getMethodBodyName(); + if (!string.IsNullOrEmpty(name)) + _diagnosticPrefix = $"[In {name}]: "; + } + + return _diagnosticPrefix; + } + } + /// /// Writes a collection of CIL instructions to the output stream. /// @@ -175,7 +210,7 @@ private int OperandToBranchDelta(CilInstruction instruction) return ThrowInvalidOperandType(instruction, typeof(ICilLabel), typeof(sbyte)); } - if (isShort && (delta < sbyte.MinValue || delta > sbyte.MaxValue)) + if (isShort && delta is < sbyte.MinValue or > sbyte.MaxValue) { _errorListener.RegisterException(new OverflowException( $"{_diagnosticPrefix}Branch target at IL_{instruction.Offset:X4} is too far away for a ShortInlineBr instruction.")); @@ -190,7 +225,7 @@ private ushort OperandToLocalIndex(CilInstruction instruction) if (instruction.OpCode.OperandType == CilOperandType.ShortInlineVar && variableIndex > byte.MaxValue) { _errorListener.RegisterException(new OverflowException( - $"{_diagnosticPrefix}Local index at IL_{instruction.Offset:X4} is too large for a ShortInlineVar instruction.")); + $"{DiagnosticPrefix}Local index at IL_{instruction.Offset:X4} is too large for a ShortInlineVar instruction.")); } return unchecked((ushort) variableIndex); @@ -202,7 +237,7 @@ private ushort OperandToArgumentIndex(CilInstruction instruction) if (instruction.OpCode.OperandType == CilOperandType.ShortInlineArgument && variableIndex > byte.MaxValue) { _errorListener.RegisterException(new OverflowException( - $"{_diagnosticPrefix}Argument index at IL_{instruction.Offset:X4} is too large for a ShortInlineArgument instruction.")); + $"{DiagnosticPrefix}Argument index at IL_{instruction.Offset:X4} is too large for a ShortInlineArgument instruction.")); } return unchecked((ushort) variableIndex); @@ -247,7 +282,7 @@ private double OperandToFloat64(CilInstruction instruction) { string found = instruction.Operand?.GetType().Name ?? "null"; _errorListener.RegisterException(new ArgumentOutOfRangeException( - $"{_diagnosticPrefix}Expected a {expectedOperand.Name} operand at IL_{instruction.Offset:X4}, but found {found}.")); + $"{DiagnosticPrefix}Expected a {expectedOperand.Name} operand at IL_{instruction.Offset:X4}, but found {found}.")); return default; } @@ -263,7 +298,7 @@ private double OperandToFloat64(CilInstruction instruction) string found = instruction.Operand?.GetType().Name ?? "null"; _errorListener.RegisterException(new ArgumentOutOfRangeException( - $"{_diagnosticPrefix}Expected a {operandTypesString} operand at IL_{instruction.Offset:X4}, but found {found}.")); + $"{DiagnosticPrefix}Expected a {operandTypesString} operand at IL_{instruction.Offset:X4}, but found {found}.")); return default; } } From e51801ed7d1655062d110694aecbb2428a0239a9 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 5 Mar 2022 13:22:47 +0100 Subject: [PATCH 028/184] Add missing DiagnosticPrefix call, ensure consistent prefixes in both label verification and assembler, simplify code in SafeToString. --- src/AsmResolver.DotNet/Code/Cil/CilLabelVerifier.cs | 2 +- src/AsmResolver.DotNet/SafeExtensions.cs | 5 ++--- src/AsmResolver.PE/DotNet/Cil/CilAssembler.cs | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/AsmResolver.DotNet/Code/Cil/CilLabelVerifier.cs b/src/AsmResolver.DotNet/Code/Cil/CilLabelVerifier.cs index 6e32ef202..9ad289287 100644 --- a/src/AsmResolver.DotNet/Code/Cil/CilLabelVerifier.cs +++ b/src/AsmResolver.DotNet/Code/Cil/CilLabelVerifier.cs @@ -130,7 +130,7 @@ private void AddDiagnostic(string message) { _diagnostics ??= new List(); _cachedName ??= _body.Owner.SafeToString(); - _diagnostics.Add(new InvalidCilInstructionException($"[{_cachedName}]: {message}")); + _diagnostics.Add(new InvalidCilInstructionException($"[In {_cachedName}]: {message}")); } } diff --git a/src/AsmResolver.DotNet/SafeExtensions.cs b/src/AsmResolver.DotNet/SafeExtensions.cs index f567a20a7..283d61555 100644 --- a/src/AsmResolver.DotNet/SafeExtensions.cs +++ b/src/AsmResolver.DotNet/SafeExtensions.cs @@ -1,4 +1,3 @@ -using System; using AsmResolver.DotNet.Builder; namespace AsmResolver.DotNet @@ -19,7 +18,7 @@ public static string SafeToString(this IMetadataMember? self) value = $"{value} (0x{self.MetadataToken.ToString()})"; return value; } - catch (Exception) + catch { return $"0x{self.MetadataToken.ToString()}"; } @@ -34,7 +33,7 @@ public static string SafeToString(this object? self) { return self.ToString(); } - catch (Exception) + catch { return self.GetType().ToString(); } diff --git a/src/AsmResolver.PE/DotNet/Cil/CilAssembler.cs b/src/AsmResolver.PE/DotNet/Cil/CilAssembler.cs index 6ef1fecef..8b82d9027 100644 --- a/src/AsmResolver.PE/DotNet/Cil/CilAssembler.cs +++ b/src/AsmResolver.PE/DotNet/Cil/CilAssembler.cs @@ -213,7 +213,7 @@ private int OperandToBranchDelta(CilInstruction instruction) if (isShort && delta is < sbyte.MinValue or > sbyte.MaxValue) { _errorListener.RegisterException(new OverflowException( - $"{_diagnosticPrefix}Branch target at IL_{instruction.Offset:X4} is too far away for a ShortInlineBr instruction.")); + $"{DiagnosticPrefix}Branch target at IL_{instruction.Offset:X4} is too far away for a ShortInlineBr instruction.")); } return delta; From 1fc7471c4883361243eec4dd8026538b8e6e7bd2 Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 6 Mar 2022 12:48:06 +0100 Subject: [PATCH 029/184] Bump appveyor versions. --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index ce0a1f25a..6de584d01 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,7 +4,7 @@ - master image: Visual Studio 2022 - version: 4.8.0-master-build.{build} + version: 4.9.0-master-build.{build} configuration: Release skip_commits: @@ -33,7 +33,7 @@ - development image: Visual Studio 2022 - version: 4.8.0-dev-build.{build} + version: 4.9.0-dev-build.{build} configuration: Release skip_commits: From 8ca98cd1caf9fc2774634b6b92d2d7783712557e Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 6 Mar 2022 12:55:20 +0100 Subject: [PATCH 030/184] Replace obsolete usage of IgnoreNullValues with new DefaultIgnoreCondition. --- src/AsmResolver.DotNet/Config/Json/RuntimeConfiguration.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/AsmResolver.DotNet/Config/Json/RuntimeConfiguration.cs b/src/AsmResolver.DotNet/Config/Json/RuntimeConfiguration.cs index d8493aaab..3a8ef6863 100644 --- a/src/AsmResolver.DotNet/Config/Json/RuntimeConfiguration.cs +++ b/src/AsmResolver.DotNet/Config/Json/RuntimeConfiguration.cs @@ -1,5 +1,6 @@ using System.IO; using System.Text.Json; +using System.Text.Json.Serialization; namespace AsmResolver.DotNet.Config.Json { @@ -12,7 +13,7 @@ public class RuntimeConfiguration { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = true, - IgnoreNullValues = true + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }; /// From 826157feb131a461f317f6990240328272f1f7be Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 6 Mar 2022 12:57:25 +0100 Subject: [PATCH 031/184] Cache TablesStream instance in member factory. --- .../Serialized/CachedSerializedMemberFactory.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/AsmResolver.DotNet/Serialized/CachedSerializedMemberFactory.cs b/src/AsmResolver.DotNet/Serialized/CachedSerializedMemberFactory.cs index 016500ecd..c21fc9e91 100644 --- a/src/AsmResolver.DotNet/Serialized/CachedSerializedMemberFactory.cs +++ b/src/AsmResolver.DotNet/Serialized/CachedSerializedMemberFactory.cs @@ -9,6 +9,7 @@ namespace AsmResolver.DotNet.Serialized internal class CachedSerializedMemberFactory { private readonly ModuleReaderContext _context; + private readonly TablesStream _tablesStream; private TypeReference?[]? _typeReferences; private TypeDefinition?[]? _typeDefinitions; @@ -38,6 +39,7 @@ internal class CachedSerializedMemberFactory internal CachedSerializedMemberFactory(ModuleReaderContext context) { _context = context ?? throw new ArgumentNullException(nameof(context)); + _tablesStream = _context.Image.DotNetDirectory!.Metadata!.GetStream(); } internal bool TryLookupMember(MetadataToken token, [NotNullWhen(true)] out IMetadataMember? member) @@ -247,9 +249,7 @@ internal bool TryLookupMember(MetadataToken token, [NotNullWhen(true)] out IMeta where TMember : class, IMetadataMember { // Obtain table. - var table = (MetadataTable) _context.Image.DotNetDirectory!.Metadata - !.GetStream() - .GetTable(token.Table); + var table = (MetadataTable) _tablesStream.GetTable(token.Table); // Check if within bounds. if (token.Rid == 0 || token.Rid > table.Count) From 014e57662be86b88611743fe667e056ac3418e47 Mon Sep 17 00:00:00 2001 From: Washi Date: Mon, 7 Mar 2022 19:58:40 +0100 Subject: [PATCH 032/184] Add failing test-case on corlib types as base-type. --- .../Properties/Resources.Designer.cs | 7 +++++ .../Properties/Resources.resx | 3 +++ .../Resources/HelloWorld.WithAttribute.dll | Bin 0 -> 36864 bytes .../TypeDefinitionTest.cs | 24 ++++++++++++++++++ .../TestBinaries/DotNet/HelloWorld/Program.cs | 2 +- 5 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 test/AsmResolver.DotNet.Tests/Resources/HelloWorld.WithAttribute.dll diff --git a/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs index bb68c3ab3..141a67ba8 100644 --- a/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs @@ -129,6 +129,13 @@ public class Resources { } } + public static byte[] HelloWorld_WithAttribute { + get { + object obj = ResourceManager.GetObject("HelloWorld_WithAttribute", 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 df11e42bb..c6ab516aa 100644 --- a/test/AsmResolver.DotNet.Tests/Properties/Resources.resx +++ b/test/AsmResolver.DotNet.Tests/Properties/Resources.resx @@ -54,6 +54,9 @@ ..\Resources\HelloWorld_Forwarder.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\HelloWorld.WithAttribute.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/Resources/HelloWorld.WithAttribute.dll b/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.WithAttribute.dll new file mode 100644 index 0000000000000000000000000000000000000000..1631efffea01eccb3ce8138cde6920a042466379 GIT binary patch literal 36864 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 S014YQ$WZG4CHjAi0{;gx!m-r= literal 0 HcmV?d00001 diff --git a/test/AsmResolver.DotNet.Tests/TypeDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/TypeDefinitionTest.cs index 451d5a373..ac2834c36 100644 --- a/test/AsmResolver.DotNet.Tests/TypeDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Tests/TypeDefinitionTest.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.Signatures.Types; using AsmResolver.DotNet.TestCases.CustomAttributes; using AsmResolver.DotNet.TestCases.Events; @@ -20,6 +21,8 @@ namespace AsmResolver.DotNet.Tests { public class TypeDefinitionTest { + private static readonly SignatureComparer Comparer = new(); + private TypeDefinition RebuildAndLookup(TypeDefinition type) { var stream = new MemoryStream(); @@ -547,5 +550,26 @@ public void InvalidMetadataLoopInBaseTypeShouldNotCrashIsValueType() Assert.False(typeB.IsValueType); Assert.False(typeB.IsEnum); } + + [Fact] + public void AddTypeWithCorLibBaseTypeToAssemblyWithCorLibTypeReferenceInAttribute() + { + // https://github.com/Washi1337/AsmResolver/issues/263 + + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld_WithAttribute); + var corlib = module.CorLibTypeFactory; + + var type = new TypeDefinition(null, "Test", TypeAttributes.Class, corlib.Object.Type); + module.TopLevelTypes.Add(type); + + var scope = corlib.Object.Scope; + + var newType = RebuildAndLookup(type); + Assert.Equal(newType.BaseType, type.BaseType, Comparer); + + Assert.Same(scope, corlib.Object.Scope); + var reference = Assert.IsAssignableFrom(corlib.Object.Scope!.GetAssembly()); + Assert.Same(module, reference.Module); + } } } diff --git a/test/TestBinaries/DotNet/HelloWorld/Program.cs b/test/TestBinaries/DotNet/HelloWorld/Program.cs index 848fe59f9..575ec4cc4 100644 --- a/test/TestBinaries/DotNet/HelloWorld/Program.cs +++ b/test/TestBinaries/DotNet/HelloWorld/Program.cs @@ -9,4 +9,4 @@ private static void Main(string[] args) Console.WriteLine("Hello, World!"); } } -} \ No newline at end of file +} From 1d85804528d7c15cae78522d597b176fd052c9a2 Mon Sep 17 00:00:00 2001 From: Washi Date: Mon, 7 Mar 2022 20:30:11 +0100 Subject: [PATCH 033/184] Add tests isolating issue of non-imported assembly refs. --- .../ModuleDefinitionTest.cs | 9 +++++ .../Signatures/TypeNameParserTest.cs | 27 +++++++++++++++ .../TypeReferenceTest.cs | 33 ++++++++++++++++--- 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs index 13fd59cf7..0ab26f484 100644 --- a/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs @@ -22,6 +22,7 @@ namespace AsmResolver.DotNet.Tests { public class ModuleDefinitionTest { + private static readonly SignatureComparer Comparer = new(); private const string NonWindowsPlatform = "Test loads a module from a base address, which is only supported on Windows."; private static ModuleDefinition Rebuild(ModuleDefinition module) @@ -347,5 +348,13 @@ public void DetectTargetStandard() Assert.Contains(DotNetRuntimeInfo.NetStandard, module.OriginalTargetRuntime.Name); Assert.Equal(2, module.OriginalTargetRuntime.Version.Major); } + + [Fact] + public void NewModuleShouldContainSingleReferenceToCorLib() + { + var module = new ModuleDefinition("SomeModule", KnownCorLibs.NetStandard_v2_0_0_0); + var reference = Assert.Single(module.AssemblyReferences); + Assert.Equal(KnownCorLibs.NetStandard_v2_0_0_0, reference, Comparer); + } } } diff --git a/test/AsmResolver.DotNet.Tests/Signatures/TypeNameParserTest.cs b/test/AsmResolver.DotNet.Tests/Signatures/TypeNameParserTest.cs index 1f10fbc54..c79e85572 100644 --- a/test/AsmResolver.DotNet.Tests/Signatures/TypeNameParserTest.cs +++ b/test/AsmResolver.DotNet.Tests/Signatures/TypeNameParserTest.cs @@ -272,5 +272,32 @@ public void ReadTypeInCorLibAssemblyWithoutScope() var actual = TypeNameParser.Parse(_module, $"{ns}.{name}"); Assert.Equal(expected, actual, _comparer); } + + [Fact] + public void ReadCorLibTypeShouldNotUpdateScopesOfUnderlyingTypes() + { + // https://github.com/Washi1337/AsmResolver/issues/263 + + var scope = _module.CorLibTypeFactory.Object.Type.Scope; + TypeNameParser.Parse(_module, + "System.Object, System.Runtime, Version=4.2.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"); + Assert.Same(scope, _module.CorLibTypeFactory.Object.Type.Scope); + } + + [Fact] + public void ReadTypeShouldReuseScopeInstanceWhenAvailable() + { + var type = TypeNameParser.Parse(_module, + "System.Array, System.Runtime, Version=4.2.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"); + Assert.Contains(type.Scope!.GetAssembly(), _module.AssemblyReferences); + } + + [Fact] + public void ReadTypeShouldUseNewScopeInstanceIfNotImportedYet() + { + var type = TypeNameParser.Parse(_module, + "SomeNamespace.SomeType, SomeAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=0123456789abcdef"); + Assert.DoesNotContain(type.Scope!.GetAssembly(), _module.AssemblyReferences); + } } } diff --git a/test/AsmResolver.DotNet.Tests/TypeReferenceTest.cs b/test/AsmResolver.DotNet.Tests/TypeReferenceTest.cs index 0a2b835fb..63db9c152 100644 --- a/test/AsmResolver.DotNet.Tests/TypeReferenceTest.cs +++ b/test/AsmResolver.DotNet.Tests/TypeReferenceTest.cs @@ -1,11 +1,16 @@ -using System.Linq; +using System; +using AsmResolver.DotNet.Signatures; +using AsmResolver.DotNet.Signatures.Types; using AsmResolver.PE.DotNet.Metadata.Tables; +using AsmResolver.PE.DotNet.Metadata.Tables.Rows; using Xunit; namespace AsmResolver.DotNet.Tests { public class TypeReferenceTest { + private static readonly SignatureComparer Comparer = new(); + [Fact] public void ReadAssemblyRefScope() { @@ -13,7 +18,7 @@ public void ReadAssemblyRefScope() var typeRef = (TypeReference) module.LookupMember(new MetadataToken(TableIndex.TypeRef, 13)); Assert.Equal("mscorlib", typeRef.Scope.Name); } - + [Fact] public void ReadName() { @@ -21,7 +26,7 @@ public void ReadName() var typeRef = (TypeReference) module.LookupMember(new MetadataToken(TableIndex.TypeRef, 13)); Assert.Equal("Console", typeRef.Name); } - + [Fact] public void ReadNamespace() { @@ -29,5 +34,25 @@ public void ReadNamespace() var typeRef = (TypeReference) module.LookupMember(new MetadataToken(TableIndex.TypeRef, 13)); Assert.Equal("System", typeRef.Namespace); } + + [Fact] + public void CorLibTypeToTypeSignatureShouldReturnCorLibTypeSignature() + { + var module = new ModuleDefinition("SomeModule"); + var reference = new TypeReference(module, module.CorLibTypeFactory.CorLibScope, "System", "Object"); + var signature = Assert.IsAssignableFrom(reference.ToTypeSignature()); + Assert.Equal(ElementType.Object, signature.ElementType); + } + + [Theory] + [InlineData("mscorlib", "System.IO", "Stream")] + [InlineData("not-mscorlib", "System", "Object")] + public void NonCorLibTypeToTypeSignatureShouldReturnTypeDefOrRef(string corlibName, string ns, string name) + { + var module = new ModuleDefinition("SomeModule"); + var reference = new TypeReference(module, new AssemblyReference(corlibName, new Version(4, 0, 0, 0)), ns, name); + var signature = Assert.IsAssignableFrom(reference.ToTypeSignature()); + Assert.Equal(signature.Type, reference, Comparer); + } } -} \ No newline at end of file +} From e0249a9368ffe2e1635a988e3daf7fb27b37d261 Mon Sep 17 00:00:00 2001 From: Washi Date: Mon, 7 Mar 2022 20:31:06 +0100 Subject: [PATCH 034/184] BUGFIX: Remove duplicated corlib reference upon creating new module. --- src/AsmResolver.DotNet/ModuleDefinition.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/AsmResolver.DotNet/ModuleDefinition.cs b/src/AsmResolver.DotNet/ModuleDefinition.cs index 8891c859c..d6b06f18a 100644 --- a/src/AsmResolver.DotNet/ModuleDefinition.cs +++ b/src/AsmResolver.DotNet/ModuleDefinition.cs @@ -303,10 +303,9 @@ public ModuleDefinition(string? name, AssemblyReference corLib) Name = name; var importer = new ReferenceImporter(this); - corLib = (AssemblyReference)importer.ImportScope(corLib); + corLib = (AssemblyReference) importer.ImportScope(corLib); CorLibTypeFactory = new CorLibTypeFactory(corLib); - AssemblyReferences.Add(corLib); OriginalTargetRuntime = DetectTargetRuntime(); MetadataResolver = new DefaultMetadataResolver(CreateAssemblyResolver(UncachedFileService.Instance)); From 5ce0ce9d76cef398ff1259d0f9a39d18fe1833e6 Mon Sep 17 00:00:00 2001 From: Washi Date: Mon, 7 Mar 2022 20:36:58 +0100 Subject: [PATCH 035/184] BUGFIX: Reuse imported assembly references when possible after parsing the assembly name. --- .../Types/Parsing/TypeNameParser.cs | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/AsmResolver.DotNet/Signatures/Types/Parsing/TypeNameParser.cs b/src/AsmResolver.DotNet/Signatures/Types/Parsing/TypeNameParser.cs index e36d97b6a..1f4082008 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/Parsing/TypeNameParser.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/Parsing/TypeNameParser.cs @@ -328,7 +328,7 @@ private List ParseDottedExpression(TypeNameTerminal terminal) private AssemblyReference ParseAssemblyNameSpec() { string assemblyName = string.Join(".", ParseDottedExpression(TypeNameTerminal.Identifier)); - var assemblyRef = new AssemblyReference(assemblyName, new Version()); + var newReference = new AssemblyReference(assemblyName, new Version()); while (TryExpect(TypeNameTerminal.Comma).HasValue) { @@ -337,22 +337,22 @@ private AssemblyReference ParseAssemblyNameSpec() switch (propertyToken.Text.ToLowerInvariant()) { case "version": - assemblyRef.Version = ParseVersion(); + newReference.Version = ParseVersion(); break; case "publickey": - assemblyRef.PublicKeyOrToken = ParseHexBlob(); - assemblyRef.HasPublicKey = true; + newReference.PublicKeyOrToken = ParseHexBlob(); + newReference.HasPublicKey = true; break; case "publickeytoken": - assemblyRef.PublicKeyOrToken = ParseHexBlob(); - assemblyRef.HasPublicKey = false; + newReference.PublicKeyOrToken = ParseHexBlob(); + newReference.HasPublicKey = false; break; case "culture": string culture = ParseCulture(); - assemblyRef.Culture = !culture.Equals("neutral", StringComparison.OrdinalIgnoreCase) + newReference.Culture = !culture.Equals("neutral", StringComparison.OrdinalIgnoreCase) ? culture : null; break; @@ -362,7 +362,15 @@ private AssemblyReference ParseAssemblyNameSpec() } } - return assemblyRef; + // Reuse imported assembly reference instance if possible. + for (int i = 0; i < _module.AssemblyReferences.Count; i++) + { + var existingReference = _module.AssemblyReferences[i]; + if (Comparer.Equals((AssemblyDescriptor) existingReference, newReference)) + return existingReference; + } + + return newReference; } private Version ParseVersion() From df83fd01d669562605c06eb087847a1bb0776a0a Mon Sep 17 00:00:00 2001 From: Washi Date: Mon, 7 Mar 2022 21:15:47 +0100 Subject: [PATCH 036/184] BUGFIX: Use finally decided scope instead of scope determined by the type name parser. --- src/AsmResolver.DotNet/KnownCorLibs.cs | 2 +- .../Signatures/Types/Parsing/TypeNameParser.cs | 2 +- test/AsmResolver.DotNet.Tests/TypeReferenceTest.cs | 8 +++----- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/AsmResolver.DotNet/KnownCorLibs.cs b/src/AsmResolver.DotNet/KnownCorLibs.cs index 951f7be5e..fbc0d3893 100644 --- a/src/AsmResolver.DotNet/KnownCorLibs.cs +++ b/src/AsmResolver.DotNet/KnownCorLibs.cs @@ -111,7 +111,7 @@ public static class KnownCorLibs }); /// - /// References System.Runtime.dll, Version=4.2.1.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET + /// References System.Runtime.dll, Version=4.2.2.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET /// assemblies targeting .NET Core 3.1. /// public static readonly AssemblyReference SystemRuntime_v4_2_2_0 = new AssemblyReference("System.Runtime", diff --git a/src/AsmResolver.DotNet/Signatures/Types/Parsing/TypeNameParser.cs b/src/AsmResolver.DotNet/Signatures/Types/Parsing/TypeNameParser.cs index 1f4082008..622092df7 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/Parsing/TypeNameParser.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/Parsing/TypeNameParser.cs @@ -57,7 +57,7 @@ private TypeSignature ParseTypeSpec() SetScope(reference, scope); // Ensure corlib type sigs are used. - if (Comparer.Equals(scope, _module.CorLibTypeFactory.CorLibScope)) + if (Comparer.Equals(typeSpec.Scope, _module.CorLibTypeFactory.CorLibScope)) { var corlibType = _module.CorLibTypeFactory.FromType(typeSpec); if (corlibType != null) diff --git a/test/AsmResolver.DotNet.Tests/TypeReferenceTest.cs b/test/AsmResolver.DotNet.Tests/TypeReferenceTest.cs index 63db9c152..a0a5ae2e5 100644 --- a/test/AsmResolver.DotNet.Tests/TypeReferenceTest.cs +++ b/test/AsmResolver.DotNet.Tests/TypeReferenceTest.cs @@ -44,13 +44,11 @@ public void CorLibTypeToTypeSignatureShouldReturnCorLibTypeSignature() Assert.Equal(ElementType.Object, signature.ElementType); } - [Theory] - [InlineData("mscorlib", "System.IO", "Stream")] - [InlineData("not-mscorlib", "System", "Object")] - public void NonCorLibTypeToTypeSignatureShouldReturnTypeDefOrRef(string corlibName, string ns, string name) + [Fact] + public void NonCorLibTypeToTypeSignatureShouldReturnTypeDefOrRef() { var module = new ModuleDefinition("SomeModule"); - var reference = new TypeReference(module, new AssemblyReference(corlibName, new Version(4, 0, 0, 0)), ns, name); + var reference = new TypeReference(module, module.CorLibTypeFactory.CorLibScope, "System", "Array"); var signature = Assert.IsAssignableFrom(reference.ToTypeSignature()); Assert.Equal(signature.Type, reference, Comparer); } From 6f6bcffeb346f3a4fb3c1ac8dc2c09efec28c8fa Mon Sep 17 00:00:00 2001 From: Jeremy Pritts Date: Tue, 8 Mar 2022 05:09:23 -0500 Subject: [PATCH 037/184] use string interpolation instead of string.Format --- src/AsmResolver.DotNet/Code/Cil/StackImbalanceException.cs | 7 +++---- src/AsmResolver.DotNet/Memory/TypeMemoryLayoutDetector.cs | 6 ++---- src/AsmResolver.DotNet/Signatures/MemberSignature.cs | 6 +++--- src/AsmResolver.DotNet/Signatures/MethodSignature.cs | 7 ++----- src/AsmResolver.DotNet/Signatures/PropertySignature.cs | 5 +---- .../Signatures/Security/SecurityAttribute.cs | 3 +-- 6 files changed, 12 insertions(+), 22 deletions(-) diff --git a/src/AsmResolver.DotNet/Code/Cil/StackImbalanceException.cs b/src/AsmResolver.DotNet/Code/Cil/StackImbalanceException.cs index 79b89bf64..ac6f97fbc 100644 --- a/src/AsmResolver.DotNet/Code/Cil/StackImbalanceException.cs +++ b/src/AsmResolver.DotNet/Code/Cil/StackImbalanceException.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace AsmResolver.DotNet.Code.Cil { @@ -13,8 +13,7 @@ public class StackImbalanceException : Exception /// The method body in which the inconsistency was detected. /// The offset at which the inconsistency was detected. public StackImbalanceException(CilMethodBody body, int offset) - : base(string.Format("Stack imbalance was detected at offset IL_{0:X4} in method body of {1}", - offset, body.Owner)) + : base($"Stack imbalance was detected at offset IL_{offset:X4} in method body of {body.Owner}") { Body = body; Offset = offset; @@ -36,4 +35,4 @@ public int Offset get; } } -} \ No newline at end of file +} diff --git a/src/AsmResolver.DotNet/Memory/TypeMemoryLayoutDetector.cs b/src/AsmResolver.DotNet/Memory/TypeMemoryLayoutDetector.cs index d0cb8c501..2ae1e422c 100644 --- a/src/AsmResolver.DotNet/Memory/TypeMemoryLayoutDetector.cs +++ b/src/AsmResolver.DotNet/Memory/TypeMemoryLayoutDetector.cs @@ -251,10 +251,8 @@ private TypeMemoryLayout InferExplicitLayout(TypeDefinition type, uint alignment // All fields in an explicitly laid out structure need to have a field offset assigned. if (!field.FieldOffset.HasValue) { - throw new ArgumentException(string.Format( - "{0} ({1}) is defined in a type with explicit layout, but does not have a field offset assigned.", - field.FullName, - field.MetadataToken.ToString())); + throw new ArgumentException( + $"{field.FullName} ({field.MetadataToken}) is defined in a type with explicit layout, but does not have a field offset assigned."); } uint offset = (uint) field.FieldOffset.Value; diff --git a/src/AsmResolver.DotNet/Signatures/MemberSignature.cs b/src/AsmResolver.DotNet/Signatures/MemberSignature.cs index 6f49dc659..a34b3bdba 100644 --- a/src/AsmResolver.DotNet/Signatures/MemberSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/MemberSignature.cs @@ -30,9 +30,9 @@ protected TypeSignature MemberReturnType /// public override string ToString() { - return string.Format("{0}{1}", - HasThis ? "instance " : string.Empty, - MemberReturnType?.FullName ?? TypeSignature.NullTypeToString); + string prefix = HasThis ? "instance " : string.Empty; + string fullName = MemberReturnType?.FullName ?? TypeSignature.NullTypeToString; + return $"{prefix}{fullName}"; } } } diff --git a/src/AsmResolver.DotNet/Signatures/MethodSignature.cs b/src/AsmResolver.DotNet/Signatures/MethodSignature.cs index e3092c22c..14787822f 100644 --- a/src/AsmResolver.DotNet/Signatures/MethodSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/MethodSignature.cs @@ -207,16 +207,13 @@ protected override void WriteContents(BlobSerializationContext context) public override string ToString() { string prefix = HasThis ? "instance " : string.Empty; + string fullName = ReturnType?.FullName ?? TypeSignature.NullTypeToString; string genericsString = GenericParameterCount > 0 ? $"<{string.Join(", ", new string('?', GenericParameterCount))}>" : string.Empty; string parameterTypesString = string.Join(", ", ParameterTypes) + (IsSentinel ? ", ..." : string.Empty); - return string.Format("{0}{1} *{2}({3})", - prefix, - ReturnType?.FullName ?? TypeSignature.NullTypeToString, - genericsString, - parameterTypesString); + return $"{prefix}{fullName} *{genericsString}({parameterTypesString})"; } } } diff --git a/src/AsmResolver.DotNet/Signatures/PropertySignature.cs b/src/AsmResolver.DotNet/Signatures/PropertySignature.cs index db2b09326..864e4d201 100644 --- a/src/AsmResolver.DotNet/Signatures/PropertySignature.cs +++ b/src/AsmResolver.DotNet/Signatures/PropertySignature.cs @@ -126,10 +126,7 @@ public override string ToString() ? $"[{string.Join(", ", ParameterTypes)}]" : string.Empty; - return string.Format("{0}{1} *{2}", - prefix, - ReturnType.FullName, - parameterTypesString); + return $"{prefix}{ReturnType.FullName} *{parameterTypesString}"; } } } diff --git a/src/AsmResolver.DotNet/Signatures/Security/SecurityAttribute.cs b/src/AsmResolver.DotNet/Signatures/Security/SecurityAttribute.cs index 11e47178b..92ea1cef5 100644 --- a/src/AsmResolver.DotNet/Signatures/Security/SecurityAttribute.cs +++ b/src/AsmResolver.DotNet/Signatures/Security/SecurityAttribute.cs @@ -107,7 +107,6 @@ public void Write(BlobSerializationContext context) /// - public override string ToString() => - string.Format("{0}({1})", AttributeType, string.Join(", ", NamedArguments)); + public override string ToString() => $"{AttributeType}({string.Join(", ", NamedArguments)})"; } } From fdf0da85f2b99e862eb7b3ae9c0249ae430bb378 Mon Sep 17 00:00:00 2001 From: Washi Date: Tue, 8 Mar 2022 21:16:24 +0100 Subject: [PATCH 038/184] Reduce allocations in typenameparser and lexer. --- .../Signatures/Types/Parsing/TypeNameLexer.cs | 9 ++- .../Types/Parsing/TypeNameParser.cs | 77 +++++++++++-------- 2 files changed, 50 insertions(+), 36 deletions(-) diff --git a/src/AsmResolver.DotNet/Signatures/Types/Parsing/TypeNameLexer.cs b/src/AsmResolver.DotNet/Signatures/Types/Parsing/TypeNameLexer.cs index 1f548bc42..c7a8d4bd7 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/Parsing/TypeNameLexer.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/Parsing/TypeNameLexer.cs @@ -5,9 +5,10 @@ namespace AsmResolver.DotNet.Signatures.Types.Parsing { - internal class TypeNameLexer + internal struct TypeNameLexer { internal static readonly ISet ReservedChars = new HashSet("*+.,&[]…"); + private static readonly char[] TrimCharacters = " ".ToCharArray(); private readonly TextReader _reader; private readonly StringBuilder _buffer = new(); @@ -16,6 +17,8 @@ internal class TypeNameLexer public TypeNameLexer(TextReader reader) { _reader = reader ?? throw new ArgumentNullException(nameof(reader)); + HasConsumedTypeName = false; + _bufferedToken = default; } public bool HasConsumedTypeName @@ -125,7 +128,7 @@ private TypeNameToken ReadNumberOrIdentifierToken() _buffer.Append(currentChar); } - return new TypeNameToken(terminal, _buffer.ToString().Trim(' ')); + return new TypeNameToken(terminal, _buffer.ToString().Trim(TrimCharacters)); } private TypeNameToken ReadIdentifierToken() @@ -159,7 +162,7 @@ private TypeNameToken ReadIdentifierToken() _buffer.Append(currentChar); } - return new TypeNameToken(TypeNameTerminal.Identifier, _buffer.ToString().Trim(' ')); + return new TypeNameToken(TypeNameTerminal.Identifier, _buffer.ToString().Trim(TrimCharacters)); } private TypeNameToken ReadSymbolToken(TypeNameTerminal terminal) diff --git a/src/AsmResolver.DotNet/Signatures/Types/Parsing/TypeNameParser.cs b/src/AsmResolver.DotNet/Signatures/Types/Parsing/TypeNameParser.cs index 622092df7..78b70c343 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/Parsing/TypeNameParser.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/Parsing/TypeNameParser.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Linq; @@ -9,19 +8,19 @@ namespace AsmResolver.DotNet.Signatures.Types.Parsing /// /// Provides a mechanism for parsing a fully assembly qualified name of a type. /// - public sealed class TypeNameParser + public struct TypeNameParser { // src/coreclr/src/vm/typeparse.cpp // https://docs.microsoft.com/en-us/dotnet/framework/reflection-and-codedom/specifying-fully-qualified-type-names private static readonly SignatureComparer Comparer = new(); private readonly ModuleDefinition _module; - private readonly TypeNameLexer _lexer; + private TypeNameLexer _lexer; private TypeNameParser(ModuleDefinition module, TypeNameLexer lexer) { _module = module ?? throw new ArgumentNullException(nameof(module)); - _lexer = lexer ?? throw new ArgumentNullException(nameof(lexer)); + _lexer = lexer; } /// @@ -320,7 +319,7 @@ private List ParseDottedExpression(TypeNameTerminal terminal) } if (result.Count == 0) - throw new FormatException($"Expected {string.Join(", ",terminal)}."); + throw new FormatException($"Expected {terminal}."); return result; } @@ -332,33 +331,32 @@ private AssemblyReference ParseAssemblyNameSpec() while (TryExpect(TypeNameTerminal.Comma).HasValue) { - var propertyToken = Expect(TypeNameTerminal.Identifier); + string propertyName = Expect(TypeNameTerminal.Identifier).Text; Expect(TypeNameTerminal.Equals); - switch (propertyToken.Text.ToLowerInvariant()) + if (propertyName.Equals("version", StringComparison.OrdinalIgnoreCase)) { - case "version": - newReference.Version = ParseVersion(); - break; - - case "publickey": - newReference.PublicKeyOrToken = ParseHexBlob(); - newReference.HasPublicKey = true; - break; - - case "publickeytoken": - newReference.PublicKeyOrToken = ParseHexBlob(); - newReference.HasPublicKey = false; - break; - - case "culture": - string culture = ParseCulture(); - newReference.Culture = !culture.Equals("neutral", StringComparison.OrdinalIgnoreCase) - ? culture - : null; - break; - - default: - throw new FormatException($"Unsupported {propertyToken.Text} assembly property."); + newReference.Version = ParseVersion(); + } + else if (propertyName.Equals("publickey", StringComparison.OrdinalIgnoreCase)) + { + newReference.PublicKeyOrToken = ParseHexBlob(); + newReference.HasPublicKey = true; + } + else if (propertyName.Equals("publickeytoken", StringComparison.OrdinalIgnoreCase)) + { + newReference.PublicKeyOrToken = ParseHexBlob(); + newReference.HasPublicKey = false; + } + else if (propertyName.Equals("culture", StringComparison.OrdinalIgnoreCase)) + { + string culture = ParseCulture(); + newReference.Culture = !culture.Equals("neutral", StringComparison.OrdinalIgnoreCase) + ? culture + : null; + } + else + { + throw new FormatException($"Unsupported {propertyName} assembly property."); } } @@ -381,18 +379,31 @@ private Version ParseVersion() private byte[]? ParseHexBlob() { - var hexString = Expect(TypeNameTerminal.Identifier, TypeNameTerminal.Number).Text; + string hexString = Expect(TypeNameTerminal.Identifier, TypeNameTerminal.Number).Text; if (hexString == "null") return null; if (hexString.Length % 2 != 0) throw new FormatException("Provided hex string does not have an even length."); byte[] result = new byte[hexString.Length / 2]; - for (int i = 0; i < hexString.Length; i+=2) - result[i / 2] = byte.Parse(hexString.Substring(i, 2), NumberStyles.HexNumber); + for (int i = 0; i < hexString.Length; i += 2) + result[i / 2] = ParseHexByte(hexString, i); return result; } + private static byte ParseHexByte(string hexString, int index) + { + return (byte) ((ParseHexNibble(hexString[index]) << 4) | ParseHexNibble(hexString[index + 1])); + } + + private static byte ParseHexNibble(char nibble) => nibble switch + { + >= '0' and <= '9' => (byte) (nibble - '0'), + >= 'A' and <= 'F' => (byte) (nibble - 'A' + 10), + >= 'a' and <= 'f' => (byte) (nibble - 'a' + 10), + _ => throw new FormatException() + }; + private string ParseCulture() { return Expect(TypeNameTerminal.Identifier).Text; From 4c6f53926f14dcb115669630a5bdf33ee2c1411d Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 10 Mar 2022 23:59:19 +0100 Subject: [PATCH 039/184] Add .NET 3.1 and .NET 6.0 LTS build targets. --- src/AsmResolver.DotNet/AsmResolver.DotNet.csproj | 2 +- src/AsmResolver.PE.File/AsmResolver.PE.File.csproj | 2 +- .../AsmResolver.PE.Win32Resources.csproj | 2 +- src/AsmResolver.PE/AsmResolver.PE.csproj | 2 +- src/AsmResolver/AsmResolver.csproj | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj index 87725f3f4..30ec35a73 100644 --- a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj +++ b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj @@ -4,11 +4,11 @@ AsmResolver.DotNet High level .NET image models for the AsmResolver executable file inspection toolsuite. exe pe directories imports exports resources dotnet cil inspection manipulation assembly disassembly - netstandard2.0 true 1701;1702;NU5105 10 enable + net6.0;netcoreapp3.1;netstandard2.0 diff --git a/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj b/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj index 97588083c..f18c18503 100644 --- a/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj +++ b/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj @@ -4,12 +4,12 @@ AsmResolver.PE.File Raw PE file models for the AsmResolver executable file inspection toolsuite. exe pe headers sections inspection manipulation assembly disassembly - netstandard2.0 true 1701;1702;NU5105 true enable 10 + net6.0;netcoreapp3.1;netstandard2.0 diff --git a/src/AsmResolver.PE.Win32Resources/AsmResolver.PE.Win32Resources.csproj b/src/AsmResolver.PE.Win32Resources/AsmResolver.PE.Win32Resources.csproj index 62df025a3..f6746bce3 100644 --- a/src/AsmResolver.PE.Win32Resources/AsmResolver.PE.Win32Resources.csproj +++ b/src/AsmResolver.PE.Win32Resources/AsmResolver.PE.Win32Resources.csproj @@ -3,11 +3,11 @@ AsmResolver.PE.Win32Resources exe pe directories imports exports resources dotnet cil inspection manipulation assembly disassembly - netstandard2.0 true 1701;1702;NU5105 enable 10 + net6.0;netcoreapp3.1;netstandard2.0 diff --git a/src/AsmResolver.PE/AsmResolver.PE.csproj b/src/AsmResolver.PE/AsmResolver.PE.csproj index 3881ad3bc..4949ab984 100644 --- a/src/AsmResolver.PE/AsmResolver.PE.csproj +++ b/src/AsmResolver.PE/AsmResolver.PE.csproj @@ -4,11 +4,11 @@ AsmResolver.PE PE image models for the AsmResolver executable file inspection toolsuite. exe pe directories imports exports resources dotnet cil inspection manipulation assembly disassembly - netstandard2.0 true 1701;1702;NU5105 10 enable + net6.0;netcoreapp3.1;netstandard2.0 diff --git a/src/AsmResolver/AsmResolver.csproj b/src/AsmResolver/AsmResolver.csproj index 75cea2584..9927b70ff 100644 --- a/src/AsmResolver/AsmResolver.csproj +++ b/src/AsmResolver/AsmResolver.csproj @@ -4,11 +4,11 @@ AsmResolver The base library for the AsmResolver executable file inspection toolsuite. exe pe dotnet cil inspection manipulation assembly disassembly - netstandard2.0 true 1701;1702;NU5105 enable 10 + net6.0;netcoreapp3.1;netstandard2.0 From af2d0ca1e4d7e94e8fcc2067f9fd4c9644066185 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 11 Mar 2022 00:00:11 +0100 Subject: [PATCH 040/184] Add CoreLib_ReadWrite benchmark --- .../AsmResolver.Benchmarks.csproj | 2 +- .../ModuleReadWriteBenchmark.cs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj b/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj index e3eb947fb..56f183cc8 100644 --- a/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj +++ b/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj @@ -2,8 +2,8 @@ Exe - netcoreapp3.1 10 + net6.0;netcoreapp3.1 diff --git a/test/AsmResolver.Benchmarks/ModuleReadWriteBenchmark.cs b/test/AsmResolver.Benchmarks/ModuleReadWriteBenchmark.cs index 46e64fdec..206b2cb80 100644 --- a/test/AsmResolver.Benchmarks/ModuleReadWriteBenchmark.cs +++ b/test/AsmResolver.Benchmarks/ModuleReadWriteBenchmark.cs @@ -1,3 +1,4 @@ +using System; using System.IO; using AsmResolver.DotNet; using BenchmarkDotNet.Attributes; @@ -11,9 +12,17 @@ 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 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); + } + [Benchmark] public void HelloWorld_Read() { @@ -52,5 +61,12 @@ public void ManyMethods_ReadWrite() var file = ModuleDefinition.FromBytes(ManyMethods); file.Write(_outputStream); } + + [Benchmark] + public void CoreLib_ReadWrite() + { + var module = ModuleDefinition.FromBytes(CoreLib); + module.Write(_outputStream); + } } } From 2c95209b53c906bef1d94d61c3e0492c1ba5a535 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 11 Mar 2022 00:07:56 +0100 Subject: [PATCH 041/184] Fix NRT in AsmResolver base package. --- src/AsmResolver/ByteArrayEqualityComparer.cs | 9 ++++++++- src/AsmResolver/Collections/OneToManyRelation.cs | 2 ++ src/AsmResolver/Collections/OneToOneRelation.cs | 6 ++++-- src/AsmResolver/Utf8String.cs | 11 +++++++---- test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs | 2 +- 5 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/AsmResolver/ByteArrayEqualityComparer.cs b/src/AsmResolver/ByteArrayEqualityComparer.cs index 82f1bba10..277226f65 100644 --- a/src/AsmResolver/ByteArrayEqualityComparer.cs +++ b/src/AsmResolver/ByteArrayEqualityComparer.cs @@ -84,8 +84,15 @@ public int GetHashCode(byte[] obj) } /// - public int Compare(byte[] x, byte[] y) + public int Compare(byte[]? x, byte[]? y) { + if (ReferenceEquals(x, y)) + return 0; + if (x is null) + return -1; + if (y is null) + return 1; + int length = Math.Min(x.Length, y.Length); for (int i = 0; i < length; i++) { diff --git a/src/AsmResolver/Collections/OneToManyRelation.cs b/src/AsmResolver/Collections/OneToManyRelation.cs index 5a513868b..570768c7e 100644 --- a/src/AsmResolver/Collections/OneToManyRelation.cs +++ b/src/AsmResolver/Collections/OneToManyRelation.cs @@ -8,6 +8,8 @@ namespace AsmResolver.Collections /// The type of objects to map. /// The type of objects to map to. public sealed class OneToManyRelation + where TKey : notnull + where TValue : notnull { private readonly Dictionary> _memberLists = new(); private readonly Dictionary _memberOwners = new(); diff --git a/src/AsmResolver/Collections/OneToOneRelation.cs b/src/AsmResolver/Collections/OneToOneRelation.cs index 46385866a..881915925 100644 --- a/src/AsmResolver/Collections/OneToOneRelation.cs +++ b/src/AsmResolver/Collections/OneToOneRelation.cs @@ -8,6 +8,8 @@ namespace AsmResolver.Collections /// The first object type. /// The second object type. public sealed class OneToOneRelation + where TKey : notnull + where TValue : notnull { private readonly Dictionary _keyToValue = new(); private readonly Dictionary _valueToKey = new(); @@ -36,7 +38,7 @@ public bool Add(TKey key, TValue value) /// /// The key. /// The value. - public TValue GetValue(TKey key) + public TValue? GetValue(TKey key) { _keyToValue.TryGetValue(key, out var value); return value; @@ -47,7 +49,7 @@ public TValue GetValue(TKey key) /// /// The value. /// The key. - public TKey GetKey(TValue value) + public TKey? GetKey(TValue value) { _valueToKey.TryGetValue(value, out var key); return key; diff --git a/src/AsmResolver/Utf8String.cs b/src/AsmResolver/Utf8String.cs index e2b87ff33..f3c43f540 100644 --- a/src/AsmResolver/Utf8String.cs +++ b/src/AsmResolver/Utf8String.cs @@ -268,22 +268,25 @@ public Utf8String Concat(byte[]? other) /// /// This operation performs a byte-level comparison of the two strings. /// - public bool Equals(Utf8String other) => ByteArrayEqualityComparer.Instance.Equals(_data, other._data); + public bool Equals(Utf8String? other) => + other is not null && ByteArrayEqualityComparer.Instance.Equals(_data, other._data); /// /// /// This operation performs a byte-level comparison of the two strings. /// - public bool Equals(byte[] other) => ByteArrayEqualityComparer.Instance.Equals(_data, other); + public bool Equals(byte[]? other) => other is not null && ByteArrayEqualityComparer.Instance.Equals(_data, other); /// /// /// This operation performs a byte-level comparison of the two strings. /// - public bool Equals(string other) => Value.Equals(other); + public bool Equals(string? other) =>other is not null && Value.Equals(other); /// - public int CompareTo(Utf8String other) => ByteArrayEqualityComparer.Instance.Compare(_data, other._data); + public int CompareTo(Utf8String? other) => other is not null + ? ByteArrayEqualityComparer.Instance.Compare(_data, other._data) + : 1; /// public IEnumerator GetEnumerator() => Value.GetEnumerator(); diff --git a/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs index 13fd59cf7..07c71ff3c 100644 --- a/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs @@ -343,7 +343,7 @@ public void DetectTargetNetCore() [Fact] public void DetectTargetStandard() { - var module = ModuleDefinition.FromFile(typeof(ISegment).Assembly.Location); + var module = ModuleDefinition.FromFile(typeof(TestCases.Types.Class).Assembly.Location); Assert.Contains(DotNetRuntimeInfo.NetStandard, module.OriginalTargetRuntime.Name); Assert.Equal(2, module.OriginalTargetRuntime.Version.Major); } From fd0d7d600c69c4e4e5bcdec645bd38c6cac326b7 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 11 Mar 2022 00:24:44 +0100 Subject: [PATCH 042/184] Fix NRT in AsmResolver.PE. --- .../DotNet/Cil/CilInstructionFormatter.cs | 12 +- .../DotNet/Cil/CilInstructionLabel.cs | 2 +- .../DotNet/Cil/CilOffsetLabel.cs | 2 +- src/AsmResolver.PE/DotNet/Cil/CilOpCode.cs | 286 +++++++++--------- .../DotNet/Metadata/Tables/MetadataToken.cs | 12 +- .../Tables/Rows/AssemblyDefinitionRow.cs | 2 +- .../Metadata/Tables/Rows/AssemblyOSRow.cs | 2 +- .../Tables/Rows/AssemblyProcessorRow.cs | 2 +- .../Metadata/Tables/Rows/AssemblyRefOSRow.cs | 2 +- .../Tables/Rows/AssemblyRefProcessorRow.cs | 2 +- .../Tables/Rows/AssemblyReferenceRow.cs | 2 +- .../Metadata/Tables/Rows/ClassLayoutRow.cs | 2 +- .../Metadata/Tables/Rows/ConstantRow.cs | 2 +- .../Tables/Rows/CustomAttributeRow.cs | 2 +- .../DotNet/Metadata/Tables/Rows/EncLogRow.cs | 2 +- .../DotNet/Metadata/Tables/Rows/EncMapRow.cs | 2 +- .../Tables/Rows/EventDefinitionRow.cs | 2 +- .../Metadata/Tables/Rows/EventMapRow.cs | 2 +- .../Metadata/Tables/Rows/EventPointerRow.cs | 2 +- .../Metadata/Tables/Rows/ExportedTypeRow.cs | 2 +- .../Tables/Rows/FieldDefinitionRow.cs | 2 +- .../Metadata/Tables/Rows/FieldLayoutRow.cs | 2 +- .../Metadata/Tables/Rows/FieldMarshalRow.cs | 2 +- .../Metadata/Tables/Rows/FieldPointerRow.cs | 2 +- .../Metadata/Tables/Rows/FieldRvaRow.cs | 2 +- .../Metadata/Tables/Rows/FileReferenceRow.cs | 2 +- .../Rows/GenericParameterConstraintRow.cs | 2 +- .../Tables/Rows/GenericParameterRow.cs | 2 +- .../Tables/Rows/ImplementationMapRow.cs | 2 +- .../Tables/Rows/InterfaceImplementationRow.cs | 2 +- .../Tables/Rows/ManifestResourceRow.cs | 2 +- .../Tables/Rows/MemberReferenceRow.cs | 2 +- .../Tables/Rows/MethodDefinitionRow.cs | 2 +- .../Tables/Rows/MethodImplementationRow.cs | 2 +- .../Metadata/Tables/Rows/MethodPointerRow.cs | 2 +- .../Tables/Rows/MethodSemanticsRow.cs | 2 +- .../Tables/Rows/MethodSpecificationRow.cs | 2 +- .../Tables/Rows/ModuleDefinitionRow.cs | 2 +- .../Tables/Rows/ModuleReferenceRow.cs | 2 +- .../Metadata/Tables/Rows/NestedClassRow.cs | 2 +- .../Tables/Rows/ParameterDefinitionRow.cs | 2 +- .../Tables/Rows/ParameterPointerRow.cs | 2 +- .../Tables/Rows/PropertyDefinitionRow.cs | 2 +- .../Metadata/Tables/Rows/PropertyMapRow.cs | 2 +- .../Tables/Rows/PropertyPointerRow.cs | 2 +- .../Tables/Rows/SecurityDeclarationRow.cs | 2 +- .../Tables/Rows/StandAloneSignatureRow.cs | 2 +- .../Metadata/Tables/Rows/TypeDefinitionRow.cs | 2 +- .../Metadata/Tables/Rows/TypeReferenceRow.cs | 2 +- .../Tables/Rows/TypeSpecificationRow.cs | 2 +- .../SerializedUserStringsStream.cs | 4 +- .../StrongName/StrongNameDataHashBuilder.cs | 4 +- .../DotNet/StrongName/StrongNamePrivateKey.cs | 22 +- .../DotNet/StrongName/StrongNamePublicKey.cs | 5 + .../Imports/DefaultSymbolResolver.cs | 2 +- .../Relocations/BaseRelocation.cs | 2 +- .../Builder/ResourceTableBuffer.cs | 3 +- 57 files changed, 229 insertions(+), 217 deletions(-) diff --git a/src/AsmResolver.PE/DotNet/Cil/CilInstructionFormatter.cs b/src/AsmResolver.PE/DotNet/Cil/CilInstructionFormatter.cs index f06b5b5e8..58bd6cdae 100644 --- a/src/AsmResolver.PE/DotNet/Cil/CilInstructionFormatter.cs +++ b/src/AsmResolver.PE/DotNet/Cil/CilInstructionFormatter.cs @@ -87,7 +87,7 @@ public string FormatInstruction(CilInstruction instruction) short longIndex => $"A_{longIndex.ToString()}", byte shortIndex => $"A_{shortIndex.ToString()}", null => InvalidOperandString, - _ => operand.ToString() + _ => operand.ToString() ?? string.Empty }; /// @@ -101,7 +101,7 @@ public string FormatInstruction(CilInstruction instruction) short longIndex => $"V_{longIndex.ToString()}", byte shortIndex => $"V_{shortIndex.ToString()}", null => InvalidOperandString, - _ => operand.ToString() + _ => operand.ToString() ?? string.Empty }; /// @@ -130,7 +130,7 @@ public string FormatInstruction(CilInstruction instruction) { MetadataToken token => FormatToken(token), null => InvalidOperandString, - _ => operand.ToString() + _ => operand.ToString() ?? string.Empty }; /// @@ -150,7 +150,7 @@ public string FormatInstruction(CilInstruction instruction) IEnumerable target => $"({string.Join(", ", target.Select(FormatBranchTarget))})", IEnumerable offsets => $"({string.Join(", ", offsets.Select(x => FormatBranchTarget(x)))})", null => InvalidOperandString, - _ => operand.ToString() + _ => operand.ToString() ?? string.Empty }; /// @@ -175,7 +175,7 @@ public string FormatInstruction(CilInstruction instruction) ICilLabel target => FormatLabel(target.Offset), int offset => FormatLabel(offset), null => InvalidOperandString, - _ => operand.ToString() + _ => operand.ToString() ?? string.Empty }; /// @@ -187,7 +187,7 @@ public string FormatInstruction(CilInstruction instruction) { MetadataToken token => FormatToken(token), null => InvalidOperandString, - _ => operand.ToString() + _ => operand.ToString() ?? string.Empty }; } diff --git a/src/AsmResolver.PE/DotNet/Cil/CilInstructionLabel.cs b/src/AsmResolver.PE/DotNet/Cil/CilInstructionLabel.cs index 9fb14dad5..4418b78a7 100644 --- a/src/AsmResolver.PE/DotNet/Cil/CilInstructionLabel.cs +++ b/src/AsmResolver.PE/DotNet/Cil/CilInstructionLabel.cs @@ -39,7 +39,7 @@ public CilInstructionLabel(CilInstruction instruction) public bool Equals(ICilLabel? other) => other is not null && Offset == other.Offset; /// - public override bool Equals(object obj) => Equals(obj as ICilLabel); + public override bool Equals(object? obj) => Equals(obj as ICilLabel); /// public override int GetHashCode() => Offset; diff --git a/src/AsmResolver.PE/DotNet/Cil/CilOffsetLabel.cs b/src/AsmResolver.PE/DotNet/Cil/CilOffsetLabel.cs index 7d0a80f0a..36fcc4709 100644 --- a/src/AsmResolver.PE/DotNet/Cil/CilOffsetLabel.cs +++ b/src/AsmResolver.PE/DotNet/Cil/CilOffsetLabel.cs @@ -27,7 +27,7 @@ public int Offset public bool Equals(ICilLabel? other) => other is not null && Offset == other.Offset; /// - public override bool Equals(object obj) => Equals(obj as ICilLabel); + public override bool Equals(object? obj) => Equals(obj as ICilLabel); /// public override int GetHashCode() => Offset; diff --git a/src/AsmResolver.PE/DotNet/Cil/CilOpCode.cs b/src/AsmResolver.PE/DotNet/Cil/CilOpCode.cs index 1a1418b6b..b10e2f377 100644 --- a/src/AsmResolver.PE/DotNet/Cil/CilOpCode.cs +++ b/src/AsmResolver.PE/DotNet/Cil/CilOpCode.cs @@ -1,143 +1,143 @@ -namespace AsmResolver.PE.DotNet.Cil -{ - /// - /// Describes the operation that a single CIL instruction performs. - /// - public readonly struct CilOpCode - { - internal const int ValueBitLength = 8; - internal const int ValueOffset = 0; - - internal const int TwoBytesBitLength = 1; - internal const int TwoBytesOffset = ValueOffset + ValueBitLength; - - internal const int StackBehaviourBitLength = 5; - internal const int StackBehaviourPushOffset = TwoBytesOffset + TwoBytesBitLength; - internal const int StackBehaviourPopOffset = StackBehaviourPushOffset + StackBehaviourBitLength; - - internal const int OpCodeTypeBitLength = 3; - internal const int OpCodeTypeOffset = StackBehaviourPopOffset + StackBehaviourBitLength; - - internal const int OperandTypeBitLength = 5; - internal const int OperandTypeOffset = OpCodeTypeOffset + OpCodeTypeBitLength; - - internal const int FlowControlBitLength = 4; - internal const int FlowControlOffset = OperandTypeOffset + OperandTypeBitLength; - - /// - /// Determines whether two operation codes encode the same operation. - /// - /// The first operation code. - /// The second operation code. - /// true if the same operation code is encoded, false otherwise. - public static bool operator ==(CilOpCode a, CilOpCode b) => a.Equals(b); - - /// - /// Determines whether two operation codes do not encode the same operation. - /// - /// The first operation code. - /// The second operation code. - /// true if a different operation code is encoded, false otherwise. - public static bool operator !=(CilOpCode a, CilOpCode b) => !(a == b); - - - // To reduce the memory footprint of a single CIL operation code, we put every property into a single 32 bit - // number, using the following layout: - // - // |31.. ..27|26.. ..22|21.19|18.. ..14|13.. ..9|8|7.. ..0| - // +---------+---------+-----+---------+---------+-+---------------+ - // | flwctrl | operand | opc | stckpop | stckpsh |L| opcode byte | - // +---------+---------+-----+---------+---------+-+---------------+ - // - - private readonly uint _value; - - internal CilOpCode(uint value) - { - _value = value; - - if (IsLarge) - CilOpCodes.MultiByteOpCodes[Byte2] = this; - else - CilOpCodes.SingleByteOpCodes[Byte1] = this; - } - - /// - /// Gets the mnemonic of the operation code. - /// - public string Mnemonic => CilOpCodeNames.Names[IsLarge ? Byte2 + 256 : Byte1]; - - /// - /// Gets the value of the operation code. - /// - public CilCode Code => (CilCode) ((ushort) ((_value >> ValueOffset) & 0xFF) | (IsLarge ? 0xFE00 : 0)); - - /// - /// Gets a value indicating whether the operation code is large or not. If this value is true, the code - /// needs two bytes to be encoded, otherwise it only needs one. - /// - public bool IsLarge => ((_value >> TwoBytesOffset) & 1) == 1; - - /// - /// Gets the size in bytes of the operation code. - /// - /// - /// This does not include the operand of the instruction. - /// - public int Size => IsLarge ? 2 : 1; - - /// - /// Gets the first byte that appears in the instruction stream encoding this operation. - /// - public byte Byte1 => (byte) (IsLarge ? 0xFE : (_value >> ValueOffset) & 0xFF); - - /// - /// Gets the second byte that appears in the instruction stream encoding this operation. - /// - /// - /// This property only has meaning if is true. - /// - public byte Byte2 => (byte) (IsLarge ? (_value >> ValueOffset) & 0xFF : 0); - - /// - /// Gets a value indicating the stack push behaviour of the instruction. - /// - public CilStackBehaviour StackBehaviourPush => (CilStackBehaviour) ((_value >> StackBehaviourPushOffset) & 0b11111); - - /// - /// Gets a value indicating the stack pop behaviour of the instruction. - /// - public CilStackBehaviour StackBehaviourPop => (CilStackBehaviour) ((_value >> StackBehaviourPopOffset) & 0b11111); - - /// - /// Gets a value indicating the category of the operation code. - /// - public CilOpCodeType OpCodeType => (CilOpCodeType) ((_value >> OpCodeTypeOffset) & 0b111); - - /// - /// Gets a value indicating the category of the operand. - /// - public CilOperandType OperandType => (CilOperandType) ((_value >> OperandTypeOffset) & 0b11111); - - /// - /// Gets a value indicating the flow control behaviour of the operation. - /// - public CilFlowControl FlowControl => (CilFlowControl) ((_value >> FlowControlOffset) & 0b1111); - - /// - public override string ToString() => Mnemonic; - - /// - /// Determines whether the provided operation code is encoding the same operation. - /// - /// The other operation code. - /// true if the same operation code is encoded, false otherwise. - public bool Equals(CilOpCode other) => Code == other.Code; - - /// - public override bool Equals(object obj) => obj is CilOpCode other && Equals(other); - - /// - public override int GetHashCode() => (int) Code; - } -} \ No newline at end of file +namespace AsmResolver.PE.DotNet.Cil +{ + /// + /// Describes the operation that a single CIL instruction performs. + /// + public readonly struct CilOpCode + { + internal const int ValueBitLength = 8; + internal const int ValueOffset = 0; + + internal const int TwoBytesBitLength = 1; + internal const int TwoBytesOffset = ValueOffset + ValueBitLength; + + internal const int StackBehaviourBitLength = 5; + internal const int StackBehaviourPushOffset = TwoBytesOffset + TwoBytesBitLength; + internal const int StackBehaviourPopOffset = StackBehaviourPushOffset + StackBehaviourBitLength; + + internal const int OpCodeTypeBitLength = 3; + internal const int OpCodeTypeOffset = StackBehaviourPopOffset + StackBehaviourBitLength; + + internal const int OperandTypeBitLength = 5; + internal const int OperandTypeOffset = OpCodeTypeOffset + OpCodeTypeBitLength; + + internal const int FlowControlBitLength = 4; + internal const int FlowControlOffset = OperandTypeOffset + OperandTypeBitLength; + + /// + /// Determines whether two operation codes encode the same operation. + /// + /// The first operation code. + /// The second operation code. + /// true if the same operation code is encoded, false otherwise. + public static bool operator ==(CilOpCode a, CilOpCode b) => a.Equals(b); + + /// + /// Determines whether two operation codes do not encode the same operation. + /// + /// The first operation code. + /// The second operation code. + /// true if a different operation code is encoded, false otherwise. + public static bool operator !=(CilOpCode a, CilOpCode b) => !(a == b); + + + // To reduce the memory footprint of a single CIL operation code, we put every property into a single 32 bit + // number, using the following layout: + // + // |31.. ..27|26.. ..22|21.19|18.. ..14|13.. ..9|8|7.. ..0| + // +---------+---------+-----+---------+---------+-+---------------+ + // | flwctrl | operand | opc | stckpop | stckpsh |L| opcode byte | + // +---------+---------+-----+---------+---------+-+---------------+ + // + + private readonly uint _value; + + internal CilOpCode(uint value) + { + _value = value; + + if (IsLarge) + CilOpCodes.MultiByteOpCodes[Byte2] = this; + else + CilOpCodes.SingleByteOpCodes[Byte1] = this; + } + + /// + /// Gets the mnemonic of the operation code. + /// + public string Mnemonic => CilOpCodeNames.Names[IsLarge ? Byte2 + 256 : Byte1]; + + /// + /// Gets the value of the operation code. + /// + public CilCode Code => (CilCode) ((ushort) ((_value >> ValueOffset) & 0xFF) | (IsLarge ? 0xFE00 : 0)); + + /// + /// Gets a value indicating whether the operation code is large or not. If this value is true, the code + /// needs two bytes to be encoded, otherwise it only needs one. + /// + public bool IsLarge => ((_value >> TwoBytesOffset) & 1) == 1; + + /// + /// Gets the size in bytes of the operation code. + /// + /// + /// This does not include the operand of the instruction. + /// + public int Size => IsLarge ? 2 : 1; + + /// + /// Gets the first byte that appears in the instruction stream encoding this operation. + /// + public byte Byte1 => (byte) (IsLarge ? 0xFE : (_value >> ValueOffset) & 0xFF); + + /// + /// Gets the second byte that appears in the instruction stream encoding this operation. + /// + /// + /// This property only has meaning if is true. + /// + public byte Byte2 => (byte) (IsLarge ? (_value >> ValueOffset) & 0xFF : 0); + + /// + /// Gets a value indicating the stack push behaviour of the instruction. + /// + public CilStackBehaviour StackBehaviourPush => (CilStackBehaviour) ((_value >> StackBehaviourPushOffset) & 0b11111); + + /// + /// Gets a value indicating the stack pop behaviour of the instruction. + /// + public CilStackBehaviour StackBehaviourPop => (CilStackBehaviour) ((_value >> StackBehaviourPopOffset) & 0b11111); + + /// + /// Gets a value indicating the category of the operation code. + /// + public CilOpCodeType OpCodeType => (CilOpCodeType) ((_value >> OpCodeTypeOffset) & 0b111); + + /// + /// Gets a value indicating the category of the operand. + /// + public CilOperandType OperandType => (CilOperandType) ((_value >> OperandTypeOffset) & 0b11111); + + /// + /// Gets a value indicating the flow control behaviour of the operation. + /// + public CilFlowControl FlowControl => (CilFlowControl) ((_value >> FlowControlOffset) & 0b1111); + + /// + public override string ToString() => Mnemonic; + + /// + /// Determines whether the provided operation code is encoding the same operation. + /// + /// The other operation code. + /// true if the same operation code is encoded, false otherwise. + public bool Equals(CilOpCode other) => Code == other.Code; + + /// + public override bool Equals(object? obj) => obj is CilOpCode other && Equals(other); + + /// + public override int GetHashCode() => (int) Code; + } +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataToken.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataToken.cs index f936a9530..56d79aa17 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataToken.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataToken.cs @@ -3,7 +3,7 @@ namespace AsmResolver.PE.DotNet.Metadata.Tables { /// - /// Represents a metadata token, referencing a member using a table and a row index. + /// Represents a metadata token, referencing a member using a table and a row index. /// public readonly struct MetadataToken : IComparable { @@ -11,7 +11,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); - + /// /// Converts a 32-bit integer to a metadata token. /// @@ -43,7 +43,7 @@ namespace AsmResolver.PE.DotNet.Metadata.Tables { return a.Equals(b); } - + /// /// Determines whether two metadata tokens are not considered equal. That is, either the table index or the row /// identifier (or both) does not match the other. @@ -119,7 +119,7 @@ public bool Equals(MetadataToken other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is MetadataToken other && Equals(other); } @@ -135,6 +135,6 @@ public int CompareTo(MetadataToken other) { return _value.CompareTo(other._value); } - + } -} \ No newline at end of file +} diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/AssemblyDefinitionRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/AssemblyDefinitionRow.cs index 92f7f8212..e1cdaaa55 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/AssemblyDefinitionRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/AssemblyDefinitionRow.cs @@ -200,7 +200,7 @@ public bool Equals(AssemblyDefinitionRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is AssemblyDefinitionRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/AssemblyOSRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/AssemblyOSRow.cs index 7a009dc9f..820abe0be 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/AssemblyOSRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/AssemblyOSRow.cs @@ -100,7 +100,7 @@ public bool Equals(AssemblyOSRow other) } /// - public override bool Equals(object obj) => obj is AssemblyOSRow other && Equals(other); + public override bool Equals(object? obj) => obj is AssemblyOSRow other && Equals(other); /// public override int GetHashCode() diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/AssemblyProcessorRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/AssemblyProcessorRow.cs index 1a02fe0ac..fd2ca7474 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/AssemblyProcessorRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/AssemblyProcessorRow.cs @@ -69,7 +69,7 @@ public bool Equals(AssemblyProcessorRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is AssemblyProcessorRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/AssemblyRefOSRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/AssemblyRefOSRow.cs index ea061ddbd..4d535d7fc 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/AssemblyRefOSRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/AssemblyRefOSRow.cs @@ -117,7 +117,7 @@ public bool Equals(AssemblyRefOSRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is AssemblyRefOSRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/AssemblyRefProcessorRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/AssemblyRefProcessorRow.cs index 337a6da27..e7e30f0cb 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/AssemblyRefProcessorRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/AssemblyRefProcessorRow.cs @@ -85,7 +85,7 @@ public bool Equals(AssemblyRefProcessorRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is AssemblyRefProcessorRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/AssemblyReferenceRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/AssemblyReferenceRow.cs index dec588c64..bdde149bc 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/AssemblyReferenceRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/AssemblyReferenceRow.cs @@ -199,7 +199,7 @@ public bool Equals(AssemblyReferenceRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is AssemblyReferenceRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ClassLayoutRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ClassLayoutRow.cs index ac354d7c1..5506ddc35 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ClassLayoutRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ClassLayoutRow.cs @@ -101,7 +101,7 @@ public bool Equals(ClassLayoutRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is ClassLayoutRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ConstantRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ConstantRow.cs index b653527d8..62da25716 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ConstantRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ConstantRow.cs @@ -133,7 +133,7 @@ public void Write(IBinaryStreamWriter writer, TableLayout layout) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is ConstantRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/CustomAttributeRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/CustomAttributeRow.cs index d2bd968a6..aab495f23 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/CustomAttributeRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/CustomAttributeRow.cs @@ -105,7 +105,7 @@ public void Write(IBinaryStreamWriter writer, TableLayout layout) } /// - public override bool Equals(object obj) => obj is CustomAttributeRow other && Equals(other); + public override bool Equals(object? obj) => obj is CustomAttributeRow other && Equals(other); /// public override int GetHashCode() diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/EncLogRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/EncLogRow.cs index e55e6dc18..f625eb81c 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/EncLogRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/EncLogRow.cs @@ -84,7 +84,7 @@ public void Write(IBinaryStreamWriter writer, TableLayout layout) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is EncLogRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/EncMapRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/EncMapRow.cs index dcdf99df5..46c5115f3 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/EncMapRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/EncMapRow.cs @@ -69,7 +69,7 @@ public void Write(IBinaryStreamWriter writer, TableLayout layout) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is EncMapRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/EventDefinitionRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/EventDefinitionRow.cs index 35ab1196c..cfa67f011 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/EventDefinitionRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/EventDefinitionRow.cs @@ -103,7 +103,7 @@ public bool Equals(EventDefinitionRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is EventDefinitionRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/EventMapRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/EventMapRow.cs index 74578f614..76cf6d5e8 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/EventMapRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/EventMapRow.cs @@ -84,7 +84,7 @@ public bool Equals(EventMapRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is EventMapRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/EventPointerRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/EventPointerRow.cs index 1c51f5593..5aa731856 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/EventPointerRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/EventPointerRow.cs @@ -69,7 +69,7 @@ public bool Equals(EventPointerRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is EventPointerRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ExportedTypeRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ExportedTypeRow.cs index 87d971d5f..cf846c6e7 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ExportedTypeRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ExportedTypeRow.cs @@ -143,7 +143,7 @@ public bool Equals(ExportedTypeRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is ExportedTypeRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldDefinitionRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldDefinitionRow.cs index 2ab35b41e..0891d851d 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldDefinitionRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldDefinitionRow.cs @@ -103,7 +103,7 @@ public bool Equals(FieldDefinitionRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is FieldDefinitionRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldLayoutRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldLayoutRow.cs index 7cd0b6bf4..e4070b33e 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldLayoutRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldLayoutRow.cs @@ -84,7 +84,7 @@ public bool Equals(FieldLayoutRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is FieldLayoutRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldMarshalRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldMarshalRow.cs index a0d5456f1..b6300f871 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldMarshalRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldMarshalRow.cs @@ -86,7 +86,7 @@ public bool Equals(FieldMarshalRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is FieldMarshalRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldPointerRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldPointerRow.cs index 31adc510c..59973970b 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldPointerRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldPointerRow.cs @@ -70,7 +70,7 @@ public bool Equals(FieldPointerRow other) /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is FieldPointerRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldRvaRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldRvaRow.cs index 8cc8a9358..97d7b6b92 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldRvaRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FieldRvaRow.cs @@ -93,7 +93,7 @@ public bool Equals(FieldRvaRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is FieldRvaRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FileReferenceRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FileReferenceRow.cs index e8308cd70..07543d961 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FileReferenceRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/FileReferenceRow.cs @@ -98,7 +98,7 @@ public bool Equals(FileReferenceRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is FileReferenceRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/GenericParameterConstraintRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/GenericParameterConstraintRow.cs index 9f2ab657f..6e5fe6f24 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/GenericParameterConstraintRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/GenericParameterConstraintRow.cs @@ -86,7 +86,7 @@ public bool Equals(GenericParameterConstraintRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is GenericParameterConstraintRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/GenericParameterRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/GenericParameterRow.cs index d052fb8c0..51f0be43c 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/GenericParameterRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/GenericParameterRow.cs @@ -116,7 +116,7 @@ public bool Equals(GenericParameterRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is GenericParameterRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ImplementationMapRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ImplementationMapRow.cs index 12e014998..bb702eadf 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ImplementationMapRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ImplementationMapRow.cs @@ -116,7 +116,7 @@ public bool Equals(ImplementationMapRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is ImplementationMapRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/InterfaceImplementationRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/InterfaceImplementationRow.cs index 66982eb6d..3d70f3f8d 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/InterfaceImplementationRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/InterfaceImplementationRow.cs @@ -86,7 +86,7 @@ public bool Equals(InterfaceImplementationRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is InterfaceImplementationRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ManifestResourceRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ManifestResourceRow.cs index ae8cd6d4f..f36d4300f 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ManifestResourceRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ManifestResourceRow.cs @@ -120,7 +120,7 @@ public bool Equals(ManifestResourceRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is ManifestResourceRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MemberReferenceRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MemberReferenceRow.cs index e953e5bc1..f9a6e0d07 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MemberReferenceRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MemberReferenceRow.cs @@ -107,7 +107,7 @@ public bool Equals(MemberReferenceRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is MemberReferenceRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDefinitionRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDefinitionRow.cs index 944503004..2b6df546f 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDefinitionRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodDefinitionRow.cs @@ -168,7 +168,7 @@ public bool Equals(MethodDefinitionRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is MethodDefinitionRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodImplementationRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodImplementationRow.cs index 8ffcdc0e9..a50f9b648 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodImplementationRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodImplementationRow.cs @@ -117,7 +117,7 @@ public bool Equals(MethodImplementationRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is MethodImplementationRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodPointerRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodPointerRow.cs index f660fd143..631824d61 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodPointerRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodPointerRow.cs @@ -69,7 +69,7 @@ public bool Equals(MethodPointerRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is MethodPointerRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodSemanticsRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodSemanticsRow.cs index 958624c72..bd56cb3c8 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodSemanticsRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodSemanticsRow.cs @@ -115,7 +115,7 @@ public bool Equals(MethodSemanticsRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is MethodSemanticsRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodSpecificationRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodSpecificationRow.cs index 629601266..e4ddb7850 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodSpecificationRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/MethodSpecificationRow.cs @@ -84,7 +84,7 @@ public bool Equals(MethodSpecificationRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is MethodSpecificationRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ModuleDefinitionRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ModuleDefinitionRow.cs index d5013b847..e29f2edcf 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ModuleDefinitionRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ModuleDefinitionRow.cs @@ -142,7 +142,7 @@ public bool Equals(ModuleDefinitionRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is ModuleDefinitionRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ModuleReferenceRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ModuleReferenceRow.cs index 93dce3f17..930d0526d 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ModuleReferenceRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ModuleReferenceRow.cs @@ -70,7 +70,7 @@ public bool Equals(ModuleReferenceRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is ModuleReferenceRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/NestedClassRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/NestedClassRow.cs index 9d937dab7..9e1d690d4 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/NestedClassRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/NestedClassRow.cs @@ -84,7 +84,7 @@ public bool Equals(NestedClassRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is NestedClassRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ParameterDefinitionRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ParameterDefinitionRow.cs index d57d48953..385c1a2eb 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ParameterDefinitionRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ParameterDefinitionRow.cs @@ -101,7 +101,7 @@ public bool Equals(ParameterDefinitionRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is ParameterDefinitionRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ParameterPointerRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ParameterPointerRow.cs index d318057ed..5033cacd2 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ParameterPointerRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/ParameterPointerRow.cs @@ -69,7 +69,7 @@ public bool Equals(ParameterPointerRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is ParameterPointerRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/PropertyDefinitionRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/PropertyDefinitionRow.cs index 4a526e590..fe2e01440 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/PropertyDefinitionRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/PropertyDefinitionRow.cs @@ -116,7 +116,7 @@ public bool Equals(PropertyDefinitionRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is PropertyDefinitionRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/PropertyMapRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/PropertyMapRow.cs index 947e1b0e4..350d6bc6f 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/PropertyMapRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/PropertyMapRow.cs @@ -99,7 +99,7 @@ public bool Equals(PropertyMapRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is PropertyMapRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/PropertyPointerRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/PropertyPointerRow.cs index 97be25f86..2026040a2 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/PropertyPointerRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/PropertyPointerRow.cs @@ -69,7 +69,7 @@ public bool Equals(PropertyPointerRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is PropertyPointerRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/SecurityDeclarationRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/SecurityDeclarationRow.cs index 4f0fd5a66..18e220604 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/SecurityDeclarationRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/SecurityDeclarationRow.cs @@ -99,7 +99,7 @@ public bool Equals(SecurityDeclarationRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is SecurityDeclarationRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/StandAloneSignatureRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/StandAloneSignatureRow.cs index 52881d8e9..0e4a1b00f 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/StandAloneSignatureRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/StandAloneSignatureRow.cs @@ -69,7 +69,7 @@ public bool Equals(StandAloneSignatureRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is StandAloneSignatureRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/TypeDefinitionRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/TypeDefinitionRow.cs index 880db0866..0d5f5c65d 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/TypeDefinitionRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/TypeDefinitionRow.cs @@ -160,7 +160,7 @@ public bool Equals(TypeDefinitionRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is TypeDefinitionRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/TypeReferenceRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/TypeReferenceRow.cs index 34850ceae..526ea07df 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/TypeReferenceRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/TypeReferenceRow.cs @@ -114,7 +114,7 @@ public bool Equals(TypeReferenceRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is TypeReferenceRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/TypeSpecificationRow.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/TypeSpecificationRow.cs index 2875db9a7..a22336b20 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/TypeSpecificationRow.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/Rows/TypeSpecificationRow.cs @@ -70,7 +70,7 @@ public bool Equals(TypeSpecificationRow other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is TypeSpecificationRow other && Equals(other); } diff --git a/src/AsmResolver.PE/DotNet/Metadata/UserStrings/SerializedUserStringsStream.cs b/src/AsmResolver.PE/DotNet/Metadata/UserStrings/SerializedUserStringsStream.cs index ed4bdcc7e..b50077898 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/UserStrings/SerializedUserStringsStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/UserStrings/SerializedUserStringsStream.cs @@ -9,7 +9,7 @@ namespace AsmResolver.PE.DotNet.Metadata.UserStrings /// public class SerializedUserStringsStream : UserStringsStream { - private readonly Dictionary _cachedStrings = new(); + private readonly Dictionary _cachedStrings = new(); private readonly BinaryStreamReader _reader; /// @@ -61,7 +61,7 @@ public SerializedUserStringsStream(string name, BinaryStreamReader reader) { index &= 0x00FFFFFF; - if (!_cachedStrings.TryGetValue(index, out string value) && index < _reader.Length) + if (!_cachedStrings.TryGetValue(index, out string? value) && index < _reader.Length) { var stringsReader = _reader.ForkRelative(index); diff --git a/src/AsmResolver.PE/DotNet/StrongName/StrongNameDataHashBuilder.cs b/src/AsmResolver.PE/DotNet/StrongName/StrongNameDataHashBuilder.cs index a4902cbb6..6fed154ad 100644 --- a/src/AsmResolver.PE/DotNet/StrongName/StrongNameDataHashBuilder.cs +++ b/src/AsmResolver.PE/DotNet/StrongName/StrongNameDataHashBuilder.cs @@ -65,7 +65,7 @@ public byte[] ComputeHash() _ => throw new NotSupportedException($"Invalid or unsupported hashing algorithm {_hashAlgorithm}.") }; - var buffer = new byte[0x1000]; + byte[] buffer = new byte[0x1000]; foreach (var range in _includedRanges) { @@ -85,7 +85,7 @@ public byte[] ComputeHash() } algorithm.TransformFinalBlock(Array.Empty(), 0, 0); - return algorithm.Hash; + return algorithm.Hash!; } private void ZeroRangesIfApplicable(byte[] buffer, OffsetRange currentRange) diff --git a/src/AsmResolver.PE/DotNet/StrongName/StrongNamePrivateKey.cs b/src/AsmResolver.PE/DotNet/StrongName/StrongNamePrivateKey.cs index 8a98cd0fe..dc517f811 100644 --- a/src/AsmResolver.PE/DotNet/StrongName/StrongNamePrivateKey.cs +++ b/src/AsmResolver.PE/DotNet/StrongName/StrongNamePrivateKey.cs @@ -92,16 +92,19 @@ public StrongNamePrivateKey(uint bitLength) /// /// The RSA parameters to import. public StrongNamePrivateKey(in RSAParameters parameters) - : base(parameters.Modulus, ByteSwap(parameters)) + : base(parameters.Modulus ?? throw new ArgumentException("The provided RSA parameters do not define a modulus."), + ByteSwap(parameters)) { Modulus = parameters.Modulus; - P = parameters.P; - Q = parameters.Q; - DP = parameters.DP; - DQ = parameters.DQ; - - InverseQ = parameters.InverseQ; - PrivateExponent = parameters.D; + P = parameters.P ?? throw new ArgumentException("The provided RSA parameters do not define prime P."); + Q = parameters.Q ?? throw new ArgumentException("The provided RSA parameters do not define prime Q.");; + DP = parameters.DP ?? throw new ArgumentException("The provided RSA parameters do not define DP.");; + DQ = parameters.DQ ?? throw new ArgumentException("The provided RSA parameters do not define DQ.");; + + InverseQ = parameters.InverseQ + ?? throw new ArgumentException("The provided RSA parameters do not define InverseQ."); + PrivateExponent = + parameters.D ?? throw new ArgumentException("The provided RSA parameters do not define D."); } /// @@ -223,6 +226,9 @@ public override void Write(IBinaryStreamWriter writer) private static uint ByteSwap(RSAParameters parameters) { + if (parameters.Exponent is null) + throw new ArgumentException("The provided RSA parameters do not define an exponent."); + uint exponent = 0; for (int i = 0; i < Math.Min(sizeof(uint), parameters.Exponent.Length); i++) exponent |= (uint) (parameters.Exponent[i] << (8 * i)); diff --git a/src/AsmResolver.PE/DotNet/StrongName/StrongNamePublicKey.cs b/src/AsmResolver.PE/DotNet/StrongName/StrongNamePublicKey.cs index 4068ff691..ae410cbb8 100644 --- a/src/AsmResolver.PE/DotNet/StrongName/StrongNamePublicKey.cs +++ b/src/AsmResolver.PE/DotNet/StrongName/StrongNamePublicKey.cs @@ -76,6 +76,11 @@ public StrongNamePublicKey(byte[] modulus, uint publicExponent) /// The RSA parameters to import. public StrongNamePublicKey(in RSAParameters parameters) { + if (parameters.Modulus is null) + throw new ArgumentException("RSA parameters does not define a modulus."); + if (parameters.Exponent is null) + throw new ArgumentException("RSA parameters does not define an exponent."); + Modulus = CopyReversed(parameters.Modulus); uint exponent = 0; for (int i = 0; i < Math.Min(sizeof(uint), parameters.Exponent.Length); i++) diff --git a/src/AsmResolver.PE/Imports/DefaultSymbolResolver.cs b/src/AsmResolver.PE/Imports/DefaultSymbolResolver.cs index 158b05a05..325ba9e19 100644 --- a/src/AsmResolver.PE/Imports/DefaultSymbolResolver.cs +++ b/src/AsmResolver.PE/Imports/DefaultSymbolResolver.cs @@ -676,7 +676,7 @@ private DefaultSymbolResolver() if (!_staticMappings.TryGetValue(moduleName, out var staticMapping)) return null; - return staticMapping.TryGetValue(symbol.Ordinal, out string exportName) + return staticMapping.TryGetValue(symbol.Ordinal, out string? exportName) ? new ExportedSymbol(SegmentReference.Null, exportName) : null; } diff --git a/src/AsmResolver.PE/Relocations/BaseRelocation.cs b/src/AsmResolver.PE/Relocations/BaseRelocation.cs index 8e71b3231..d9a5f5713 100644 --- a/src/AsmResolver.PE/Relocations/BaseRelocation.cs +++ b/src/AsmResolver.PE/Relocations/BaseRelocation.cs @@ -56,7 +56,7 @@ public bool Equals(BaseRelocation other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is BaseRelocation other && Equals(other); } diff --git a/src/AsmResolver.PE/Win32Resources/Builder/ResourceTableBuffer.cs b/src/AsmResolver.PE/Win32Resources/Builder/ResourceTableBuffer.cs index 0da671e29..cd8485fdd 100644 --- a/src/AsmResolver.PE/Win32Resources/Builder/ResourceTableBuffer.cs +++ b/src/AsmResolver.PE/Win32Resources/Builder/ResourceTableBuffer.cs @@ -8,8 +8,9 @@ namespace AsmResolver.PE.Win32Resources.Builder /// /// The type of entries to store in the table. public abstract class ResourceTableBuffer : SegmentBase + where TEntry : notnull { - private readonly IDictionary _entryOffsets = new Dictionary(); + private readonly Dictionary _entryOffsets = new(); private readonly ISegment _parentBuffer; private uint _length; From 39aacb00012a7cf0fb88193e6ff2d55dcf1559a2 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 11 Mar 2022 00:39:42 +0100 Subject: [PATCH 043/184] Fix NRT in AsmResolver.DotNet. --- src/AsmResolver.DotNet/AssemblyDefinition.cs | 11 +++++--- .../DotNetDirectoryBuffer.MemberTree.cs | 4 +-- .../StringsStreamBlobSuffixComparer.cs | 9 ++++++- .../Tables/SortedMetadataTableBuffer.cs | 1 + .../Metadata/Tables/TablesStreamBuffer.cs | 2 ++ .../Builder/TokenMapping.cs | 4 +-- .../Code/Cil/CilMethodBody.cs | 2 +- .../Code/Cil/DynamicCilOperandResolver.cs | 26 +++++++++++++------ .../DotNetCoreAssemblyResolver.cs | 6 ++++- src/AsmResolver.DotNet/DynamicMethodHelper.cs | 6 ++--- src/AsmResolver.DotNet/ReferenceImporter.cs | 8 +++--- .../ReflectionAssemblyDescriptor.cs | 3 ++- src/AsmResolver.DotNet/SafeExtensions.cs | 4 +-- .../SerializedAssemblyDefinition.cs | 2 +- .../Signatures/BoxedArgument.cs | 2 +- .../Signatures/CustomAttributeArgument.cs | 4 +-- .../Signatures/Types/ArrayDimension.cs | 14 +++++----- .../Signatures/Types/TypeSignature.cs | 9 ++++--- 18 files changed, 73 insertions(+), 44 deletions(-) diff --git a/src/AsmResolver.DotNet/AssemblyDefinition.cs b/src/AsmResolver.DotNet/AssemblyDefinition.cs index e99d44242..220980bcb 100644 --- a/src/AsmResolver.DotNet/AssemblyDefinition.cs +++ b/src/AsmResolver.DotNet/AssemblyDefinition.cs @@ -280,11 +280,14 @@ public void Write(string filePath, IPEImageBuilder imageBuilder, IPEFileBuilder if (directory is null || !Directory.Exists(directory)) throw new DirectoryNotFoundException(); - foreach (var module in Modules) + for (int i = 0; i < Modules.Count; i++) { - string modulePath = module == ManifestModule - ? filePath - : Path.Combine(directory, module.Name); + var module = Modules[i]; + string modulePath; + if (module == ManifestModule) + modulePath = filePath; + else + modulePath = Path.Combine(directory, module.Name ?? $"module{i}.bin"); module.Write(modulePath, imageBuilder, fileBuilder); } diff --git a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.MemberTree.cs b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.MemberTree.cs index ff010957a..8e2e2ee27 100644 --- a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.MemberTree.cs +++ b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.MemberTree.cs @@ -302,7 +302,7 @@ public void FinalizeTypes() for (uint rid = 1; rid <= typeDefTable.Count; rid++) { var typeToken = new MetadataToken(TableIndex.TypeDef, rid); - var type = _tokenMapping.GetTypeByToken(typeToken); + var type = _tokenMapping.GetTypeByToken(typeToken)!; // Update extends, field list and method list columns. ref var typeRow = ref typeDefTable.GetRowRef(rid); @@ -409,7 +409,7 @@ private void FinalizeMethods(ref bool paramPtrRequired) for (uint rid = 1; rid <= definitionTable.Count; rid++) { var newToken = new MetadataToken(TableIndex.Method, rid); - var method = _tokenMapping.GetMethodByToken(newToken); + var method = _tokenMapping.GetMethodByToken(newToken)!; // Serialize method body and update column. ref var row = ref definitionTable.GetRowRef(rid); diff --git a/src/AsmResolver.DotNet/Builder/Metadata/Strings/StringsStreamBlobSuffixComparer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Strings/StringsStreamBlobSuffixComparer.cs index 47da9a1a0..3faccd70c 100644 --- a/src/AsmResolver.DotNet/Builder/Metadata/Strings/StringsStreamBlobSuffixComparer.cs +++ b/src/AsmResolver.DotNet/Builder/Metadata/Strings/StringsStreamBlobSuffixComparer.cs @@ -29,8 +29,15 @@ public int Compare(KeyValuePair x, KeyValuePair - public int Compare(byte[] x, byte[] y) + public int Compare(byte[]? x, byte[]? y) { + if (ReferenceEquals(x, y)) + return 0; + if (x is null) + return -1; + if (y is null) + return 1; + for (int i = x.Length - 1, j = y.Length - 1; i >= 0 && j >= 0; i--, j--) { int charComparison = x[i].CompareTo(y[j]); diff --git a/src/AsmResolver.DotNet/Builder/Metadata/Tables/SortedMetadataTableBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Tables/SortedMetadataTableBuffer.cs index d8d9b9aa4..7f33b1694 100644 --- a/src/AsmResolver.DotNet/Builder/Metadata/Tables/SortedMetadataTableBuffer.cs +++ b/src/AsmResolver.DotNet/Builder/Metadata/Tables/SortedMetadataTableBuffer.cs @@ -12,6 +12,7 @@ namespace AsmResolver.DotNet.Builder.Metadata.Tables /// The type of members that are assigned new metadata rows. /// The type of rows to store. public class SortedMetadataTableBuffer : ISortedMetadataTableBuffer + where TKey : notnull where TRow : struct, IMetadataRow { private readonly List<(TKey Key, TRow Row)> _entries = new(); diff --git a/src/AsmResolver.DotNet/Builder/Metadata/Tables/TablesStreamBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Tables/TablesStreamBuffer.cs index 47fab20d9..49651b052 100644 --- a/src/AsmResolver.DotNet/Builder/Metadata/Tables/TablesStreamBuffer.cs +++ b/src/AsmResolver.DotNet/Builder/Metadata/Tables/TablesStreamBuffer.cs @@ -183,6 +183,7 @@ private IMetadataTableBuffer Distinct(TableIndex table) } private ISortedMetadataTableBuffer Sorted(TableIndex table, int primaryColumn) + where TKey : notnull where TRow : struct, IMetadataRow { return new SortedMetadataTableBuffer( @@ -191,6 +192,7 @@ private IMetadataTableBuffer Distinct(TableIndex table) } private ISortedMetadataTableBuffer Sorted(TableIndex table, int primaryColumn, int secondaryColumn) + where TKey : notnull where TRow : struct, IMetadataRow { return new SortedMetadataTableBuffer( diff --git a/src/AsmResolver.DotNet/Builder/TokenMapping.cs b/src/AsmResolver.DotNet/Builder/TokenMapping.cs index 9ae079ba4..678dc1753 100644 --- a/src/AsmResolver.DotNet/Builder/TokenMapping.cs +++ b/src/AsmResolver.DotNet/Builder/TokenMapping.cs @@ -102,13 +102,13 @@ public void Register(IMetadataMember member, MetadataToken newToken) /// /// The new token. /// The type, or null if no type is assigned to the provided token. - public TypeDefinition GetTypeByToken(MetadataToken newToken) => _typeDefTokens.GetKey(newToken); + public TypeDefinition? GetTypeByToken(MetadataToken newToken) => _typeDefTokens.GetKey(newToken); /// /// Gets the method assigned to the provided metadata token. /// /// The new token. /// The type, or null if no method is assigned to the provided token. - public MethodDefinition GetMethodByToken(MetadataToken newToken) => _methodTokens.GetKey(newToken); + public MethodDefinition? GetMethodByToken(MetadataToken newToken) => _methodTokens.GetKey(newToken); } } diff --git a/src/AsmResolver.DotNet/Code/Cil/CilMethodBody.cs b/src/AsmResolver.DotNet/Code/Cil/CilMethodBody.cs index de4b9718a..633440c24 100644 --- a/src/AsmResolver.DotNet/Code/Cil/CilMethodBody.cs +++ b/src/AsmResolver.DotNet/Code/Cil/CilMethodBody.cs @@ -147,7 +147,7 @@ public static CilMethodBody FromDynamicMethod(MethodDefinition method, object dy //Get Runtime Fields byte[] code = FieldReader.ReadField(dynamicMethodObj, "m_code")!; object scope = FieldReader.ReadField(dynamicMethodObj, "m_scope")!; - var tokenList = FieldReader.ReadField>(scope, "m_tokens")!; + 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")!; diff --git a/src/AsmResolver.DotNet/Code/Cil/DynamicCilOperandResolver.cs b/src/AsmResolver.DotNet/Code/Cil/DynamicCilOperandResolver.cs index 203e7f554..d3244f8e2 100644 --- a/src/AsmResolver.DotNet/Code/Cil/DynamicCilOperandResolver.cs +++ b/src/AsmResolver.DotNet/Code/Cil/DynamicCilOperandResolver.cs @@ -16,11 +16,11 @@ namespace AsmResolver.DotNet.Code.Cil public class DynamicCilOperandResolver : PhysicalCilOperandResolver { private readonly ModuleReaderContext _readerContext; - private readonly IList _tokens; + private readonly IList _tokens; private readonly ReferenceImporter _importer; /// - public DynamicCilOperandResolver(SerializedModuleDefinition contextModule, CilMethodBody methodBody, IList tokens) + public DynamicCilOperandResolver(SerializedModuleDefinition contextModule, CilMethodBody methodBody, IList tokens) : base(contextModule, methodBody) { _tokens = tokens ?? throw new ArgumentNullException(nameof(tokens)); @@ -34,13 +34,13 @@ public DynamicCilOperandResolver(SerializedModuleDefinition contextModule, CilMe switch (token.Table) { case TableIndex.TypeDef: - var 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: - var field = _tokens[(int) token.Rid]; + object? field = _tokens[(int) token.Rid]; if (field is null) return null; @@ -61,10 +61,18 @@ public DynamicCilOperandResolver(SerializedModuleDefinition contextModule, CilMe case TableIndex.Method: case TableIndex.MemberRef: - var obj = _tokens[(int) token.Rid]; + object? obj = _tokens[(int) token.Rid]; + + if (obj is null) + return null; if (obj is RuntimeMethodHandle methodHandle) - return _importer.ImportMethod(MethodBase.GetMethodFromHandle(methodHandle)); + { + var method = MethodBase.GetMethodFromHandle(methodHandle); + return method is not null + ? _importer.ImportMethod(method) + : null; + } if (obj.GetType().FullName == "System.Reflection.Emit.GenericMethodInfo") { @@ -75,7 +83,9 @@ public DynamicCilOperandResolver(SerializedModuleDefinition contextModule, CilMe hasHandle ? mMethod : mHandle, context); - return _importer.ImportMethod(method); + return method is not null + ? _importer.ImportMethod(method) + : null; } if (obj.GetType().FullName == "System.Reflection.Emit.VarArgMethod") @@ -84,7 +94,7 @@ public DynamicCilOperandResolver(SerializedModuleDefinition contextModule, CilMe break; case TableIndex.StandAloneSig: - var reader = ByteArrayDataSource.CreateReader((byte[]) _tokens[(int) token.Rid]); + var reader = ByteArrayDataSource.CreateReader((byte[]) _tokens[(int) token.Rid]!); return CallingConventionSignature.FromReader(new BlobReadContext(_readerContext), ref reader); } diff --git a/src/AsmResolver.DotNet/DotNetCoreAssemblyResolver.cs b/src/AsmResolver.DotNet/DotNetCoreAssemblyResolver.cs index 87aef8062..2bf0ed76d 100644 --- a/src/AsmResolver.DotNet/DotNetCoreAssemblyResolver.cs +++ b/src/AsmResolver.DotNet/DotNetCoreAssemblyResolver.cs @@ -172,10 +172,14 @@ private sealed class RuntimeNameComparer : IComparer public static readonly RuntimeNameComparer Instance = new(); /// - public int Compare(string x, string y) + public int Compare(string? x, string? y) { if (x == y) return 0; + if (x is null) + return -1; + if (y is null) + return 1; if (x == KnownRuntimeNames.NetCoreApp) return 1; if (y == KnownRuntimeNames.NetCoreApp) diff --git a/src/AsmResolver.DotNet/DynamicMethodHelper.cs b/src/AsmResolver.DotNet/DynamicMethodHelper.cs index e6ad33f2b..7e856d135 100644 --- a/src/AsmResolver.DotNet/DynamicMethodHelper.cs +++ b/src/AsmResolver.DotNet/DynamicMethodHelper.cs @@ -60,7 +60,7 @@ private static void InterpretEHInfo(CilMethodBody methodBody, ReferenceImporter int tryEnd = FieldReader.ReadField(ehInfo, "m_endAddr"); 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 exceptionType = FieldReader.ReadField(ehInfo, "m_catchClass")![i]; var handlerType = (CilExceptionHandlerType) FieldReader.ReadField(ehInfo, "m_type")![i]; var endTryLabel = instructions.GetByOffset(tryEnd)?.CreateLabel() ?? new CilOffsetLabel(tryEnd); @@ -74,7 +74,7 @@ private static void InterpretEHInfo(CilMethodBody methodBody, ReferenceImporter FilterStart = null, HandlerStart = instructions.GetLabel(handlerStart), HandlerEnd = instructions.GetLabel(handlerEnd), - ExceptionType = exceptionType != null ? importer.ImportType(exceptionType) : null + ExceptionType = exceptionType is not null ? importer.ImportType(exceptionType) : null }; methodBody.ExceptionHandlers.Add(handler); @@ -113,7 +113,7 @@ public static object ResolveDynamicResolver(object dynamicMethodObj) dynamicMethodObj = Activator.CreateInstance(dynamicResolver, (BindingFlags) (-1), null, new[] { ilGenerator - }, null); + }, null)!; } return dynamicMethodObj; } diff --git a/src/AsmResolver.DotNet/ReferenceImporter.cs b/src/AsmResolver.DotNet/ReferenceImporter.cs index 5f2429407..b1c858e76 100644 --- a/src/AsmResolver.DotNet/ReferenceImporter.cs +++ b/src/AsmResolver.DotNet/ReferenceImporter.cs @@ -435,8 +435,8 @@ public virtual IMethodDescriptor ImportMethod(MethodBase method) ? ImportTypeSignature(info.ReturnType) : TargetModule.CorLibTypeFactory.Void; - var parameters = (method.DeclaringType != null && method.DeclaringType.IsConstructedGenericType) - ? method.Module.ResolveMethod(method.MetadataToken).GetParameters() + var parameters = method.DeclaringType is { IsConstructedGenericType: true } + ? method.Module.ResolveMethod(method.MetadataToken)!.GetParameters() : method.GetParameters(); var parameterTypes = new TypeSignature[parameters.Length]; @@ -512,8 +512,8 @@ public MemberReference ImportField(FieldInfo field) if (field is null) throw new ArgumentNullException(nameof(field)); - if (field.DeclaringType != null && field.DeclaringType.IsConstructedGenericType) - field = field.Module.ResolveField(field.MetadataToken); + if (field.DeclaringType is { IsConstructedGenericType: true }) + field = field.Module.ResolveField(field.MetadataToken)!; var scope = field.DeclaringType != null ? ImportType(field.DeclaringType) diff --git a/src/AsmResolver.DotNet/ReflectionAssemblyDescriptor.cs b/src/AsmResolver.DotNet/ReflectionAssemblyDescriptor.cs index 66cb50656..adf51b04e 100644 --- a/src/AsmResolver.DotNet/ReflectionAssemblyDescriptor.cs +++ b/src/AsmResolver.DotNet/ReflectionAssemblyDescriptor.cs @@ -1,3 +1,4 @@ +using System; using System.Reflection; using AsmResolver.PE.DotNet.Metadata.Tables; @@ -22,7 +23,7 @@ public ReflectionAssemblyDescriptor(ModuleDefinition parentModule, AssemblyName { _parentModule = parentModule; _assemblyName = assemblyName; - Version = assemblyName.Version; + Version = assemblyName.Version ?? new Version(); } /// diff --git a/src/AsmResolver.DotNet/SafeExtensions.cs b/src/AsmResolver.DotNet/SafeExtensions.cs index 283d61555..47de4b13b 100644 --- a/src/AsmResolver.DotNet/SafeExtensions.cs +++ b/src/AsmResolver.DotNet/SafeExtensions.cs @@ -11,7 +11,7 @@ public static string SafeToString(this IMetadataMember? self) try { - string value = self.ToString(); + string value = self.ToString()!; if (value.Length > 200) value = $"{value.Remove(197)}... (truncated)"; if (self.MetadataToken.Rid != 0) @@ -31,7 +31,7 @@ public static string SafeToString(this object? self) try { - return self.ToString(); + return self.ToString()!; } catch { diff --git a/src/AsmResolver.DotNet/Serialized/SerializedAssemblyDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedAssemblyDefinition.cs index b4310c159..c5b4e557f 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedAssemblyDefinition.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedAssemblyDefinition.cs @@ -166,7 +166,7 @@ public override bool TryGetTargetFramework(out DotNetRuntimeInfo info) continue; object? element = attribute.Signature.FixedArguments[0].Element; - if (element is string or Utf8String && DotNetRuntimeInfo.TryParse(element.ToString(), out info)) + if (element is string or Utf8String && DotNetRuntimeInfo.TryParse(element.ToString()!, out info)) return true; } diff --git a/src/AsmResolver.DotNet/Signatures/BoxedArgument.cs b/src/AsmResolver.DotNet/Signatures/BoxedArgument.cs index 3f8d05d5a..26a5dcf14 100644 --- a/src/AsmResolver.DotNet/Signatures/BoxedArgument.cs +++ b/src/AsmResolver.DotNet/Signatures/BoxedArgument.cs @@ -41,7 +41,7 @@ private bool Equals(BoxedArgument other) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return ReferenceEquals(this, obj) || obj is BoxedArgument other && Equals(other); } diff --git a/src/AsmResolver.DotNet/Signatures/CustomAttributeArgument.cs b/src/AsmResolver.DotNet/Signatures/CustomAttributeArgument.cs index 96c1fbb89..58823dd1b 100644 --- a/src/AsmResolver.DotNet/Signatures/CustomAttributeArgument.cs +++ b/src/AsmResolver.DotNet/Signatures/CustomAttributeArgument.cs @@ -118,12 +118,12 @@ public override string ToString() return ElementToString(obj); } - private string ElementToString(object? element) => element switch + private static string ElementToString(object? element) => element switch { null => "null", IList list => $"{{{string.Join(", ", list.Select(ElementToString))}}}", string x => x.CreateEscapedString(), - _ => element.ToString() + _ => element.ToString() ?? string.Empty }; /// diff --git a/src/AsmResolver.DotNet/Signatures/Types/ArrayDimension.cs b/src/AsmResolver.DotNet/Signatures/Types/ArrayDimension.cs index e06fbf743..8e13ee6a4 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/ArrayDimension.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/ArrayDimension.cs @@ -3,7 +3,7 @@ namespace AsmResolver.DotNet.Signatures.Types { /// - /// Represents a single dimension in an array specification. + /// Represents a single dimension in an array specification. /// public readonly struct ArrayDimension : IEquatable { @@ -39,7 +39,7 @@ public ArrayDimension(int? size, int? lowerBound) } /// - /// Gets or sets the lower bound for each index in the dimension (if specified). + /// Gets or sets the lower bound for each index in the dimension (if specified). /// /// /// When this value is not specified (null), a lower bound of 0 is assumed by the CLR. @@ -52,12 +52,10 @@ public ArrayDimension(int? size, int? lowerBound) /// /// Determines whether two dimensions are considered equal. /// - public bool Equals(ArrayDimension other) => - Size == other.Size && LowerBound == other.LowerBound; + public bool Equals(ArrayDimension other) => Size == other.Size && LowerBound == other.LowerBound; /// - public override bool Equals(object obj) => - obj is ArrayDimension other && Equals(other); + public override bool Equals(object? obj) => obj is ArrayDimension other && Equals(other); /// public override int GetHashCode() @@ -67,6 +65,6 @@ public override int GetHashCode() return (Size.GetHashCode() * 397) ^ LowerBound.GetHashCode(); } } - + } -} \ No newline at end of file +} diff --git a/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs index 50b806c51..2373fc3bf 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs @@ -23,7 +23,7 @@ static TypeSignature() (BindingFlags) (-1), null, new[] {typeof(IntPtr)}, - null); + null)!; } /// @@ -163,8 +163,11 @@ public static TypeSignature FromReader(in BlobReadContext context, ref BinaryStr }; // Let the runtime translate the address to a type and import it. - var clrType = (Type) GetTypeFromHandleUnsafeMethod.Invoke(null, new object[] {address}); - var asmResType = new ReferenceImporter(context.ReaderContext.ParentModule).ImportType(clrType); + 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); default: From 4364eb9f30da77c0e912f5eab9f5a598ddf409a0 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 11 Mar 2022 00:46:30 +0100 Subject: [PATCH 044/184] Bump version in Directory.Build.props --- Directory.Build.props | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index bc687b4c7..09d02e0ef 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,13 +1,13 @@ - Copyright © Washi 2016-2021 + Copyright © Washi 2016-2022 https://github.com/Washi1337/AsmResolver https://github.com/Washi1337/AsmResolver/LICENSE.md https://github.com/Washi1337/AsmResolver git - 9 - 4.8.0 + 10 + 4.9.0 From c5a61f637fea2b6a7e7cb2d50cddb243be3f64a5 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 11 Mar 2022 13:05:12 +0100 Subject: [PATCH 045/184] Move langversion 10 into Directory.Build.props --- .../AsmResolver.DotNet.csproj | 1 - .../AsmResolver.PE.File.csproj | 1 - .../AsmResolver.PE.Win32Resources.csproj | 1 - src/AsmResolver.PE/AsmResolver.PE.csproj | 5 ++-- src/AsmResolver/AsmResolver.csproj | 1 - .../AsmResolver.Benchmarks.csproj | 1 - .../AsmResolver.DotNet.Tests.csproj | 1 - .../AsmResolver.PE.File.Tests.csproj | 1 - .../AsmResolver.PE.Tests.csproj | 5 ---- ...AsmResolver.PE.Win32Resources.Tests.csproj | 5 ---- .../AsmResolver.Tests.csproj | 5 ---- ...r.DotNet.TestCases.CustomAttributes.csproj | 15 ++++++------ ...AsmResolver.DotNet.TestCases.Events.csproj | 15 ++++++------ ...AsmResolver.DotNet.TestCases.Fields.csproj | 1 - ...mResolver.DotNet.TestCases.Generics.csproj | 1 - ...smResolver.DotNet.TestCases.Methods.csproj | 23 +++++++++---------- ...olver.DotNet.TestCases.MultiModules.csproj | 1 - ...lver.DotNet.TestCases.NestedClasses.csproj | 1 - ...esolver.DotNet.TestCases.Properties.csproj | 15 ++++++------ ...Resolver.DotNet.TestCases.Resources.csproj | 1 - .../AsmResolver.DotNet.TestCases.Types.csproj | 15 ++++++------ .../DotNet/HelloWorld/HelloWorld.csproj | 1 - .../DotNet/TheAnswer/TheAnswer.csproj | 1 - ...smResolver.PE.Exports.OrdinalMapper.csproj | 1 - 24 files changed, 41 insertions(+), 77 deletions(-) diff --git a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj index 30ec35a73..522cacb7b 100644 --- a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj +++ b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj @@ -6,7 +6,6 @@ exe pe directories imports exports resources dotnet cil inspection manipulation assembly disassembly true 1701;1702;NU5105 - 10 enable net6.0;netcoreapp3.1;netstandard2.0 diff --git a/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj b/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj index f18c18503..6341887ff 100644 --- a/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj +++ b/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj @@ -8,7 +8,6 @@ 1701;1702;NU5105 true enable - 10 net6.0;netcoreapp3.1;netstandard2.0 diff --git a/src/AsmResolver.PE.Win32Resources/AsmResolver.PE.Win32Resources.csproj b/src/AsmResolver.PE.Win32Resources/AsmResolver.PE.Win32Resources.csproj index f6746bce3..0baf5f6c0 100644 --- a/src/AsmResolver.PE.Win32Resources/AsmResolver.PE.Win32Resources.csproj +++ b/src/AsmResolver.PE.Win32Resources/AsmResolver.PE.Win32Resources.csproj @@ -6,7 +6,6 @@ true 1701;1702;NU5105 enable - 10 net6.0;netcoreapp3.1;netstandard2.0 diff --git a/src/AsmResolver.PE/AsmResolver.PE.csproj b/src/AsmResolver.PE/AsmResolver.PE.csproj index 4949ab984..43c3a61d5 100644 --- a/src/AsmResolver.PE/AsmResolver.PE.csproj +++ b/src/AsmResolver.PE/AsmResolver.PE.csproj @@ -6,7 +6,6 @@ exe pe directories imports exports resources dotnet cil inspection manipulation assembly disassembly true 1701;1702;NU5105 - 10 enable net6.0;netcoreapp3.1;netstandard2.0 @@ -18,11 +17,11 @@ bin\Release\AsmResolver.PE.xml - + - + all diff --git a/src/AsmResolver/AsmResolver.csproj b/src/AsmResolver/AsmResolver.csproj index 9927b70ff..da0e0e6cb 100644 --- a/src/AsmResolver/AsmResolver.csproj +++ b/src/AsmResolver/AsmResolver.csproj @@ -7,7 +7,6 @@ true 1701;1702;NU5105 enable - 10 net6.0;netcoreapp3.1;netstandard2.0 diff --git a/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj b/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj index 56f183cc8..5931a82d2 100644 --- a/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj +++ b/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj @@ -2,7 +2,6 @@ Exe - 10 net6.0;netcoreapp3.1 diff --git a/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj b/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj index aa4054fa5..30de93753 100644 --- a/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj +++ b/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj @@ -2,7 +2,6 @@ net5.0 - 10 false 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 1c8b905d7..97ca135a4 100644 --- a/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj +++ b/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj @@ -2,7 +2,6 @@ netcoreapp3.1 - 10 false diff --git a/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj b/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj index bdd463044..a34649959 100644 --- a/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj +++ b/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj @@ -2,13 +2,8 @@ netcoreapp3.1 - false - true - - 10 - enable 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 3e2ef16ff..f827592b7 100644 --- a/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj +++ b/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj @@ -2,13 +2,8 @@ netcoreapp3.1 - false - AsmResolver.PE.Win32Resources.Tests - - 10 - warnings diff --git a/test/AsmResolver.Tests/AsmResolver.Tests.csproj b/test/AsmResolver.Tests/AsmResolver.Tests.csproj index 30e201029..a7428e961 100644 --- a/test/AsmResolver.Tests/AsmResolver.Tests.csproj +++ b/test/AsmResolver.Tests/AsmResolver.Tests.csproj @@ -2,13 +2,8 @@ netcoreapp3.1 - false - true - - 10 - enable diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.CustomAttributes/AsmResolver.DotNet.TestCases.CustomAttributes.csproj b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.CustomAttributes/AsmResolver.DotNet.TestCases.CustomAttributes.csproj index 5fa483f12..27560206d 100644 --- a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.CustomAttributes/AsmResolver.DotNet.TestCases.CustomAttributes.csproj +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.CustomAttributes/AsmResolver.DotNet.TestCases.CustomAttributes.csproj @@ -1,8 +1,7 @@ - - - - netstandard2.0 - 10 - - - + + + + netstandard2.0 + + + diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Events/AsmResolver.DotNet.TestCases.Events.csproj b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Events/AsmResolver.DotNet.TestCases.Events.csproj index 5fa483f12..27560206d 100644 --- a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Events/AsmResolver.DotNet.TestCases.Events.csproj +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Events/AsmResolver.DotNet.TestCases.Events.csproj @@ -1,8 +1,7 @@ - - - - netstandard2.0 - 10 - - - + + + + netstandard2.0 + + + diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Fields/AsmResolver.DotNet.TestCases.Fields.csproj b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Fields/AsmResolver.DotNet.TestCases.Fields.csproj index 5840e70fd..27560206d 100644 --- a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Fields/AsmResolver.DotNet.TestCases.Fields.csproj +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Fields/AsmResolver.DotNet.TestCases.Fields.csproj @@ -2,7 +2,6 @@ netstandard2.0 - 10 diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/AsmResolver.DotNet.TestCases.Generics.csproj b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/AsmResolver.DotNet.TestCases.Generics.csproj index 5840e70fd..27560206d 100644 --- a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/AsmResolver.DotNet.TestCases.Generics.csproj +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Generics/AsmResolver.DotNet.TestCases.Generics.csproj @@ -2,7 +2,6 @@ netstandard2.0 - 10 diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/AsmResolver.DotNet.TestCases.Methods.csproj b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/AsmResolver.DotNet.TestCases.Methods.csproj index 320097974..a17fd4c4a 100644 --- a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/AsmResolver.DotNet.TestCases.Methods.csproj +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Methods/AsmResolver.DotNet.TestCases.Methods.csproj @@ -1,12 +1,11 @@ - - - - netstandard2.0 - 10 - - - - - - - + + + + netstandard2.0 + + + + + + + diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.MultiModules/AsmResolver.DotNet.TestCases.MultiModules.csproj b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.MultiModules/AsmResolver.DotNet.TestCases.MultiModules.csproj index d387b2437..94607d638 100644 --- a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.MultiModules/AsmResolver.DotNet.TestCases.MultiModules.csproj +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.MultiModules/AsmResolver.DotNet.TestCases.MultiModules.csproj @@ -2,7 +2,6 @@ netstandard2.0 - 10 AsmResolver.DotNet.TestCases.MultiModules diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.NestedClasses/AsmResolver.DotNet.TestCases.NestedClasses.csproj b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.NestedClasses/AsmResolver.DotNet.TestCases.NestedClasses.csproj index 5840e70fd..27560206d 100644 --- a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.NestedClasses/AsmResolver.DotNet.TestCases.NestedClasses.csproj +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.NestedClasses/AsmResolver.DotNet.TestCases.NestedClasses.csproj @@ -2,7 +2,6 @@ netstandard2.0 - 10 diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Properties/AsmResolver.DotNet.TestCases.Properties.csproj b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Properties/AsmResolver.DotNet.TestCases.Properties.csproj index 5fa483f12..27560206d 100644 --- a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Properties/AsmResolver.DotNet.TestCases.Properties.csproj +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Properties/AsmResolver.DotNet.TestCases.Properties.csproj @@ -1,8 +1,7 @@ - - - - netstandard2.0 - 10 - - - + + + + netstandard2.0 + + + diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Resources/AsmResolver.DotNet.TestCases.Resources.csproj b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Resources/AsmResolver.DotNet.TestCases.Resources.csproj index 37d68115c..da037df75 100644 --- a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Resources/AsmResolver.DotNet.TestCases.Resources.csproj +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Resources/AsmResolver.DotNet.TestCases.Resources.csproj @@ -2,7 +2,6 @@ netstandard2.0 - 10 true diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Types/AsmResolver.DotNet.TestCases.Types.csproj b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Types/AsmResolver.DotNet.TestCases.Types.csproj index 5fa483f12..27560206d 100644 --- a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Types/AsmResolver.DotNet.TestCases.Types.csproj +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Types/AsmResolver.DotNet.TestCases.Types.csproj @@ -1,8 +1,7 @@ - - - - netstandard2.0 - 10 - - - + + + + netstandard2.0 + + + diff --git a/test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj b/test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj index e12456cfc..21604ba0d 100644 --- a/test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj +++ b/test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj @@ -3,7 +3,6 @@ Exe net47;netcoreapp3.1 - 10 Resources\Icon.ico diff --git a/test/TestBinaries/DotNet/TheAnswer/TheAnswer.csproj b/test/TestBinaries/DotNet/TheAnswer/TheAnswer.csproj index cdfdf3186..264964033 100644 --- a/test/TestBinaries/DotNet/TheAnswer/TheAnswer.csproj +++ b/test/TestBinaries/DotNet/TheAnswer/TheAnswer.csproj @@ -3,7 +3,6 @@ Exe net47;netcoreapp3.1 - 10 diff --git a/tools/AsmResolver.PE.Exports.OrdinalMapper/AsmResolver.PE.Exports.OrdinalMapper.csproj b/tools/AsmResolver.PE.Exports.OrdinalMapper/AsmResolver.PE.Exports.OrdinalMapper.csproj index fc63a13a3..399605bab 100644 --- a/tools/AsmResolver.PE.Exports.OrdinalMapper/AsmResolver.PE.Exports.OrdinalMapper.csproj +++ b/tools/AsmResolver.PE.Exports.OrdinalMapper/AsmResolver.PE.Exports.OrdinalMapper.csproj @@ -3,7 +3,6 @@ Exe net472 - 10 enable From d0d2f5cf3c562e659ce5969d7a6baf7a0444726b Mon Sep 17 00:00:00 2001 From: Sam Byass Date: Wed, 16 Mar 2022 19:09:50 +0000 Subject: [PATCH 046/184] Use ConcurrentDictionary as backing cache for DefaultMetadataResolver --- src/AsmResolver.DotNet/DefaultMetadataResolver.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/AsmResolver.DotNet/DefaultMetadataResolver.cs b/src/AsmResolver.DotNet/DefaultMetadataResolver.cs index 839feaa96..2d0031695 100644 --- a/src/AsmResolver.DotNet/DefaultMetadataResolver.cs +++ b/src/AsmResolver.DotNet/DefaultMetadataResolver.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.Signatures.Types; @@ -24,7 +25,7 @@ public class DefaultMetadataResolver : IMetadataResolver public DefaultMetadataResolver(IAssemblyResolver assemblyResolver) { AssemblyResolver = assemblyResolver ?? throw new ArgumentNullException(nameof(assemblyResolver)); - _typeCache = new Dictionary(); + _typeCache = new ConcurrentDictionary(); } /// From 826e0552d36c4fa04f7f402f6611353ac323edfd Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 17 Mar 2022 18:46:22 +0100 Subject: [PATCH 047/184] Add nested typedef import test. --- .../ReferenceImporterTest.cs | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs index be9f40fc4..1563dd71c 100644 --- a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs +++ b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs @@ -11,9 +11,9 @@ namespace AsmResolver.DotNet.Tests { public class ReferenceImporterTest { - private static readonly SignatureComparer _comparer = new SignatureComparer(); + private static readonly SignatureComparer Comparer = new(); - private readonly AssemblyReference _dummyAssembly = new AssemblyReference("SomeAssembly", new Version(1, 2, 3, 4)); + private readonly AssemblyReference _dummyAssembly = new("SomeAssembly", new Version(1, 2, 3, 4)); private readonly ModuleDefinition _module; private readonly ReferenceImporter _importer; @@ -28,7 +28,7 @@ public void ImportNewAssemblyShouldAddToModule() { var result = _importer.ImportScope(_dummyAssembly); - Assert.Equal(_dummyAssembly, result, _comparer); + Assert.Equal(_dummyAssembly, result, Comparer); Assert.Contains(result, _module.AssemblyReferences); } @@ -52,7 +52,7 @@ public void ImportNewTypeShouldCreateNewReference() var type = new TypeReference(_dummyAssembly, "SomeNamespace", "SomeName"); var result = _importer.ImportType(type); - Assert.Equal(type, result, _comparer); + Assert.Equal(type, result, Comparer); Assert.Equal(_module, result.Module); } @@ -78,7 +78,7 @@ public void ImportTypeDefFromDifferentModuleShouldReturnTypeRef() var result = _importer.ImportType(definition); Assert.IsAssignableFrom(result); - Assert.Equal(definition, result, _comparer); + Assert.Equal(definition, result, Comparer); } [Fact] @@ -100,11 +100,32 @@ public void ImportNestedTypeShouldImportParentType() var result = _importer.ImportType(nested); - Assert.Equal(nested, result, _comparer); + Assert.Equal(nested, result, Comparer); Assert.Equal(_module, result.Module); Assert.Equal(_module, result.DeclaringType.Module); } + [Fact] + public void ImportNestedTypeDefinitionShouldImportParentType() + { + var otherAssembly = new AssemblyDefinition(_dummyAssembly.Name, _dummyAssembly.Version); + var otherModule = new ModuleDefinition("OtherModule"); + otherAssembly.Modules.Add(otherModule); + + var objectType = otherModule.CorLibTypeFactory.Object.ToTypeDefOrRef(); + var declaringType = new TypeDefinition("SomeNamespace", "SomeName", TypeAttributes.Class | TypeAttributes.Public, objectType); + var nestedType = new TypeDefinition(null, "NestedType", TypeAttributes.Class | TypeAttributes.NestedPublic, objectType); + declaringType.NestedTypes.Add(nestedType); + otherModule.TopLevelTypes.Add(declaringType); + + var reference = _importer.ImportType(nestedType); + + Assert.NotNull(reference.DeclaringType); + Assert.Equal(declaringType, reference.DeclaringType, Comparer); + Assert.Equal(_module, reference.Module); + Assert.Equal(_module, reference.DeclaringType.Module); + } + [Fact] public void ImportSimpleTypeFromReflectionShouldResultInTypeRef() { @@ -170,7 +191,7 @@ public void ImportMethodFromExternalModuleShouldResultInMemberRef() var result = _importer.ImportMethod(method); - Assert.Equal(method, result, _comparer); + Assert.Equal(method, result, Comparer); Assert.Same(_module, result.Module); } @@ -217,7 +238,7 @@ public void ImportGenericMethodFromReflectionShouldResultInMethodSpec() Assert.Equal(new TypeSignature[] { _module.CorLibTypeFactory.String - }, specification.Signature.TypeArguments, _comparer); + }, specification.Signature.TypeArguments, Comparer); } [Fact] @@ -231,7 +252,7 @@ public void ImportFieldFromExternalModuleShouldResultInMemberRef() var result = _importer.ImportField(field); - Assert.Equal(field, result, _comparer); + Assert.Equal(field, result, Comparer); Assert.Same(_module, result.Module); } From 5dc012371f3a53458426153c426f53db1b1a7989 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 17 Mar 2022 18:48:06 +0100 Subject: [PATCH 048/184] BUGFIX: import resolution scope instead of module for typedefs. --- src/AsmResolver.DotNet/ReferenceImporter.cs | 6 +++++- .../ReferenceImporterTest.cs | 14 ++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/AsmResolver.DotNet/ReferenceImporter.cs b/src/AsmResolver.DotNet/ReferenceImporter.cs index b1c858e76..e132b2b71 100644 --- a/src/AsmResolver.DotNet/ReferenceImporter.cs +++ b/src/AsmResolver.DotNet/ReferenceImporter.cs @@ -142,7 +142,11 @@ protected virtual ITypeDefOrRef ImportType(TypeDefinition type) if (type.Module == TargetModule) return type; - return new TypeReference(TargetModule, ImportScope(type.Module!), type.Namespace, type.Name); + return new TypeReference( + TargetModule, + ImportScope(((ITypeDescriptor) type).Scope!), + type.Namespace, + type.Name); } /// diff --git a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs index 1563dd71c..735ad198c 100644 --- a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs +++ b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs @@ -113,8 +113,18 @@ public void ImportNestedTypeDefinitionShouldImportParentType() otherAssembly.Modules.Add(otherModule); var objectType = otherModule.CorLibTypeFactory.Object.ToTypeDefOrRef(); - var declaringType = new TypeDefinition("SomeNamespace", "SomeName", TypeAttributes.Class | TypeAttributes.Public, objectType); - var nestedType = new TypeDefinition(null, "NestedType", TypeAttributes.Class | TypeAttributes.NestedPublic, objectType); + + var declaringType = new TypeDefinition( + "SomeNamespace", + "SomeName", + TypeAttributes.Class | TypeAttributes.Public, + objectType); + var nestedType = new TypeDefinition( + null, + "NestedType", + TypeAttributes.Class | TypeAttributes.NestedPublic, + objectType); + declaringType.NestedTypes.Add(nestedType); otherModule.TopLevelTypes.Add(declaringType); From f2d7bf6aef1c18bc9772934bdc3768d5e0e158d8 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 17 Mar 2022 19:52:05 +0100 Subject: [PATCH 049/184] Add typesig importer tests with non-imported embedded references. --- .../ReferenceImporterTest.cs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs index be9f40fc4..7ee5a63d2 100644 --- a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs +++ b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs @@ -264,5 +264,47 @@ public void ImportFieldFromReflectionShouldResultInMemberRef() Assert.Equal(field.DeclaringType.FullName, result.DeclaringType.FullName); Assert.Equal(field.FieldType.FullName, ((FieldSignature) result.Signature).FieldType.FullName); } + + [Fact] + public void ImportTypeSigWithNonImportedEmbeddedReferenceShouldResultInNewInstance() + { + // https://github.com/Washi1337/AsmResolver/issues/268 + + var genericType = new TypeDefinition("SomeNamespace", "SomeName", TypeAttributes.Class); + genericType.GenericParameters.Add(new GenericParameter("T")); + _module.TopLevelTypes.Add(genericType); + + var instance = genericType.MakeGenericInstanceType( + new TypeDefOrRefSignature( + new TypeReference(_module.CorLibTypeFactory.CorLibScope, "System.IO", "Stream"), false) + ); + + var imported = _importer.ImportTypeSignature(instance); + + var newInstance = Assert.IsAssignableFrom(imported); + Assert.NotSame(instance, newInstance); + Assert.Equal(_module, newInstance.Module); + Assert.Equal(_module, newInstance.TypeArguments[0].Module); + } + + [Fact] + public void ImportTypeSigWithImportedEmbeddedReferenceShouldResultInSameInstance() + { + // https://github.com/Washi1337/AsmResolver/issues/268 + + var genericType = new TypeDefinition("SomeNamespace", "SomeName", TypeAttributes.Class); + genericType.GenericParameters.Add(new GenericParameter("T")); + _module.TopLevelTypes.Add(genericType); + + var instance = genericType.MakeGenericInstanceType( + new TypeDefOrRefSignature( + new TypeReference(_module, _module.CorLibTypeFactory.CorLibScope, "System.IO", "Stream"), false) + ); + + var imported = _importer.ImportTypeSignature(instance); + + var newInstance = Assert.IsAssignableFrom(imported); + Assert.Same(instance, newInstance); + } } } From 94d5064a8df0947a451ecc8a4e8b02403ba89f8f Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 17 Mar 2022 19:56:59 +0100 Subject: [PATCH 050/184] Add IImportable, let reference importer do more thorough checks on whether a member is imported or not. --- src/AsmResolver.DotNet/AssemblyDefinition.cs | 3 +++ src/AsmResolver.DotNet/AssemblyDescriptor.cs | 7 ++++- src/AsmResolver.DotNet/AssemblyReference.cs | 3 +++ src/AsmResolver.DotNet/EventDefinition.cs | 7 +++++ src/AsmResolver.DotNet/ExportedType.cs | 7 +++-- src/AsmResolver.DotNet/FieldDefinition.cs | 7 +++++ src/AsmResolver.DotNet/IImportable.cs | 18 +++++++++++++ src/AsmResolver.DotNet/IMemberDescriptor.cs | 2 +- src/AsmResolver.DotNet/IResolutionScope.cs | 2 +- src/AsmResolver.DotNet/InvalidTypeDefOrRef.cs | 3 +++ src/AsmResolver.DotNet/MemberReference.cs | 7 +++++ src/AsmResolver.DotNet/MethodDefinition.cs | 7 +++++ src/AsmResolver.DotNet/MethodSpecification.cs | 7 +++++ src/AsmResolver.DotNet/ModuleDefinition.cs | 3 +++ src/AsmResolver.DotNet/ModuleReference.cs | 3 +++ src/AsmResolver.DotNet/PropertyDefinition.cs | 7 +++++ src/AsmResolver.DotNet/ReferenceImporter.cs | 26 +++++++++---------- .../ReflectionAssemblyDescriptor.cs | 3 +++ .../Signatures/CallingConventionSignature.cs | 5 +++- .../GenericInstanceMethodSignature.cs | 12 +++++++++ .../Signatures/LocalVariablesSignature.cs | 12 +++++++++ .../Signatures/MemberSignature.cs | 3 +++ .../Signatures/MethodSignatureBase.cs | 23 ++++++++++++++++ .../Signatures/Types/CorLibTypeSignature.cs | 3 +++ .../Types/CustomModifierTypeSignature.cs | 4 +++ .../Types/FunctionPointerTypeSignature.cs | 4 +++ .../Types/GenericInstanceTypeSignature.cs | 15 +++++++++++ .../Types/GenericParameterSignature.cs | 3 +++ .../Signatures/Types/SentinelTypeSignature.cs | 3 +++ .../Signatures/Types/TypeDefOrRefSignature.cs | 3 +++ .../Signatures/Types/TypeSignature.cs | 3 +++ .../Types/TypeSpecificationSignature.cs | 3 +++ src/AsmResolver.DotNet/TypeDefinition.cs | 3 +++ src/AsmResolver.DotNet/TypeReference.cs | 3 +++ src/AsmResolver.DotNet/TypeSpecification.cs | 3 +++ 35 files changed, 207 insertions(+), 20 deletions(-) create mode 100644 src/AsmResolver.DotNet/IImportable.cs diff --git a/src/AsmResolver.DotNet/AssemblyDefinition.cs b/src/AsmResolver.DotNet/AssemblyDefinition.cs index 220980bcb..43fe53c77 100644 --- a/src/AsmResolver.DotNet/AssemblyDefinition.cs +++ b/src/AsmResolver.DotNet/AssemblyDefinition.cs @@ -222,6 +222,9 @@ protected virtual IList GetModules() return _publicKeyToken; } + /// + public override bool IsImportedInModule(ModuleDefinition module) => ManifestModule == module; + /// public override AssemblyDefinition Resolve() => this; diff --git a/src/AsmResolver.DotNet/AssemblyDescriptor.cs b/src/AsmResolver.DotNet/AssemblyDescriptor.cs index 248812ba8..87a816029 100644 --- a/src/AsmResolver.DotNet/AssemblyDescriptor.cs +++ b/src/AsmResolver.DotNet/AssemblyDescriptor.cs @@ -2,18 +2,20 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Reflection; using System.Security.Cryptography; using System.Threading; using AsmResolver.Collections; using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; +using AssemblyHashAlgorithm = AsmResolver.PE.DotNet.Metadata.Tables.Rows.AssemblyHashAlgorithm; namespace AsmResolver.DotNet { /// /// Provides a base implementation for describing a self-describing .NET assembly hosted by a common language runtime (CLR). /// - public abstract class AssemblyDescriptor : MetadataMember, IHasCustomAttribute, IFullNameProvider + public abstract class AssemblyDescriptor : MetadataMember, IHasCustomAttribute, IFullNameProvider, IImportable { private const int PublicKeyTokenLength = 8; @@ -212,6 +214,9 @@ public IList CustomAttributes /// public override string ToString() => FullName; + /// + public abstract bool IsImportedInModule(ModuleDefinition module); + /// /// Computes the token of a public key using the provided hashing algorithm. /// diff --git a/src/AsmResolver.DotNet/AssemblyReference.cs b/src/AsmResolver.DotNet/AssemblyReference.cs index b5548144e..f37630de6 100644 --- a/src/AsmResolver.DotNet/AssemblyReference.cs +++ b/src/AsmResolver.DotNet/AssemblyReference.cs @@ -147,6 +147,9 @@ public AssemblyReference(AssemblyDescriptor descriptor) /// protected virtual byte[]? GetHashValue() => null; + /// + public override bool IsImportedInModule(ModuleDefinition module) => Module == module; + /// public override AssemblyDefinition? Resolve() => Module?.MetadataResolver.AssemblyResolver.Resolve(this); diff --git a/src/AsmResolver.DotNet/EventDefinition.cs b/src/AsmResolver.DotNet/EventDefinition.cs index 649ce3734..e333a7e5c 100644 --- a/src/AsmResolver.DotNet/EventDefinition.cs +++ b/src/AsmResolver.DotNet/EventDefinition.cs @@ -170,6 +170,13 @@ public IList CustomAttributes IMemberDefinition IMemberDescriptor.Resolve() => this; + /// + public bool IsImportedInModule(ModuleDefinition module) + { + return Module == module + && (EventType?.IsImportedInModule(module) ?? false); + } + /// /// Obtains the list of custom attributes assigned to the member. /// diff --git a/src/AsmResolver.DotNet/ExportedType.cs b/src/AsmResolver.DotNet/ExportedType.cs index 935a0a795..d0845621b 100644 --- a/src/AsmResolver.DotNet/ExportedType.cs +++ b/src/AsmResolver.DotNet/ExportedType.cs @@ -28,8 +28,8 @@ public class ExportedType : protected ExportedType(MetadataToken token) : base(token) { - _name = new LazyVariable(() => GetName()); - _namespace = new LazyVariable(() => GetNamespace()); + _name = new LazyVariable(GetName); + _namespace = new LazyVariable(GetNamespace); _implementation = new LazyVariable(GetImplementation); } @@ -145,6 +145,9 @@ public IList CustomAttributes /// public TypeDefinition? Resolve() => Module?.MetadataResolver.ResolveType(this); + /// + public bool IsImportedInModule(ModuleDefinition module) => Module == module; + IMemberDefinition? IMemberDescriptor.Resolve() => Resolve(); /// diff --git a/src/AsmResolver.DotNet/FieldDefinition.cs b/src/AsmResolver.DotNet/FieldDefinition.cs index 08dc3832d..585f5ef3d 100644 --- a/src/AsmResolver.DotNet/FieldDefinition.cs +++ b/src/AsmResolver.DotNet/FieldDefinition.cs @@ -386,6 +386,13 @@ public IList CustomAttributes FieldDefinition IFieldDescriptor.Resolve() => this; + /// + public bool IsImportedInModule(ModuleDefinition module) + { + return Module == module + && (Signature?.IsImportedInModule(module) ?? false); + } + IMemberDefinition IMemberDescriptor.Resolve() => this; /// diff --git a/src/AsmResolver.DotNet/IImportable.cs b/src/AsmResolver.DotNet/IImportable.cs new file mode 100644 index 000000000..1d6fa8875 --- /dev/null +++ b/src/AsmResolver.DotNet/IImportable.cs @@ -0,0 +1,18 @@ +namespace AsmResolver.DotNet +{ + /// + /// Represents an entity in a .NET module that can be imported using the . + /// + public interface IImportable + { + /// + /// Determines whether the descriptor of the member is fully imported in the provided module. + /// + /// The module that is supposed to import the member. + /// true if the descriptor of the member is fully imported by the module, false otherwise. + /// + /// This method verifies all references in the descriptor of the member, but does not verify the contents (e.g. a method body). + /// + bool IsImportedInModule(ModuleDefinition module); + } +} diff --git a/src/AsmResolver.DotNet/IMemberDescriptor.cs b/src/AsmResolver.DotNet/IMemberDescriptor.cs index 75cf4f91d..e837e453b 100644 --- a/src/AsmResolver.DotNet/IMemberDescriptor.cs +++ b/src/AsmResolver.DotNet/IMemberDescriptor.cs @@ -3,7 +3,7 @@ namespace AsmResolver.DotNet /// /// Provides members for describing a (reference to a) member defined in a .NET assembly. /// - public interface IMemberDescriptor : IFullNameProvider, IModuleProvider + public interface IMemberDescriptor : IFullNameProvider, IModuleProvider, IImportable { /// /// When this member is defined in a type, gets the enclosing type. diff --git a/src/AsmResolver.DotNet/IResolutionScope.cs b/src/AsmResolver.DotNet/IResolutionScope.cs index 22c397ff0..444a63298 100644 --- a/src/AsmResolver.DotNet/IResolutionScope.cs +++ b/src/AsmResolver.DotNet/IResolutionScope.cs @@ -3,7 +3,7 @@ namespace AsmResolver.DotNet /// /// Represents a member that can be referenced by a ResolutionScope coded index. /// - public interface IResolutionScope : IMetadataMember, INameProvider, IModuleProvider + public interface IResolutionScope : IMetadataMember, INameProvider, IModuleProvider, IImportable { /// /// Gets the underlying assembly that this scope defines. diff --git a/src/AsmResolver.DotNet/InvalidTypeDefOrRef.cs b/src/AsmResolver.DotNet/InvalidTypeDefOrRef.cs index 7e63115fc..a4fd0d641 100644 --- a/src/AsmResolver.DotNet/InvalidTypeDefOrRef.cs +++ b/src/AsmResolver.DotNet/InvalidTypeDefOrRef.cs @@ -73,6 +73,9 @@ public static InvalidTypeDefOrRef Get(InvalidTypeSignatureError error) return instance; } + /// + public bool IsImportedInModule(ModuleDefinition module) => false; + IMemberDefinition? IMemberDescriptor.Resolve() => null; TypeDefinition? ITypeDescriptor.Resolve() => null; diff --git a/src/AsmResolver.DotNet/MemberReference.cs b/src/AsmResolver.DotNet/MemberReference.cs index 000940b0a..8254f8a3a 100644 --- a/src/AsmResolver.DotNet/MemberReference.cs +++ b/src/AsmResolver.DotNet/MemberReference.cs @@ -153,6 +153,13 @@ public IList CustomAttributes throw new ArgumentOutOfRangeException(); } + /// + public bool IsImportedInModule(ModuleDefinition module) + { + return Module == module + && (Signature?.IsImportedInModule(module) ?? false); + } + FieldDefinition? IFieldDescriptor.Resolve() { if (!IsField) diff --git a/src/AsmResolver.DotNet/MethodDefinition.cs b/src/AsmResolver.DotNet/MethodDefinition.cs index e79a423a4..59b6eb989 100644 --- a/src/AsmResolver.DotNet/MethodDefinition.cs +++ b/src/AsmResolver.DotNet/MethodDefinition.cs @@ -691,6 +691,13 @@ public IList GenericParameters MethodDefinition IMethodDescriptor.Resolve() => this; + /// + public bool IsImportedInModule(ModuleDefinition module) + { + return Module == module + && (Signature?.IsImportedInModule(module) ?? false); + } + IMemberDefinition IMemberDescriptor.Resolve() => this; /// diff --git a/src/AsmResolver.DotNet/MethodSpecification.cs b/src/AsmResolver.DotNet/MethodSpecification.cs index 02da0eb30..430c9952e 100644 --- a/src/AsmResolver.DotNet/MethodSpecification.cs +++ b/src/AsmResolver.DotNet/MethodSpecification.cs @@ -99,6 +99,13 @@ public IList CustomAttributes /// public MethodDefinition? Resolve() => Method?.Resolve(); + /// + public bool IsImportedInModule(ModuleDefinition module) + { + return (Method?.IsImportedInModule(module) ?? false) + && (Signature?.IsImportedInModule(module) ?? false); + } + IMemberDefinition? IMemberDescriptor.Resolve() => Resolve(); /// diff --git a/src/AsmResolver.DotNet/ModuleDefinition.cs b/src/AsmResolver.DotNet/ModuleDefinition.cs index d6b06f18a..36b89e058 100644 --- a/src/AsmResolver.DotNet/ModuleDefinition.cs +++ b/src/AsmResolver.DotNet/ModuleDefinition.cs @@ -1104,6 +1104,9 @@ protected IAssemblyResolver CreateAssemblyResolver(IFileService fileService) /// public override string ToString() => Name ?? string.Empty; + /// + bool IImportable.IsImportedInModule(ModuleDefinition module) => this == module; + /// /// Rebuilds the .NET module to a portable executable file and writes it to the file system. /// diff --git a/src/AsmResolver.DotNet/ModuleReference.cs b/src/AsmResolver.DotNet/ModuleReference.cs index 4feacbf12..9191d9ad7 100644 --- a/src/AsmResolver.DotNet/ModuleReference.cs +++ b/src/AsmResolver.DotNet/ModuleReference.cs @@ -76,6 +76,9 @@ public IList CustomAttributes } } + /// + public bool IsImportedInModule(ModuleDefinition module) => Module == module; + /// /// Obtains the name of the module. /// diff --git a/src/AsmResolver.DotNet/PropertyDefinition.cs b/src/AsmResolver.DotNet/PropertyDefinition.cs index 05c94c9b2..93348cedb 100644 --- a/src/AsmResolver.DotNet/PropertyDefinition.cs +++ b/src/AsmResolver.DotNet/PropertyDefinition.cs @@ -187,6 +187,13 @@ public IList CustomAttributes IMemberDefinition IMemberDescriptor.Resolve() => this; + /// + public bool IsImportedInModule(ModuleDefinition module) + { + return Module == module + && (Signature?.IsImportedInModule(module) ?? false); + } + /// /// Obtains the name of the property definition. /// diff --git a/src/AsmResolver.DotNet/ReferenceImporter.cs b/src/AsmResolver.DotNet/ReferenceImporter.cs index b1c858e76..0d7f54673 100644 --- a/src/AsmResolver.DotNet/ReferenceImporter.cs +++ b/src/AsmResolver.DotNet/ReferenceImporter.cs @@ -47,7 +47,7 @@ public IResolutionScope ImportScope(IResolutionScope? scope) { if (scope is null) throw new ArgumentNullException(nameof(scope)); - if (scope.Module == TargetModule) + if (scope.IsImportedInModule(TargetModule)) return scope; return scope switch @@ -69,7 +69,7 @@ protected virtual AssemblyReference ImportAssembly(AssemblyDescriptor assembly) { if (assembly is null) throw new ArgumentNullException(nameof(assembly)); - if (assembly is AssemblyReference r && r.Module == TargetModule) + if (assembly is AssemblyReference r && assembly.IsImportedInModule(TargetModule)) return r; var reference = TargetModule.AssemblyReferences.FirstOrDefault(a => _comparer.Equals(a, assembly)); @@ -92,7 +92,7 @@ public virtual ModuleReference ImportModule(ModuleReference module) { if (module is null) throw new ArgumentNullException(nameof(module)); - if (module.Module == TargetModule) + if (module.IsImportedInModule(TargetModule)) return module; var reference = TargetModule.ModuleReferences.FirstOrDefault(a => _comparer.Equals(a, module)); @@ -139,7 +139,7 @@ protected virtual ITypeDefOrRef ImportType(TypeDefinition type) { AssertTypeIsValid(type); - if (type.Module == TargetModule) + if (type.IsImportedInModule(TargetModule)) return type; return new TypeReference(TargetModule, ImportScope(type.Module!), type.Namespace, type.Name); @@ -154,7 +154,7 @@ protected virtual ITypeDefOrRef ImportType(TypeReference type) { AssertTypeIsValid(type); - if (type.Module == TargetModule) + if (type.IsImportedInModule(TargetModule)) return type; return new TypeReference(TargetModule, ImportScope(type.Scope!), type.Namespace, type.Name); @@ -171,7 +171,7 @@ protected virtual ITypeDefOrRef ImportType(TypeSpecification type) if (type.Signature is null) throw new ArgumentNullException(nameof(type)); - if (type.Module == TargetModule) + if (type.IsImportedInModule(TargetModule)) return type; return new TypeSpecification(ImportTypeSignature(type.Signature)); @@ -186,7 +186,7 @@ public virtual TypeSignature ImportTypeSignature(TypeSignature type) { if (type is null) throw new ArgumentNullException(nameof(type)); - if (type.Module == TargetModule) + if (type.IsImportedInModule(TargetModule)) return type; return type.AcceptVisitor(this); @@ -212,11 +212,8 @@ public virtual ITypeDefOrRef ImportType(Type type) throw new ArgumentNullException(nameof(type)); var importedTypeSig = ImportTypeSignature(type); - if (importedTypeSig is TypeDefOrRefSignature - || importedTypeSig is CorLibTypeSignature) - { + if (importedTypeSig is TypeDefOrRefSignature or CorLibTypeSignature) return importedTypeSig.GetUnderlyingTypeDefOrRef()!; - } return new TypeSpecification(importedTypeSig); } @@ -266,6 +263,7 @@ private TypeSignature ImportArrayType(Type type) var result = new ArrayTypeSignature(baseType); for (int i = 0; i < rank; i++) result.Dimensions.Add(new ArrayDimension()); + return result; } @@ -318,7 +316,7 @@ public virtual IMethodDefOrRef ImportMethod(IMethodDefOrRef method) if (method.Signature is null) throw new ArgumentException("Cannot import a method that does not have a signature."); - if (method.Module == TargetModule) + if (method.IsImportedInModule(TargetModule)) return method; return new MemberReference( @@ -406,7 +404,7 @@ public virtual MethodSpecification ImportMethod(MethodSpecification method) if (method.DeclaringType is null) throw new ArgumentException("Cannot import a method that is not added to a type."); - if (method.Module == TargetModule) + if (method.IsImportedInModule(TargetModule)) return method; var memberRef = ImportMethod(method.Method); @@ -479,7 +477,7 @@ public virtual IFieldDescriptor ImportField(IFieldDescriptor field) if (field.Signature is null) throw new ArgumentException("Cannot import a field that does not have a signature."); - if (field.Module == TargetModule) + if (field.IsImportedInModule(TargetModule)) return field; return new MemberReference( diff --git a/src/AsmResolver.DotNet/ReflectionAssemblyDescriptor.cs b/src/AsmResolver.DotNet/ReflectionAssemblyDescriptor.cs index adf51b04e..d192bf2ba 100644 --- a/src/AsmResolver.DotNet/ReflectionAssemblyDescriptor.cs +++ b/src/AsmResolver.DotNet/ReflectionAssemblyDescriptor.cs @@ -32,6 +32,9 @@ public ReflectionAssemblyDescriptor(ModuleDefinition parentModule, AssemblyName /// protected override Utf8String? GetCulture() => _assemblyName.CultureName; + /// + public override bool IsImportedInModule(ModuleDefinition module) => false; + /// public override bool IsCorLib => Name is not null && KnownCorLibs.KnownCorLibNames.Contains(Name); diff --git a/src/AsmResolver.DotNet/Signatures/CallingConventionSignature.cs b/src/AsmResolver.DotNet/Signatures/CallingConventionSignature.cs index f7d33ae5c..4ed13994d 100644 --- a/src/AsmResolver.DotNet/Signatures/CallingConventionSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/CallingConventionSignature.cs @@ -7,7 +7,7 @@ namespace AsmResolver.DotNet.Signatures /// Provides a base for all signature that deal with a calling convention. This includes most member signatures, /// such as method and field signatures. /// - public abstract class CallingConventionSignature : ExtendableBlobSignature + public abstract class CallingConventionSignature : ExtendableBlobSignature, IImportable { private const CallingConventionAttributes SignatureTypeMask = (CallingConventionAttributes)0xF; @@ -148,5 +148,8 @@ public bool IsSentinel set => Attributes = (Attributes & ~CallingConventionAttributes.Sentinel) | (value ? CallingConventionAttributes.Sentinel : 0); } + + /// + public abstract bool IsImportedInModule(ModuleDefinition module); } } diff --git a/src/AsmResolver.DotNet/Signatures/GenericInstanceMethodSignature.cs b/src/AsmResolver.DotNet/Signatures/GenericInstanceMethodSignature.cs index c17695745..240bd396e 100644 --- a/src/AsmResolver.DotNet/Signatures/GenericInstanceMethodSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/GenericInstanceMethodSignature.cs @@ -91,6 +91,18 @@ protected override void WriteContents(BlobSerializationContext context) TypeArguments[i].Write(context); } + /// + public override bool IsImportedInModule(ModuleDefinition module) + { + for (int i = 0; i < TypeArguments.Count; i++) + { + if (!TypeArguments[i].IsImportedInModule(module)) + return false; + } + + return true; + } + /// public override string ToString() { diff --git a/src/AsmResolver.DotNet/Signatures/LocalVariablesSignature.cs b/src/AsmResolver.DotNet/Signatures/LocalVariablesSignature.cs index ccf81a2a5..e8c8de393 100644 --- a/src/AsmResolver.DotNet/Signatures/LocalVariablesSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/LocalVariablesSignature.cs @@ -68,6 +68,18 @@ public IList VariableTypes get; } + /// + public override bool IsImportedInModule(ModuleDefinition module) + { + for (int i = 0; i < VariableTypes.Count; i++) + { + if (!VariableTypes[i].IsImportedInModule(module)) + return false; + } + + return true; + } + /// protected override void WriteContents(BlobSerializationContext context) { diff --git a/src/AsmResolver.DotNet/Signatures/MemberSignature.cs b/src/AsmResolver.DotNet/Signatures/MemberSignature.cs index a34b3bdba..2d5e412d1 100644 --- a/src/AsmResolver.DotNet/Signatures/MemberSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/MemberSignature.cs @@ -27,6 +27,9 @@ protected TypeSignature MemberReturnType set; } + /// + public override bool IsImportedInModule(ModuleDefinition module) => MemberReturnType.IsImportedInModule(module); + /// public override string ToString() { diff --git a/src/AsmResolver.DotNet/Signatures/MethodSignatureBase.cs b/src/AsmResolver.DotNet/Signatures/MethodSignatureBase.cs index 50452b60f..4e58f029a 100644 --- a/src/AsmResolver.DotNet/Signatures/MethodSignatureBase.cs +++ b/src/AsmResolver.DotNet/Signatures/MethodSignatureBase.cs @@ -78,6 +78,29 @@ public IList SentinelParameterTypes get; } = new List(); + /// + public override bool IsImportedInModule(ModuleDefinition module) + { + if (!ReturnType.IsImportedInModule(module)) + return false; + + for (int i = 0; i < ParameterTypes.Count; i++) + { + var x = ParameterTypes[i]; + if (!x.IsImportedInModule(module)) + return false; + } + + for (int i = 0; i < SentinelParameterTypes.Count; i++) + { + var x = SentinelParameterTypes[i]; + if (!x.IsImportedInModule(module)) + return false; + } + + return true; + } + /// /// Initializes the and properties by reading /// the parameter count, return type and parameter fields of the signature from the provided input stream. diff --git a/src/AsmResolver.DotNet/Signatures/Types/CorLibTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/CorLibTypeSignature.cs index 69db2a00c..ae2a6974c 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/CorLibTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/CorLibTypeSignature.cs @@ -78,6 +78,9 @@ ElementType switch return Type.Resolve(); } + /// + public override bool IsImportedInModule(ModuleDefinition module) => Module == module; + /// public override ITypeDefOrRef? GetUnderlyingTypeDefOrRef() { diff --git a/src/AsmResolver.DotNet/Signatures/Types/CustomModifierTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/CustomModifierTypeSignature.cs index 34baa3006..64ec085e1 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/CustomModifierTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/CustomModifierTypeSignature.cs @@ -75,6 +75,10 @@ public override string Name TState state) => visitor.VisitCustomModifierType(this, state); + /// + public override bool IsImportedInModule(ModuleDefinition module) => + ModifierType.IsImportedInModule(module) && base.IsImportedInModule(module); + /// protected override void WriteContents(BlobSerializationContext context) { diff --git a/src/AsmResolver.DotNet/Signatures/Types/FunctionPointerTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/FunctionPointerTypeSignature.cs index d5aeb810c..ff2acc2c8 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/FunctionPointerTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/FunctionPointerTypeSignature.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; namespace AsmResolver.DotNet.Signatures.Types @@ -48,6 +49,9 @@ public MethodSignature Signature public override ITypeDefOrRef? GetUnderlyingTypeDefOrRef() => Signature?.ReturnType?.Module?.CorLibTypeFactory.IntPtr.Type; + /// + public override bool IsImportedInModule(ModuleDefinition module) => Signature.IsImportedInModule(module); + /// protected override void WriteContents(BlobSerializationContext context) { diff --git a/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs index 6872344f2..7ed057d8b 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs @@ -108,6 +108,21 @@ public override bool IsValueType /// public override ITypeDefOrRef? GetUnderlyingTypeDefOrRef() => GenericType; + /// + public override bool IsImportedInModule(ModuleDefinition module) + { + if (!GenericType.IsImportedInModule(module)) + return false; + + for (int i = 0; i < TypeArguments.Count; i++) + { + if (!TypeArguments[i].IsImportedInModule(module)) + return false; + } + + return true; + } + /// protected override void WriteContents(BlobSerializationContext context) { diff --git a/src/AsmResolver.DotNet/Signatures/Types/GenericParameterSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/GenericParameterSignature.cs index f3af9b9d8..803e056a2 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/GenericParameterSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/GenericParameterSignature.cs @@ -82,6 +82,9 @@ public int Index /// public override TypeDefinition? Resolve() => null; + /// + public override bool IsImportedInModule(ModuleDefinition module) => Module == module; + /// public override ITypeDefOrRef? GetUnderlyingTypeDefOrRef() => null; diff --git a/src/AsmResolver.DotNet/Signatures/Types/SentinelTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/SentinelTypeSignature.cs index 79e5f7709..49e7faa18 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/SentinelTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/SentinelTypeSignature.cs @@ -33,6 +33,9 @@ public class SentinelTypeSignature : TypeSignature /// public override ITypeDefOrRef? GetUnderlyingTypeDefOrRef() => null; + /// + public override bool IsImportedInModule(ModuleDefinition module) => true; + /// protected override void WriteContents(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 0222d9b59..cab577dc4 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/TypeDefOrRefSignature.cs @@ -60,6 +60,9 @@ public override bool IsValueType /// public override TypeDefinition? Resolve() => Type.Resolve(); + /// + public override bool IsImportedInModule(ModuleDefinition module) => Type.IsImportedInModule(module); + /// public override ITypeDefOrRef ToTypeDefOrRef() => Type; diff --git a/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs index 2373fc3bf..f5419ddfe 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs @@ -376,6 +376,9 @@ internal static void WriteFieldOrPropType(BlobSerializationContext context, Type public TypeSignature InstantiateGenericTypes(GenericContext context) => AcceptVisitor(GenericTypeActivator.Instance, context); + /// + public abstract bool IsImportedInModule(ModuleDefinition module); + /// /// Visit the current type signature using the provided visitor. /// diff --git a/src/AsmResolver.DotNet/Signatures/Types/TypeSpecificationSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/TypeSpecificationSignature.cs index cd0bf2d18..1fa4130e5 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/TypeSpecificationSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/TypeSpecificationSignature.cs @@ -37,6 +37,9 @@ public TypeSignature BaseType public override ITypeDefOrRef? GetUnderlyingTypeDefOrRef() => BaseType.GetUnderlyingTypeDefOrRef(); + /// + public override bool IsImportedInModule(ModuleDefinition module) => BaseType.IsImportedInModule(module); + /// protected override void WriteContents(BlobSerializationContext context) { diff --git a/src/AsmResolver.DotNet/TypeDefinition.cs b/src/AsmResolver.DotNet/TypeDefinition.cs index 65852f98c..d1757a8f8 100644 --- a/src/AsmResolver.DotNet/TypeDefinition.cs +++ b/src/AsmResolver.DotNet/TypeDefinition.cs @@ -680,6 +680,9 @@ public TypeSignature ToTypeSignature() ?? new TypeDefOrRefSignature(this, IsValueType); } + /// + public bool IsImportedInModule(ModuleDefinition module) => Module == module; + /// public bool IsAccessibleFromType(TypeDefinition type) { diff --git a/src/AsmResolver.DotNet/TypeReference.cs b/src/AsmResolver.DotNet/TypeReference.cs index 57a5fe8b6..113a8892d 100644 --- a/src/AsmResolver.DotNet/TypeReference.cs +++ b/src/AsmResolver.DotNet/TypeReference.cs @@ -136,6 +136,9 @@ public TypeSignature ToTypeSignature() ?? new TypeDefOrRefSignature(this, IsValueType); } + /// + public bool IsImportedInModule(ModuleDefinition module) => Module == module; + /// public TypeDefinition? Resolve() => Module?.MetadataResolver.ResolveType(this); diff --git a/src/AsmResolver.DotNet/TypeSpecification.cs b/src/AsmResolver.DotNet/TypeSpecification.cs index 9d9a4713d..d268bf85a 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 bool IsImportedInModule(ModuleDefinition module) => Signature?.IsImportedInModule(module) ?? false; + /// public TypeDefinition? Resolve() => Module?.MetadataResolver.ResolveType(this); From 78b3400aa1b9abbe50a4ca174f0c1f6d635de5ce Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 17 Mar 2022 21:00:51 +0100 Subject: [PATCH 051/184] Add more type sig importing tests. --- .../ReferenceImporterTest.cs | 106 +++++++++++++++++- 1 file changed, 104 insertions(+), 2 deletions(-) diff --git a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs index 7ee5a63d2..c469f479c 100644 --- a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs +++ b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs @@ -266,7 +266,30 @@ public void ImportFieldFromReflectionShouldResultInMemberRef() } [Fact] - public void ImportTypeSigWithNonImportedEmbeddedReferenceShouldResultInNewInstance() + public void ImportNonImportedTypeDefOrRefShouldResultInNewInstance() + { + var signature = new TypeReference(_module.CorLibTypeFactory.CorLibScope, "System.IO", "Stream") + .ToTypeSignature(); + + var imported = _importer.ImportTypeSignature(signature); + + Assert.NotSame(signature, imported); + Assert.Equal(signature, imported, _comparer); + Assert.Equal(_module, imported.Module); + } + + [Fact] + public void ImportFullyImportedTypeDefOrRefShouldResultInSameInstance() + { + var signature = new TypeReference(_module, _module.CorLibTypeFactory.CorLibScope, "System.IO", "Stream") + .ToTypeSignature(); + + var imported = _importer.ImportTypeSignature(signature); + Assert.Same(signature, imported); + } + + [Fact] + public void ImportGenericTypeSigWithNonImportedTypeArgumentShouldResultInNewInstance() { // https://github.com/Washi1337/AsmResolver/issues/268 @@ -288,7 +311,7 @@ public void ImportTypeSigWithNonImportedEmbeddedReferenceShouldResultInNewInstan } [Fact] - public void ImportTypeSigWithImportedEmbeddedReferenceShouldResultInSameInstance() + public void ImportFullyImportedGenericTypeSigShouldResultInSameInstance() { // https://github.com/Washi1337/AsmResolver/issues/268 @@ -306,5 +329,84 @@ public void ImportTypeSigWithImportedEmbeddedReferenceShouldResultInSameInstance var newInstance = Assert.IsAssignableFrom(imported); Assert.Same(instance, newInstance); } + + [Fact] + public void ImportCustomModifierTypeWithNonImportedModifierTypeShouldResultInNewInstance() + { + var signature = new TypeReference(_module, _dummyAssembly, "SomeNamespace", "SomeType") + .ToTypeSignature() + .MakeModifierType(new TypeReference(_dummyAssembly, "SomeNamespace", "SomeModifierType"), true); + + var imported = _importer.ImportTypeSignature(signature); + + var newInstance = Assert.IsAssignableFrom(imported); + Assert.NotSame(signature, newInstance); + Assert.Equal(_module, newInstance.Module); + Assert.Equal(_module, newInstance.ModifierType.Module); + } + + [Fact] + public void ImportFullyImportedCustomModifierTypeShouldResultInSameInstance() + { + var signature = new TypeReference(_module, _dummyAssembly, "SomeNamespace", "SomeType") + .ToTypeSignature() + .MakeModifierType(new TypeReference(_module, _dummyAssembly, "SomeNamespace", "SomeModifierType"), true); + + var imported = _importer.ImportTypeSignature(signature); + + var newInstance = Assert.IsAssignableFrom(imported); + Assert.Same(signature, newInstance); + } + + [Fact] + public void ImportFunctionPointerTypeWithNonImportedParameterShouldResultInNewInstance() + { + var signature = MethodSignature + .CreateStatic( + _module.CorLibTypeFactory.Void, + new TypeReference(_dummyAssembly, "SomeNamespace", "SomeType").ToTypeSignature()) + .MakeFunctionPointerType(); + + var imported = _importer.ImportTypeSignature(signature); + + var newInstance = Assert.IsAssignableFrom(imported); + Assert.NotSame(signature, newInstance); + Assert.Equal(signature, newInstance, _comparer); + Assert.Equal(_module, newInstance.Module); + Assert.Equal(_module, newInstance.Signature.ParameterTypes[0].Module); + } + + [Fact] + public void ImportFunctionPointerTypeWithNonImportedReturnTypeShouldResultInNewInstance() + { + var signature = MethodSignature + .CreateStatic( + new TypeReference(_dummyAssembly, "SomeNamespace", "SomeType").ToTypeSignature(), + _module.CorLibTypeFactory.Int32) + .MakeFunctionPointerType(); + + var imported = _importer.ImportTypeSignature(signature); + + var newInstance = Assert.IsAssignableFrom(imported); + Assert.NotSame(signature, newInstance); + Assert.Equal(signature, newInstance, _comparer); + Assert.Equal(_module, newInstance.Module); + Assert.Equal(_module, newInstance.Signature.ReturnType.Module); + } + + [Fact] + public void ImportFullyImportedFunctionPointerTypeShouldResultInSameInstance() + { + var signature = MethodSignature + .CreateStatic( + _module.CorLibTypeFactory.Void, + new TypeReference(_module, _dummyAssembly, "SomeNamespace", "SomeType").ToTypeSignature()) + .MakeFunctionPointerType(); + + var imported = _importer.ImportTypeSignature(signature); + + var newInstance = Assert.IsAssignableFrom(imported); + Assert.Same(signature, newInstance); + } } } From 4e86ea3d6ae951885cefbf69dae3033b3c769390 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 17 Mar 2022 21:01:17 +0100 Subject: [PATCH 052/184] BUGFIX: Let TypeSpecificationSignature.Module return the module of the base type. --- .../Signatures/Types/TypeSpecificationSignature.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/AsmResolver.DotNet/Signatures/Types/TypeSpecificationSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/TypeSpecificationSignature.cs index 1fa4130e5..412964357 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/TypeSpecificationSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/TypeSpecificationSignature.cs @@ -23,6 +23,9 @@ public TypeSignature BaseType set; } + /// + public override ModuleDefinition? Module => BaseType.Module; + /// public override string? Namespace => BaseType.Namespace; From 49d34d1ab6be0ed9b62281fd1d975b77e5ad1ee2 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 17 Mar 2022 21:01:35 +0100 Subject: [PATCH 053/184] BUGFIX: Let SignatureComparer.Equals implement comparisons for FunctionPointerTypeSignature. --- .../SignatureComparer.TypeSignature.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeSignature.cs b/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeSignature.cs index f7afe504a..e31005637 100644 --- a/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeSignature.cs @@ -20,6 +20,7 @@ public partial class SignatureComparer : IEqualityComparer, IEqualityComparer, IEqualityComparer, + IEqualityComparer, IEqualityComparer>, IEqualityComparer> { @@ -59,6 +60,7 @@ public bool Equals(TypeSignature? x, TypeSignature? y) case ElementType.Boxed: return Equals(x as BoxedTypeSignature, y as BoxedTypeSignature); case ElementType.FnPtr: + return Equals(x as FunctionPointerTypeSignature, y as FunctionPointerTypeSignature); case ElementType.Internal: case ElementType.Modifier: throw new NotSupportedException(); @@ -309,6 +311,22 @@ public int GetHashCode(ArrayTypeSignature obj) } } + /// + public bool Equals(FunctionPointerTypeSignature? x, FunctionPointerTypeSignature? y) + { + if (ReferenceEquals(x, y)) + return true; + if (x is null || y is null) + return false; + return Equals(x.Signature, y.Signature); + } + + /// + public int GetHashCode(FunctionPointerTypeSignature obj) + { + return obj.Signature.GetHashCode(); + } + /// public bool Equals(IList? x, IList? y) { From 1051ad23caea989527751b648a70dcf8620495a5 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 18 Mar 2022 20:52:37 +0100 Subject: [PATCH 054/184] Remove excessive null-forgiving operator. --- src/AsmResolver.DotNet/ReferenceImporter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AsmResolver.DotNet/ReferenceImporter.cs b/src/AsmResolver.DotNet/ReferenceImporter.cs index e132b2b71..35636cd41 100644 --- a/src/AsmResolver.DotNet/ReferenceImporter.cs +++ b/src/AsmResolver.DotNet/ReferenceImporter.cs @@ -144,7 +144,7 @@ protected virtual ITypeDefOrRef ImportType(TypeDefinition type) return new TypeReference( TargetModule, - ImportScope(((ITypeDescriptor) type).Scope!), + ImportScope(((ITypeDescriptor) type).Scope), type.Namespace, type.Name); } From 275937dae3ef716edcdd43f602b0632201c7175e Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 18 Mar 2022 21:00:14 +0100 Subject: [PATCH 055/184] Address merge conflicts. --- test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs index dfb2969cc..13b0a7bb5 100644 --- a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs +++ b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs @@ -305,7 +305,7 @@ public void ImportNonImportedTypeDefOrRefShouldResultInNewInstance() var imported = _importer.ImportTypeSignature(signature); Assert.NotSame(signature, imported); - Assert.Equal(signature, imported, _comparer); + Assert.Equal(signature, imported, Comparer); Assert.Equal(_module, imported.Module); } @@ -402,7 +402,7 @@ public void ImportFunctionPointerTypeWithNonImportedParameterShouldResultInNewIn var newInstance = Assert.IsAssignableFrom(imported); Assert.NotSame(signature, newInstance); - Assert.Equal(signature, newInstance, _comparer); + Assert.Equal(signature, newInstance, Comparer); Assert.Equal(_module, newInstance.Module); Assert.Equal(_module, newInstance.Signature.ParameterTypes[0].Module); } @@ -420,7 +420,7 @@ public void ImportFunctionPointerTypeWithNonImportedReturnTypeShouldResultInNewI var newInstance = Assert.IsAssignableFrom(imported); Assert.NotSame(signature, newInstance); - Assert.Equal(signature, newInstance, _comparer); + Assert.Equal(signature, newInstance, Comparer); Assert.Equal(_module, newInstance.Module); Assert.Equal(_module, newInstance.Signature.ReturnType.Module); } From 2ac4251c1c709f985f8a2c4b0250d372c29bb666 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 18 Mar 2022 21:15:31 +0100 Subject: [PATCH 056/184] Clarify remarks for IsImportedInModule. --- src/AsmResolver.DotNet/IImportable.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/AsmResolver.DotNet/IImportable.cs b/src/AsmResolver.DotNet/IImportable.cs index 1d6fa8875..9fdbc2c76 100644 --- a/src/AsmResolver.DotNet/IImportable.cs +++ b/src/AsmResolver.DotNet/IImportable.cs @@ -11,7 +11,8 @@ public interface IImportable /// The module that is supposed to import the member. /// true if the descriptor of the member is fully imported by the module, false otherwise. /// - /// This method verifies all references in the descriptor of the member, but does not verify the contents (e.g. a method body). + /// This method verifies all references in the descriptor of the member only. It does not verify any additional + /// data or contents (such as a method body) associated to the member. /// bool IsImportedInModule(ModuleDefinition module); } From b09801bf1d9e4acdbcb6b26938d8ed912bc4d356 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 18 Mar 2022 21:16:25 +0100 Subject: [PATCH 057/184] Add IImportable to IImplementation and derivatives. --- src/AsmResolver.DotNet/ExportedType.cs | 6 ++++- src/AsmResolver.DotNet/FileReference.cs | 3 +++ src/AsmResolver.DotNet/IImplementation.cs | 2 +- .../Types/GenericParameterSignature.cs | 9 ++++--- .../ReferenceImporterTest.cs | 25 +++++++++++++++++++ 5 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/AsmResolver.DotNet/ExportedType.cs b/src/AsmResolver.DotNet/ExportedType.cs index d0845621b..bf83bd24d 100644 --- a/src/AsmResolver.DotNet/ExportedType.cs +++ b/src/AsmResolver.DotNet/ExportedType.cs @@ -146,7 +146,11 @@ public IList CustomAttributes public TypeDefinition? Resolve() => Module?.MetadataResolver.ResolveType(this); /// - public bool IsImportedInModule(ModuleDefinition module) => Module == module; + public bool IsImportedInModule(ModuleDefinition module) + { + return Module == module + && (Implementation?.IsImportedInModule(module) ?? false); + } IMemberDefinition? IMemberDescriptor.Resolve() => Resolve(); diff --git a/src/AsmResolver.DotNet/FileReference.cs b/src/AsmResolver.DotNet/FileReference.cs index e8f78cdb0..ef1f3c225 100644 --- a/src/AsmResolver.DotNet/FileReference.cs +++ b/src/AsmResolver.DotNet/FileReference.cs @@ -120,6 +120,9 @@ public IList CustomAttributes } } + /// + public bool IsImportedInModule(ModuleDefinition module) => Module == module; + /// /// Obtains the name of the referenced file. /// diff --git a/src/AsmResolver.DotNet/IImplementation.cs b/src/AsmResolver.DotNet/IImplementation.cs index 1674e3120..7d99282d7 100644 --- a/src/AsmResolver.DotNet/IImplementation.cs +++ b/src/AsmResolver.DotNet/IImplementation.cs @@ -4,7 +4,7 @@ namespace AsmResolver.DotNet /// Represents a member that is either a reference to an external file, assembly or type, and can be referenced by /// an Implementation coded index. /// - public interface IImplementation : IFullNameProvider, IModuleProvider, IHasCustomAttribute + public interface IImplementation : IFullNameProvider, IModuleProvider, IHasCustomAttribute, IImportable { } } diff --git a/src/AsmResolver.DotNet/Signatures/Types/GenericParameterSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/GenericParameterSignature.cs index 803e056a2..24f487016 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/GenericParameterSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/GenericParameterSignature.cs @@ -9,8 +9,6 @@ namespace AsmResolver.DotNet.Signatures.Types /// public class GenericParameterSignature : TypeSignature { - private readonly IResolutionScope? _scope; - /// /// Creates a new reference to a generic parameter. /// @@ -30,7 +28,7 @@ public GenericParameterSignature(GenericParameterType parameterType, int index) /// The index of the referenced parameter. public GenericParameterSignature(ModuleDefinition module, GenericParameterType parameterType, int index) { - _scope = module; + Scope = module; ParameterType = parameterType; Index = index; } @@ -74,7 +72,10 @@ public int Index public override string? Namespace => null; /// - public override IResolutionScope? Scope => _scope; + public override IResolutionScope? Scope + { + get; + } /// public override bool IsValueType => false; diff --git a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs index 13b0a7bb5..83ec26082 100644 --- a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs +++ b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs @@ -309,6 +309,20 @@ public void ImportNonImportedTypeDefOrRefShouldResultInNewInstance() Assert.Equal(_module, imported.Module); } + [Fact] + public void ImportTypeSpecWithNonImportedBaseTypeShouldResultInNewInstance() + { + var signature = new TypeReference(_module.CorLibTypeFactory.CorLibScope, "System.IO", "Stream") + .ToTypeSignature() + .MakeSzArrayType(); + + var imported = _importer.ImportTypeSignature(signature); + var newInstance = Assert.IsAssignableFrom(imported); + Assert.NotSame(signature, newInstance); + Assert.Equal(signature, newInstance, Comparer); + Assert.Equal(_module, newInstance.BaseType.Module); + } + [Fact] public void ImportFullyImportedTypeDefOrRefShouldResultInSameInstance() { @@ -319,6 +333,17 @@ public void ImportFullyImportedTypeDefOrRefShouldResultInSameInstance() Assert.Same(signature, imported); } + [Fact] + public void ImportFullyImportedTypeSpecShouldResultInSameInstance() + { + var signature = new TypeReference(_module, _module.CorLibTypeFactory.CorLibScope, "System.IO", "Stream") + .ToTypeSignature() + .MakeSzArrayType(); + + var imported = _importer.ImportTypeSignature(signature); + Assert.Same(signature, imported); + } + [Fact] public void ImportGenericTypeSigWithNonImportedTypeArgumentShouldResultInNewInstance() { From 36dcd8b294c34db047527f74130bb1358f3a677a Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 19 Mar 2022 14:24:08 +0100 Subject: [PATCH 058/184] Initial setup for .NET bundle manifests. --- .../DotNet/Bundles/BundleFile.cs | 38 ++++++ .../DotNet/Bundles/BundleFileType.cs | 12 ++ .../DotNet/Bundles/BundleManifest.cs | 125 ++++++++++++++++++ .../DotNet/Bundles/BundleManifestFlags.cs | 11 ++ .../DotNet/Bundles/SerializedBundleFile.cs | 22 +++ .../Bundles/SerializedBundleManifest.cs | 39 ++++++ .../DotNet/Bundles/BundleManifestTest.cs | 45 +++++++ .../Properties/Resources.Designer.cs | 21 +++ .../Properties/Resources.resx | 9 ++ .../Resources/HelloWorld.SingleFile.v1.exe | Bin 0 -> 178015 bytes .../Resources/HelloWorld.SingleFile.v2.exe | Bin 0 -> 129408 bytes .../Resources/HelloWorld.SingleFile.v6.exe | Bin 0 -> 152984 bytes 12 files changed, 322 insertions(+) create mode 100644 src/AsmResolver.PE/DotNet/Bundles/BundleFile.cs create mode 100644 src/AsmResolver.PE/DotNet/Bundles/BundleFileType.cs create mode 100644 src/AsmResolver.PE/DotNet/Bundles/BundleManifest.cs create mode 100644 src/AsmResolver.PE/DotNet/Bundles/BundleManifestFlags.cs create mode 100644 src/AsmResolver.PE/DotNet/Bundles/SerializedBundleFile.cs create mode 100644 src/AsmResolver.PE/DotNet/Bundles/SerializedBundleManifest.cs create mode 100644 test/AsmResolver.PE.Tests/DotNet/Bundles/BundleManifestTest.cs create mode 100644 test/AsmResolver.PE.Tests/Resources/HelloWorld.SingleFile.v1.exe create mode 100644 test/AsmResolver.PE.Tests/Resources/HelloWorld.SingleFile.v2.exe create mode 100644 test/AsmResolver.PE.Tests/Resources/HelloWorld.SingleFile.v6.exe diff --git a/src/AsmResolver.PE/DotNet/Bundles/BundleFile.cs b/src/AsmResolver.PE/DotNet/Bundles/BundleFile.cs new file mode 100644 index 000000000..0b90418f7 --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Bundles/BundleFile.cs @@ -0,0 +1,38 @@ +namespace AsmResolver.PE.DotNet.Bundles +{ + public class BundleFile + { + private readonly LazyVariable _contents; + + public BundleFile() + { + _contents = new LazyVariable(GetContents); + } + + public string RelativePath + { + get; + set; + } + + public BundleFileType Type + { + get; + set; + } + + public bool IsCompressed + { + get; + set; + } + + public ISegment Contents + { + get => _contents.Value; + set => _contents.Value = value; + } + + protected virtual ISegment? GetContents() => null; + } +} diff --git a/src/AsmResolver.PE/DotNet/Bundles/BundleFileType.cs b/src/AsmResolver.PE/DotNet/Bundles/BundleFileType.cs new file mode 100644 index 000000000..58ad0aead --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Bundles/BundleFileType.cs @@ -0,0 +1,12 @@ +namespace AsmResolver.PE.DotNet.Bundles +{ + public enum BundleFileType + { + Unknown, + Assembly, + NativeBinary, + DepsJson, + RuntimeConfigJson, + Symbols + } +} diff --git a/src/AsmResolver.PE/DotNet/Bundles/BundleManifest.cs b/src/AsmResolver.PE/DotNet/Bundles/BundleManifest.cs new file mode 100644 index 000000000..ad1e22fe7 --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Bundles/BundleManifest.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using AsmResolver.IO; + +namespace AsmResolver.PE.DotNet.Bundles +{ + public class BundleManifest + { + private static readonly byte[] BundleSignature = { + 0x8b, 0x12, 0x02, 0xb9, 0x6a, 0x61, 0x20, 0x38, + 0x72, 0x7b, 0x93, 0x02, 0x14, 0xd7, 0xa0, 0x32, + 0x13, 0xf5, 0xb9, 0xe6, 0xef, 0xae, 0x33, 0x18, + 0xee, 0x3b, 0x2d, 0xce, 0x24, 0xb3, 0x6a, 0xae + }; + + private IList? _files; + + protected BundleManifest() + { + } + + public BundleManifest(uint version, string bundleId) + { + MajorVersion = version; + MinorVersion = 0; + BundleID = bundleId; + } + + public uint MajorVersion + { + get; + set; + } + + public uint MinorVersion + { + get; + set; + } + + public string BundleID + { + get; + set; + } + + public BundleManifestFlags Flags + { + get; + set; + } + + public IList Files + { + get + { + if (_files is null) + Interlocked.CompareExchange(ref _files, GetFiles(), null); + return _files; + } + } + + public static BundleManifest FromFile(string filePath) + { + return FromBytes(System.IO.File.ReadAllBytes(filePath)); + } + + public static BundleManifest FromBytes(byte[] data) + { + return FromDataSource(new ByteArrayDataSource(data)); + } + + public static BundleManifest FromBytes(byte[] data, ulong offset) + { + return FromDataSource(new ByteArrayDataSource(data), offset); + } + + public static BundleManifest FromDataSource(IDataSource source) + { + long address = FindBundleManifestAddress(source); + if (address == -1) + throw new BadImageFormatException("File does not contain an AppHost bundle signature."); + + return FromDataSource(source, (ulong) address); + } + + public static BundleManifest FromDataSource(IDataSource source, ulong offset) + { + var reader = new BinaryStreamReader(source, 0, 0, (uint) source.Length) + { + Offset = offset + }; + + return FromReader(reader); + } + + public static BundleManifest FromReader(BinaryStreamReader reader) => new SerializedBundleManifest(reader); + + public static long FindBundleManifestAddress(IDataSource source) + { + for (ulong i = sizeof(ulong); i < source.Length - (ulong) BundleSignature.Length; i++) + { + bool fullMatch = true; + for (int j = 0; fullMatch && j < BundleSignature.Length; j++) + { + if (source[i + (ulong) j] != BundleSignature[j]) + fullMatch = false; + } + + if (fullMatch) + { + var reader = new BinaryStreamReader(source, i - sizeof(ulong), 0, 8); + ulong address = reader.ReadUInt64(); + if (source.IsValidAddress(address)) + return (long) address; + } + } + + return -1; + } + + protected virtual IList GetFiles() => new List(); + } +} diff --git a/src/AsmResolver.PE/DotNet/Bundles/BundleManifestFlags.cs b/src/AsmResolver.PE/DotNet/Bundles/BundleManifestFlags.cs new file mode 100644 index 000000000..c40ad02cb --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Bundles/BundleManifestFlags.cs @@ -0,0 +1,11 @@ +using System; + +namespace AsmResolver.PE.DotNet.Bundles +{ + [Flags] + public enum BundleManifestFlags : ulong + { + None = 0, + NetCoreApp3CompatibilityMode = 1 + } +} diff --git a/src/AsmResolver.PE/DotNet/Bundles/SerializedBundleFile.cs b/src/AsmResolver.PE/DotNet/Bundles/SerializedBundleFile.cs new file mode 100644 index 000000000..a6991a9e7 --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Bundles/SerializedBundleFile.cs @@ -0,0 +1,22 @@ +using AsmResolver.IO; + +namespace AsmResolver.PE.DotNet.Bundles +{ + public class SerializedBundleFile : BundleFile + { + public SerializedBundleFile(ref BinaryStreamReader reader, uint bundleVersionFormat) + { + ulong offset = reader.ReadUInt64(); + ulong size = reader.ReadUInt64(); + + if (bundleVersionFormat >= 6) + { + ulong compressedSize = reader.ReadUInt64(); + IsCompressed = compressedSize != 0; + } + + Type = (BundleFileType) reader.ReadByte(); + RelativePath = reader.ReadBinaryFormatterString(); + } + } +} diff --git a/src/AsmResolver.PE/DotNet/Bundles/SerializedBundleManifest.cs b/src/AsmResolver.PE/DotNet/Bundles/SerializedBundleManifest.cs new file mode 100644 index 000000000..8a8c3bdfa --- /dev/null +++ b/src/AsmResolver.PE/DotNet/Bundles/SerializedBundleManifest.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.PE.DotNet.Bundles +{ + public class SerializedBundleManifest : BundleManifest + { + private readonly uint _originalMajorVersion; + private readonly BinaryStreamReader _fileEntriesReader; + private readonly uint _originalFileCount; + + public SerializedBundleManifest(BinaryStreamReader reader) + { + MajorVersion = _originalMajorVersion = reader.ReadUInt32(); + MinorVersion = reader.ReadUInt32(); + _originalFileCount = reader.ReadUInt32(); + BundleID = reader.ReadBinaryFormatterString(); + + if (MajorVersion >= 2) + { + reader.Offset += 4 * sizeof(ulong); + Flags = (BundleManifestFlags) reader.ReadUInt64(); + } + + _fileEntriesReader = reader; + } + + protected override IList GetFiles() + { + var reader = _fileEntriesReader; + var result = new List(); + + for (int i = 0; i < _originalFileCount; i++) + result.Add(new SerializedBundleFile(ref reader, _originalMajorVersion)); + + return result; + } + } +} diff --git a/test/AsmResolver.PE.Tests/DotNet/Bundles/BundleManifestTest.cs b/test/AsmResolver.PE.Tests/DotNet/Bundles/BundleManifestTest.cs new file mode 100644 index 000000000..73166ad9c --- /dev/null +++ b/test/AsmResolver.PE.Tests/DotNet/Bundles/BundleManifestTest.cs @@ -0,0 +1,45 @@ +using System.Linq; +using AsmResolver.PE.DotNet.Bundles; +using Xunit; + +namespace AsmResolver.PE.Tests.DotNet.Bundles +{ + public class BundleManifestTest + { + [Fact] + public void ReadBundleManifestHeaderV1() + { + var manifest = BundleManifest.FromBytes(Properties.Resources.HelloWorld_SingleFile_V1); + Assert.Equal(1u, manifest.MajorVersion); + Assert.Equal("j7LK4is5ipe1CCtiafaTb8uhSOR7JhI=", manifest.BundleID); + Assert.Equal(new[] + { + "HelloWorld.dll", "HelloWorld.deps.json", "HelloWorld.runtimeconfig.json" + }, manifest.Files.Select(f => f.RelativePath)); + } + + [Fact] + public void ReadBundleManifestHeaderV2() + { + var manifest = BundleManifest.FromBytes(Properties.Resources.HelloWorld_SingleFile_V2); + Assert.Equal(2u, manifest.MajorVersion); + Assert.Equal("poUQ+RBCefcEL4xrSAXdE2I5M+5D_Pk=", manifest.BundleID); + Assert.Equal(new[] + { + "HelloWorld.dll", "HelloWorld.deps.json", "HelloWorld.runtimeconfig.json" + }, manifest.Files.Select(f => f.RelativePath)); + } + + [Fact] + public void ReadBundleManifestHeaderV6() + { + var manifest = BundleManifest.FromBytes(Properties.Resources.HelloWorld_SingleFile_V6); + Assert.Equal(6u, manifest.MajorVersion); + Assert.Equal("lc43r48XAQNxN7Cx8QQvO9JgZI5lqPA=", manifest.BundleID); + Assert.Equal(new[] + { + "HelloWorld.dll", "HelloWorld.deps.json", "HelloWorld.runtimeconfig.json" + }, manifest.Files.Select(f => f.RelativePath)); + } + } +} diff --git a/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs index 388c1419d..e47a1c7c9 100644 --- a/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs @@ -94,6 +94,27 @@ public class Resources { } } + public static byte[] HelloWorld_SingleFile_V1 { + get { + object obj = ResourceManager.GetObject("HelloWorld_SingleFile_V1", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] HelloWorld_SingleFile_V2 { + get { + object obj = ResourceManager.GetObject("HelloWorld_SingleFile_V2", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] HelloWorld_SingleFile_V6 { + get { + object obj = ResourceManager.GetObject("HelloWorld_SingleFile_V6", resourceCulture); + return ((byte[])(obj)); + } + } + public static byte[] SimpleDll { get { object obj = ResourceManager.GetObject("SimpleDll", resourceCulture); diff --git a/test/AsmResolver.PE.Tests/Properties/Resources.resx b/test/AsmResolver.PE.Tests/Properties/Resources.resx index e9a93ecd7..1b2a1e7be 100644 --- a/test/AsmResolver.PE.Tests/Properties/Resources.resx +++ b/test/AsmResolver.PE.Tests/Properties/Resources.resx @@ -39,6 +39,15 @@ ..\Resources\HelloWorld.TablesStream.ExtraData.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\HelloWorld.SingleFile.v1.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.SingleFile.v2.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.SingleFile.v6.exe;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 diff --git a/test/AsmResolver.PE.Tests/Resources/HelloWorld.SingleFile.v1.exe b/test/AsmResolver.PE.Tests/Resources/HelloWorld.SingleFile.v1.exe new file mode 100644 index 0000000000000000000000000000000000000000..67e3b49a25d845f18dc2cde124c962cd763fb1ea GIT binary patch literal 178015 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

*`G9?zzxGWwke>Bu)N*0UBnxfdBvi literal 0 HcmV?d00001 diff --git a/test/AsmResolver.PE.Tests/Resources/HelloWorld.SingleFile.v6.exe b/test/AsmResolver.PE.Tests/Resources/HelloWorld.SingleFile.v6.exe new file mode 100644 index 0000000000000000000000000000000000000000..e980f978ebf60bd5b692383cc7c04efd2baaefe5 GIT binary patch literal 152984 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 YH7w;qh$$2L5xkH~;_u literal 0 HcmV?d00001 From ed38bd62458e629b00a8fde81c0f6a76d1ece058 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 19 Mar 2022 14:58:46 +0100 Subject: [PATCH 059/184] Support for reading bundled file contents. --- .../DotNet/Bundles/BundleFile.cs | 47 ++++++++++++++++ .../DotNet/Bundles/SerializedBundleFile.cs | 12 +++- .../DotNet/Bundles/BundleFileTest.cs | 56 +++++++++++++++++++ 3 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 test/AsmResolver.PE.Tests/DotNet/Bundles/BundleFileTest.cs diff --git a/src/AsmResolver.PE/DotNet/Bundles/BundleFile.cs b/src/AsmResolver.PE/DotNet/Bundles/BundleFile.cs index 0b90418f7..a78abcad8 100644 --- a/src/AsmResolver.PE/DotNet/Bundles/BundleFile.cs +++ b/src/AsmResolver.PE/DotNet/Bundles/BundleFile.cs @@ -1,3 +1,8 @@ +using System; +using System.IO; +using System.IO.Compression; +using AsmResolver.IO; + namespace AsmResolver.PE.DotNet.Bundles { public class BundleFile @@ -33,6 +38,48 @@ public ISegment Contents set => _contents.Value = value; } + public bool CanRead => Contents is IReadableSegment; + protected virtual ISegment? GetContents() => null; + + public bool TryGetReader(out BinaryStreamReader reader) + { + if (Contents is IReadableSegment segment) + { + reader = segment.CreateReader(); + return true; + } + + reader = default; + return false; + } + + public byte[] GetData() => GetData(true); + + public byte[] GetData(bool decompressIfRequired) + { + if (TryGetReader(out var reader)) + { + byte[] contents = reader.ReadToEnd(); + if (decompressIfRequired && IsCompressed) + { + using var outputStream = new MemoryStream(); + + using var inputStream = new MemoryStream(contents); + using var deflate = new DeflateStream(inputStream, CompressionMode.Decompress); + { + deflate.CopyTo(outputStream); + } + + contents = outputStream.ToArray(); + } + + return contents; + } + + throw new InvalidOperationException("Contents of file is not readable."); + } + + public override string ToString() => RelativePath; } } diff --git a/src/AsmResolver.PE/DotNet/Bundles/SerializedBundleFile.cs b/src/AsmResolver.PE/DotNet/Bundles/SerializedBundleFile.cs index a6991a9e7..0918d2a2f 100644 --- a/src/AsmResolver.PE/DotNet/Bundles/SerializedBundleFile.cs +++ b/src/AsmResolver.PE/DotNet/Bundles/SerializedBundleFile.cs @@ -4,6 +4,8 @@ namespace AsmResolver.PE.DotNet.Bundles { public class SerializedBundleFile : BundleFile { + private readonly BinaryStreamReader _contentsReader; + public SerializedBundleFile(ref BinaryStreamReader reader, uint bundleVersionFormat) { ulong offset = reader.ReadUInt64(); @@ -12,11 +14,19 @@ public SerializedBundleFile(ref BinaryStreamReader reader, uint bundleVersionFor if (bundleVersionFormat >= 6) { ulong compressedSize = reader.ReadUInt64(); - IsCompressed = compressedSize != 0; + if (compressedSize != 0) + { + size = compressedSize; + IsCompressed = true; + } } Type = (BundleFileType) reader.ReadByte(); RelativePath = reader.ReadBinaryFormatterString(); + + _contentsReader = reader.ForkAbsolute(offset, (uint) size); } + + protected override ISegment GetContents() => _contentsReader.ReadSegment(_contentsReader.Length); } } diff --git a/test/AsmResolver.PE.Tests/DotNet/Bundles/BundleFileTest.cs b/test/AsmResolver.PE.Tests/DotNet/Bundles/BundleFileTest.cs new file mode 100644 index 000000000..a21f2195f --- /dev/null +++ b/test/AsmResolver.PE.Tests/DotNet/Bundles/BundleFileTest.cs @@ -0,0 +1,56 @@ +using System.Linq; +using System.Text; +using AsmResolver.PE.DotNet.Bundles; +using AsmResolver.PE.DotNet.Metadata.Strings; +using AsmResolver.PE.DotNet.Metadata.Tables; +using AsmResolver.PE.DotNet.Metadata.Tables.Rows; +using Xunit; + +namespace AsmResolver.PE.Tests.DotNet.Bundles +{ + public class BundleFileTest + { + [Fact] + public void ReadUncompressedStringContents() + { + var manifest = BundleManifest.FromBytes(Properties.Resources.HelloWorld_SingleFile_V6); + var file = manifest.Files.First(f => f.Type == BundleFileType.RuntimeConfigJson); + string contents = Encoding.UTF8.GetString(file.GetData()); + + Assert.Equal(@"{ + ""runtimeOptions"": { + ""tfm"": ""net6.0"", + ""framework"": { + ""name"": ""Microsoft.NETCore.App"", + ""version"": ""6.0.0"" + }, + ""configProperties"": { + ""System.Reflection.Metadata.MetadataUpdater.IsSupported"": false + } + } +}", contents); + } + + [Fact] + public void ReadUncompressedAssemblyContents() + { + var manifest = BundleManifest.FromBytes(Properties.Resources.HelloWorld_SingleFile_V6); + var bundleFile = manifest.Files.First(f => f.RelativePath == "HelloWorld.dll"); + + var embeddedImage = PEImage.FromBytes(bundleFile.GetData()); + var metadata = embeddedImage.DotNetDirectory!.Metadata!; + + uint nameIndex = metadata + .GetStream() + .GetTable() + .GetByRid(1) + .Name; + + string? name = metadata + .GetStream() + .GetStringByIndex(nameIndex); + + Assert.Equal("HelloWorld.dll", name); + } + } +} From 3de7eebcba1e94181dd4898c53b9cd96d2f1c08d Mon Sep 17 00:00:00 2001 From: Jeremy Pritts Date: Wed, 23 Mar 2022 00:09:20 -0400 Subject: [PATCH 060/184] use actual types for private readonly --- .../Builder/Metadata/Blob/BlobStreamBuffer.cs | 2 +- .../Builder/Metadata/Guid/GuidStreamBuffer.cs | 2 +- .../Builder/Metadata/UserStrings/UserStringsStreamBuffer.cs | 2 +- .../Builder/Resources/DotNetResourcesDirectoryBuffer.cs | 2 +- src/AsmResolver.DotNet/Collections/ParameterCollection.cs | 2 +- src/AsmResolver.DotNet/DefaultMetadataResolver.cs | 4 ++-- src/AsmResolver.PE.File/SerializedPEFile.cs | 2 +- src/AsmResolver.PE.Win32Resources/Icon/IconResource.cs | 4 ++-- src/AsmResolver.PE.Win32Resources/Version/StringTable.cs | 2 +- .../Version/VersionInfoResource.cs | 2 +- src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs | 2 +- 11 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/AsmResolver.DotNet/Builder/Metadata/Blob/BlobStreamBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Blob/BlobStreamBuffer.cs index b9283f323..691afffa6 100644 --- a/src/AsmResolver.DotNet/Builder/Metadata/Blob/BlobStreamBuffer.cs +++ b/src/AsmResolver.DotNet/Builder/Metadata/Blob/BlobStreamBuffer.cs @@ -14,7 +14,7 @@ namespace AsmResolver.DotNet.Builder.Metadata.Blob public class BlobStreamBuffer : IMetadataStreamBuffer { private readonly MemoryStream _rawStream = new(); - private readonly IBinaryStreamWriter _writer; + private readonly BinaryStreamWriter _writer; private readonly Dictionary _blobs = new(ByteArrayEqualityComparer.Instance); ///

diff --git a/src/AsmResolver.DotNet/Builder/Metadata/Guid/GuidStreamBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Guid/GuidStreamBuffer.cs index 710ff651a..aacf2ba7d 100644 --- a/src/AsmResolver.DotNet/Builder/Metadata/Guid/GuidStreamBuffer.cs +++ b/src/AsmResolver.DotNet/Builder/Metadata/Guid/GuidStreamBuffer.cs @@ -13,7 +13,7 @@ namespace AsmResolver.DotNet.Builder.Metadata.Guid public class GuidStreamBuffer : IMetadataStreamBuffer { private readonly MemoryStream _rawStream = new(); - private readonly IBinaryStreamWriter _writer; + private readonly BinaryStreamWriter _writer; private readonly Dictionary _guids = new(); /// diff --git a/src/AsmResolver.DotNet/Builder/Metadata/UserStrings/UserStringsStreamBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/UserStrings/UserStringsStreamBuffer.cs index 15df10919..c6706b3cf 100644 --- a/src/AsmResolver.DotNet/Builder/Metadata/UserStrings/UserStringsStreamBuffer.cs +++ b/src/AsmResolver.DotNet/Builder/Metadata/UserStrings/UserStringsStreamBuffer.cs @@ -14,7 +14,7 @@ namespace AsmResolver.DotNet.Builder.Metadata.UserStrings public class UserStringsStreamBuffer : IMetadataStreamBuffer { private readonly MemoryStream _rawStream = new(); - private readonly IBinaryStreamWriter _writer; + private readonly BinaryStreamWriter _writer; private readonly Dictionary _strings = new(); /// diff --git a/src/AsmResolver.DotNet/Builder/Resources/DotNetResourcesDirectoryBuffer.cs b/src/AsmResolver.DotNet/Builder/Resources/DotNetResourcesDirectoryBuffer.cs index 524b55ae2..073df1eec 100644 --- a/src/AsmResolver.DotNet/Builder/Resources/DotNetResourcesDirectoryBuffer.cs +++ b/src/AsmResolver.DotNet/Builder/Resources/DotNetResourcesDirectoryBuffer.cs @@ -12,7 +12,7 @@ namespace AsmResolver.DotNet.Builder.Resources public class DotNetResourcesDirectoryBuffer { private readonly MemoryStream _rawStream = new(); - private readonly IBinaryStreamWriter _writer; + private readonly BinaryStreamWriter _writer; private readonly Dictionary _dataOffsets = new(ByteArrayEqualityComparer.Instance); /// diff --git a/src/AsmResolver.DotNet/Collections/ParameterCollection.cs b/src/AsmResolver.DotNet/Collections/ParameterCollection.cs index 1d6639dcc..a2e8b4c5d 100644 --- a/src/AsmResolver.DotNet/Collections/ParameterCollection.cs +++ b/src/AsmResolver.DotNet/Collections/ParameterCollection.cs @@ -15,7 +15,7 @@ namespace AsmResolver.DotNet.Collections [DebuggerDisplay("Count = {" + nameof(Count) + "}")] public class ParameterCollection : IReadOnlyList { - private readonly IList _parameters = new List(); + private readonly List _parameters = new List(); private readonly MethodDefinition _owner; private bool _hasThis; diff --git a/src/AsmResolver.DotNet/DefaultMetadataResolver.cs b/src/AsmResolver.DotNet/DefaultMetadataResolver.cs index 2d0031695..30bab5716 100644 --- a/src/AsmResolver.DotNet/DefaultMetadataResolver.cs +++ b/src/AsmResolver.DotNet/DefaultMetadataResolver.cs @@ -12,7 +12,7 @@ namespace AsmResolver.DotNet /// public class DefaultMetadataResolver : IMetadataResolver { - private readonly IDictionary _typeCache; + private readonly ConcurrentDictionary _typeCache; private readonly SignatureComparer _comparer = new() { AcceptNewerAssemblyVersionNumbers = true @@ -55,7 +55,7 @@ public IAssemblyResolver AssemblyResolver // Check if type definition has changed since last lookup. if (typeDef.IsTypeOf(type.Namespace, type.Name)) return typeDef; - _typeCache.Remove(type); + _typeCache.TryRemove(type, out _); } return null; diff --git a/src/AsmResolver.PE.File/SerializedPEFile.cs b/src/AsmResolver.PE.File/SerializedPEFile.cs index ded5c5b04..44af78a71 100644 --- a/src/AsmResolver.PE.File/SerializedPEFile.cs +++ b/src/AsmResolver.PE.File/SerializedPEFile.cs @@ -10,7 +10,7 @@ namespace AsmResolver.PE.File /// public class SerializedPEFile : PEFile { - private readonly IList _sectionHeaders; + private readonly List _sectionHeaders; private readonly BinaryStreamReader _reader; /// diff --git a/src/AsmResolver.PE.Win32Resources/Icon/IconResource.cs b/src/AsmResolver.PE.Win32Resources/Icon/IconResource.cs index d7db9fd79..9904b1a1a 100644 --- a/src/AsmResolver.PE.Win32Resources/Icon/IconResource.cs +++ b/src/AsmResolver.PE.Win32Resources/Icon/IconResource.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; @@ -12,7 +12,7 @@ public class IconResource : IWin32Resource /// /// Used to keep track of icon groups. /// - private readonly IDictionary _entries = new Dictionary(); + private readonly Dictionary _entries = new Dictionary(); /// /// Obtains the icon group resources from the provided root win32 resources directory. diff --git a/src/AsmResolver.PE.Win32Resources/Version/StringTable.cs b/src/AsmResolver.PE.Win32Resources/Version/StringTable.cs index 2089b4eec..3d88a0b1a 100644 --- a/src/AsmResolver.PE.Win32Resources/Version/StringTable.cs +++ b/src/AsmResolver.PE.Win32Resources/Version/StringTable.cs @@ -122,7 +122,7 @@ public static StringTable FromReader(ref BinaryStreamReader reader) return new KeyValuePair(header.Key, value); } - private readonly IDictionary _entries = new Dictionary(); + private readonly Dictionary _entries = new Dictionary(); /// /// Creates a new string table. diff --git a/src/AsmResolver.PE.Win32Resources/Version/VersionInfoResource.cs b/src/AsmResolver.PE.Win32Resources/Version/VersionInfoResource.cs index 2b7334ef2..666591d7c 100644 --- a/src/AsmResolver.PE.Win32Resources/Version/VersionInfoResource.cs +++ b/src/AsmResolver.PE.Win32Resources/Version/VersionInfoResource.cs @@ -98,7 +98,7 @@ private static VersionTableEntry ReadNextEntry(ref BinaryStreamReader reader) } private FixedVersionInfo _fixedVersionInfo = new FixedVersionInfo(); - private readonly IDictionary _entries = new Dictionary(); + private readonly Dictionary _entries = new Dictionary(); /// public override string Key => VsVersionInfoKey; diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs index 017bb9490..4d903de19 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs @@ -31,7 +31,7 @@ public class TablesStream : SegmentBase, IMetadataStream /// public const string UncompressedStreamName = "#Schema"; - private readonly IDictionary _indexEncoders; + private readonly Dictionary _indexEncoders; private readonly LazyVariable> _tables; private readonly LazyVariable> _layouts; From 095ac6b2720d0675919aae71de5872da006ca2dc Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 23 Mar 2022 11:46:36 +0100 Subject: [PATCH 061/184] Move Bundles into AsmResolver.DotNet. --- .../Bundles/BundleFile.cs | 2 +- .../Bundles/BundleFileType.cs | 2 +- .../Bundles/BundleManifest.cs | 2 +- .../Bundles/BundleManifestFlags.cs | 2 +- .../Bundles/SerializedBundleFile.cs | 2 +- .../Bundles/SerializedBundleManifest.cs | 2 +- .../Bundles/BundleFileTest.cs | 20 ++++------------- .../Bundles/BundleManifestTest.cs | 4 ++-- .../Properties/Resources.Designer.cs | 21 ++++++++++++++++++ .../Properties/Resources.resx | 9 ++++++++ .../Resources/HelloWorld.SingleFile.v1.exe | Bin .../Resources/HelloWorld.SingleFile.v2.exe | Bin .../Resources/HelloWorld.SingleFile.v6.exe | Bin .../Properties/Resources.Designer.cs | 21 ------------------ .../Properties/Resources.resx | 9 -------- 15 files changed, 42 insertions(+), 54 deletions(-) rename src/{AsmResolver.PE/DotNet => AsmResolver.DotNet}/Bundles/BundleFile.cs (98%) rename src/{AsmResolver.PE/DotNet => AsmResolver.DotNet}/Bundles/BundleFileType.cs (80%) rename src/{AsmResolver.PE/DotNet => AsmResolver.DotNet}/Bundles/BundleManifest.cs (98%) rename src/{AsmResolver.PE/DotNet => AsmResolver.DotNet}/Bundles/BundleManifestFlags.cs (78%) rename src/{AsmResolver.PE/DotNet => AsmResolver.DotNet}/Bundles/SerializedBundleFile.cs (95%) rename src/{AsmResolver.PE/DotNet => AsmResolver.DotNet}/Bundles/SerializedBundleManifest.cs (96%) rename test/{AsmResolver.PE.Tests/DotNet => AsmResolver.DotNet.Tests}/Bundles/BundleFileTest.cs (66%) rename test/{AsmResolver.PE.Tests/DotNet => AsmResolver.DotNet.Tests}/Bundles/BundleManifestTest.cs (95%) rename test/{AsmResolver.PE.Tests => AsmResolver.DotNet.Tests}/Resources/HelloWorld.SingleFile.v1.exe (100%) rename test/{AsmResolver.PE.Tests => AsmResolver.DotNet.Tests}/Resources/HelloWorld.SingleFile.v2.exe (100%) rename test/{AsmResolver.PE.Tests => AsmResolver.DotNet.Tests}/Resources/HelloWorld.SingleFile.v6.exe (100%) diff --git a/src/AsmResolver.PE/DotNet/Bundles/BundleFile.cs b/src/AsmResolver.DotNet/Bundles/BundleFile.cs similarity index 98% rename from src/AsmResolver.PE/DotNet/Bundles/BundleFile.cs rename to src/AsmResolver.DotNet/Bundles/BundleFile.cs index a78abcad8..52458f2a9 100644 --- a/src/AsmResolver.PE/DotNet/Bundles/BundleFile.cs +++ b/src/AsmResolver.DotNet/Bundles/BundleFile.cs @@ -3,7 +3,7 @@ using System.IO.Compression; using AsmResolver.IO; -namespace AsmResolver.PE.DotNet.Bundles +namespace AsmResolver.DotNet.Bundles { public class BundleFile { diff --git a/src/AsmResolver.PE/DotNet/Bundles/BundleFileType.cs b/src/AsmResolver.DotNet/Bundles/BundleFileType.cs similarity index 80% rename from src/AsmResolver.PE/DotNet/Bundles/BundleFileType.cs rename to src/AsmResolver.DotNet/Bundles/BundleFileType.cs index 58ad0aead..e6fc46d45 100644 --- a/src/AsmResolver.PE/DotNet/Bundles/BundleFileType.cs +++ b/src/AsmResolver.DotNet/Bundles/BundleFileType.cs @@ -1,4 +1,4 @@ -namespace AsmResolver.PE.DotNet.Bundles +namespace AsmResolver.DotNet.Bundles { public enum BundleFileType { diff --git a/src/AsmResolver.PE/DotNet/Bundles/BundleManifest.cs b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs similarity index 98% rename from src/AsmResolver.PE/DotNet/Bundles/BundleManifest.cs rename to src/AsmResolver.DotNet/Bundles/BundleManifest.cs index ad1e22fe7..a360730c6 100644 --- a/src/AsmResolver.PE/DotNet/Bundles/BundleManifest.cs +++ b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs @@ -3,7 +3,7 @@ using System.Threading; using AsmResolver.IO; -namespace AsmResolver.PE.DotNet.Bundles +namespace AsmResolver.DotNet.Bundles { public class BundleManifest { diff --git a/src/AsmResolver.PE/DotNet/Bundles/BundleManifestFlags.cs b/src/AsmResolver.DotNet/Bundles/BundleManifestFlags.cs similarity index 78% rename from src/AsmResolver.PE/DotNet/Bundles/BundleManifestFlags.cs rename to src/AsmResolver.DotNet/Bundles/BundleManifestFlags.cs index c40ad02cb..86b2c0969 100644 --- a/src/AsmResolver.PE/DotNet/Bundles/BundleManifestFlags.cs +++ b/src/AsmResolver.DotNet/Bundles/BundleManifestFlags.cs @@ -1,6 +1,6 @@ using System; -namespace AsmResolver.PE.DotNet.Bundles +namespace AsmResolver.DotNet.Bundles { [Flags] public enum BundleManifestFlags : ulong diff --git a/src/AsmResolver.PE/DotNet/Bundles/SerializedBundleFile.cs b/src/AsmResolver.DotNet/Bundles/SerializedBundleFile.cs similarity index 95% rename from src/AsmResolver.PE/DotNet/Bundles/SerializedBundleFile.cs rename to src/AsmResolver.DotNet/Bundles/SerializedBundleFile.cs index 0918d2a2f..5b995a816 100644 --- a/src/AsmResolver.PE/DotNet/Bundles/SerializedBundleFile.cs +++ b/src/AsmResolver.DotNet/Bundles/SerializedBundleFile.cs @@ -1,6 +1,6 @@ using AsmResolver.IO; -namespace AsmResolver.PE.DotNet.Bundles +namespace AsmResolver.DotNet.Bundles { public class SerializedBundleFile : BundleFile { diff --git a/src/AsmResolver.PE/DotNet/Bundles/SerializedBundleManifest.cs b/src/AsmResolver.DotNet/Bundles/SerializedBundleManifest.cs similarity index 96% rename from src/AsmResolver.PE/DotNet/Bundles/SerializedBundleManifest.cs rename to src/AsmResolver.DotNet/Bundles/SerializedBundleManifest.cs index 8a8c3bdfa..40e4fcc67 100644 --- a/src/AsmResolver.PE/DotNet/Bundles/SerializedBundleManifest.cs +++ b/src/AsmResolver.DotNet/Bundles/SerializedBundleManifest.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using AsmResolver.IO; -namespace AsmResolver.PE.DotNet.Bundles +namespace AsmResolver.DotNet.Bundles { public class SerializedBundleManifest : BundleManifest { diff --git a/test/AsmResolver.PE.Tests/DotNet/Bundles/BundleFileTest.cs b/test/AsmResolver.DotNet.Tests/Bundles/BundleFileTest.cs similarity index 66% rename from test/AsmResolver.PE.Tests/DotNet/Bundles/BundleFileTest.cs rename to test/AsmResolver.DotNet.Tests/Bundles/BundleFileTest.cs index a21f2195f..cf0bddfb0 100644 --- a/test/AsmResolver.PE.Tests/DotNet/Bundles/BundleFileTest.cs +++ b/test/AsmResolver.DotNet.Tests/Bundles/BundleFileTest.cs @@ -1,12 +1,12 @@ using System.Linq; using System.Text; -using AsmResolver.PE.DotNet.Bundles; +using AsmResolver.DotNet.Bundles; using AsmResolver.PE.DotNet.Metadata.Strings; using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; using Xunit; -namespace AsmResolver.PE.Tests.DotNet.Bundles +namespace AsmResolver.DotNet.Tests.Bundles { public class BundleFileTest { @@ -37,20 +37,8 @@ public void ReadUncompressedAssemblyContents() var manifest = BundleManifest.FromBytes(Properties.Resources.HelloWorld_SingleFile_V6); var bundleFile = manifest.Files.First(f => f.RelativePath == "HelloWorld.dll"); - var embeddedImage = PEImage.FromBytes(bundleFile.GetData()); - var metadata = embeddedImage.DotNetDirectory!.Metadata!; - - uint nameIndex = metadata - .GetStream() - .GetTable() - .GetByRid(1) - .Name; - - string? name = metadata - .GetStream() - .GetStringByIndex(nameIndex); - - Assert.Equal("HelloWorld.dll", name); + var embeddedImage = ModuleDefinition.FromBytes(bundleFile.GetData()); + Assert.Equal("HelloWorld.dll", embeddedImage.Name); } } } diff --git a/test/AsmResolver.PE.Tests/DotNet/Bundles/BundleManifestTest.cs b/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs similarity index 95% rename from test/AsmResolver.PE.Tests/DotNet/Bundles/BundleManifestTest.cs rename to test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs index 73166ad9c..e25c841e6 100644 --- a/test/AsmResolver.PE.Tests/DotNet/Bundles/BundleManifestTest.cs +++ b/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs @@ -1,8 +1,8 @@ using System.Linq; -using AsmResolver.PE.DotNet.Bundles; +using AsmResolver.DotNet.Bundles; using Xunit; -namespace AsmResolver.PE.Tests.DotNet.Bundles +namespace AsmResolver.DotNet.Tests.Bundles { public class BundleManifestTest { diff --git a/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs index 141a67ba8..e824f4b21 100644 --- a/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs @@ -136,6 +136,27 @@ public class Resources { } } + public static byte[] HelloWorld_SingleFile_V1 { + get { + object obj = ResourceManager.GetObject("HelloWorld_SingleFile_V1", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] HelloWorld_SingleFile_V2 { + get { + object obj = ResourceManager.GetObject("HelloWorld_SingleFile_V2", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] HelloWorld_SingleFile_V6 { + get { + object obj = ResourceManager.GetObject("HelloWorld_SingleFile_V6", 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 c6ab516aa..0ff471e63 100644 --- a/test/AsmResolver.DotNet.Tests/Properties/Resources.resx +++ b/test/AsmResolver.DotNet.Tests/Properties/Resources.resx @@ -57,6 +57,15 @@ ..\Resources\HelloWorld.WithAttribute.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\HelloWorld.SingleFile.v1.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.SingleFile.v2.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.SingleFile.v6.exe;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.PE.Tests/Resources/HelloWorld.SingleFile.v1.exe b/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.SingleFile.v1.exe similarity index 100% rename from test/AsmResolver.PE.Tests/Resources/HelloWorld.SingleFile.v1.exe rename to test/AsmResolver.DotNet.Tests/Resources/HelloWorld.SingleFile.v1.exe diff --git a/test/AsmResolver.PE.Tests/Resources/HelloWorld.SingleFile.v2.exe b/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.SingleFile.v2.exe similarity index 100% rename from test/AsmResolver.PE.Tests/Resources/HelloWorld.SingleFile.v2.exe rename to test/AsmResolver.DotNet.Tests/Resources/HelloWorld.SingleFile.v2.exe diff --git a/test/AsmResolver.PE.Tests/Resources/HelloWorld.SingleFile.v6.exe b/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.SingleFile.v6.exe similarity index 100% rename from test/AsmResolver.PE.Tests/Resources/HelloWorld.SingleFile.v6.exe rename to test/AsmResolver.DotNet.Tests/Resources/HelloWorld.SingleFile.v6.exe diff --git a/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs index e47a1c7c9..388c1419d 100644 --- a/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs @@ -94,27 +94,6 @@ public class Resources { } } - public static byte[] HelloWorld_SingleFile_V1 { - get { - object obj = ResourceManager.GetObject("HelloWorld_SingleFile_V1", resourceCulture); - return ((byte[])(obj)); - } - } - - public static byte[] HelloWorld_SingleFile_V2 { - get { - object obj = ResourceManager.GetObject("HelloWorld_SingleFile_V2", resourceCulture); - return ((byte[])(obj)); - } - } - - public static byte[] HelloWorld_SingleFile_V6 { - get { - object obj = ResourceManager.GetObject("HelloWorld_SingleFile_V6", resourceCulture); - return ((byte[])(obj)); - } - } - public static byte[] SimpleDll { get { object obj = ResourceManager.GetObject("SimpleDll", resourceCulture); diff --git a/test/AsmResolver.PE.Tests/Properties/Resources.resx b/test/AsmResolver.PE.Tests/Properties/Resources.resx index 1b2a1e7be..e9a93ecd7 100644 --- a/test/AsmResolver.PE.Tests/Properties/Resources.resx +++ b/test/AsmResolver.PE.Tests/Properties/Resources.resx @@ -39,15 +39,6 @@ ..\Resources\HelloWorld.TablesStream.ExtraData.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - ..\Resources\HelloWorld.SingleFile.v1.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - ..\Resources\HelloWorld.SingleFile.v2.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - ..\Resources\HelloWorld.SingleFile.v6.exe;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 From 3f19b446ab44b687ba43dc3b3b0d529e16a9d8b2 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 23 Mar 2022 12:49:20 +0100 Subject: [PATCH 062/184] Basic write support for bundles via apphost templates. --- src/AsmResolver.DotNet/Bundles/BundleFile.cs | 16 +- .../Bundles/BundleManifest.cs | 169 ++++++++++++++++-- .../Bundles/SerializedBundleManifest.cs | 7 +- .../Bundles/BundleManifestTest.cs | 103 ++++++++++- test/AsmResolver.Tests/Runners/PERunner.cs | 11 ++ 5 files changed, 289 insertions(+), 17 deletions(-) diff --git a/src/AsmResolver.DotNet/Bundles/BundleFile.cs b/src/AsmResolver.DotNet/Bundles/BundleFile.cs index 52458f2a9..76e4cf33f 100644 --- a/src/AsmResolver.DotNet/Bundles/BundleFile.cs +++ b/src/AsmResolver.DotNet/Bundles/BundleFile.cs @@ -1,11 +1,12 @@ using System; using System.IO; using System.IO.Compression; +using AsmResolver.Collections; using AsmResolver.IO; namespace AsmResolver.DotNet.Bundles { - public class BundleFile + public class BundleFile : IOwnedCollectionElement { private readonly LazyVariable _contents; @@ -14,6 +15,19 @@ public BundleFile() _contents = new LazyVariable(GetContents); } + public BundleManifest? ParentManifest + { + get; + private set; + } + + /// + BundleManifest? IOwnedCollectionElement.Owner + { + get => ParentManifest; + set => ParentManifest = value; + } + public string RelativePath { get; diff --git a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs index a360730c6..522d798a2 100644 --- a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs +++ b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs @@ -1,19 +1,28 @@ using System; using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; using System.Threading; +using AsmResolver.Collections; using AsmResolver.IO; namespace AsmResolver.DotNet.Bundles { public class BundleManifest { - private static readonly byte[] BundleSignature = { + private static readonly byte[] BundleSignature = + { 0x8b, 0x12, 0x02, 0xb9, 0x6a, 0x61, 0x20, 0x38, 0x72, 0x7b, 0x93, 0x02, 0x14, 0xd7, 0xa0, 0x32, 0x13, 0xf5, 0xb9, 0xe6, 0xef, 0xae, 0x33, 0x18, 0xee, 0x3b, 0x2d, 0xce, 0x24, 0xb3, 0x6a, 0xae }; + private static readonly byte[] AppBinaryPathPlaceholder = + Encoding.UTF8.GetBytes("c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2"); + private IList? _files; protected BundleManifest() @@ -97,29 +106,165 @@ public static BundleManifest FromDataSource(IDataSource source, ulong offset) public static BundleManifest FromReader(BinaryStreamReader reader) => new SerializedBundleManifest(reader); - public static long FindBundleManifestAddress(IDataSource source) + private static long FindInFile(IDataSource source, byte[] data) { - for (ulong i = sizeof(ulong); i < source.Length - (ulong) BundleSignature.Length; i++) + for (ulong i = sizeof(ulong); i < source.Length - (ulong) data.Length; i++) { bool fullMatch = true; - for (int j = 0; fullMatch && j < BundleSignature.Length; j++) + for (int j = 0; fullMatch && j < data.Length; j++) { - if (source[i + (ulong) j] != BundleSignature[j]) + if (source[i + (ulong) j] != data[j]) fullMatch = false; } if (fullMatch) - { - var reader = new BinaryStreamReader(source, i - sizeof(ulong), 0, 8); - ulong address = reader.ReadUInt64(); - if (source.IsValidAddress(address)) - return (long) address; - } + return (long) i; } return -1; } - protected virtual IList GetFiles() => new List(); + private static long ReadBundleManifestAddress(IDataSource source, long signatureAddress) + { + var reader = new BinaryStreamReader(source, (ulong) signatureAddress - sizeof(ulong), 0, 8); + ulong manifestAddress = reader.ReadUInt64(); + + return source.IsValidAddress(manifestAddress) + ? (long) manifestAddress + : -1; + } + + public static long FindBundleManifestAddress(IDataSource source) + { + long signatureAddress = FindInFile(source, BundleSignature); + if (signatureAddress == -1) + return -1; + + return ReadBundleManifestAddress(source, signatureAddress); + } + + protected virtual IList GetFiles() => new OwnedCollection(this); + + public void WriteUsingTemplate(string appHostTemplatePath, Stream outputStream, string appBinaryPath) + { + WriteUsingTemplate(System.IO.File.ReadAllBytes(appHostTemplatePath), outputStream, appBinaryPath, + RuntimeInformation.IsOSPlatform(OSPlatform.Linux) + && RuntimeInformation.ProcessArchitecture + == Architecture.Arm64); + } + + public void WriteUsingTemplate(byte[] appHostTemplate, Stream outputStream, string appBinaryPath) + { + WriteUsingTemplate(appHostTemplate, outputStream, appBinaryPath, + RuntimeInformation.IsOSPlatform(OSPlatform.Linux) + && RuntimeInformation.ProcessArchitecture + == Architecture.Arm64); + } + + public void WriteUsingTemplate(byte[] appHostTemplate, Stream outputStream, string appBinaryPath, bool isArm64Linux) + { + WriteUsingTemplate(appHostTemplate, new BinaryStreamWriter(outputStream), appBinaryPath, isArm64Linux); + } + + public void WriteUsingTemplate(byte[] appHostTemplate, IBinaryStreamWriter writer, string appBinaryPath, bool isArm64Linux) + { + byte[] appBinaryPathBytes = Encoding.UTF8.GetBytes(appBinaryPath); + if (appBinaryPathBytes.Length > 1024) + throw new ArgumentException("Application binary path cannot exceed 1024 bytes."); + + long signatureAddress = FindInFile(new ByteArrayDataSource(appHostTemplate), BundleSignature); + if (signatureAddress == -1) + throw new ArgumentException("AppHost template does not contain the bundle signature."); + + long appBinaryPathAddress = FindInFile(new ByteArrayDataSource(appHostTemplate), AppBinaryPathPlaceholder); + if (appBinaryPathAddress == -1) + throw new ArgumentException("AppHost template does not contain the application binary path placeholder."); + + writer.WriteBytes(appHostTemplate); + writer.Offset = writer.Length; + ulong headerAddress = Write(writer, isArm64Linux); + + writer.Offset = (ulong) signatureAddress - sizeof(ulong); + writer.WriteUInt64(headerAddress); + + writer.Offset = (ulong) appBinaryPathAddress; + writer.WriteBytes(appBinaryPathBytes); + if (AppBinaryPathPlaceholder.Length > appBinaryPathBytes.Length) + writer.WriteZeroes(AppBinaryPathPlaceholder.Length - appBinaryPathBytes.Length); + } + + public ulong Write(IBinaryStreamWriter writer, bool isArm64Linux) + { + WriteFileContents(writer, isArm64Linux + ? 4096u + : 16u); + + ulong headerAddress = writer.Offset; + WriteManifestHeader(writer); + + return headerAddress; + } + + private void WriteFileContents(IBinaryStreamWriter writer, uint alignment) + { + for (int i = 0; i < Files.Count; i++) + { + var file = Files[i]; + + if (file.Type == BundleFileType.Assembly) + writer.Align(alignment); + + file.Contents.UpdateOffsets(writer.Offset, (uint) writer.Offset); + file.Contents.Write(writer); + } + } + + private void WriteManifestHeader(IBinaryStreamWriter writer) + { + writer.WriteUInt32(MajorVersion); + writer.WriteUInt32(MinorVersion); + writer.WriteInt32(Files.Count); + writer.WriteBinaryFormatterString(BundleID); + + if (MajorVersion >= 2) + { + WriteFileOffsetSizePair(writer, Files.FirstOrDefault(f => f.Type == BundleFileType.DepsJson)); + WriteFileOffsetSizePair(writer, Files.FirstOrDefault(f => f.Type == BundleFileType.RuntimeConfigJson)); + writer.WriteUInt64((ulong) Flags); + } + + WriteFileHeaders(writer); + } + + private void WriteFileHeaders(IBinaryStreamWriter writer) + { + for (int i = 0; i < Files.Count; i++) + { + var file = Files[i]; + + WriteFileOffsetSizePair(writer, file); + + if (MajorVersion >= 6) + writer.WriteUInt64(file.IsCompressed ? file.Contents.GetPhysicalSize() : 0); + + writer.WriteByte((byte) file.Type); + writer.WriteBinaryFormatterString(file.RelativePath); + } + } + + private static void WriteFileOffsetSizePair(IBinaryStreamWriter writer, BundleFile? file) + { + if (file is not null) + { + writer.WriteUInt64(file.Contents.Offset); + writer.WriteUInt64((ulong) file.GetData().Length); + } + else + { + writer.WriteUInt64(0); + writer.WriteUInt64(0); + } + } + } } diff --git a/src/AsmResolver.DotNet/Bundles/SerializedBundleManifest.cs b/src/AsmResolver.DotNet/Bundles/SerializedBundleManifest.cs index 40e4fcc67..0e67023ba 100644 --- a/src/AsmResolver.DotNet/Bundles/SerializedBundleManifest.cs +++ b/src/AsmResolver.DotNet/Bundles/SerializedBundleManifest.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using AsmResolver.Collections; using AsmResolver.IO; namespace AsmResolver.DotNet.Bundles @@ -7,13 +8,13 @@ public class SerializedBundleManifest : BundleManifest { private readonly uint _originalMajorVersion; private readonly BinaryStreamReader _fileEntriesReader; - private readonly uint _originalFileCount; + private readonly int _originalFileCount; public SerializedBundleManifest(BinaryStreamReader reader) { MajorVersion = _originalMajorVersion = reader.ReadUInt32(); MinorVersion = reader.ReadUInt32(); - _originalFileCount = reader.ReadUInt32(); + _originalFileCount = reader.ReadInt32(); BundleID = reader.ReadBinaryFormatterString(); if (MajorVersion >= 2) @@ -28,7 +29,7 @@ public SerializedBundleManifest(BinaryStreamReader reader) protected override IList GetFiles() { var reader = _fileEntriesReader; - var result = new List(); + var result = new OwnedCollection(this); for (int i = 0; i < _originalFileCount; i++) result.Add(new SerializedBundleFile(ref reader, _originalMajorVersion)); diff --git a/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs b/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs index e25c841e6..ab55a5c42 100644 --- a/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs +++ b/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs @@ -1,11 +1,22 @@ +using System; +using System.IO; using System.Linq; +using System.Runtime.InteropServices; using AsmResolver.DotNet.Bundles; +using AsmResolver.Tests.Runners; using Xunit; namespace AsmResolver.DotNet.Tests.Bundles { - public class BundleManifestTest + public class BundleManifestTest : IClassFixture { + private readonly TemporaryDirectoryFixture _fixture; + + public BundleManifestTest(TemporaryDirectoryFixture fixture) + { + _fixture = fixture; + } + [Fact] public void ReadBundleManifestHeaderV1() { @@ -41,5 +52,95 @@ public void ReadBundleManifestHeaderV6() "HelloWorld.dll", "HelloWorld.deps.json", "HelloWorld.runtimeconfig.json" }, manifest.Files.Select(f => f.RelativePath)); } + + [SkippableFact] + public void WriteBundleManifestV1Windows() + { + Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); + AssertReadWriteManifestWindowsPreservesOutput( + Properties.Resources.HelloWorld_SingleFile_V1, + "3.1", + "HelloWorld.dll", + $"Hello, World!{Environment.NewLine}"); + } + + [SkippableFact] + public void WriteBundleManifestV2Windows() + { + Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); + AssertReadWriteManifestWindowsPreservesOutput( + Properties.Resources.HelloWorld_SingleFile_V2, + "5.0", + "HelloWorld.dll", + $"Hello, World!{Environment.NewLine}"); + } + + [SkippableFact] + public void WriteBundleManifestV6Windows() + { + Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); + AssertReadWriteManifestWindowsPreservesOutput( + Properties.Resources.HelloWorld_SingleFile_V6, + "6.0", + "HelloWorld.dll", + $"Hello, World!{Environment.NewLine}"); + } + + private void AssertReadWriteManifestWindowsPreservesOutput( + byte[] inputFile, + string sdkVersion, + string fileName, + string expectedOutput) + { + var manifest = BundleManifest.FromBytes(inputFile); + + string sdkPath = Path.Combine(DotNetCorePathProvider.DefaultInstallationPath!, "sdk"); + string? sdkVersionPath = null; + foreach (string dir in Directory.GetDirectories(sdkPath)) + { + if (Path.GetFileName(dir).StartsWith(sdkVersion)) + { + sdkVersionPath = Path.Combine(dir); + break; + } + } + + if (string.IsNullOrEmpty(sdkVersionPath)) + { + throw new InvalidOperationException( + $"Could not find the apphost template for .NET SDK version {sdkVersion}. This is an indication that the test environment does not have this SDK installed."); + } + + string appHostPathTemplate = Path.Combine(sdkVersionPath, "AppHostTemplate", "apphost.exe"); + + using var stream = new MemoryStream(); + manifest.WriteUsingTemplate(appHostPathTemplate, stream, fileName); + + var newManifest = BundleManifest.FromBytes(stream.ToArray()); + AssertBundlesAreEqual(manifest, newManifest); + + string output = _fixture + .GetRunner() + .RunAndCaptureOutput(Path.ChangeExtension(fileName, ".exe"), stream.ToArray()); + Assert.Equal(expectedOutput, output); + } + + private static void AssertBundlesAreEqual(BundleManifest manifest, BundleManifest newManifest) + { + Assert.Equal(manifest.MajorVersion, newManifest.MajorVersion); + Assert.Equal(manifest.MinorVersion, newManifest.MinorVersion); + Assert.Equal(manifest.BundleID, newManifest.BundleID); + + Assert.Equal(manifest.Files.Count, newManifest.Files.Count); + for (int i = 0; i < manifest.Files.Count; i++) + { + var file = manifest.Files[i]; + var newFile = newManifest.Files[i]; + Assert.Equal(file.Type, newFile.Type); + Assert.Equal(file.RelativePath, newFile.RelativePath); + Assert.Equal(file.IsCompressed, newFile.IsCompressed); + Assert.Equal(file.GetData(), newFile.GetData()); + } + } } } diff --git a/test/AsmResolver.Tests/Runners/PERunner.cs b/test/AsmResolver.Tests/Runners/PERunner.cs index 3f5a674fb..c2e4ff405 100644 --- a/test/AsmResolver.Tests/Runners/PERunner.cs +++ b/test/AsmResolver.Tests/Runners/PERunner.cs @@ -58,6 +58,17 @@ public string Rebuild(PEFile peFile, string fileName, string testClass, string t return fullPath; } + public string RunAndCaptureOutput(string fileName, byte[] contents, string[]? arguments = null, + int timeout = 5000, + [CallerFilePath] string testClass = "File", + [CallerMemberName] string testMethod = "Test") + { + testClass = Path.GetFileNameWithoutExtension(testClass); + string testExecutablePath = GetTestExecutablePath(testClass, testMethod, fileName); + File.WriteAllBytes(testExecutablePath, contents); + return RunAndCaptureOutput(testExecutablePath, arguments, timeout); + } + public string RunAndCaptureOutput(string filePath, string[]? arguments = null, int timeout = 5000) { var info = GetStartInfo(filePath, arguments); From 9f86ff2016bef27d890d14991ed4a924ec4e4e55 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 23 Mar 2022 13:50:02 +0100 Subject: [PATCH 063/184] Add compression/decompression capabilities. --- src/AsmResolver.DotNet/Bundles/BundleFile.cs | 28 +++++++++++++- .../Bundles/BundleManifest.cs | 4 +- .../Bundles/BundleManifestTest.cs | 37 +++++++++++++------ 3 files changed, 55 insertions(+), 14 deletions(-) diff --git a/src/AsmResolver.DotNet/Bundles/BundleFile.cs b/src/AsmResolver.DotNet/Bundles/BundleFile.cs index 76e4cf33f..e01b3b494 100644 --- a/src/AsmResolver.DotNet/Bundles/BundleFile.cs +++ b/src/AsmResolver.DotNet/Bundles/BundleFile.cs @@ -80,7 +80,7 @@ public byte[] GetData(bool decompressIfRequired) using var outputStream = new MemoryStream(); using var inputStream = new MemoryStream(contents); - using var deflate = new DeflateStream(inputStream, CompressionMode.Decompress); + using (var deflate = new DeflateStream(inputStream, CompressionMode.Decompress)) { deflate.CopyTo(outputStream); } @@ -94,6 +94,32 @@ public byte[] GetData(bool decompressIfRequired) throw new InvalidOperationException("Contents of file is not readable."); } + public void Compress() + { + if (IsCompressed) + throw new InvalidOperationException("File is already compressed."); + + using var inputStream = new MemoryStream(GetData()); + + using var outputStream = new MemoryStream(); + using (var deflate = new DeflateStream(outputStream, CompressionLevel.Optimal)) + { + inputStream.CopyTo(deflate); + } + + Contents = new DataSegment(outputStream.ToArray()); + IsCompressed = true; + } + + public void Decompress() + { + if (!IsCompressed) + throw new InvalidOperationException("File is not compressed."); + + Contents = new DataSegment(GetData(true)); + IsCompressed = false; + } + public override string ToString() => RelativePath; } } diff --git a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs index 522d798a2..9200ad5ee 100644 --- a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs +++ b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs @@ -182,7 +182,7 @@ public void WriteUsingTemplate(byte[] appHostTemplate, IBinaryStreamWriter write writer.WriteBytes(appHostTemplate); writer.Offset = writer.Length; - ulong headerAddress = Write(writer, isArm64Linux); + ulong headerAddress = WriteManifest(writer, isArm64Linux); writer.Offset = (ulong) signatureAddress - sizeof(ulong); writer.WriteUInt64(headerAddress); @@ -193,7 +193,7 @@ public void WriteUsingTemplate(byte[] appHostTemplate, IBinaryStreamWriter write writer.WriteZeroes(AppBinaryPathPlaceholder.Length - appBinaryPathBytes.Length); } - public ulong Write(IBinaryStreamWriter writer, bool isArm64Linux) + public ulong WriteManifest(IBinaryStreamWriter writer, bool isArm64Linux) { WriteFileContents(writer, isArm64Linux ? 4096u diff --git a/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs b/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs index ab55a5c42..59b429d16 100644 --- a/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs +++ b/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Runtime.InteropServices; using AsmResolver.DotNet.Bundles; +using AsmResolver.IO; using AsmResolver.Tests.Runners; using Xunit; @@ -57,8 +58,8 @@ public void ReadBundleManifestHeaderV6() public void WriteBundleManifestV1Windows() { Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); - AssertReadWriteManifestWindowsPreservesOutput( - Properties.Resources.HelloWorld_SingleFile_V1, + AssertWriteManifestWindowsPreservesOutput( + BundleManifest.FromBytes(Properties.Resources.HelloWorld_SingleFile_V1), "3.1", "HelloWorld.dll", $"Hello, World!{Environment.NewLine}"); @@ -68,8 +69,8 @@ public void WriteBundleManifestV1Windows() public void WriteBundleManifestV2Windows() { Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); - AssertReadWriteManifestWindowsPreservesOutput( - Properties.Resources.HelloWorld_SingleFile_V2, + AssertWriteManifestWindowsPreservesOutput( + BundleManifest.FromBytes(Properties.Resources.HelloWorld_SingleFile_V2), "5.0", "HelloWorld.dll", $"Hello, World!{Environment.NewLine}"); @@ -79,21 +80,35 @@ public void WriteBundleManifestV2Windows() public void WriteBundleManifestV6Windows() { Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); - AssertReadWriteManifestWindowsPreservesOutput( - Properties.Resources.HelloWorld_SingleFile_V6, + AssertWriteManifestWindowsPreservesOutput( + BundleManifest.FromBytes(Properties.Resources.HelloWorld_SingleFile_V6), "6.0", "HelloWorld.dll", $"Hello, World!{Environment.NewLine}"); } - private void AssertReadWriteManifestWindowsPreservesOutput( - byte[] inputFile, + [SkippableFact] + public void MarkFilesAsCompressed() + { + Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); + var manifest = BundleManifest.FromBytes(Properties.Resources.HelloWorld_SingleFile_V6); + manifest.Files.First(f => f.RelativePath == "HelloWorld.dll").Compress(); + + using var stream = new MemoryStream(); + ulong address = manifest.WriteManifest(new BinaryStreamWriter(stream), false); + + var reader = ByteArrayDataSource.CreateReader(stream.ToArray()); + reader.Offset = address; + var newManifest = BundleManifest.FromReader(reader); + AssertBundlesAreEqual(manifest, newManifest); + } + + private void AssertWriteManifestWindowsPreservesOutput( + BundleManifest manifest, string sdkVersion, string fileName, string expectedOutput) { - var manifest = BundleManifest.FromBytes(inputFile); - string sdkPath = Path.Combine(DotNetCorePathProvider.DefaultInstallationPath!, "sdk"); string? sdkVersionPath = null; foreach (string dir in Directory.GetDirectories(sdkPath)) @@ -120,7 +135,7 @@ public void WriteBundleManifestV6Windows() AssertBundlesAreEqual(manifest, newManifest); string output = _fixture - .GetRunner() + .GetRunner() .RunAndCaptureOutput(Path.ChangeExtension(fileName, ".exe"), stream.ToArray()); Assert.Equal(expectedOutput, output); } From 7346b12241d4e0438d7e8571c947375e63d82f50 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 23 Mar 2022 14:09:51 +0100 Subject: [PATCH 064/184] Add read benchmarks, improve FindInFile performance by reading blocks instead of individual bytes. --- .../Bundles/BundleManifest.cs | 24 +++++++++++++----- .../DotNetBundleBenchmark.cs | 19 ++++++++++++++ .../Properties/Resources.Designer.cs | 10 ++++++++ .../Properties/Resources.resx | 3 +++ .../Resources/HelloWorld.SingleFile.v6.exe | Bin 0 -> 152984 bytes .../Bundles/BundleManifestTest.cs | 10 ++++++-- 6 files changed, 57 insertions(+), 9 deletions(-) create mode 100644 test/AsmResolver.Benchmarks/DotNetBundleBenchmark.cs create mode 100644 test/AsmResolver.Benchmarks/Resources/HelloWorld.SingleFile.v6.exe diff --git a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs index 9200ad5ee..9ab4574fb 100644 --- a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs +++ b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs @@ -108,17 +108,27 @@ public static BundleManifest FromDataSource(IDataSource source, ulong offset) private static long FindInFile(IDataSource source, byte[] data) { - for (ulong i = sizeof(ulong); i < source.Length - (ulong) data.Length; i++) + byte[] buffer = new byte[0x1000]; + + ulong start = 0; + while (start < source.Length) { - bool fullMatch = true; - for (int j = 0; fullMatch && j < data.Length; j++) + int read = source.ReadBytes(start, buffer, 0, buffer.Length); + + for (int i = sizeof(ulong); i < read - data.Length; i++) { - if (source[i + (ulong) j] != data[j]) - fullMatch = false; + bool fullMatch = true; + for (int j = 0; fullMatch && j < data.Length; j++) + { + if (buffer[i + j] != data[j]) + fullMatch = false; + } + + if (fullMatch) + return (long) start + i; } - if (fullMatch) - return (long) i; + start += (ulong) read; } return -1; diff --git a/test/AsmResolver.Benchmarks/DotNetBundleBenchmark.cs b/test/AsmResolver.Benchmarks/DotNetBundleBenchmark.cs new file mode 100644 index 000000000..00acd4fc9 --- /dev/null +++ b/test/AsmResolver.Benchmarks/DotNetBundleBenchmark.cs @@ -0,0 +1,19 @@ +using System.IO; +using AsmResolver.DotNet.Bundles; +using BenchmarkDotNet.Attributes; + +namespace AsmResolver.Benchmarks +{ + [MemoryDiagnoser] + public class DotNetBundleBenchmark + { + private static readonly byte[] HelloWorldSingleFileV6 = Properties.Resources.HelloWorld_SingleFile_V6; + private readonly MemoryStream _outputStream = new(); + + [Benchmark] + public void ReadBundleManifestV6() + { + _ = BundleManifest.FromBytes(HelloWorldSingleFileV6); + } + } +} diff --git a/test/AsmResolver.Benchmarks/Properties/Resources.Designer.cs b/test/AsmResolver.Benchmarks/Properties/Resources.Designer.cs index 86ed39706..ee7805eca 100644 --- a/test/AsmResolver.Benchmarks/Properties/Resources.Designer.cs +++ b/test/AsmResolver.Benchmarks/Properties/Resources.Designer.cs @@ -99,5 +99,15 @@ public class Resources { return ((byte[])(obj)); } } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + public static byte[] HelloWorld_SingleFile_V6 { + get { + object obj = ResourceManager.GetObject("HelloWorld_SingleFile_V6", resourceCulture); + return ((byte[])(obj)); + } + } } } diff --git a/test/AsmResolver.Benchmarks/Properties/Resources.resx b/test/AsmResolver.Benchmarks/Properties/Resources.resx index 83f78bc25..585e32796 100644 --- a/test/AsmResolver.Benchmarks/Properties/Resources.resx +++ b/test/AsmResolver.Benchmarks/Properties/Resources.resx @@ -130,4 +130,7 @@ ..\Resources\HelloWorld.ManyMethods.deflate;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\HelloWorld.SingleFile.v6.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + diff --git a/test/AsmResolver.Benchmarks/Resources/HelloWorld.SingleFile.v6.exe b/test/AsmResolver.Benchmarks/Resources/HelloWorld.SingleFile.v6.exe new file mode 100644 index 0000000000000000000000000000000000000000..e980f978ebf60bd5b692383cc7c04efd2baaefe5 GIT binary patch literal 152984 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 YH7w;qh$$2L5xkH~;_u literal 0 HcmV?d00001 diff --git a/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs b/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs index 59b429d16..740895546 100644 --- a/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs +++ b/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using AsmResolver.DotNet.Bundles; using AsmResolver.IO; @@ -107,7 +108,9 @@ public void MarkFilesAsCompressed() BundleManifest manifest, string sdkVersion, string fileName, - string expectedOutput) + string expectedOutput, + [CallerFilePath] string className = "File", + [CallerMemberName] string methodName = "Method") { string sdkPath = Path.Combine(DotNetCorePathProvider.DefaultInstallationPath!, "sdk"); string? sdkVersionPath = null; @@ -136,7 +139,10 @@ public void MarkFilesAsCompressed() string output = _fixture .GetRunner() - .RunAndCaptureOutput(Path.ChangeExtension(fileName, ".exe"), stream.ToArray()); + .RunAndCaptureOutput(Path.ChangeExtension(fileName, ".exe"), stream.ToArray(), null, + 5000, + className, + methodName); Assert.Equal(expectedOutput, output); } From d9ab29d4649cc0be4a031e44463861b45f5b0100 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 23 Mar 2022 14:42:46 +0100 Subject: [PATCH 065/184] Add xmldoc to bundle models. --- src/AsmResolver.DotNet/Bundles/BundleFile.cs | 60 +++++++- .../Bundles/BundleFileType.cs | 26 ++++ .../Bundles/BundleManifest.cs | 132 +++++++++++++++++- .../Bundles/BundleManifestFlags.cs | 10 ++ .../Bundles/SerializedBundleFile.cs | 10 ++ .../Bundles/SerializedBundleManifest.cs | 8 ++ 6 files changed, 242 insertions(+), 4 deletions(-) diff --git a/src/AsmResolver.DotNet/Bundles/BundleFile.cs b/src/AsmResolver.DotNet/Bundles/BundleFile.cs index e01b3b494..981588232 100644 --- a/src/AsmResolver.DotNet/Bundles/BundleFile.cs +++ b/src/AsmResolver.DotNet/Bundles/BundleFile.cs @@ -6,15 +6,26 @@ namespace AsmResolver.DotNet.Bundles { + /// + /// Represents a single file in a .NET bundle manifest. + /// public class BundleFile : IOwnedCollectionElement { private readonly LazyVariable _contents; - public BundleFile() + /// + /// Creates a new empty bundle file. + /// + /// The path of the file, relative to the root of the bundle. + public BundleFile(string relativePath) { + RelativePath = relativePath; _contents = new LazyVariable(GetContents); } + /// + /// Gets the parent manifest this file was added to. + /// public BundleManifest? ParentManifest { get; @@ -28,34 +39,61 @@ public BundleFile() set => ParentManifest = value; } + /// + /// Gets or sets the path to the file, relative to the root directory of the bundle. + /// public string RelativePath { get; set; } + /// + /// Gets or sets the type of the file. + /// public BundleFileType Type { get; set; } + /// + /// Gets or sets a value indicating whether the data stored in is compressed or not. + /// public bool IsCompressed { get; set; } + /// + /// Gets or sets the raw contents of the file. + /// public ISegment Contents { get => _contents.Value; set => _contents.Value = value; } + /// + /// Gets a value whether the contents of the file can be read using a . + /// public bool CanRead => Contents is IReadableSegment; + /// + /// Obtains the raw contents of the file. + /// + /// The contents. + /// + /// This method is called upon initialization of the property. + /// protected virtual ISegment? GetContents() => null; + /// + /// Attempts to create a that points to the start of the raw contents of the file. + /// + /// The reader. + /// true if the reader was constructed successfully, false otherwise. public bool TryGetReader(out BinaryStreamReader reader) { if (Contents is IReadableSegment segment) @@ -68,8 +106,17 @@ public bool TryGetReader(out BinaryStreamReader reader) return false; } + /// + /// Reads (and decompresses if necessary) the contents of the file. + /// + /// The contents. public byte[] GetData() => GetData(true); + /// + /// Reads the contents of the file. + /// + /// true if the contents should be decompressed or not when necessary. + /// The contents. public byte[] GetData(bool decompressIfRequired) { if (TryGetReader(out var reader)) @@ -94,6 +141,11 @@ public byte[] GetData(bool decompressIfRequired) throw new InvalidOperationException("Contents of file is not readable."); } + /// + /// Marks the file as compressed, compresses the file contents, and replaces the value of + /// with the result. + /// + /// Occurs when the file was already compressed. public void Compress() { if (IsCompressed) @@ -111,6 +163,11 @@ public void Compress() IsCompressed = true; } + /// + /// Marks the file as uncompressed, decompresses the file contents, and replaces the value of + /// with the result. + /// + /// Occurs when the file was already compressed. public void Decompress() { if (!IsCompressed) @@ -120,6 +177,7 @@ public void Decompress() IsCompressed = false; } + /// public override string ToString() => RelativePath; } } diff --git a/src/AsmResolver.DotNet/Bundles/BundleFileType.cs b/src/AsmResolver.DotNet/Bundles/BundleFileType.cs index e6fc46d45..8ac16b290 100644 --- a/src/AsmResolver.DotNet/Bundles/BundleFileType.cs +++ b/src/AsmResolver.DotNet/Bundles/BundleFileType.cs @@ -1,12 +1,38 @@ namespace AsmResolver.DotNet.Bundles { + /// + /// Provides members defining all possible file types that can be stored in a bundled .NET application. + /// public enum BundleFileType { + /// + /// Indicates the file type is unknown. + /// Unknown, + + /// + /// Indicates the file is a .NET assembly. + /// Assembly, + + /// + /// Indicates the file is a native binary. + /// NativeBinary, + + /// + /// Indicates the file is the deps.json file associated to a .NET assembly. + /// DepsJson, + + /// + /// Indicates the file is the runtimeconfig.json file associated to a .NET assembly. + /// RuntimeConfigJson, + + /// + /// Indicates the file contains symbols. + /// Symbols } } diff --git a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs index 9ab4574fb..a57a7b894 100644 --- a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs +++ b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs @@ -10,6 +10,9 @@ namespace AsmResolver.DotNet.Bundles { + /// + /// Represents a set of bundled files embedded in a .NET application host or single-file host. + /// public class BundleManifest { private static readonly byte[] BundleSignature = @@ -25,41 +28,76 @@ public class BundleManifest private IList? _files; + /// + /// Initializes an empty bundle manifest. + /// protected BundleManifest() { + BundleID = string.Empty; } - public BundleManifest(uint version, string bundleId) + /// + /// Creates a new bundle manifest. + /// + /// The file format version. + /// The unique bundle manifest identifier. + public BundleManifest(uint majorVersionNumber, string bundleId) { - MajorVersion = version; + MajorVersion = majorVersionNumber; MinorVersion = 0; BundleID = bundleId; } + /// + /// Gets or sets the major file format version of the bundle. + /// + /// + /// Version numbers recognized by the CLR are: + /// + /// 1 for .NET Core 3.1 + /// 2 for .NET 5.0 + /// 6 for .NET 6.0 + /// + /// public uint MajorVersion { get; set; } + /// + /// Gets or sets the minor file format version of the bundle. + /// + /// + /// This value is ignored by the CLR and should be set to 0. + /// public uint MinorVersion { get; set; } + /// + /// Gets or sets the unique identifier for the bundle manifest. + /// public string BundleID { get; set; } + /// + /// Gets or sets flags associated to the bundle. + /// public BundleManifestFlags Flags { get; set; } + /// + /// Gets a collection of files stored in the bundle. + /// public IList Files { get @@ -70,21 +108,42 @@ public IList Files } } + /// + /// Attempts to automatically locate and parse the bundle header in the provided file. + /// + /// The path to the file to read. + /// The read manifest. public static BundleManifest FromFile(string filePath) { - return FromBytes(System.IO.File.ReadAllBytes(filePath)); + return FromBytes(File.ReadAllBytes(filePath)); } + /// + /// Attempts to automatically locate and parse the bundle header in the provided file. + /// + /// The raw contents of the file to read. + /// The read manifest. public static BundleManifest FromBytes(byte[] data) { return FromDataSource(new ByteArrayDataSource(data)); } + /// + /// Parses the bundle header in the provided file at the provided address. + /// + /// The raw contents of the file to read. + /// The address within the file to start reading the bundle at. + /// The read manifest. public static BundleManifest FromBytes(byte[] data, ulong offset) { return FromDataSource(new ByteArrayDataSource(data), offset); } + /// + /// Attempts to automatically locate and parse the bundle header in the provided file. + /// + /// The raw contents of the file to read. + /// The read manifest. public static BundleManifest FromDataSource(IDataSource source) { long address = FindBundleManifestAddress(source); @@ -94,6 +153,12 @@ public static BundleManifest FromDataSource(IDataSource source) return FromDataSource(source, (ulong) address); } + /// + /// Parses the bundle header in the provided file at the provided address. + /// + /// The raw contents of the file to read. + /// The address within the file to start reading the bundle at. + /// The read manifest. public static BundleManifest FromDataSource(IDataSource source, ulong offset) { var reader = new BinaryStreamReader(source, 0, 0, (uint) source.Length) @@ -104,10 +169,18 @@ public static BundleManifest FromDataSource(IDataSource source, ulong offset) return FromReader(reader); } + /// + /// Parses the bundle header from the provided input stream. + /// + /// The input stream pointing to the start of the bundle to read. + /// The read manifest. public static BundleManifest FromReader(BinaryStreamReader reader) => new SerializedBundleManifest(reader); private static long FindInFile(IDataSource source, byte[] data) { + // Note: For performance reasons, we read data from the data source in blocks, such that we avoid + // virtual-dispatch calls and do the searching directly on a byte array instead. + byte[] buffer = new byte[0x1000]; ulong start = 0; @@ -144,6 +217,11 @@ private static long ReadBundleManifestAddress(IDataSource source, long signature : -1; } + /// + /// Attempts to find the start of the bundle header in the provided file. + /// + /// The file to locate the bundle header in. + /// The offset, or -1 if none was found. public static long FindBundleManifestAddress(IDataSource source) { long signatureAddress = FindInFile(source, BundleSignature); @@ -153,8 +231,25 @@ public static long FindBundleManifestAddress(IDataSource source) return ReadBundleManifestAddress(source, signatureAddress); } + /// + /// Obtains the list of files stored in the bundle. + /// + /// The files + /// + /// This method is called upon initialization of the property. + /// protected virtual IList GetFiles() => new OwnedCollection(this); + /// + /// Constructs a new application host file based on the bundle manifest. + /// + /// + /// The path to the application host file template to use. By default this is stored in + /// <DOTNET-INSTALLATION-PATH>/sdk/<version>/AppHostTemplate or + /// <DOTNET-INSTALLATION-PATH>/packs/Microsoft.NETCore.App.Host.<runtime-identifier>/<version>/runtimes/<runtime-identifier>/native. + /// + /// The output stream to write to. + /// The name of the file in the bundle that contains the entry point of the application. public void WriteUsingTemplate(string appHostTemplatePath, Stream outputStream, string appBinaryPath) { WriteUsingTemplate(System.IO.File.ReadAllBytes(appHostTemplatePath), outputStream, appBinaryPath, @@ -163,6 +258,12 @@ public void WriteUsingTemplate(string appHostTemplatePath, Stream outputStream, == Architecture.Arm64); } + /// + /// Constructs a new application host file based on the bundle manifest. + /// + /// The application host template file to use. + /// The output stream to write to. + /// The name of the file in the bundle that contains the entry point of the application. public void WriteUsingTemplate(byte[] appHostTemplate, Stream outputStream, string appBinaryPath) { WriteUsingTemplate(appHostTemplate, outputStream, appBinaryPath, @@ -171,11 +272,25 @@ public void WriteUsingTemplate(byte[] appHostTemplate, Stream outputStream, stri == Architecture.Arm64); } + /// + /// Constructs a new application host file based on the bundle manifest. + /// + /// The application host template file to use. + /// The output stream to write to. + /// The name of the file in the bundle that contains the entry point of the application. + /// true if the application host is a Linux ELF binary targeting ARM64. public void WriteUsingTemplate(byte[] appHostTemplate, Stream outputStream, string appBinaryPath, bool isArm64Linux) { WriteUsingTemplate(appHostTemplate, new BinaryStreamWriter(outputStream), appBinaryPath, isArm64Linux); } + /// + /// Constructs a new application host file based on the bundle manifest. + /// + /// The application host template file to use. + /// The output stream to write to. + /// The name of the file in the bundle that contains the entry point of the application. + /// true if the application host is a Linux ELF binary targeting ARM64. public void WriteUsingTemplate(byte[] appHostTemplate, IBinaryStreamWriter writer, string appBinaryPath, bool isArm64Linux) { byte[] appBinaryPathBytes = Encoding.UTF8.GetBytes(appBinaryPath); @@ -203,6 +318,17 @@ public void WriteUsingTemplate(byte[] appHostTemplate, IBinaryStreamWriter write writer.WriteZeroes(AppBinaryPathPlaceholder.Length - appBinaryPathBytes.Length); } + /// + /// Writes the manifest to an output stream. + /// + /// The output stream to write to. + /// true if the application host is a Linux ELF binary targeting ARM64. + /// The address of the bundle header. + /// + /// This does not necessarily produce a working executable file, it only writes the contents of the entire manifest, + /// without a host application that invokes the manifest. If you want to produce a runnable executable, use one + /// of the or one of its overloads instead. + /// public ulong WriteManifest(IBinaryStreamWriter writer, bool isArm64Linux) { WriteFileContents(writer, isArm64Linux diff --git a/src/AsmResolver.DotNet/Bundles/BundleManifestFlags.cs b/src/AsmResolver.DotNet/Bundles/BundleManifestFlags.cs index 86b2c0969..0a2a9a277 100644 --- a/src/AsmResolver.DotNet/Bundles/BundleManifestFlags.cs +++ b/src/AsmResolver.DotNet/Bundles/BundleManifestFlags.cs @@ -2,10 +2,20 @@ namespace AsmResolver.DotNet.Bundles { + /// + /// Provides members defining all flags that can be assigned to a bundle manifest. + /// [Flags] public enum BundleManifestFlags : ulong { + /// + /// Indicates no flags were assigned. + /// None = 0, + + /// + /// Indicates the bundle was compiled in .NET Core 3 compatibility mode. + /// NetCoreApp3CompatibilityMode = 1 } } diff --git a/src/AsmResolver.DotNet/Bundles/SerializedBundleFile.cs b/src/AsmResolver.DotNet/Bundles/SerializedBundleFile.cs index 5b995a816..79581a262 100644 --- a/src/AsmResolver.DotNet/Bundles/SerializedBundleFile.cs +++ b/src/AsmResolver.DotNet/Bundles/SerializedBundleFile.cs @@ -2,11 +2,20 @@ namespace AsmResolver.DotNet.Bundles { + /// + /// Represents a lazily initialized implementation of that is read from an existing file. + /// public class SerializedBundleFile : BundleFile { private readonly BinaryStreamReader _contentsReader; + /// + /// Reads a bundle file entry from the provided input stream. + /// + /// The input stream. + /// The file format version of the bundle. public SerializedBundleFile(ref BinaryStreamReader reader, uint bundleVersionFormat) + : base(string.Empty) { ulong offset = reader.ReadUInt64(); ulong size = reader.ReadUInt64(); @@ -27,6 +36,7 @@ public SerializedBundleFile(ref BinaryStreamReader reader, uint bundleVersionFor _contentsReader = reader.ForkAbsolute(offset, (uint) size); } + /// protected override ISegment GetContents() => _contentsReader.ReadSegment(_contentsReader.Length); } } diff --git a/src/AsmResolver.DotNet/Bundles/SerializedBundleManifest.cs b/src/AsmResolver.DotNet/Bundles/SerializedBundleManifest.cs index 0e67023ba..2fce34fae 100644 --- a/src/AsmResolver.DotNet/Bundles/SerializedBundleManifest.cs +++ b/src/AsmResolver.DotNet/Bundles/SerializedBundleManifest.cs @@ -4,12 +4,19 @@ namespace AsmResolver.DotNet.Bundles { + /// + /// Represents a lazily initialized implementation of that is read from an existing file. + /// public class SerializedBundleManifest : BundleManifest { private readonly uint _originalMajorVersion; private readonly BinaryStreamReader _fileEntriesReader; private readonly int _originalFileCount; + /// + /// Reads a bundle manifest from the provided input stream. + /// + /// The input stream. public SerializedBundleManifest(BinaryStreamReader reader) { MajorVersion = _originalMajorVersion = reader.ReadUInt32(); @@ -26,6 +33,7 @@ public SerializedBundleManifest(BinaryStreamReader reader) _fileEntriesReader = reader; } + /// protected override IList GetFiles() { var reader = _fileEntriesReader; From e4df13df02ef795a2c46b6eee83a3cdbadfddddf Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 23 Mar 2022 17:34:15 +0100 Subject: [PATCH 066/184] Add convenience BundleFile constructors. --- src/AsmResolver.DotNet/Bundles/BundleFile.cs | 24 ++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/AsmResolver.DotNet/Bundles/BundleFile.cs b/src/AsmResolver.DotNet/Bundles/BundleFile.cs index 981588232..84dba4101 100644 --- a/src/AsmResolver.DotNet/Bundles/BundleFile.cs +++ b/src/AsmResolver.DotNet/Bundles/BundleFile.cs @@ -23,6 +23,30 @@ public BundleFile(string relativePath) _contents = new LazyVariable(GetContents); } + /// + /// Creates a new bundle file. + /// + /// The path of the file, relative to the root of the bundle. + /// The type of the file. + /// The contents of the file. + public BundleFile(string relativePath, BundleFileType type, byte[] contents) + : this(relativePath, type, new DataSegment(contents)) + { + } + + /// + /// Creates a new empty bundle file. + /// + /// The path of the file, relative to the root of the bundle. + /// The type of the file. + /// The contents of the file. + public BundleFile(string relativePath, BundleFileType type, ISegment contents) + { + RelativePath = relativePath; + Type = type; + _contents = new LazyVariable(contents); + } + /// /// Gets the parent manifest this file was added to. /// From 303564599df3f80366b92f1dacaad4de2773072d Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 23 Mar 2022 18:25:17 +0100 Subject: [PATCH 067/184] Add documentation on bundles API. --- docs/dotnet/bundles.rst | 147 ++++++++++++++++++ docs/index.rst | 1 + .../Bundles/BundleManifest.cs | 23 +++ 3 files changed, 171 insertions(+) create mode 100644 docs/dotnet/bundles.rst diff --git a/docs/dotnet/bundles.rst b/docs/dotnet/bundles.rst new file mode 100644 index 000000000..488260461 --- /dev/null +++ b/docs/dotnet/bundles.rst @@ -0,0 +1,147 @@ +AppHost / SingleFileHost Bundles +================================ + +Since the release of .NET Core 3.1, it is possible to deploy .NET assemblies as a single binary. These files are executables that do not contain a traditional .NET metadata header, and run natively on the underlying operating system via a platform-specific application host bootstrapper. + +AsmResolver supports extracting the embedded files from these types of binaries. Additionally, given an application host template provided by the .NET SDK, AsmResolver also supports constructing new bundles as well. All relevant code is found in the following namespace: + +.. code-block:: csharp + + using AsmResolver.DotNet.Bundles; + + +Creating Bundles +---------------- + +.NET bundles are represented using the ``BundleManifest`` class. Creating new bundles can be done using any of the constructors: + +.. code-block:: csharp + + var manifest = new BundleManifest( + majorVersionNumber: 6, + bundleId: ""); + + +The major version number refers to the file format that should be used when saving the manifest. Below an overview of the values that are recognized by the CLR: + ++----------------------+----------------------------+ +| .NET Version Number | Bundle File Format Version | ++======================+============================+ +| .NET Core 3.1 | 1 | ++----------------------+----------------------------+ +| .NET 5.0 | 2 | ++----------------------+----------------------------+ +| .NET 6.0 | 6 | ++----------------------+----------------------------+ + +It is also possible to change the version number as well as the bundle ID later, since these values are exposed as mutable properties ``MajorVersion`` and ``BundleID`` + +.. code-block:: csharp + + manifest.MajorVersion = 6; + manifest.BundleID = GenerateRandomID(); + + +Reading Bundles +--------------- + +Reading and extracting existing bundle manifests from an executable can be done by using one of the ``FromXXX`` methods: + +.. code-block:: csharp + + var manifest = BundleManifest.FromFile(@"C:\Path\To\Executable.exe"); + +.. code-block:: csharp + + byte[] contents = ... + var manifest = BundleManifest.FromBytes(contents); + +.. code-block:: csharp + + IDataSource contents = ... + var manifest = BundleManifest.FromDataSource(contents); + + +Similar to the official .NET bundler and extractor, the methods above locate the bundle in the file by looking for a specific signature first. However, official implementations of the application hosting program itself actually do not verify or use this signature in any shape or form. This means that a third party can replace or remove this signature, or write their own implementation of an application host that does not adhere to this standard, and thus throw off static analysis of the file. + +AsmResolver does not provide built-in alternative heuristics for finding the right start address of the bundle header. However, it is possible to implement one yourself and provide the resulting start address in one of the overloads of the ``FromXXX`` methods: + +.. code-block:: csharp + + byte[] contents = ... + ulong bundleAddress = ... + var manifest = BundleManifest.FromBytes(contents, bundleAddress); + +.. code-block:: csharp + + IDataSource contents = ... + ulong bundleAddress = ... + var manifest = BundleManifest.FromDataSource(contents, bundleAddress); + + +Writing Bundles +--------------- + +Constructing new bundled executable files requires a template file that AsmResolver can base the final output on. This is similar how .NET compilers themselves do this as well. By default, the .NET SDK installs template binaries in either ``/sdk//AppHostTemplate`` or ``/packs/Microsoft.NETCore.App.Host.//runtimes//native``. It is then possible to use ``WriteUsingTemplate`` to construct a new binary. + +.. code-block:: csharp + + BundleManifest manifest = ... + var manifest = manifest.WriteUsingTemplate( + @"C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Host.win-x64\6.0.0\runtimes\win-x64\native\apphost.exe", + @"C:\User\Admin\HelloWorld.exe", + @"HelloWorld.dll"); + + +Typically on Windows, use an ```apphost.exe`` template if you want to construct a native binary that is framework dependent, and ``singlefilehost.exe`` for a fully self-contained binary. + + +Managing Files +-------------- + +Files in a bundle are represented using the ``BundleFile`` class, and are exposed by the ``BundleManifest.Files`` property. Both the class as well as the list itself is fully mutable, and thus can be used to add, remove or modify files in the bundle. + +Creating a new file can be done using the constructors: + +.. code-block:: csharp + + var newFile = new BundleFile( + relativePath: "HelloWorld.dll", + type: BundleFileType.Assembly, + contents: System.IO.File.ReadAllBytes(@"C:\Binaries\HelloWorld.dll")); + + manifest.Files.Add(newFile); + + +It is also possible to iterate over all files and inspect their contents using ``GetData``: + +.. code-block:: csharp + + foreach (var file in manifest.Files) + { + string path = file.RelativePath; + byte[] contents = file.GetData(); + + Console.WriteLine($"Extracting {path}..."); + System.IO.File.WriteAllBytes(path, contents); + } + + +Changing the contents of an existing file can be done using the ``Contents`` property. + +.. code-block:: csharp + + BundleFile file = ... + file.Contents = new DataSegment(new byte[] { 1, 2, 3, 4 }); + + +If the bundle manifest is put into a single-file host template (e.g. ``singlefilehost.exe``), then files can also be compressed or decompressed: + +.. code-block:: csharp + + file.Compress(); + // file.Contents now contains the compressed version of the data and file.IsCompressed = true + + file.Decompress(); + // file.Contents now contains the decompressed version of the data and file.IsCompressed = false + diff --git a/docs/index.rst b/docs/index.rst index 6e56f82d4..89f3b0aeb 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -62,4 +62,5 @@ Table of Contents: dotnet/cloning dotnet/token-allocation dotnet/type-memory-layout + dotnet/bundles dotnet/advanced-pe-image-building.rst diff --git a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs index a57a7b894..3fa82444e 100644 --- a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs +++ b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs @@ -231,6 +231,13 @@ public static long FindBundleManifestAddress(IDataSource source) return ReadBundleManifestAddress(source, signatureAddress); } + /// + /// Gets a value indicating whether the provided data source contains a conventional bundled assembly signature. + /// + /// The file to locate the bundle header in. + /// true if a bundle signature was found, false otherwise. + public static bool IsBundledAssembly(IDataSource source) => FindBundleManifestAddress(source) != -1; + /// /// Obtains the list of files stored in the bundle. /// @@ -240,6 +247,22 @@ public static long FindBundleManifestAddress(IDataSource source) /// protected virtual IList GetFiles() => new OwnedCollection(this); + /// + /// Constructs a new application host file based on the bundle manifest. + /// + /// + /// The path to the application host file template to use. By default this is stored in + /// <DOTNET-INSTALLATION-PATH>/sdk/<version>/AppHostTemplate or + /// <DOTNET-INSTALLATION-PATH>/packs/Microsoft.NETCore.App.Host.<runtime-identifier>/<version>/runtimes/<runtime-identifier>/native. + /// + /// The output path to write to. + /// The name of the file in the bundle that contains the entry point of the application. + public void WriteUsingTemplate(string appHostTemplatePath, string outputPath, string appBinaryPath) + { + using var fs = File.Create(outputPath); + WriteUsingTemplate(appHostTemplatePath, fs, appBinaryPath); + } + /// /// Constructs a new application host file based on the bundle manifest. /// From bbfdddbf01e3964ef97a7e7701a96696f5a9be9d Mon Sep 17 00:00:00 2001 From: Jeremy Pritts Date: Sat, 26 Mar 2022 14:15:55 -0400 Subject: [PATCH 068/184] redirect object base type when member cloning --- .../Cloning/CloneContextAwareReferenceImporter.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/AsmResolver.DotNet/Cloning/CloneContextAwareReferenceImporter.cs b/src/AsmResolver.DotNet/Cloning/CloneContextAwareReferenceImporter.cs index 4fd836cfe..fb67345e0 100644 --- a/src/AsmResolver.DotNet/Cloning/CloneContextAwareReferenceImporter.cs +++ b/src/AsmResolver.DotNet/Cloning/CloneContextAwareReferenceImporter.cs @@ -40,5 +40,13 @@ public override IMethodDefOrRef ImportMethod(IMethodDefOrRef method) ? (IMethodDefOrRef) clonedMethod : base.ImportMethod(method); } + + /// + protected override ITypeDefOrRef ImportType(TypeReference type) + { + return type.Namespace == "System" && type.Name == nameof(System.Object) + ? _context.Module.CorLibTypeFactory.Object.Type + : base.ImportType(type); + } } -} \ No newline at end of file +} From 61a712d6e63e45536c795a313243b9f1ec3b8360 Mon Sep 17 00:00:00 2001 From: Jeremy Pritts Date: Sun, 27 Mar 2022 01:46:12 -0400 Subject: [PATCH 069/184] use an instantiator for creating the reference importer --- .../Cloning/CloneContextAwareReferenceImporter.cs | 7 +++++-- src/AsmResolver.DotNet/Cloning/MemberCloneContext.cs | 8 +++++--- src/AsmResolver.DotNet/Cloning/MemberCloner.cs | 8 ++++++-- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/AsmResolver.DotNet/Cloning/CloneContextAwareReferenceImporter.cs b/src/AsmResolver.DotNet/Cloning/CloneContextAwareReferenceImporter.cs index 4fd836cfe..235f5a277 100644 --- a/src/AsmResolver.DotNet/Cloning/CloneContextAwareReferenceImporter.cs +++ b/src/AsmResolver.DotNet/Cloning/CloneContextAwareReferenceImporter.cs @@ -5,7 +5,10 @@ namespace AsmResolver.DotNet.Cloning /// public class CloneContextAwareReferenceImporter : ReferenceImporter { - private readonly MemberCloneContext _context; + /// + /// The working space for this member cloning procedure. + /// + protected readonly MemberCloneContext _context; /// /// Creates a new instance of the class. @@ -41,4 +44,4 @@ public override IMethodDefOrRef ImportMethod(IMethodDefOrRef method) : base.ImportMethod(method); } } -} \ No newline at end of file +} diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloneContext.cs b/src/AsmResolver.DotNet/Cloning/MemberCloneContext.cs index 5f6fa56e9..43092c939 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloneContext.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloneContext.cs @@ -12,10 +12,12 @@ public class MemberCloneContext /// Creates a new instance of the class. /// /// The target module to copy the cloned members into. - public MemberCloneContext(ModuleDefinition module) + /// The instantiator for creating the reference importer + public MemberCloneContext(ModuleDefinition module, + Func? importerInstantiator = null) { Module = module ?? throw new ArgumentNullException(nameof(module)); - Importer = new CloneContextAwareReferenceImporter(this); + Importer = importerInstantiator?.Invoke(this) ?? new CloneContextAwareReferenceImporter(this); } /// @@ -42,4 +44,4 @@ public ReferenceImporter Importer get; } = new Dictionary(); } -} \ No newline at end of file +} diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.cs index eae611adb..2d1a6022b 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 Func? _importerInstantiator; private readonly ModuleDefinition _targetModule; private readonly HashSet _typesToClone = new(); @@ -29,9 +30,12 @@ public partial class MemberCloner /// Creates a new instance of the class. /// /// The target module to copy the members into. - public MemberCloner(ModuleDefinition targetModule) + /// The instantiator for creating the reference importer + public MemberCloner(ModuleDefinition targetModule, + Func? importerInstantiator = null) { _targetModule = targetModule ?? throw new ArgumentNullException(nameof(targetModule)); + _importerInstantiator = importerInstantiator; } /// @@ -220,7 +224,7 @@ public MemberCloner Include(EventDefinition @event) /// An object representing the result of the cloning process. public MemberCloneResult Clone() { - var context = new MemberCloneContext(_targetModule); + var context = new MemberCloneContext(_targetModule, _importerInstantiator); CreateMemberStubs(context); DeepCopyMembers(context); From 4176cd86016740d9b48d8f50f47c9d589105fa14 Mon Sep 17 00:00:00 2001 From: Washi Date: Mon, 28 Mar 2022 10:23:50 +0200 Subject: [PATCH 070/184] Add PEFile.EofData. --- src/AsmResolver.PE.File/PEFile.cs | 36 +++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/src/AsmResolver.PE.File/PEFile.cs b/src/AsmResolver.PE.File/PEFile.cs index e89eac9ff..f8274652c 100644 --- a/src/AsmResolver.PE.File/PEFile.cs +++ b/src/AsmResolver.PE.File/PEFile.cs @@ -21,6 +21,7 @@ public class PEFile : IPEFile public const uint ValidPESignature = 0x4550; // "PE\0\0" private readonly LazyVariable _extraSectionData; + private readonly LazyVariable _eofData; private IList? _sections; /// @@ -43,6 +44,7 @@ public PEFile(DosHeader dosHeader, FileHeader fileHeader, OptionalHeader optiona FileHeader = fileHeader ?? throw new ArgumentNullException(nameof(fileHeader)); OptionalHeader = optionalHeader ?? throw new ArgumentNullException(nameof(optionalHeader)); _extraSectionData = new LazyVariable(GetExtraSectionData); + _eofData = new LazyVariable(GetEofData); MappingMode = PEMappingMode.Unmapped; } @@ -101,6 +103,15 @@ public PEMappingMode MappingMode set => _extraSectionData.Value = value; } + /// + /// Gets or sets the data appended to the end of the file (EoF), if available. + /// + public ISegment? EofData + { + get => _eofData.Value; + set => _eofData.Value = value; + } + /// /// Reads an unmapped PE file from the disk. /// @@ -360,7 +371,7 @@ public bool TryCreateReaderAtRva(uint rva, uint size, out BinaryStreamReader rea /// public void UpdateHeaders() { - var oldSections = Sections.Select(_ => _.CreateHeader()).ToList(); + var oldSections = Sections.Select(x => x.CreateHeader()).ToList(); FileHeader.NumberOfSections = (ushort) Sections.Count; @@ -383,6 +394,8 @@ public void UpdateHeaders() var lastSection = Sections[Sections.Count - 1]; OptionalHeader.SizeOfImage = lastSection.Rva + lastSection.GetVirtualSize().Align(OptionalHeader.SectionAlignment); + + EofData?.UpdateOffsets(lastSection.Offset + lastSection.GetPhysicalSize(), OptionalHeader.SizeOfImage); } /// @@ -474,28 +487,30 @@ public void Write(IBinaryStreamWriter writer) // NT headers writer.Offset = DosHeader.NextHeaderOffset; - writer.WriteUInt32(ValidPESignature); FileHeader.Write(writer); OptionalHeader.Write(writer); // Section headers. writer.Offset = OptionalHeader.Offset + FileHeader.SizeOfOptionalHeader; - foreach (var section in Sections) - section.CreateHeader().Write(writer); + for (int i = 0; i < Sections.Count; i++) + Sections[i].CreateHeader().Write(writer); // Data between section headers and sections. ExtraSectionData?.Write(writer); // Sections. - writer.Offset = OptionalHeader.SizeOfHeaders; - foreach (var section in Sections) + for (int i = 0; i < Sections.Count; i++) { + var section = Sections[i]; writer.Offset = section.Offset; section.Contents?.Write(writer); writer.Align(OptionalHeader.FileAlignment); } + + // EOF Data. + EofData?.Write(writer); } /// @@ -515,5 +530,14 @@ public void Write(IBinaryStreamWriter writer) /// This method is called upon the initialization of the property. /// protected virtual ISegment? GetExtraSectionData() => null; + + /// + /// Obtains any data appended to the end of the file (EoF). + /// + /// The extra data. + /// + /// This method is called upon the initialization of the property. + /// + private ISegment? GetEofData() => null; } } From 56ec9aa8a522d4bdb44ced6dcb79a4c8f558fd4f Mon Sep 17 00:00:00 2001 From: Washi Date: Mon, 28 Mar 2022 10:41:58 +0200 Subject: [PATCH 071/184] Read support EofData. --- src/AsmResolver.PE.File/PEFile.cs | 2 +- src/AsmResolver.PE.File/SerializedPEFile.cs | 16 +++++++++++++++- test/AsmResolver.PE.File.Tests/PEFileTest.cs | 16 ++++++++++++++++ .../Properties/Resources.Designer.cs | 10 ++++++++++ .../Properties/Resources.resx | 3 +++ .../Resources/HelloWorld.EOF.exe | Bin 0 -> 4634 bytes 6 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 test/AsmResolver.PE.File.Tests/Resources/HelloWorld.EOF.exe diff --git a/src/AsmResolver.PE.File/PEFile.cs b/src/AsmResolver.PE.File/PEFile.cs index f8274652c..276e4aa0b 100644 --- a/src/AsmResolver.PE.File/PEFile.cs +++ b/src/AsmResolver.PE.File/PEFile.cs @@ -538,6 +538,6 @@ public void Write(IBinaryStreamWriter writer) /// /// This method is called upon the initialization of the property. /// - private ISegment? GetEofData() => null; + protected virtual ISegment? GetEofData() => null; } } diff --git a/src/AsmResolver.PE.File/SerializedPEFile.cs b/src/AsmResolver.PE.File/SerializedPEFile.cs index ded5c5b04..73a3d1e7d 100644 --- a/src/AsmResolver.PE.File/SerializedPEFile.cs +++ b/src/AsmResolver.PE.File/SerializedPEFile.cs @@ -46,7 +46,7 @@ public SerializedPEFile(in BinaryStreamReader reader, PEMappingMode mode) // Data between section headers and sections. int extraSectionDataLength = (int) (DosHeader.Offset + OptionalHeader.SizeOfHeaders - _reader.Offset); if (extraSectionDataLength != 0) - ExtraSectionData = DataSegment.FromReader(ref _reader, extraSectionDataLength); + ExtraSectionData = _reader.ReadSegment((uint) extraSectionDataLength); } /// @@ -77,5 +77,19 @@ protected override IList GetSections() return result; } + /// + protected override ISegment? GetEofData() + { + if (MappingMode != PEMappingMode.Unmapped) + return null; + + var lastSection = _sectionHeaders[_sectionHeaders.Count - 1]; + ulong offset = lastSection.PointerToRawData + lastSection.SizeOfRawData; + + var reader = _reader.ForkAbsolute(offset); + return reader.Length > 0 + ? reader.ReadSegment(reader.Length) + : null; + } } } diff --git a/test/AsmResolver.PE.File.Tests/PEFileTest.cs b/test/AsmResolver.PE.File.Tests/PEFileTest.cs index c7fe42964..ec0d0909f 100644 --- a/test/AsmResolver.PE.File.Tests/PEFileTest.cs +++ b/test/AsmResolver.PE.File.Tests/PEFileTest.cs @@ -2,6 +2,7 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Text; using AsmResolver.IO; using AsmResolver.PE.File.Headers; using AsmResolver.Tests.Runners; @@ -168,5 +169,20 @@ public void SectionsInMappedBinaryShouldUseVirtualAddressesAsOffset() Assert.Equal(expected, actual); } } + + [Fact] + public void PEWithNoEofData() + { + var file = PEFile.FromBytes(Properties.Resources.HelloWorld); + Assert.Null(file.EofData); + } + + [Fact] + public void ReadEofData() + { + var file = PEFile.FromBytes(Properties.Resources.HelloWorld_EOF); + var readable = Assert.IsAssignableFrom(file.EofData); + Assert.Equal(Encoding.ASCII.GetBytes("abcdefghijklmnopqrstuvwxyz"), readable.ToArray()); + } } } diff --git a/test/AsmResolver.PE.File.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.PE.File.Tests/Properties/Resources.Designer.cs index 0e74fe94c..f2f817fd5 100644 --- a/test/AsmResolver.PE.File.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.PE.File.Tests/Properties/Resources.Designer.cs @@ -80,6 +80,16 @@ public class Resources { } } + /// + /// Looks up a localized resource of type System.Byte[]. + /// + public static byte[] HelloWorld_EOF { + get { + object obj = ResourceManager.GetObject("HelloWorld_EOF", resourceCulture); + return ((byte[])(obj)); + } + } + /// /// Looks up a localized resource of type System.Byte[]. /// diff --git a/test/AsmResolver.PE.File.Tests/Properties/Resources.resx b/test/AsmResolver.PE.File.Tests/Properties/Resources.resx index 293c5e462..c33ee0174 100644 --- a/test/AsmResolver.PE.File.Tests/Properties/Resources.resx +++ b/test/AsmResolver.PE.File.Tests/Properties/Resources.resx @@ -124,6 +124,9 @@ ..\Resources\HelloWorld.dump;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\HelloWorld.EOF.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\Resources\NativeMemoryDemos.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 diff --git a/test/AsmResolver.PE.File.Tests/Resources/HelloWorld.EOF.exe b/test/AsmResolver.PE.File.Tests/Resources/HelloWorld.EOF.exe new file mode 100644 index 0000000000000000000000000000000000000000..90549fef61c5defcf86eb24ba3a349abc4eccb0f GIT binary patch literal 4634 zcmeHK|8HEy5ufMS#&K#pQ3^_E8#db$^ot*8n6Sw|y_|A@+UQ6D+Tc2O@ zz8r7goqd2%)B;i@KtTd25zJTaM3bdOZI_2+p)%saw`Cb6j_Ig6o|vLp0qw>Y=hAx{k|WfJW!t z{;Ot&SXDi|3q~F}&~+H*^dFhde{b5*8e{wF<7V0%OOK5{z^Z%51$+X_h94*T8^*sf zo{vJuuPB!I02lP@7>8H$^bg?d*6^9t`7Fjh*!l;ir`@20ksDk$&fH0~lZk&CV{}*S z&p^!6p}`h9Rvb#kxf9J9@z_hhrY51Im zf7dXp;YY?MxCdxctBmz>6b#rO7<*|{<2k|mK1Yum!=Q_rdTQSbSjh)y(1*rOdI47L z0qmvyfc^AMz};BS@Bs~{G@RG)K@ESPp+oo4on!&;C6jhxrAd3~7~t3FLBL@;5BM$m zIp7%m0`R-^HsB2X7V!IeJ`dP^xfvlCrQ2RWgud18?!1%}i$S^O%2VViI|yB;L}#Vz z2Iqj6X(sSh;7U3dI+0v(d`VeZs#Pjh$(2(PPQ6r%BuyzLy^`A~I8o%e}1>3|x0AwNT5omCOWQ)p7OYigc|ljj1-j zX>l%u^LDhI!3r$D(PpgF{K)a7o{yZ8<2q3Xw;eK5Zb`~FR3tq!Sz{*F;}aol$oV#r zphe5^Themfl{WiJrX2-gb3W@>6+cjsW2@#=Q*rF34D&LqJGNBG>0l!qDo7lFqUF*> zsMUwR|13#-!7c?P=p0}Oyi5nd96R)8>Nh*@Uw-w-TNAH7^QRwEw=j&p9fFJ=Aba*O zIoM4AeXl)IT-!f>{Ynq0Zw&SsN!mZapG%?Zu5+PPT?+i!En8OEo5DsIY$^kC{FM-$ zG3ah{X|~Y17~f9fGs(FIDh9vWVr3mwb*;t{uF!5?BU&6WK4egTJMPF32HkKaJhK9#Cz zC7;db?0wfh&aExKn7L3{Exz1f9odt`=R4f2YJH8b< zQWdj7v?QZq+o@tux>!tnD*7@SPj@iQYPm%Dvs1^ubDWah?`iH14}bKtqfft7e@|Tb z%ZU$uQc5ZZoBtT&g0bzbSz%(%Lo-1*yCwA{)!9Kxv+QyO;CAC)5*dws3H~1u=+d=S z;1#tG+vnofREHsP6+dx{c^10dAsM5w{KhsG-(d(DQ(E@oSCV?Ed6m@mCe=YSyRIT1#A;nw(^c_`P62tA!jjM-K9ogifE< zk+rxTBCco!a4pO#@GVq#2V7tCiC*a*hph%|iXi1+zH$pqyR}Rj?}qp{P9ro~xEE3)hstCMl_VvkZwF;0^~uy~VeaUO zlu(i7mn}E&!=FNqNw+SG9g;Z=F1X~k*IN#OC%0vid9nNU7lgPpRR3!OS%auumQr`EBiR)N;mgHmJH z_{Fdw>(Ui2!^xDT=KXqbNrov=bEa&(auCLK%T+RQN-G(;g*8pnM{crxVx$!W2uzGL yPv{hX4PWpk;{SuL9{+;d`K5XPlL%NPyDZl$8_vZ`uIC5UheH+B>YH1QNB#}c`^dHc literal 0 HcmV?d00001 From 878fb587f33ad296ea9bde1c311a6f771eb3f7c9 Mon Sep 17 00:00:00 2001 From: Washi Date: Mon, 28 Mar 2022 10:45:33 +0200 Subject: [PATCH 072/184] EOF writer tests. --- test/AsmResolver.PE.File.Tests/PEFileTest.cs | 60 +++++++++++++++++++- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/test/AsmResolver.PE.File.Tests/PEFileTest.cs b/test/AsmResolver.PE.File.Tests/PEFileTest.cs index ec0d0909f..7a1c47463 100644 --- a/test/AsmResolver.PE.File.Tests/PEFileTest.cs +++ b/test/AsmResolver.PE.File.Tests/PEFileTest.cs @@ -181,8 +181,64 @@ public void PEWithNoEofData() public void ReadEofData() { var file = PEFile.FromBytes(Properties.Resources.HelloWorld_EOF); - var readable = Assert.IsAssignableFrom(file.EofData); - Assert.Equal(Encoding.ASCII.GetBytes("abcdefghijklmnopqrstuvwxyz"), readable.ToArray()); + byte[] data = Assert.IsAssignableFrom(file.EofData).ToArray(); + Assert.Equal(Encoding.ASCII.GetBytes("abcdefghijklmnopqrstuvwxyz"), data); + } + + [Fact] + public void AddNewEofData() + { + byte[] expected = { 1, 2, 3, 4 }; + + var file = PEFile.FromBytes(Properties.Resources.HelloWorld); + Assert.Null(file.EofData); + file.EofData = new DataSegment(expected); + + using var stream = new MemoryStream(); + file.Write(stream); + byte[] newFileBytes = stream.ToArray(); + + Assert.Equal(expected, newFileBytes[^expected.Length..]); + + var newFile = PEFile.FromBytes(newFileBytes); + var readable = Assert.IsAssignableFrom(newFile.EofData); + Assert.Equal(expected, readable.ToArray()); + } + + [Fact] + public void ModifyExistingEofData() + { + var file = PEFile.FromBytes(Properties.Resources.HelloWorld_EOF); + byte[] data = Assert.IsAssignableFrom(file.EofData).ToArray(); + Array.Reverse(data); + file.EofData = new DataSegment(data); + + using var stream = new MemoryStream(); + file.Write(stream); + byte[] newFileBytes = stream.ToArray(); + + Assert.Equal(data, newFileBytes[^data.Length..]); + + var newFile = PEFile.FromBytes(newFileBytes); + byte[] newData = Assert.IsAssignableFrom(newFile.EofData).ToArray(); + Assert.Equal(data, newData); + } + + [Fact] + public void RemoveExistingEofData() + { + var file = PEFile.FromBytes(Properties.Resources.HelloWorld_EOF); + byte[] originalData = Assert.IsAssignableFrom(file.EofData).ToArray(); + file.EofData = null; + + using var stream = new MemoryStream(); + file.Write(stream); + byte[] newFileBytes = stream.ToArray(); + + Assert.NotEqual(originalData, newFileBytes[^originalData.Length..]); + + var newFile = PEFile.FromBytes(newFileBytes); + Assert.Null(newFile.EofData); } } } From c24c6fa44c612503f57aaaa1ee0ac923af57fdb4 Mon Sep 17 00:00:00 2001 From: Washi Date: Mon, 28 Mar 2022 12:02:07 +0200 Subject: [PATCH 073/184] Extract BundlerParameters from WriteUsingTemplate parameters. Update apphost win32resources and subsystem. --- .../Bundles/BundleManifest.cs | 135 +++++++----- .../Bundles/BundlerParameters.cs | 199 ++++++++++++++++++ .../Bundles/BundleManifestTest.cs | 2 +- 3 files changed, 281 insertions(+), 55 deletions(-) create mode 100644 src/AsmResolver.DotNet/Bundles/BundlerParameters.cs diff --git a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs index 3fa82444e..1423ef80d 100644 --- a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs +++ b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs @@ -7,6 +7,10 @@ using System.Threading; using AsmResolver.Collections; using AsmResolver.IO; +using AsmResolver.PE; +using AsmResolver.PE.File; +using AsmResolver.PE.File.Headers; +using AsmResolver.PE.Win32Resources.Builder; namespace AsmResolver.DotNet.Bundles { @@ -250,87 +254,54 @@ public static long FindBundleManifestAddress(IDataSource source) /// /// Constructs a new application host file based on the bundle manifest. /// - /// - /// The path to the application host file template to use. By default this is stored in - /// <DOTNET-INSTALLATION-PATH>/sdk/<version>/AppHostTemplate or - /// <DOTNET-INSTALLATION-PATH>/packs/Microsoft.NETCore.App.Host.<runtime-identifier>/<version>/runtimes/<runtime-identifier>/native. - /// - /// The output path to write to. - /// The name of the file in the bundle that contains the entry point of the application. - public void WriteUsingTemplate(string appHostTemplatePath, string outputPath, string appBinaryPath) + /// The path of the file to write to. + /// The parameters to use for bundling all files into a single executable. + public void WriteUsingTemplate(string outputPath, in BundlerParameters parameters) { using var fs = File.Create(outputPath); - WriteUsingTemplate(appHostTemplatePath, fs, appBinaryPath); + WriteUsingTemplate(fs, parameters); } /// /// Constructs a new application host file based on the bundle manifest. /// - /// - /// The path to the application host file template to use. By default this is stored in - /// <DOTNET-INSTALLATION-PATH>/sdk/<version>/AppHostTemplate or - /// <DOTNET-INSTALLATION-PATH>/packs/Microsoft.NETCore.App.Host.<runtime-identifier>/<version>/runtimes/<runtime-identifier>/native. - /// /// The output stream to write to. - /// The name of the file in the bundle that contains the entry point of the application. - public void WriteUsingTemplate(string appHostTemplatePath, Stream outputStream, string appBinaryPath) + /// The parameters to use for bundling all files into a single executable. + public void WriteUsingTemplate(Stream outputStream, in BundlerParameters parameters) { - WriteUsingTemplate(System.IO.File.ReadAllBytes(appHostTemplatePath), outputStream, appBinaryPath, - RuntimeInformation.IsOSPlatform(OSPlatform.Linux) - && RuntimeInformation.ProcessArchitecture - == Architecture.Arm64); + WriteUsingTemplate(new BinaryStreamWriter(outputStream), parameters); } /// /// Constructs a new application host file based on the bundle manifest. /// - /// The application host template file to use. - /// The output stream to write to. - /// The name of the file in the bundle that contains the entry point of the application. - public void WriteUsingTemplate(byte[] appHostTemplate, Stream outputStream, string appBinaryPath) + /// The output stream to write to. + /// The parameters to use for bundling all files into a single executable. + public void WriteUsingTemplate(IBinaryStreamWriter writer, BundlerParameters parameters) { - WriteUsingTemplate(appHostTemplate, outputStream, appBinaryPath, - RuntimeInformation.IsOSPlatform(OSPlatform.Linux) - && RuntimeInformation.ProcessArchitecture - == Architecture.Arm64); - } + var appBinaryEntry = Files.FirstOrDefault(f => f.RelativePath == parameters.ApplicationBinaryPath); + if (appBinaryEntry is null) + throw new ArgumentException($"Application {parameters.ApplicationBinaryPath} does not exist within the bundle."); - /// - /// Constructs a new application host file based on the bundle manifest. - /// - /// The application host template file to use. - /// The output stream to write to. - /// The name of the file in the bundle that contains the entry point of the application. - /// true if the application host is a Linux ELF binary targeting ARM64. - public void WriteUsingTemplate(byte[] appHostTemplate, Stream outputStream, string appBinaryPath, bool isArm64Linux) - { - WriteUsingTemplate(appHostTemplate, new BinaryStreamWriter(outputStream), appBinaryPath, isArm64Linux); - } + if (!parameters.IsArm64Linux) + EnsureAppHostPEHeadersAreUpToDate(ref parameters); - /// - /// Constructs a new application host file based on the bundle manifest. - /// - /// The application host template file to use. - /// The output stream to write to. - /// The name of the file in the bundle that contains the entry point of the application. - /// true if the application host is a Linux ELF binary targeting ARM64. - public void WriteUsingTemplate(byte[] appHostTemplate, IBinaryStreamWriter writer, string appBinaryPath, bool isArm64Linux) - { - byte[] appBinaryPathBytes = Encoding.UTF8.GetBytes(appBinaryPath); + byte[] appBinaryPathBytes = Encoding.UTF8.GetBytes(parameters.ApplicationBinaryPath); if (appBinaryPathBytes.Length > 1024) throw new ArgumentException("Application binary path cannot exceed 1024 bytes."); - long signatureAddress = FindInFile(new ByteArrayDataSource(appHostTemplate), BundleSignature); + var appHostTemplateSource = new ByteArrayDataSource(parameters.ApplicationHostTemplate); + long signatureAddress = FindInFile(appHostTemplateSource, BundleSignature); if (signatureAddress == -1) throw new ArgumentException("AppHost template does not contain the bundle signature."); - long appBinaryPathAddress = FindInFile(new ByteArrayDataSource(appHostTemplate), AppBinaryPathPlaceholder); + long appBinaryPathAddress = FindInFile(appHostTemplateSource, AppBinaryPathPlaceholder); if (appBinaryPathAddress == -1) throw new ArgumentException("AppHost template does not contain the application binary path placeholder."); - writer.WriteBytes(appHostTemplate); + writer.WriteBytes(parameters.ApplicationHostTemplate); writer.Offset = writer.Length; - ulong headerAddress = WriteManifest(writer, isArm64Linux); + ulong headerAddress = WriteManifest(writer, parameters.IsArm64Linux); writer.Offset = (ulong) signatureAddress - sizeof(ulong); writer.WriteUInt64(headerAddress); @@ -341,6 +312,62 @@ public void WriteUsingTemplate(byte[] appHostTemplate, IBinaryStreamWriter write writer.WriteZeroes(AppBinaryPathPlaceholder.Length - appBinaryPathBytes.Length); } + private static void EnsureAppHostPEHeadersAreUpToDate(ref BundlerParameters parameters) + { + PEFile file; + try + { + file = PEFile.FromBytes(parameters.ApplicationHostTemplate); + } + catch (BadImageFormatException) + { + // Template is not a PE file. + return; + } + + bool changed = false; + + // Ensure same Windows subsystem is used (typically required for GUI applications). + if (file.OptionalHeader.SubSystem != parameters.SubSystem) + { + file.OptionalHeader.SubSystem = parameters.SubSystem; + changed = true; + } + + // If the app binary has resources (such as an icon or version info), we need to copy it into the + // AppHost template so that they are also visible from the final packed executable. + if (parameters.Resources is { } directory) + { + // Put original resource directory in a new .rsrc section. + var buffer = new ResourceDirectoryBuffer(); + buffer.AddDirectory(directory); + var rsrc = new PESection(".rsrc", SectionFlags.MemoryRead | SectionFlags.ContentInitializedData); + rsrc.Contents = buffer; + + // Find .reloc section, and insert .rsrc before it if it is present. + int sectionIndex = file.Sections.Count - 1; + for (int i = file.Sections.Count - 1; i >= 0; i--) + { + if (file.Sections[i].Name == ".reloc") + { + sectionIndex = i; + break; + } + } + + file.Sections.Insert(sectionIndex, rsrc); + changed = true; + } + + // Rebuild AppHost PE file if necessary. + if (changed) + { + using var stream = new MemoryStream(); + file.Write(stream); + parameters.ApplicationHostTemplate = stream.ToArray(); + } + } + /// /// Writes the manifest to an output stream. /// diff --git a/src/AsmResolver.DotNet/Bundles/BundlerParameters.cs b/src/AsmResolver.DotNet/Bundles/BundlerParameters.cs new file mode 100644 index 000000000..0dc8282a6 --- /dev/null +++ b/src/AsmResolver.DotNet/Bundles/BundlerParameters.cs @@ -0,0 +1,199 @@ +using System.IO; +using AsmResolver.IO; +using AsmResolver.PE; +using AsmResolver.PE.File.Headers; +using AsmResolver.PE.Win32Resources; + +namespace AsmResolver.DotNet.Bundles +{ + /// + /// Defines parameters for the .NET application bundler. + /// + public struct BundlerParameters + { + /// + /// Initializes new bundler parameters. + /// + /// + /// The path to the application host file template to use. By default this is stored in + /// <DOTNET-INSTALLATION-PATH>/sdk/<version>/AppHostTemplate or + /// <DOTNET-INSTALLATION-PATH>/packs/Microsoft.NETCore.App.Host.<runtime-identifier>/<version>/runtimes/<runtime-identifier>/native. + /// + /// + /// The name of the file in the bundle that contains the entry point of the application. + /// + public BundlerParameters(string appHostTemplatePath, string appBinaryPath) + : this(File.ReadAllBytes(appHostTemplatePath), appBinaryPath) + { + } + + /// + /// Initializes new bundler parameters. + /// + /// The application host template file to use. + /// + /// The name of the file in the bundle that contains the entry point of the application. + /// + public BundlerParameters(byte[] appHostTemplate, string appBinaryPath) + { + ApplicationHostTemplate = appHostTemplate; + ApplicationBinaryPath = appBinaryPath; + IsArm64Linux = false; + Resources = null; + SubSystem = SubSystem.WindowsCui; + } + + /// + /// Initializes new bundler parameters. + /// + /// + /// The path to the application host file template to use. By default this is stored in + /// <DOTNET-INSTALLATION-PATH>/sdk/<version>/AppHostTemplate or + /// <DOTNET-INSTALLATION-PATH>/packs/Microsoft.NETCore.App.Host.<runtime-identifier>/<version>/runtimes/<runtime-identifier>/native. + /// + /// + /// The name of the file in the bundle that contains the entry point of the application. + /// + /// + /// The path to copy the PE headers and Win32 resources from. This is typically the original native executable + /// file that hosts the CLR, or the original AppHost file the bundle was extracted from. + /// + public BundlerParameters(string appHostTemplatePath, string appBinaryPath, string? imagePathToCopyHeadersFrom) + : this( + File.ReadAllBytes(appHostTemplatePath), + appBinaryPath, + !string.IsNullOrEmpty(imagePathToCopyHeadersFrom) + ? PEImage.FromFile(imagePathToCopyHeadersFrom!) + : null + ) + { + } + + /// + /// Initializes new bundler parameters. + /// + /// The application host template file to use. + /// + /// The name of the file in the bundle that contains the entry point of the application. + /// + /// + /// The binary to copy the PE headers and Win32 resources from. This is typically the original native executable + /// file that hosts the CLR, or the original AppHost file the bundle was extracted from. + /// + public BundlerParameters(byte[] appHostTemplate, string appBinaryPath, IDataSource? imageToCopyHeadersFrom) + : this( + appHostTemplate, + appBinaryPath, + imageToCopyHeadersFrom is not null + ? PEImage.FromDataSource(imageToCopyHeadersFrom) + : null + ) + { + } + + /// + /// Initializes new bundler parameters. + /// + /// The application host template file to use. + /// + /// The name of the file in the bundle that contains the entry point of the application. + /// + /// + /// The PE image to copy the headers and Win32 resources from. This is typically the original native executable + /// file that hosts the CLR, or the original AppHost file the bundle was extracted from. + /// + public BundlerParameters(byte[] appHostTemplate, string appBinaryPath, IPEImage? imageToCopyHeadersFrom) + : this( + appHostTemplate, + appBinaryPath, + imageToCopyHeadersFrom?.SubSystem ?? SubSystem.WindowsCui, + imageToCopyHeadersFrom?.Resources + ) + { + } + + /// + /// Initializes new bundler parameters. + /// + /// The application host template file to use. + /// + /// The name of the file in the bundle that contains the entry point of the application. + /// + /// The subsystem to use in the final Windows PE binary. + /// The resources to copy into the final Windows PE binary. + public BundlerParameters( + byte[] appHostTemplate, + string appBinaryPath, + SubSystem subSystem, + IResourceDirectory? resources) + { + ApplicationHostTemplate = appHostTemplate; + ApplicationBinaryPath = appBinaryPath; + IsArm64Linux = false; + SubSystem = subSystem; + Resources = resources; + } + + /// + /// Gets or sets the template application hosting binary. + /// + /// + /// By default, the official implementations of the application host can be found in one of the following + /// installation directories: + /// + /// <DOTNET-INSTALLATION-PATH>/sdk/<version>/AppHostTemplate + /// <DOTNET-INSTALLATION-PATH>/packs/Microsoft.NETCore.App.Host.<runtime-identifier>/<version>/runtimes/<runtime-identifier>/native + /// + /// It is therefore recommended to use the contents of one of these templates to ensure compatibility. + /// + public byte[] ApplicationHostTemplate + { + get; + set; + } + + /// + /// Gets or sets the path to the binary within the bundle that contains the application's entry point. + /// + public string ApplicationBinaryPath + { + get; + set; + } + + /// + /// Gets a value indicating whether the bundled executable targets the Linux operating system on ARM64. + /// + public bool IsArm64Linux + { + get; + set; + } + + /// + /// Gets or sets the Win32 resources directory to copy into the final PE executable. + /// + /// + /// This field is ignored if is set to true, or + /// does not contain a proper PE image. + /// + public IResourceDirectory? Resources + { + get; + set; + } + + /// + /// Gets or sets the Windows subsystem the final PE executable should target. + /// + /// + /// This field is ignored if is set to true, or + /// does not contain a proper PE image. + /// + public SubSystem SubSystem + { + get; + set; + } + } +} diff --git a/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs b/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs index 740895546..88a3da352 100644 --- a/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs +++ b/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs @@ -132,7 +132,7 @@ public void MarkFilesAsCompressed() string appHostPathTemplate = Path.Combine(sdkVersionPath, "AppHostTemplate", "apphost.exe"); using var stream = new MemoryStream(); - manifest.WriteUsingTemplate(appHostPathTemplate, stream, fileName); + manifest.WriteUsingTemplate(stream, new BundlerParameters(appHostPathTemplate, fileName)); var newManifest = BundleManifest.FromBytes(stream.ToArray()); AssertBundlesAreEqual(manifest, newManifest); From 31c70817d9df1d220b044b171b23b65b375cf821 Mon Sep 17 00:00:00 2001 From: Washi Date: Mon, 28 Mar 2022 12:27:42 +0200 Subject: [PATCH 074/184] BUGFIX: Update win32 resource data dir in final bundle PE file. --- .../Bundles/BundleManifest.cs | 9 +- .../Bundles/BundlerParameters.cs | 22 +++++ .../AsmResolver.DotNet.Tests.csproj | 1 + .../Bundles/BundleManifestTest.cs | 92 +++++++++++++++--- .../Properties/Resources.Designer.cs | 7 ++ .../Properties/Resources.resx | 3 + ...HelloWorld.SingleFile.v6.WithResources.exe | Bin 0 -> 219620 bytes 7 files changed, 119 insertions(+), 15 deletions(-) create mode 100644 test/AsmResolver.DotNet.Tests/Resources/HelloWorld.SingleFile.v6.WithResources.exe diff --git a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs index 1423ef80d..c21e6d20f 100644 --- a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs +++ b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs @@ -344,7 +344,7 @@ private static void EnsureAppHostPEHeadersAreUpToDate(ref BundlerParameters para var rsrc = new PESection(".rsrc", SectionFlags.MemoryRead | SectionFlags.ContentInitializedData); rsrc.Contents = buffer; - // Find .reloc section, and insert .rsrc before it if it is present. + // Find .reloc section, and insert .rsrc before it if it is present. Otherwise just append to the end. int sectionIndex = file.Sections.Count - 1; for (int i = file.Sections.Count - 1; i >= 0; i--) { @@ -356,6 +356,13 @@ private static void EnsureAppHostPEHeadersAreUpToDate(ref BundlerParameters para } file.Sections.Insert(sectionIndex, rsrc); + + // Update resource data directory va + size. + file.AlignSections(); + file.OptionalHeader.DataDirectories[(int) DataDirectoryIndex.ResourceDirectory] = new DataDirectory( + buffer.Rva, + buffer.GetPhysicalSize()); + changed = true; } diff --git a/src/AsmResolver.DotNet/Bundles/BundlerParameters.cs b/src/AsmResolver.DotNet/Bundles/BundlerParameters.cs index 0dc8282a6..2e83c3c16 100644 --- a/src/AsmResolver.DotNet/Bundles/BundlerParameters.cs +++ b/src/AsmResolver.DotNet/Bundles/BundlerParameters.cs @@ -69,6 +69,28 @@ public BundlerParameters(string appHostTemplatePath, string appBinaryPath, strin { } + /// + /// Initializes new bundler parameters. + /// + /// The application host template file to use. + /// + /// The name of the file in the bundle that contains the entry point of the application. + /// + /// + /// The binary to copy the PE headers and Win32 resources from. This is typically the original native executable + /// file that hosts the CLR, or the original AppHost file the bundle was extracted from. + /// + public BundlerParameters(byte[] appHostTemplate, string appBinaryPath, byte[]? imageToCopyHeadersFrom) + : this( + appHostTemplate, + appBinaryPath, + imageToCopyHeadersFrom is not null + ? PEImage.FromBytes(imageToCopyHeadersFrom) + : null + ) + { + } + /// /// Initializes new bundler parameters. /// diff --git a/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj b/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj index 30de93753..a29c5c0d5 100644 --- a/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj +++ b/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj @@ -20,6 +20,7 @@ + diff --git a/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs b/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs index 88a3da352..2bd797187 100644 --- a/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs +++ b/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs @@ -5,6 +5,10 @@ using System.Runtime.InteropServices; using AsmResolver.DotNet.Bundles; using AsmResolver.IO; +using AsmResolver.PE; +using AsmResolver.PE.File; +using AsmResolver.PE.File.Headers; +using AsmResolver.PE.Win32Resources.Version; using AsmResolver.Tests.Runners; using Xunit; @@ -104,6 +108,56 @@ public void MarkFilesAsCompressed() AssertBundlesAreEqual(manifest, newManifest); } + [Theory] + [InlineData(SubSystem.WindowsCui)] + [InlineData(SubSystem.WindowsGui)] + public void WriteWithSubSystem(SubSystem subSystem) + { + var manifest = BundleManifest.FromBytes(Properties.Resources.HelloWorld_SingleFile_V6); + string appHostTemplatePath = FindAppHostTemplate("6.0"); + + using var stream = new MemoryStream(); + manifest.WriteUsingTemplate(stream, new BundlerParameters(appHostTemplatePath, "HelloWorld.dll") + { + SubSystem = subSystem + }); + + var newFile = PEFile.FromBytes(stream.ToArray()); + Assert.Equal(subSystem, newFile.OptionalHeader.SubSystem); + } + + [Fact] + public void WriteWithWin32Resources() + { + var manifest = BundleManifest.FromBytes(Properties.Resources.HelloWorld_SingleFile_V6_WithResources); + string appHostTemplatePath = FindAppHostTemplate("6.0"); + + // Obtain expected version info. + var oldImage = PEImage.FromBytes(Properties.Resources.HelloWorld_SingleFile_V6_WithResources); + var versionInfo = VersionInfoResource.FromDirectory(oldImage.Resources!)!; + + // Bundle with PE image as template for PE headers and resources. + using var stream = new MemoryStream(); + manifest.WriteUsingTemplate(stream, new BundlerParameters( + File.ReadAllBytes(appHostTemplatePath), + "HelloWorld.dll", + oldImage)); + + // Verify new file still runs as expected. + string output = _fixture + .GetRunner() + .RunAndCaptureOutput("HelloWorld.exe", stream.ToArray()); + + Assert.Equal($"Hello, World!{Environment.NewLine}", output); + + // Verify that resources were added properly. + var newImage = PEImage.FromBytes(stream.ToArray()); + Assert.NotNull(newImage.Resources); + var newVersionInfo = VersionInfoResource.FromDirectory(newImage.Resources); + Assert.NotNull(newVersionInfo); + Assert.Equal(versionInfo.FixedVersionInfo.FileVersion, newVersionInfo.FixedVersionInfo.FileVersion); + } + private void AssertWriteManifestWindowsPreservesOutput( BundleManifest manifest, string sdkVersion, @@ -111,6 +165,29 @@ public void MarkFilesAsCompressed() string expectedOutput, [CallerFilePath] string className = "File", [CallerMemberName] string methodName = "Method") + { + string appHostTemplatePath = FindAppHostTemplate(sdkVersion); + + using var stream = new MemoryStream(); + manifest.WriteUsingTemplate(stream, new BundlerParameters(appHostTemplatePath, fileName)); + + var newManifest = BundleManifest.FromBytes(stream.ToArray()); + AssertBundlesAreEqual(manifest, newManifest); + + string output = _fixture + .GetRunner() + .RunAndCaptureOutput( + Path.ChangeExtension(fileName, ".exe"), + stream.ToArray(), + null, + 5000, + className, + methodName); + + Assert.Equal(expectedOutput, output); + } + + private static string FindAppHostTemplate(string sdkVersion) { string sdkPath = Path.Combine(DotNetCorePathProvider.DefaultInstallationPath!, "sdk"); string? sdkVersionPath = null; @@ -130,20 +207,7 @@ public void MarkFilesAsCompressed() } string appHostPathTemplate = Path.Combine(sdkVersionPath, "AppHostTemplate", "apphost.exe"); - - using var stream = new MemoryStream(); - manifest.WriteUsingTemplate(stream, new BundlerParameters(appHostPathTemplate, fileName)); - - var newManifest = BundleManifest.FromBytes(stream.ToArray()); - AssertBundlesAreEqual(manifest, newManifest); - - string output = _fixture - .GetRunner() - .RunAndCaptureOutput(Path.ChangeExtension(fileName, ".exe"), stream.ToArray(), null, - 5000, - className, - methodName); - Assert.Equal(expectedOutput, output); + return appHostPathTemplate; } private static void AssertBundlesAreEqual(BundleManifest manifest, BundleManifest newManifest) diff --git a/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs index e824f4b21..c2e4010ea 100644 --- a/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs @@ -157,6 +157,13 @@ public class Resources { } } + public static byte[] HelloWorld_SingleFile_V6_WithResources { + get { + object obj = ResourceManager.GetObject("HelloWorld_SingleFile_V6_WithResources", 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 0ff471e63..5e722a4a9 100644 --- a/test/AsmResolver.DotNet.Tests/Properties/Resources.resx +++ b/test/AsmResolver.DotNet.Tests/Properties/Resources.resx @@ -66,6 +66,9 @@ ..\Resources\HelloWorld.SingleFile.v6.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\HelloWorld.SingleFile.v6.WithResources.exe;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/Resources/HelloWorld.SingleFile.v6.WithResources.exe b/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.SingleFile.v6.WithResources.exe new file mode 100644 index 0000000000000000000000000000000000000000..9da59aba9f824126a4fd9d8f3eafd4ea4d97ded8 GIT binary patch literal 219620 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 D)&5AX literal 0 HcmV?d00001 From 45e5d7db6f7ed1cd864d7c6a1104e171f1f4438a Mon Sep 17 00:00:00 2001 From: Washi Date: Mon, 28 Mar 2022 12:44:08 +0200 Subject: [PATCH 075/184] Update API docs with descriptions of new BundlerParameters struct. --- docs/dotnet/bundles.rst | 32 +++++++++++++++---- .../Bundles/BundleManifestTest.cs | 7 ++++ 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/docs/dotnet/bundles.rst b/docs/dotnet/bundles.rst index 488260461..65cbdc774 100644 --- a/docs/dotnet/bundles.rst +++ b/docs/dotnet/bundles.rst @@ -82,18 +82,38 @@ AsmResolver does not provide built-in alternative heuristics for finding the rig Writing Bundles --------------- -Constructing new bundled executable files requires a template file that AsmResolver can base the final output on. This is similar how .NET compilers themselves do this as well. By default, the .NET SDK installs template binaries in either ``/sdk//AppHostTemplate`` or ``/packs/Microsoft.NETCore.App.Host.//runtimes//native``. It is then possible to use ``WriteUsingTemplate`` to construct a new binary. +Constructing new bundled executable files requires a template file that AsmResolver can base the final output on. This is similar how .NET compilers themselves do this as well. By default, the .NET SDK installs template binaries in one of the following directories: + +- ``/sdk//AppHostTemplate`` +- ``/packs/Microsoft.NETCore.App.Host.//runtimes//native`` + +Using this template file, it is then possible to write a new bundled executable file using ``WriteUsingTemplate``: .. code-block:: csharp BundleManifest manifest = ... - var manifest = manifest.WriteUsingTemplate( - @"C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Host.win-x64\6.0.0\runtimes\win-x64\native\apphost.exe", - @"C:\User\Admin\HelloWorld.exe", - @"HelloWorld.dll"); + manifest.WriteUsingTemplate( + @"C:\Path\To\Output\File.exe", + new BundlerParameters( + appHostTemplatePath: @"C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Host.win-x64\6.0.0\runtimes\win-x64\native\apphost.exe", + appBinaryPath: @"HelloWorld.dll")); + + +Typically on Windows, use an ```apphost.exe`` template if you want to construct a native binary that is framework dependent, and ``singlefilehost.exe`` for a fully self-contained binary. On Linux, use the ``apphost`` and ``singlefilehost`` ELF equivalents. +For bundle executable files targeting Windows, it may be required to copy over some values from the original PE file into the final bundle executable file. Usually these values include fields from the PE headers (such as the executable's sub-system target) and Win32 resources (such as application icons and version information). AsmResolver can automatically update these headers by specifying a source image in the ``BundlerParameters``: -Typically on Windows, use an ```apphost.exe`` template if you want to construct a native binary that is framework dependent, and ``singlefilehost.exe`` for a fully self-contained binary. +.. code-block:: csharp + + BundleManifest manifest = ... + manifest.WriteUsingTemplate( + @"C:\Path\To\Output\File.exe", + new BundlerParameters( + appHostTemplatePath: @"C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Host.win-x64\6.0.0\runtimes\win-x64\native\apphost.exe", + appBinaryPath: @"HelloWorld.dll", + imagePathToCopyHeadersFrom: @"C:\Path\To\Original\HelloWorld.exe")); + +``BundleManifest`` also defines other ```WriteUsingTemplate`` overloads taking ``byte[]``, ``IDataSource`` or ``IPEImage`` instances instead of paths. Managing Files diff --git a/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs b/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs index 2bd797187..9ee1b23cd 100644 --- a/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs +++ b/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs @@ -143,6 +143,13 @@ public void WriteWithWin32Resources() "HelloWorld.dll", oldImage)); + manifest.WriteUsingTemplate( + @"C:\Path\To\Output\File.exe", + new BundlerParameters( + appHostTemplatePath: @"C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Host.win-x64\6.0.0\runtimes\win-x64\native\apphost.exe", + appBinaryPath: @"HelloWorld.dll", + imagePathToCopyHeadersFrom: @"C:\Path\To\HelloWorld.exe")); + // Verify new file still runs as expected. string output = _fixture .GetRunner() From 9872aacda8975bb4a67fb1b1b5b77fd622030777 Mon Sep 17 00:00:00 2001 From: Washi Date: Mon, 28 Mar 2022 12:48:18 +0200 Subject: [PATCH 076/184] Remove hardcoded paths in tests. --- .../Bundles/BundleManifestTest.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs b/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs index 9ee1b23cd..e2a1388d7 100644 --- a/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs +++ b/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs @@ -126,9 +126,11 @@ public void WriteWithSubSystem(SubSystem subSystem) Assert.Equal(subSystem, newFile.OptionalHeader.SubSystem); } - [Fact] + [SkippableFact] public void WriteWithWin32Resources() { + Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); + var manifest = BundleManifest.FromBytes(Properties.Resources.HelloWorld_SingleFile_V6_WithResources); string appHostTemplatePath = FindAppHostTemplate("6.0"); @@ -143,13 +145,6 @@ public void WriteWithWin32Resources() "HelloWorld.dll", oldImage)); - manifest.WriteUsingTemplate( - @"C:\Path\To\Output\File.exe", - new BundlerParameters( - appHostTemplatePath: @"C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Host.win-x64\6.0.0\runtimes\win-x64\native\apphost.exe", - appBinaryPath: @"HelloWorld.dll", - imagePathToCopyHeadersFrom: @"C:\Path\To\HelloWorld.exe")); - // Verify new file still runs as expected. string output = _fixture .GetRunner() From 8d116207099d819f821ef95290e8cb63b65cf7ce Mon Sep 17 00:00:00 2001 From: Washi Date: Mon, 28 Mar 2022 13:00:26 +0200 Subject: [PATCH 077/184] Small typo fixes and removal of some compiler warnings. --- docs/dotnet/bundles.rst | 4 ++-- src/AsmResolver.DotNet/Bundles/BundleManifest.cs | 2 +- test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/dotnet/bundles.rst b/docs/dotnet/bundles.rst index 65cbdc774..e96491ae9 100644 --- a/docs/dotnet/bundles.rst +++ b/docs/dotnet/bundles.rst @@ -99,9 +99,9 @@ Using this template file, it is then possible to write a new bundled executable appBinaryPath: @"HelloWorld.dll")); -Typically on Windows, use an ```apphost.exe`` template if you want to construct a native binary that is framework dependent, and ``singlefilehost.exe`` for a fully self-contained binary. On Linux, use the ``apphost`` and ``singlefilehost`` ELF equivalents. +Typically on Windows, use an ``apphost.exe`` template if you want to construct a native binary that is framework dependent, and ``singlefilehost.exe`` for a fully self-contained binary. On Linux, use the ``apphost`` and ``singlefilehost`` ELF equivalents. -For bundle executable files targeting Windows, it may be required to copy over some values from the original PE file into the final bundle executable file. Usually these values include fields from the PE headers (such as the executable's sub-system target) and Win32 resources (such as application icons and version information). AsmResolver can automatically update these headers by specifying a source image in the ``BundlerParameters``: +For bundle executable files targeting Windows, it may be required to copy over some values from the original PE file into the final bundle executable file. Usually these values include fields from the PE headers (such as the executable's sub-system target) and Win32 resources (such as application icons and version information). AsmResolver can automatically update these headers by specifying a source image to pull this data from in the ``BundlerParameters``: .. code-block:: csharp diff --git a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs index c21e6d20f..f5a957a28 100644 --- a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs +++ b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs @@ -384,7 +384,7 @@ private static void EnsureAppHostPEHeadersAreUpToDate(ref BundlerParameters para /// /// This does not necessarily produce a working executable file, it only writes the contents of the entire manifest, /// without a host application that invokes the manifest. If you want to produce a runnable executable, use one - /// of the or one of its overloads instead. + /// of the WriteUsingTemplate methods instead. /// public ulong WriteManifest(IBinaryStreamWriter writer, bool isArm64Linux) { diff --git a/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs b/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs index e2a1388d7..e4852bbb6 100644 --- a/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs +++ b/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs @@ -208,8 +208,7 @@ private static string FindAppHostTemplate(string sdkVersion) $"Could not find the apphost template for .NET SDK version {sdkVersion}. This is an indication that the test environment does not have this SDK installed."); } - string appHostPathTemplate = Path.Combine(sdkVersionPath, "AppHostTemplate", "apphost.exe"); - return appHostPathTemplate; + return Path.Combine(sdkVersionPath, "AppHostTemplate", "apphost.exe"); } private static void AssertBundlesAreEqual(BundleManifest manifest, BundleManifest newManifest) From 45e96ce98b7cb7941ab87c67ba172f1871efdeec Mon Sep 17 00:00:00 2001 From: Jeremy Pritts Date: Mon, 28 Mar 2022 13:31:03 -0400 Subject: [PATCH 078/184] check that object is in a cor lib assembly --- .../Cloning/CloneContextAwareReferenceImporter.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/AsmResolver.DotNet/Cloning/CloneContextAwareReferenceImporter.cs b/src/AsmResolver.DotNet/Cloning/CloneContextAwareReferenceImporter.cs index fb67345e0..966c32e3d 100644 --- a/src/AsmResolver.DotNet/Cloning/CloneContextAwareReferenceImporter.cs +++ b/src/AsmResolver.DotNet/Cloning/CloneContextAwareReferenceImporter.cs @@ -44,7 +44,9 @@ public override IMethodDefOrRef ImportMethod(IMethodDefOrRef method) /// protected override ITypeDefOrRef ImportType(TypeReference type) { - return type.Namespace == "System" && type.Name == nameof(System.Object) + return type.Namespace == "System" + && type.Name == nameof(System.Object) + && (type.Scope?.GetAssembly()?.IsCorLib ?? false) ? _context.Module.CorLibTypeFactory.Object.Type : base.ImportType(type); } From bc688fd43cbfddd9e010adea933a7d081fce1e60 Mon Sep 17 00:00:00 2001 From: Jeremy Pritts Date: Mon, 28 Mar 2022 13:42:14 -0400 Subject: [PATCH 079/184] fix constructors for better binary compatibility --- src/AsmResolver.DotNet/Cloning/MemberCloneContext.cs | 8 +++++++- src/AsmResolver.DotNet/Cloning/MemberCloner.cs | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloneContext.cs b/src/AsmResolver.DotNet/Cloning/MemberCloneContext.cs index 43092c939..4bcf5894b 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloneContext.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloneContext.cs @@ -8,13 +8,19 @@ namespace AsmResolver.DotNet.Cloning /// public class MemberCloneContext { + /// + /// Creates a new instance of the class. + /// + /// The target module to copy the cloned members into. + public MemberCloneContext(ModuleDefinition module) : this(module, null) { } + /// /// Creates a new instance of the class. /// /// The target module to copy the cloned members into. /// The instantiator for creating the reference importer public MemberCloneContext(ModuleDefinition module, - Func? importerInstantiator = null) + Func? importerInstantiator) { Module = module ?? throw new ArgumentNullException(nameof(module)); Importer = importerInstantiator?.Invoke(this) ?? new CloneContextAwareReferenceImporter(this); diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.cs index 2d1a6022b..acd0d1811 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloner.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.cs @@ -26,13 +26,19 @@ public partial class MemberCloner private readonly HashSet _propertiesToClone = new(); private readonly HashSet _eventsToClone = new(); + /// + /// Creates a new instance of the class. + /// + /// The target module to copy the members into. + public MemberCloner(ModuleDefinition targetModule) : this(targetModule, null) { } + /// /// Creates a new instance of the class. /// /// The target module to copy the members into. /// The instantiator for creating the reference importer public MemberCloner(ModuleDefinition targetModule, - Func? importerInstantiator = null) + Func? importerInstantiator) { _targetModule = targetModule ?? throw new ArgumentNullException(nameof(targetModule)); _importerInstantiator = importerInstantiator; From 7b8d92eaf7865f26c93ce74d7df2d1215f29e149 Mon Sep 17 00:00:00 2001 From: Jeremy Pritts Date: Mon, 28 Mar 2022 13:51:52 -0400 Subject: [PATCH 080/184] make line ending setting coorespond to code base line endings --- .editorconfig | 2 +- AsmResolver.sln | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.editorconfig b/.editorconfig index b1533ed3f..3c5663e27 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,7 +1,7 @@ [*] charset = utf-8 -end_of_line = lf +end_of_line = crlf trim_trailing_whitespace = true insert_final_newline = true indent_style = space diff --git a/AsmResolver.sln b/AsmResolver.sln index 313cb8c0b..526ac85f6 100644 --- a/AsmResolver.sln +++ b/AsmResolver.sln @@ -71,9 +71,18 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TlsTest", "test\TestBinarie EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CallManagedExport", "test\TestBinaries\Native\CallManagedExport\CallManagedExport.vcxproj", "{40483E28-C703-4933-BA5B-9512EF6E6A21}" EndProject -Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "HelloWorldVB", "test\TestBinaries\DotNet\HelloWorldVB\HelloWorldVB.vbproj", "{CF6A7E02-37DC-4963-AC14-76D74ADCD87A}" -EndProject -Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "ClassLibraryVB", "test\TestBinaries\DotNet\ClassLibraryVB\ClassLibraryVB.vbproj", "{2D1DF5DA-7367-4490-B3F0-B996348E150B}" +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "HelloWorldVB", "test\TestBinaries\DotNet\HelloWorldVB\HelloWorldVB.vbproj", "{CF6A7E02-37DC-4963-AC14-76D74ADCD87A}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "ClassLibraryVB", "test\TestBinaries\DotNet\ClassLibraryVB\ClassLibraryVB.vbproj", "{2D1DF5DA-7367-4490-B3F0-B996348E150B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{66C7E95F-0C1A-466E-988A-C84D5542458B}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + .gitignore = .gitignore + CONTRIBUTING.md = CONTRIBUTING.md + LICENSE.md = LICENSE.md + README.md = README.md + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution From c0bfc4578895a4c7fbf73a40fb02abcf72776304 Mon Sep 17 00:00:00 2001 From: Jeremy Pritts Date: Tue, 29 Mar 2022 16:54:56 -0400 Subject: [PATCH 081/184] add an ApplyMethod for PropertyDefinition and EventDefinition --- src/AsmResolver.DotNet/EventDefinition.cs | 31 +++++++++++++++----- src/AsmResolver.DotNet/PropertyDefinition.cs | 18 ++++++++++-- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/AsmResolver.DotNet/EventDefinition.cs b/src/AsmResolver.DotNet/EventDefinition.cs index e333a7e5c..c9a899e15 100644 --- a/src/AsmResolver.DotNet/EventDefinition.cs +++ b/src/AsmResolver.DotNet/EventDefinition.cs @@ -136,19 +136,19 @@ public IList Semantics } /// - /// Gets the method definition representing the add accessor of this event definition. + /// Gets the method definition representing the first add accessor of this event definition. /// public MethodDefinition? AddMethod => Semantics.FirstOrDefault(s => s.Attributes == MethodSemanticsAttributes.AddOn)?.Method; /// - /// Gets the method definition representing the remove accessor of this event definition. + /// Gets the method definition representing the first remove accessor of this event definition. /// public MethodDefinition? RemoveMethod => Semantics.FirstOrDefault(s => s.Attributes == MethodSemanticsAttributes.RemoveOn)?.Method; /// - /// Gets the method definition representing the fire accessor of this event definition. + /// Gets the method definition representing the first fire accessor of this event definition. /// public MethodDefinition? FireMethod => Semantics.FirstOrDefault(s => s.Attributes == MethodSemanticsAttributes.Fire)?.Method; @@ -164,6 +164,23 @@ public IList CustomAttributes } } + /// + /// Clear and apply these methods to the event definition. + /// + /// The method definition representing the add accessor of this event definition. + /// The method definition representing the remove accessor of this event definition. + /// The method definition representing the fire accessor of this event definition. + public void ApplyMethods(MethodDefinition? addMethod, MethodDefinition? removeMethod, MethodDefinition? fireMethod) + { + Semantics.Clear(); + if (addMethod is not null) + Semantics.Add(new MethodSemantics(addMethod, MethodSemanticsAttributes.AddOn)); + if (removeMethod is not null) + Semantics.Add(new MethodSemantics(removeMethod, MethodSemanticsAttributes.RemoveOn)); + if (fireMethod is not null) + Semantics.Add(new MethodSemantics(fireMethod, MethodSemanticsAttributes.Fire)); + } + /// public bool IsAccessibleFromType(TypeDefinition type) => Semantics.Any(s => s.Method?.IsAccessibleFromType(type) ?? false); @@ -188,7 +205,7 @@ public bool IsImportedInModule(ModuleDefinition module) new OwnedCollection(this); /// - /// Obtains the name of the property definition. + /// Obtains the name of the event definition. /// /// The name. /// @@ -197,7 +214,7 @@ public bool IsImportedInModule(ModuleDefinition module) protected virtual Utf8String? GetName() => null; /// - /// Obtains the event type of the property definition. + /// Obtains the event type of the event definition. /// /// The event type. /// @@ -206,7 +223,7 @@ public bool IsImportedInModule(ModuleDefinition module) protected virtual ITypeDefOrRef? GetEventType() => null; /// - /// Obtains the declaring type of the property definition. + /// Obtains the declaring type of the event definition. /// /// The declaring type. /// @@ -215,7 +232,7 @@ public bool IsImportedInModule(ModuleDefinition module) protected virtual TypeDefinition? GetDeclaringType() => null; /// - /// Obtains the methods associated to this property definition. + /// Obtains the methods associated to this event definition. /// /// The method semantic objects. /// diff --git a/src/AsmResolver.DotNet/PropertyDefinition.cs b/src/AsmResolver.DotNet/PropertyDefinition.cs index 93348cedb..6b6975a9c 100644 --- a/src/AsmResolver.DotNet/PropertyDefinition.cs +++ b/src/AsmResolver.DotNet/PropertyDefinition.cs @@ -170,17 +170,31 @@ public IList CustomAttributes } /// - /// Gets the method definition representing the get accessor of this property definition. + /// Gets the method definition representing the first get accessor of this property definition. /// public MethodDefinition? GetMethod => Semantics.FirstOrDefault(s => s.Attributes == MethodSemanticsAttributes.Getter)?.Method; /// - /// Gets the method definition representing the set accessor of this property definition. + /// Gets the method definition representing the first set accessor of this property definition. /// public MethodDefinition? SetMethod => Semantics.FirstOrDefault(s => s.Attributes == MethodSemanticsAttributes.Setter)?.Method; + /// + /// Clear and apply these methods to the property definition. + /// + /// The method definition representing the get accessor of this property definition. + /// The method definition representing the set accessor of this property definition. + public void ApplyMethods(MethodDefinition? getMethod, MethodDefinition? setMethod) + { + Semantics.Clear(); + if (getMethod is not null) + Semantics.Add(new MethodSemantics(getMethod, MethodSemanticsAttributes.Getter)); + if (setMethod is not null) + Semantics.Add(new MethodSemantics(setMethod, MethodSemanticsAttributes.Setter)); + } + /// public bool IsAccessibleFromType(TypeDefinition type) => Semantics.Any(s => s.Method?.IsAccessibleFromType(type) ?? false); From 51666952a00a8d4cceccd251aafbc9c0ac2662a2 Mon Sep 17 00:00:00 2001 From: Jeremy Pritts Date: Wed, 30 Mar 2022 09:29:34 -0400 Subject: [PATCH 082/184] change name to SetSemanticMethods --- src/AsmResolver.DotNet/EventDefinition.cs | 2 +- src/AsmResolver.DotNet/PropertyDefinition.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AsmResolver.DotNet/EventDefinition.cs b/src/AsmResolver.DotNet/EventDefinition.cs index c9a899e15..f319e1270 100644 --- a/src/AsmResolver.DotNet/EventDefinition.cs +++ b/src/AsmResolver.DotNet/EventDefinition.cs @@ -170,7 +170,7 @@ public IList CustomAttributes /// The method definition representing the add accessor of this event definition. /// The method definition representing the remove accessor of this event definition. /// The method definition representing the fire accessor of this event definition. - public void ApplyMethods(MethodDefinition? addMethod, MethodDefinition? removeMethod, MethodDefinition? fireMethod) + public void SetSemanticMethods(MethodDefinition? addMethod, MethodDefinition? removeMethod, MethodDefinition? fireMethod) { Semantics.Clear(); if (addMethod is not null) diff --git a/src/AsmResolver.DotNet/PropertyDefinition.cs b/src/AsmResolver.DotNet/PropertyDefinition.cs index 6b6975a9c..3df05c38c 100644 --- a/src/AsmResolver.DotNet/PropertyDefinition.cs +++ b/src/AsmResolver.DotNet/PropertyDefinition.cs @@ -186,7 +186,7 @@ public IList CustomAttributes /// /// The method definition representing the get accessor of this property definition. /// The method definition representing the set accessor of this property definition. - public void ApplyMethods(MethodDefinition? getMethod, MethodDefinition? setMethod) + public void SetSemanticMethods(MethodDefinition? getMethod, MethodDefinition? setMethod) { Semantics.Clear(); if (getMethod is not null) From b5ac4462a109d3b98330ee61a71f19c57e467a2e Mon Sep 17 00:00:00 2001 From: Jeremy Pritts Date: Wed, 30 Mar 2022 09:39:14 -0400 Subject: [PATCH 083/184] name changes and context property in the cloning reference importer --- .../Cloning/CloneContextAwareReferenceImporter.cs | 10 ++++++---- src/AsmResolver.DotNet/Cloning/MemberCloneContext.cs | 6 +++--- src/AsmResolver.DotNet/Cloning/MemberCloner.cs | 10 +++++----- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/AsmResolver.DotNet/Cloning/CloneContextAwareReferenceImporter.cs b/src/AsmResolver.DotNet/Cloning/CloneContextAwareReferenceImporter.cs index 235f5a277..0382fb8a9 100644 --- a/src/AsmResolver.DotNet/Cloning/CloneContextAwareReferenceImporter.cs +++ b/src/AsmResolver.DotNet/Cloning/CloneContextAwareReferenceImporter.cs @@ -5,10 +5,7 @@ namespace AsmResolver.DotNet.Cloning /// public class CloneContextAwareReferenceImporter : ReferenceImporter { - /// - /// The working space for this member cloning procedure. - /// - protected readonly MemberCloneContext _context; + private readonly MemberCloneContext _context; /// /// Creates a new instance of the class. @@ -20,6 +17,11 @@ public CloneContextAwareReferenceImporter(MemberCloneContext context) _context = context; } + /// + /// The working space for this member cloning procedure. + /// + protected MemberCloneContext Context => _context; + /// protected override ITypeDefOrRef ImportType(TypeDefinition type) { diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloneContext.cs b/src/AsmResolver.DotNet/Cloning/MemberCloneContext.cs index 4bcf5894b..78c52c8c3 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloneContext.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloneContext.cs @@ -18,12 +18,12 @@ public class MemberCloneContext /// Creates a new instance of the class. /// /// The target module to copy the cloned members into. - /// The instantiator for creating the reference importer + /// The factory for creating the reference importer public MemberCloneContext(ModuleDefinition module, - Func? importerInstantiator) + Func? importerFactory) { Module = module ?? throw new ArgumentNullException(nameof(module)); - Importer = importerInstantiator?.Invoke(this) ?? new CloneContextAwareReferenceImporter(this); + Importer = importerFactory?.Invoke(this) ?? new CloneContextAwareReferenceImporter(this); } /// diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.cs index acd0d1811..1580d1efa 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloner.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.cs @@ -17,7 +17,7 @@ namespace AsmResolver.DotNet.Cloning /// public partial class MemberCloner { - private readonly Func? _importerInstantiator; + private readonly Func? _importerFactory; private readonly ModuleDefinition _targetModule; private readonly HashSet _typesToClone = new(); @@ -36,12 +36,12 @@ public partial class MemberCloner /// Creates a new instance of the class. /// /// The target module to copy the members into. - /// The instantiator for creating the reference importer + /// The factory for creating the reference importer public MemberCloner(ModuleDefinition targetModule, - Func? importerInstantiator) + Func? importerFactory) { _targetModule = targetModule ?? throw new ArgumentNullException(nameof(targetModule)); - _importerInstantiator = importerInstantiator; + _importerFactory = importerFactory; } /// @@ -230,7 +230,7 @@ public MemberCloner Include(EventDefinition @event) /// An object representing the result of the cloning process. public MemberCloneResult Clone() { - var context = new MemberCloneContext(_targetModule, _importerInstantiator); + var context = new MemberCloneContext(_targetModule, _importerFactory); CreateMemberStubs(context); DeepCopyMembers(context); From 11f2fe1ae55b2b39690206b4c19a2f914c27eaa2 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 30 Mar 2022 21:38:15 +0200 Subject: [PATCH 084/184] Prioritize app binary path verification over updating / rebuilding the template PE file. --- src/AsmResolver.DotNet/Bundles/BundleManifest.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs index f5a957a28..f5fd82f8d 100644 --- a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs +++ b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs @@ -2,12 +2,10 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Runtime.InteropServices; using System.Text; using System.Threading; using AsmResolver.Collections; using AsmResolver.IO; -using AsmResolver.PE; using AsmResolver.PE.File; using AsmResolver.PE.File.Headers; using AsmResolver.PE.Win32Resources.Builder; @@ -283,13 +281,13 @@ public void WriteUsingTemplate(IBinaryStreamWriter writer, BundlerParameters par if (appBinaryEntry is null) throw new ArgumentException($"Application {parameters.ApplicationBinaryPath} does not exist within the bundle."); - if (!parameters.IsArm64Linux) - EnsureAppHostPEHeadersAreUpToDate(ref parameters); - byte[] appBinaryPathBytes = Encoding.UTF8.GetBytes(parameters.ApplicationBinaryPath); if (appBinaryPathBytes.Length > 1024) throw new ArgumentException("Application binary path cannot exceed 1024 bytes."); + if (!parameters.IsArm64Linux) + EnsureAppHostPEHeadersAreUpToDate(ref parameters); + var appHostTemplateSource = new ByteArrayDataSource(parameters.ApplicationHostTemplate); long signatureAddress = FindInFile(appHostTemplateSource, BundleSignature); if (signatureAddress == -1) From 59ead8c26cec188375353a4d1b22423ab4691a6e Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 30 Mar 2022 21:54:32 +0200 Subject: [PATCH 085/184] Add deterministic bundle ID generation. --- .../Bundles/BundleManifest.cs | 46 ++++++++++++++++++- .../Bundles/BundleManifestTest.cs | 43 +++++++++++++++++ 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs index f5fd82f8d..bd8e6f80b 100644 --- a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs +++ b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Security.Cryptography; using System.Text; using System.Threading; using AsmResolver.Collections; @@ -17,6 +18,8 @@ namespace AsmResolver.DotNet.Bundles /// public class BundleManifest { + private const int DefaultBundleIDLength = 12; + private static readonly byte[] BundleSignature = { 0x8b, 0x12, 0x02, 0xb9, 0x6a, 0x61, 0x20, 0x38, @@ -35,13 +38,22 @@ public class BundleManifest /// protected BundleManifest() { - BundleID = string.Empty; } /// /// Creates a new bundle manifest. /// /// The file format version. + public BundleManifest(uint majorVersionNumber) + { + MajorVersion = majorVersionNumber; + MinorVersion = 0; + } + + /// + /// Creates a new bundle manifest with a specific bundle identifier. + /// + /// The file format version. /// The unique bundle manifest identifier. public BundleManifest(uint majorVersionNumber, string bundleId) { @@ -82,7 +94,11 @@ public uint MinorVersion /// /// Gets or sets the unique identifier for the bundle manifest. /// - public string BundleID + /// + /// When this property is set to null, the bundle identifier will be generated upon writing the manifest + /// based on the contents of the manifest. + /// + public string? BundleID { get; set; @@ -249,6 +265,30 @@ public static long FindBundleManifestAddress(IDataSource source) /// protected virtual IList GetFiles() => new OwnedCollection(this); + /// + /// Generates a bundle identifier based on the SHA-256 hashes of all files in the manifest. + /// + /// The generated bundle identifier. + public string GenerateDeterministicBundleID() + { + using var manifestHasher = SHA256.Create(); + + for (int i = 0; i < Files.Count; i++) + { + var file = Files[i]; + using var fileHasher = SHA256.Create(); + byte[] fileHash = fileHasher.ComputeHash(file.GetData()); + manifestHasher.TransformBlock(fileHash, 0, fileHash.Length, fileHash, 0); + } + + manifestHasher.TransformFinalBlock(Array.Empty(), 0, 0); + byte[] manifestHash = manifestHasher.Hash; + + return Convert.ToBase64String(manifestHash) + .Substring(DefaultBundleIDLength) + .Replace('/', '_'); + } + /// /// Constructs a new application host file based on the bundle manifest. /// @@ -415,6 +455,8 @@ private void WriteManifestHeader(IBinaryStreamWriter writer) writer.WriteUInt32(MajorVersion); writer.WriteUInt32(MinorVersion); writer.WriteInt32(Files.Count); + + BundleID ??= GenerateDeterministicBundleID(); writer.WriteBinaryFormatterString(BundleID); if (MajorVersion >= 2) diff --git a/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs b/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs index e4852bbb6..0bed69a5a 100644 --- a/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs +++ b/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Text; using AsmResolver.DotNet.Bundles; using AsmResolver.IO; using AsmResolver.PE; @@ -160,6 +161,48 @@ public void WriteWithWin32Resources() Assert.Equal(versionInfo.FixedVersionInfo.FileVersion, newVersionInfo.FixedVersionInfo.FileVersion); } + [Fact] + public void NewManifestShouldGenerateBundleIdIfUnset() + { + var manifest = new BundleManifest(6); + + manifest.Files.Add(new BundleFile("HelloWorld.dll", BundleFileType.Assembly, + Properties.Resources.HelloWorld_NetCore)); + manifest.Files.Add(new BundleFile("HelloWorld.runtimeconfig.json", BundleFileType.RuntimeConfigJson, + Encoding.UTF8.GetBytes(@"{ + ""runtimeOptions"": { + ""tfm"": ""net6.0"", + ""includedFrameworks"": [ + { + ""name"": ""Microsoft.NETCore.App"", + ""version"": ""6.0.0"" + } + ] + } +}"))); + + Assert.Null(manifest.BundleID); + + using var stream = new MemoryStream(); + manifest.WriteUsingTemplate(stream, new BundlerParameters( + FindAppHostTemplate("6.0"), + "HelloWorld.dll")); + + Assert.NotNull(manifest.BundleID); + } + + [Fact] + public void SameManifestContentsShouldResultInSameBundleID() + { + var manifest = BundleManifest.FromBytes(Properties.Resources.HelloWorld_SingleFile_V6); + + var newManifest = new BundleManifest(manifest.MajorVersion); + foreach (var file in manifest.Files) + newManifest.Files.Add(new BundleFile(file.RelativePath, file.Type, file.GetData())); + + Assert.Equal(manifest.BundleID, newManifest.GenerateDeterministicBundleID()); + } + private void AssertWriteManifestWindowsPreservesOutput( BundleManifest manifest, string sdkVersion, From 405c13da5373784707abddc38e62efedcbca9d93 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 30 Mar 2022 21:56:49 +0200 Subject: [PATCH 086/184] Update docs on bundle ID generation. --- docs/dotnet/bundles.rst | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/docs/dotnet/bundles.rst b/docs/dotnet/bundles.rst index e96491ae9..670b9eb3d 100644 --- a/docs/dotnet/bundles.rst +++ b/docs/dotnet/bundles.rst @@ -17,9 +17,7 @@ Creating Bundles .. code-block:: csharp - var manifest = new BundleManifest( - majorVersionNumber: 6, - bundleId: ""); + var manifest = new BundleManifest(majorVersionNumber: 6); The major version number refers to the file format that should be used when saving the manifest. Below an overview of the values that are recognized by the CLR: @@ -34,12 +32,23 @@ The major version number refers to the file format that should be used when savi | .NET 6.0 | 6 | +----------------------+----------------------------+ +To create a new bundle with a specific bundle identifier, use the overloaded constructor + +.. code-block:: csharp + + var manifest = new BundleManifest(6, "MyBundleID"); + + It is also possible to change the version number as well as the bundle ID later, since these values are exposed as mutable properties ``MajorVersion`` and ``BundleID`` .. code-block:: csharp manifest.MajorVersion = 6; - manifest.BundleID = GenerateRandomID(); + manifest.BundleID = manifest.GenerateDeterministicBundleID(); + +.. note:: + + If ``BundleID`` is left unset (``null``), it will be automatically assigned a new one using ``GenerateDeterministicBundleID`` upon writing. Reading Bundles From c5545dc39db34add58deb522343ab35c3a339b6b Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 30 Mar 2022 22:09:36 +0200 Subject: [PATCH 087/184] Move EofData and ExtraSectionData into IPEFile interface. --- src/AsmResolver.PE.File/IPEFile.cs | 18 ++++++++++++++++++ src/AsmResolver.PE.File/PEFile.cs | 8 ++------ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/AsmResolver.PE.File/IPEFile.cs b/src/AsmResolver.PE.File/IPEFile.cs index 6f22ca61a..57e4e50a9 100644 --- a/src/AsmResolver.PE.File/IPEFile.cs +++ b/src/AsmResolver.PE.File/IPEFile.cs @@ -61,6 +61,24 @@ PEMappingMode MappingMode get; } + /// + /// Gets or sets the padding data in between the last section header and the first section. + /// + public ISegment? ExtraSectionData + { + get; + set; + } + + /// + /// Gets or sets the data appended to the end of the file (EoF), if available. + /// + public ISegment? EofData + { + get; + set; + } + /// /// Finds the section containing the provided virtual address. /// diff --git a/src/AsmResolver.PE.File/PEFile.cs b/src/AsmResolver.PE.File/PEFile.cs index 276e4aa0b..d1dc0f2d7 100644 --- a/src/AsmResolver.PE.File/PEFile.cs +++ b/src/AsmResolver.PE.File/PEFile.cs @@ -94,18 +94,14 @@ public PEMappingMode MappingMode protected set; } - /// - /// Gets or sets the padding data in between the last section header and the first section. - /// + /// public ISegment? ExtraSectionData { get => _extraSectionData.Value; set => _extraSectionData.Value = value; } - /// - /// Gets or sets the data appended to the end of the file (EoF), if available. - /// + /// public ISegment? EofData { get => _eofData.Value; From 2da80a30a63bed91984e602fe11ae67a5aca9cf5 Mon Sep 17 00:00:00 2001 From: Jeremy Pritts Date: Wed, 30 Mar 2022 19:45:06 -0400 Subject: [PATCH 088/184] allow net 6 assembly trimming --- src/AsmResolver.PE.File/AsmResolver.PE.File.csproj | 1 + .../AsmResolver.PE.Win32Resources.csproj | 1 + src/AsmResolver.PE/AsmResolver.PE.csproj | 1 + src/AsmResolver/AsmResolver.csproj | 3 ++- .../DotNet/Metadata/UserStringsStreamTest.cs | 2 +- 5 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj b/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj index 6341887ff..5c0bfda0f 100644 --- a/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj +++ b/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj @@ -9,6 +9,7 @@ true enable net6.0;netcoreapp3.1;netstandard2.0 + true diff --git a/src/AsmResolver.PE.Win32Resources/AsmResolver.PE.Win32Resources.csproj b/src/AsmResolver.PE.Win32Resources/AsmResolver.PE.Win32Resources.csproj index 0baf5f6c0..219f54c0d 100644 --- a/src/AsmResolver.PE.Win32Resources/AsmResolver.PE.Win32Resources.csproj +++ b/src/AsmResolver.PE.Win32Resources/AsmResolver.PE.Win32Resources.csproj @@ -7,6 +7,7 @@ 1701;1702;NU5105 enable net6.0;netcoreapp3.1;netstandard2.0 + true diff --git a/src/AsmResolver.PE/AsmResolver.PE.csproj b/src/AsmResolver.PE/AsmResolver.PE.csproj index 43c3a61d5..058547040 100644 --- a/src/AsmResolver.PE/AsmResolver.PE.csproj +++ b/src/AsmResolver.PE/AsmResolver.PE.csproj @@ -8,6 +8,7 @@ 1701;1702;NU5105 enable net6.0;netcoreapp3.1;netstandard2.0 + true diff --git a/src/AsmResolver/AsmResolver.csproj b/src/AsmResolver/AsmResolver.csproj index da0e0e6cb..07dbf2281 100644 --- a/src/AsmResolver/AsmResolver.csproj +++ b/src/AsmResolver/AsmResolver.csproj @@ -3,11 +3,12 @@ AsmResolver The base library for the AsmResolver executable file inspection toolsuite. - exe pe dotnet cil inspection manipulation assembly disassembly + exe pe dotnet cil inspection manipulation assembly disassembly true 1701;1702;NU5105 enable net6.0;netcoreapp3.1;netstandard2.0 + true diff --git a/test/AsmResolver.PE.Tests/DotNet/Metadata/UserStringsStreamTest.cs b/test/AsmResolver.PE.Tests/DotNet/Metadata/UserStringsStreamTest.cs index 9b3c274ac..fb350b97a 100644 --- a/test/AsmResolver.PE.Tests/DotNet/Metadata/UserStringsStreamTest.cs +++ b/test/AsmResolver.PE.Tests/DotNet/Metadata/UserStringsStreamTest.cs @@ -22,7 +22,7 @@ private static void AssertHasString(byte[] streamData, string needle) [InlineData("")] [InlineData("ABC")] [InlineData("DEF")] - public void FindExistingString(string? value) => AssertHasString(new byte[] + public void FindExistingString(string value) => AssertHasString(new byte[] { 0x00, 0x07, 0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x00, From 28e8d230fbe8c2e1239345a1c211a6a9fd4040aa Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 2 Apr 2022 15:02:31 +0200 Subject: [PATCH 089/184] Add clarifying remarks on BundleFile.IsCompressed and Compress(). --- src/AsmResolver.DotNet/Bundles/BundleFile.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/AsmResolver.DotNet/Bundles/BundleFile.cs b/src/AsmResolver.DotNet/Bundles/BundleFile.cs index 84dba4101..958eeba16 100644 --- a/src/AsmResolver.DotNet/Bundles/BundleFile.cs +++ b/src/AsmResolver.DotNet/Bundles/BundleFile.cs @@ -84,6 +84,13 @@ public BundleFileType Type /// /// Gets or sets a value indicating whether the data stored in is compressed or not. /// + /// + /// The default implementation of the application host by Microsoft only supports compressing files if it is + /// a fully self-contained binary and the file is not the .deps.json nor the .runtmeconfig.json + /// file. This property does not do validation on any of these conditions. As such, if the file is supposed to be + /// compressed with any of these conditions not met, a custom application host template needs to be provided + /// upon serializing the bundle for it to be runnable. + /// public bool IsCompressed { get; @@ -170,6 +177,13 @@ public byte[] GetData(bool decompressIfRequired) /// with the result. /// /// Occurs when the file was already compressed. + /// + /// The default implementation of the application host by Microsoft only supports compressing files if it is + /// a fully self-contained binary and the file is not the .deps.json nor the .runtmeconfig.json + /// file. This method does not do validation on any of these conditions. As such, if the file is supposed to be + /// compressed with any of these conditions not met, a custom application host template needs to be provided + /// upon serializing the bundle for it to be runnable. + /// public void Compress() { if (IsCompressed) @@ -191,7 +205,7 @@ public void Compress() /// Marks the file as uncompressed, decompresses the file contents, and replaces the value of /// with the result. /// - /// Occurs when the file was already compressed. + /// Occurs when the file was not compressed. public void Decompress() { if (!IsCompressed) From e35aa59e0f8a6fd51f12401b47607314f0687919 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 2 Apr 2022 15:13:22 +0200 Subject: [PATCH 090/184] Bump version numbers. --- Directory.Build.props | 2 +- appveyor.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 09d02e0ef..648c5ce2f 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,7 +7,7 @@ https://github.com/Washi1337/AsmResolver git 10 - 4.9.0 + 4.10.0 diff --git a/appveyor.yml b/appveyor.yml index 6de584d01..fe19b7d43 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,7 +4,7 @@ - master image: Visual Studio 2022 - version: 4.9.0-master-build.{build} + version: 4.10.0-master-build.{build} configuration: Release skip_commits: @@ -33,7 +33,7 @@ - development image: Visual Studio 2022 - version: 4.9.0-dev-build.{build} + version: 4.10.0-dev-build.{build} configuration: Release skip_commits: From 03ec8b81752f476e778a4a9812545bd98c3f5e0f Mon Sep 17 00:00:00 2001 From: Jeremy Pritts Date: Mon, 4 Apr 2022 17:38:04 -0400 Subject: [PATCH 091/184] add json source generation for RuntimeConfiguration --- .../Config/Json/RuntimeConfiguration.cs | 12 ++---------- .../Json/RuntimeConfigurationSerializerContext.cs | 13 +++++++++++++ 2 files changed, 15 insertions(+), 10 deletions(-) create mode 100644 src/AsmResolver.DotNet/Config/Json/RuntimeConfigurationSerializerContext.cs diff --git a/src/AsmResolver.DotNet/Config/Json/RuntimeConfiguration.cs b/src/AsmResolver.DotNet/Config/Json/RuntimeConfiguration.cs index 3a8ef6863..227751398 100644 --- a/src/AsmResolver.DotNet/Config/Json/RuntimeConfiguration.cs +++ b/src/AsmResolver.DotNet/Config/Json/RuntimeConfiguration.cs @@ -1,6 +1,5 @@ using System.IO; using System.Text.Json; -using System.Text.Json.Serialization; namespace AsmResolver.DotNet.Config.Json { @@ -9,13 +8,6 @@ namespace AsmResolver.DotNet.Config.Json /// public class RuntimeConfiguration { - private static readonly JsonSerializerOptions JsonSerializerOptions = new() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - WriteIndented = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - }; - /// /// Parses runtime configuration from a JSON file. /// @@ -33,7 +25,7 @@ public class RuntimeConfiguration /// The parsed runtime configuration. public static RuntimeConfiguration? FromJson(string json) { - return JsonSerializer.Deserialize(json, JsonSerializerOptions); + return JsonSerializer.Deserialize(json, RuntimeConfigurationSerializerContext.Default.RuntimeConfiguration); } /// @@ -67,7 +59,7 @@ public RuntimeOptions RuntimeOptions /// The JSON string. public string ToJson() { - return JsonSerializer.Serialize(this, JsonSerializerOptions); + return JsonSerializer.Serialize(this, RuntimeConfigurationSerializerContext.Default.RuntimeConfiguration); } /// diff --git a/src/AsmResolver.DotNet/Config/Json/RuntimeConfigurationSerializerContext.cs b/src/AsmResolver.DotNet/Config/Json/RuntimeConfigurationSerializerContext.cs new file mode 100644 index 000000000..7f77c1dc6 --- /dev/null +++ b/src/AsmResolver.DotNet/Config/Json/RuntimeConfigurationSerializerContext.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; + +namespace AsmResolver.DotNet.Config.Json +{ + [JsonSourceGenerationOptions( + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + WriteIndented = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] + [JsonSerializable(typeof(RuntimeConfiguration))] + internal partial class RuntimeConfigurationSerializerContext : JsonSerializerContext + { + } +} From af40369d5fa7ca557057785dba745ad0258fcca2 Mon Sep 17 00:00:00 2001 From: Jeremy Pritts Date: Mon, 4 Apr 2022 17:40:32 -0400 Subject: [PATCH 092/184] add annotations for DynamicMethodHelper --- .../Code/Cil/CilMethodBody.cs | 1 + .../DynamicMethodDefinition.cs | 3 +- src/AsmResolver.DotNet/DynamicMethodHelper.cs | 3 +- .../RequiresUnreferencedCodeAttribute.cs | 34 +++++++++++++++++++ 4 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 src/AsmResolver.DotNet/RequiresUnreferencedCodeAttribute.cs diff --git a/src/AsmResolver.DotNet/Code/Cil/CilMethodBody.cs b/src/AsmResolver.DotNet/Code/Cil/CilMethodBody.cs index 633440c24..180e474f8 100644 --- a/src/AsmResolver.DotNet/Code/Cil/CilMethodBody.cs +++ b/src/AsmResolver.DotNet/Code/Cil/CilMethodBody.cs @@ -136,6 +136,7 @@ public bool VerifyLabelsOnBuild /// 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)) diff --git a/src/AsmResolver.DotNet/DynamicMethodDefinition.cs b/src/AsmResolver.DotNet/DynamicMethodDefinition.cs index 90e5865bf..b95d8f003 100644 --- a/src/AsmResolver.DotNet/DynamicMethodDefinition.cs +++ b/src/AsmResolver.DotNet/DynamicMethodDefinition.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Reflection; using AsmResolver.DotNet.Code.Cil; using AsmResolver.DotNet.Signatures; @@ -18,6 +18,7 @@ public class DynamicMethodDefinition : MethodDefinition /// /// 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)) { diff --git a/src/AsmResolver.DotNet/DynamicMethodHelper.cs b/src/AsmResolver.DotNet/DynamicMethodHelper.cs index 7e856d135..f3cece5db 100644 --- a/src/AsmResolver.DotNet/DynamicMethodHelper.cs +++ b/src/AsmResolver.DotNet/DynamicMethodHelper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -81,6 +81,7 @@ private static void InterpretEHInfo(CilMethodBody methodBody, ReferenceImporter } } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls GetTypes")] public static object ResolveDynamicResolver(object dynamicMethodObj) { //Convert dynamicMethodObj to DynamicResolver diff --git a/src/AsmResolver.DotNet/RequiresUnreferencedCodeAttribute.cs b/src/AsmResolver.DotNet/RequiresUnreferencedCodeAttribute.cs new file mode 100644 index 000000000..c5fd0fea6 --- /dev/null +++ b/src/AsmResolver.DotNet/RequiresUnreferencedCodeAttribute.cs @@ -0,0 +1,34 @@ +#if !NET6_0_OR_GREATER +namespace System.Diagnostics.CodeAnalysis +{ + /// + /// Indicates that the specified method requires dynamic access to code that is not + /// referenced statically, for example, through System.Reflection. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Method, Inherited = false)] + internal sealed class RequiresUnreferencedCodeAttribute : Attribute + { + /// + /// Gets a message that contains information about the usage of unreferenced code. + /// + public string Message { get; } + + /// + /// Gets or sets an optional URL that contains more information about the method, + /// why it requires unreferenced code, and what options a consumer has to deal with + /// it. + /// + public string? Url { get; set; } + + /// + /// Initializes a new instance of the System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute + /// class with the specified message. + /// + /// A message that contains information about the usage of unreferenced code. + public RequiresUnreferencedCodeAttribute(string message) + { + Message = message; + } + } +} +#endif From 891af44cfa89a2a031ab6f88394635b78b5fdde2 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 7 Apr 2022 20:23:56 +0200 Subject: [PATCH 093/184] Add discord server link. --- README.md | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index db28f348a..31582ebfe 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,11 @@ AsmResolver =========== - [![Master branch build status](https://img.shields.io/appveyor/ci/Washi1337/AsmResolver/master.svg)](https://ci.appveyor.com/project/Washi1337/asmresolver/branch/master) [![Nuget feed](https://img.shields.io/nuget/v/AsmResolver.svg)](https://www.nuget.org/packages/AsmResolver/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Documentation Status](https://readthedocs.org/projects/asmresolver/badge/?version=latest)](https://asmresolver.readthedocs.io/en/latest/?badge=latest) + [![Master branch build status](https://img.shields.io/appveyor/ci/Washi1337/AsmResolver/master.svg)](https://ci.appveyor.com/project/Washi1337/asmresolver/branch/master) + [![Nuget feed](https://img.shields.io/nuget/v/AsmResolver.svg)](https://www.nuget.org/packages/AsmResolver/) + [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) + [![Documentation Status](https://readthedocs.org/projects/asmresolver/badge/?version=latest)](https://asmresolver.readthedocs.io/en/latest/?badge=latest) + [![Discord](https://img.shields.io/discord/961647807591243796.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/Y7DTBkbhJJ) AsmResolver is a PE inspection library allowing .NET programmers to read, modify and write executable files. This includes .NET as well as native images. The library exposes high-level representations of the PE, while still allowing the user to access low-level structures. @@ -53,6 +57,12 @@ Contributing See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on general workflow and code style. +Found a bug or have questions? +------------------------------ +Please use the [issue tracker](https://github.com/Washi1337/AsmResolver/issues). Try to be as descriptive as possible. + +You can also join the [Discord](https://discord.gg/Y7DTBkbhJJ) to engage more directly with the community. + Acknowledgements ---------------- @@ -60,12 +70,7 @@ AsmResolver started out as a hobby project, but has grown into a community proje - Special thanks to all the people who contributed [directly with code commits](https://github.com/Washi1337/AsmResolver/graphs/contributors). -- Another big thank you to all the people that suggested new features, provided feedback on the API design, have done extensive testing, and/or reported bugs on the [issue board](https://github.com/Washi1337/AsmResolver/issues), by e-mail, or through DMs. +- Another big thank you to all the people that suggested new features, provided feedback on the API design, have done extensive testing, and/or reported bugs on the [issue board](https://github.com/Washi1337/AsmResolver/issues), by e-mail, or through DMs. If you feel you have been under-represented in these acknowledgements, feel free to contact me. - -Found a bug or have questions? ------------------------------- -Please use the [issue tracker](https://github.com/Washi1337/AsmResolver/issues). Try to be as descriptive as possible. - From 32f8ae737144a5c6fd18ac33257b9daaceee60de Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 8 Apr 2022 20:31:39 +0200 Subject: [PATCH 094/184] Surpress null warning on using Hash property after call TransformFinalBlock. --- .../Bundles/BundleManifest.cs | 1006 ++++++++--------- 1 file changed, 503 insertions(+), 503 deletions(-) diff --git a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs index bd8e6f80b..2acc2a57b 100644 --- a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs +++ b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs @@ -1,503 +1,503 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Security.Cryptography; -using System.Text; -using System.Threading; -using AsmResolver.Collections; -using AsmResolver.IO; -using AsmResolver.PE.File; -using AsmResolver.PE.File.Headers; -using AsmResolver.PE.Win32Resources.Builder; - -namespace AsmResolver.DotNet.Bundles -{ - /// - /// Represents a set of bundled files embedded in a .NET application host or single-file host. - /// - public class BundleManifest - { - private const int DefaultBundleIDLength = 12; - - private static readonly byte[] BundleSignature = - { - 0x8b, 0x12, 0x02, 0xb9, 0x6a, 0x61, 0x20, 0x38, - 0x72, 0x7b, 0x93, 0x02, 0x14, 0xd7, 0xa0, 0x32, - 0x13, 0xf5, 0xb9, 0xe6, 0xef, 0xae, 0x33, 0x18, - 0xee, 0x3b, 0x2d, 0xce, 0x24, 0xb3, 0x6a, 0xae - }; - - private static readonly byte[] AppBinaryPathPlaceholder = - Encoding.UTF8.GetBytes("c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2"); - - private IList? _files; - - /// - /// Initializes an empty bundle manifest. - /// - protected BundleManifest() - { - } - - /// - /// Creates a new bundle manifest. - /// - /// The file format version. - public BundleManifest(uint majorVersionNumber) - { - MajorVersion = majorVersionNumber; - MinorVersion = 0; - } - - /// - /// Creates a new bundle manifest with a specific bundle identifier. - /// - /// The file format version. - /// The unique bundle manifest identifier. - public BundleManifest(uint majorVersionNumber, string bundleId) - { - MajorVersion = majorVersionNumber; - MinorVersion = 0; - BundleID = bundleId; - } - - /// - /// Gets or sets the major file format version of the bundle. - /// - /// - /// Version numbers recognized by the CLR are: - /// - /// 1 for .NET Core 3.1 - /// 2 for .NET 5.0 - /// 6 for .NET 6.0 - /// - /// - public uint MajorVersion - { - get; - set; - } - - /// - /// Gets or sets the minor file format version of the bundle. - /// - /// - /// This value is ignored by the CLR and should be set to 0. - /// - public uint MinorVersion - { - get; - set; - } - - /// - /// Gets or sets the unique identifier for the bundle manifest. - /// - /// - /// When this property is set to null, the bundle identifier will be generated upon writing the manifest - /// based on the contents of the manifest. - /// - public string? BundleID - { - get; - set; - } - - /// - /// Gets or sets flags associated to the bundle. - /// - public BundleManifestFlags Flags - { - get; - set; - } - - /// - /// Gets a collection of files stored in the bundle. - /// - public IList Files - { - get - { - if (_files is null) - Interlocked.CompareExchange(ref _files, GetFiles(), null); - return _files; - } - } - - /// - /// Attempts to automatically locate and parse the bundle header in the provided file. - /// - /// The path to the file to read. - /// The read manifest. - public static BundleManifest FromFile(string filePath) - { - return FromBytes(File.ReadAllBytes(filePath)); - } - - /// - /// Attempts to automatically locate and parse the bundle header in the provided file. - /// - /// The raw contents of the file to read. - /// The read manifest. - public static BundleManifest FromBytes(byte[] data) - { - return FromDataSource(new ByteArrayDataSource(data)); - } - - /// - /// Parses the bundle header in the provided file at the provided address. - /// - /// The raw contents of the file to read. - /// The address within the file to start reading the bundle at. - /// The read manifest. - public static BundleManifest FromBytes(byte[] data, ulong offset) - { - return FromDataSource(new ByteArrayDataSource(data), offset); - } - - /// - /// Attempts to automatically locate and parse the bundle header in the provided file. - /// - /// The raw contents of the file to read. - /// The read manifest. - public static BundleManifest FromDataSource(IDataSource source) - { - long address = FindBundleManifestAddress(source); - if (address == -1) - throw new BadImageFormatException("File does not contain an AppHost bundle signature."); - - return FromDataSource(source, (ulong) address); - } - - /// - /// Parses the bundle header in the provided file at the provided address. - /// - /// The raw contents of the file to read. - /// The address within the file to start reading the bundle at. - /// The read manifest. - public static BundleManifest FromDataSource(IDataSource source, ulong offset) - { - var reader = new BinaryStreamReader(source, 0, 0, (uint) source.Length) - { - Offset = offset - }; - - return FromReader(reader); - } - - /// - /// Parses the bundle header from the provided input stream. - /// - /// The input stream pointing to the start of the bundle to read. - /// The read manifest. - public static BundleManifest FromReader(BinaryStreamReader reader) => new SerializedBundleManifest(reader); - - private static long FindInFile(IDataSource source, byte[] data) - { - // Note: For performance reasons, we read data from the data source in blocks, such that we avoid - // virtual-dispatch calls and do the searching directly on a byte array instead. - - byte[] buffer = new byte[0x1000]; - - ulong start = 0; - while (start < source.Length) - { - int read = source.ReadBytes(start, buffer, 0, buffer.Length); - - for (int i = sizeof(ulong); i < read - data.Length; i++) - { - bool fullMatch = true; - for (int j = 0; fullMatch && j < data.Length; j++) - { - if (buffer[i + j] != data[j]) - fullMatch = false; - } - - if (fullMatch) - return (long) start + i; - } - - start += (ulong) read; - } - - return -1; - } - - private static long ReadBundleManifestAddress(IDataSource source, long signatureAddress) - { - var reader = new BinaryStreamReader(source, (ulong) signatureAddress - sizeof(ulong), 0, 8); - ulong manifestAddress = reader.ReadUInt64(); - - return source.IsValidAddress(manifestAddress) - ? (long) manifestAddress - : -1; - } - - /// - /// Attempts to find the start of the bundle header in the provided file. - /// - /// The file to locate the bundle header in. - /// The offset, or -1 if none was found. - public static long FindBundleManifestAddress(IDataSource source) - { - long signatureAddress = FindInFile(source, BundleSignature); - if (signatureAddress == -1) - return -1; - - return ReadBundleManifestAddress(source, signatureAddress); - } - - /// - /// Gets a value indicating whether the provided data source contains a conventional bundled assembly signature. - /// - /// The file to locate the bundle header in. - /// true if a bundle signature was found, false otherwise. - public static bool IsBundledAssembly(IDataSource source) => FindBundleManifestAddress(source) != -1; - - /// - /// Obtains the list of files stored in the bundle. - /// - /// The files - /// - /// This method is called upon initialization of the property. - /// - protected virtual IList GetFiles() => new OwnedCollection(this); - - /// - /// Generates a bundle identifier based on the SHA-256 hashes of all files in the manifest. - /// - /// The generated bundle identifier. - public string GenerateDeterministicBundleID() - { - using var manifestHasher = SHA256.Create(); - - for (int i = 0; i < Files.Count; i++) - { - var file = Files[i]; - using var fileHasher = SHA256.Create(); - byte[] fileHash = fileHasher.ComputeHash(file.GetData()); - manifestHasher.TransformBlock(fileHash, 0, fileHash.Length, fileHash, 0); - } - - manifestHasher.TransformFinalBlock(Array.Empty(), 0, 0); - byte[] manifestHash = manifestHasher.Hash; - - return Convert.ToBase64String(manifestHash) - .Substring(DefaultBundleIDLength) - .Replace('/', '_'); - } - - /// - /// Constructs a new application host file based on the bundle manifest. - /// - /// The path of the file to write to. - /// The parameters to use for bundling all files into a single executable. - public void WriteUsingTemplate(string outputPath, in BundlerParameters parameters) - { - using var fs = File.Create(outputPath); - WriteUsingTemplate(fs, parameters); - } - - /// - /// Constructs a new application host file based on the bundle manifest. - /// - /// The output stream to write to. - /// The parameters to use for bundling all files into a single executable. - public void WriteUsingTemplate(Stream outputStream, in BundlerParameters parameters) - { - WriteUsingTemplate(new BinaryStreamWriter(outputStream), parameters); - } - - /// - /// Constructs a new application host file based on the bundle manifest. - /// - /// The output stream to write to. - /// The parameters to use for bundling all files into a single executable. - public void WriteUsingTemplate(IBinaryStreamWriter writer, BundlerParameters parameters) - { - var appBinaryEntry = Files.FirstOrDefault(f => f.RelativePath == parameters.ApplicationBinaryPath); - if (appBinaryEntry is null) - throw new ArgumentException($"Application {parameters.ApplicationBinaryPath} does not exist within the bundle."); - - byte[] appBinaryPathBytes = Encoding.UTF8.GetBytes(parameters.ApplicationBinaryPath); - if (appBinaryPathBytes.Length > 1024) - throw new ArgumentException("Application binary path cannot exceed 1024 bytes."); - - if (!parameters.IsArm64Linux) - EnsureAppHostPEHeadersAreUpToDate(ref parameters); - - var appHostTemplateSource = new ByteArrayDataSource(parameters.ApplicationHostTemplate); - long signatureAddress = FindInFile(appHostTemplateSource, BundleSignature); - if (signatureAddress == -1) - throw new ArgumentException("AppHost template does not contain the bundle signature."); - - long appBinaryPathAddress = FindInFile(appHostTemplateSource, AppBinaryPathPlaceholder); - if (appBinaryPathAddress == -1) - throw new ArgumentException("AppHost template does not contain the application binary path placeholder."); - - writer.WriteBytes(parameters.ApplicationHostTemplate); - writer.Offset = writer.Length; - ulong headerAddress = WriteManifest(writer, parameters.IsArm64Linux); - - writer.Offset = (ulong) signatureAddress - sizeof(ulong); - writer.WriteUInt64(headerAddress); - - writer.Offset = (ulong) appBinaryPathAddress; - writer.WriteBytes(appBinaryPathBytes); - if (AppBinaryPathPlaceholder.Length > appBinaryPathBytes.Length) - writer.WriteZeroes(AppBinaryPathPlaceholder.Length - appBinaryPathBytes.Length); - } - - private static void EnsureAppHostPEHeadersAreUpToDate(ref BundlerParameters parameters) - { - PEFile file; - try - { - file = PEFile.FromBytes(parameters.ApplicationHostTemplate); - } - catch (BadImageFormatException) - { - // Template is not a PE file. - return; - } - - bool changed = false; - - // Ensure same Windows subsystem is used (typically required for GUI applications). - if (file.OptionalHeader.SubSystem != parameters.SubSystem) - { - file.OptionalHeader.SubSystem = parameters.SubSystem; - changed = true; - } - - // If the app binary has resources (such as an icon or version info), we need to copy it into the - // AppHost template so that they are also visible from the final packed executable. - if (parameters.Resources is { } directory) - { - // Put original resource directory in a new .rsrc section. - var buffer = new ResourceDirectoryBuffer(); - buffer.AddDirectory(directory); - var rsrc = new PESection(".rsrc", SectionFlags.MemoryRead | SectionFlags.ContentInitializedData); - rsrc.Contents = buffer; - - // Find .reloc section, and insert .rsrc before it if it is present. Otherwise just append to the end. - int sectionIndex = file.Sections.Count - 1; - for (int i = file.Sections.Count - 1; i >= 0; i--) - { - if (file.Sections[i].Name == ".reloc") - { - sectionIndex = i; - break; - } - } - - file.Sections.Insert(sectionIndex, rsrc); - - // Update resource data directory va + size. - file.AlignSections(); - file.OptionalHeader.DataDirectories[(int) DataDirectoryIndex.ResourceDirectory] = new DataDirectory( - buffer.Rva, - buffer.GetPhysicalSize()); - - changed = true; - } - - // Rebuild AppHost PE file if necessary. - if (changed) - { - using var stream = new MemoryStream(); - file.Write(stream); - parameters.ApplicationHostTemplate = stream.ToArray(); - } - } - - /// - /// Writes the manifest to an output stream. - /// - /// The output stream to write to. - /// true if the application host is a Linux ELF binary targeting ARM64. - /// The address of the bundle header. - /// - /// This does not necessarily produce a working executable file, it only writes the contents of the entire manifest, - /// without a host application that invokes the manifest. If you want to produce a runnable executable, use one - /// of the WriteUsingTemplate methods instead. - /// - public ulong WriteManifest(IBinaryStreamWriter writer, bool isArm64Linux) - { - WriteFileContents(writer, isArm64Linux - ? 4096u - : 16u); - - ulong headerAddress = writer.Offset; - WriteManifestHeader(writer); - - return headerAddress; - } - - private void WriteFileContents(IBinaryStreamWriter writer, uint alignment) - { - for (int i = 0; i < Files.Count; i++) - { - var file = Files[i]; - - if (file.Type == BundleFileType.Assembly) - writer.Align(alignment); - - file.Contents.UpdateOffsets(writer.Offset, (uint) writer.Offset); - file.Contents.Write(writer); - } - } - - private void WriteManifestHeader(IBinaryStreamWriter writer) - { - writer.WriteUInt32(MajorVersion); - writer.WriteUInt32(MinorVersion); - writer.WriteInt32(Files.Count); - - BundleID ??= GenerateDeterministicBundleID(); - writer.WriteBinaryFormatterString(BundleID); - - if (MajorVersion >= 2) - { - WriteFileOffsetSizePair(writer, Files.FirstOrDefault(f => f.Type == BundleFileType.DepsJson)); - WriteFileOffsetSizePair(writer, Files.FirstOrDefault(f => f.Type == BundleFileType.RuntimeConfigJson)); - writer.WriteUInt64((ulong) Flags); - } - - WriteFileHeaders(writer); - } - - private void WriteFileHeaders(IBinaryStreamWriter writer) - { - for (int i = 0; i < Files.Count; i++) - { - var file = Files[i]; - - WriteFileOffsetSizePair(writer, file); - - if (MajorVersion >= 6) - writer.WriteUInt64(file.IsCompressed ? file.Contents.GetPhysicalSize() : 0); - - writer.WriteByte((byte) file.Type); - writer.WriteBinaryFormatterString(file.RelativePath); - } - } - - private static void WriteFileOffsetSizePair(IBinaryStreamWriter writer, BundleFile? file) - { - if (file is not null) - { - writer.WriteUInt64(file.Contents.Offset); - writer.WriteUInt64((ulong) file.GetData().Length); - } - else - { - writer.WriteUInt64(0); - writer.WriteUInt64(0); - } - } - - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using AsmResolver.Collections; +using AsmResolver.IO; +using AsmResolver.PE.File; +using AsmResolver.PE.File.Headers; +using AsmResolver.PE.Win32Resources.Builder; + +namespace AsmResolver.DotNet.Bundles +{ + /// + /// Represents a set of bundled files embedded in a .NET application host or single-file host. + /// + public class BundleManifest + { + private const int DefaultBundleIDLength = 12; + + private static readonly byte[] BundleSignature = + { + 0x8b, 0x12, 0x02, 0xb9, 0x6a, 0x61, 0x20, 0x38, + 0x72, 0x7b, 0x93, 0x02, 0x14, 0xd7, 0xa0, 0x32, + 0x13, 0xf5, 0xb9, 0xe6, 0xef, 0xae, 0x33, 0x18, + 0xee, 0x3b, 0x2d, 0xce, 0x24, 0xb3, 0x6a, 0xae + }; + + private static readonly byte[] AppBinaryPathPlaceholder = + Encoding.UTF8.GetBytes("c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2"); + + private IList? _files; + + /// + /// Initializes an empty bundle manifest. + /// + protected BundleManifest() + { + } + + /// + /// Creates a new bundle manifest. + /// + /// The file format version. + public BundleManifest(uint majorVersionNumber) + { + MajorVersion = majorVersionNumber; + MinorVersion = 0; + } + + /// + /// Creates a new bundle manifest with a specific bundle identifier. + /// + /// The file format version. + /// The unique bundle manifest identifier. + public BundleManifest(uint majorVersionNumber, string bundleId) + { + MajorVersion = majorVersionNumber; + MinorVersion = 0; + BundleID = bundleId; + } + + /// + /// Gets or sets the major file format version of the bundle. + /// + /// + /// Version numbers recognized by the CLR are: + /// + /// 1 for .NET Core 3.1 + /// 2 for .NET 5.0 + /// 6 for .NET 6.0 + /// + /// + public uint MajorVersion + { + get; + set; + } + + /// + /// Gets or sets the minor file format version of the bundle. + /// + /// + /// This value is ignored by the CLR and should be set to 0. + /// + public uint MinorVersion + { + get; + set; + } + + /// + /// Gets or sets the unique identifier for the bundle manifest. + /// + /// + /// When this property is set to null, the bundle identifier will be generated upon writing the manifest + /// based on the contents of the manifest. + /// + public string? BundleID + { + get; + set; + } + + /// + /// Gets or sets flags associated to the bundle. + /// + public BundleManifestFlags Flags + { + get; + set; + } + + /// + /// Gets a collection of files stored in the bundle. + /// + public IList Files + { + get + { + if (_files is null) + Interlocked.CompareExchange(ref _files, GetFiles(), null); + return _files; + } + } + + /// + /// Attempts to automatically locate and parse the bundle header in the provided file. + /// + /// The path to the file to read. + /// The read manifest. + public static BundleManifest FromFile(string filePath) + { + return FromBytes(File.ReadAllBytes(filePath)); + } + + /// + /// Attempts to automatically locate and parse the bundle header in the provided file. + /// + /// The raw contents of the file to read. + /// The read manifest. + public static BundleManifest FromBytes(byte[] data) + { + return FromDataSource(new ByteArrayDataSource(data)); + } + + /// + /// Parses the bundle header in the provided file at the provided address. + /// + /// The raw contents of the file to read. + /// The address within the file to start reading the bundle at. + /// The read manifest. + public static BundleManifest FromBytes(byte[] data, ulong offset) + { + return FromDataSource(new ByteArrayDataSource(data), offset); + } + + /// + /// Attempts to automatically locate and parse the bundle header in the provided file. + /// + /// The raw contents of the file to read. + /// The read manifest. + public static BundleManifest FromDataSource(IDataSource source) + { + long address = FindBundleManifestAddress(source); + if (address == -1) + throw new BadImageFormatException("File does not contain an AppHost bundle signature."); + + return FromDataSource(source, (ulong) address); + } + + /// + /// Parses the bundle header in the provided file at the provided address. + /// + /// The raw contents of the file to read. + /// The address within the file to start reading the bundle at. + /// The read manifest. + public static BundleManifest FromDataSource(IDataSource source, ulong offset) + { + var reader = new BinaryStreamReader(source, 0, 0, (uint) source.Length) + { + Offset = offset + }; + + return FromReader(reader); + } + + /// + /// Parses the bundle header from the provided input stream. + /// + /// The input stream pointing to the start of the bundle to read. + /// The read manifest. + public static BundleManifest FromReader(BinaryStreamReader reader) => new SerializedBundleManifest(reader); + + private static long FindInFile(IDataSource source, byte[] data) + { + // Note: For performance reasons, we read data from the data source in blocks, such that we avoid + // virtual-dispatch calls and do the searching directly on a byte array instead. + + byte[] buffer = new byte[0x1000]; + + ulong start = 0; + while (start < source.Length) + { + int read = source.ReadBytes(start, buffer, 0, buffer.Length); + + for (int i = sizeof(ulong); i < read - data.Length; i++) + { + bool fullMatch = true; + for (int j = 0; fullMatch && j < data.Length; j++) + { + if (buffer[i + j] != data[j]) + fullMatch = false; + } + + if (fullMatch) + return (long) start + i; + } + + start += (ulong) read; + } + + return -1; + } + + private static long ReadBundleManifestAddress(IDataSource source, long signatureAddress) + { + var reader = new BinaryStreamReader(source, (ulong) signatureAddress - sizeof(ulong), 0, 8); + ulong manifestAddress = reader.ReadUInt64(); + + return source.IsValidAddress(manifestAddress) + ? (long) manifestAddress + : -1; + } + + /// + /// Attempts to find the start of the bundle header in the provided file. + /// + /// The file to locate the bundle header in. + /// The offset, or -1 if none was found. + public static long FindBundleManifestAddress(IDataSource source) + { + long signatureAddress = FindInFile(source, BundleSignature); + if (signatureAddress == -1) + return -1; + + return ReadBundleManifestAddress(source, signatureAddress); + } + + /// + /// Gets a value indicating whether the provided data source contains a conventional bundled assembly signature. + /// + /// The file to locate the bundle header in. + /// true if a bundle signature was found, false otherwise. + public static bool IsBundledAssembly(IDataSource source) => FindBundleManifestAddress(source) != -1; + + /// + /// Obtains the list of files stored in the bundle. + /// + /// The files + /// + /// This method is called upon initialization of the property. + /// + protected virtual IList GetFiles() => new OwnedCollection(this); + + /// + /// Generates a bundle identifier based on the SHA-256 hashes of all files in the manifest. + /// + /// The generated bundle identifier. + public string GenerateDeterministicBundleID() + { + using var manifestHasher = SHA256.Create(); + + for (int i = 0; i < Files.Count; i++) + { + var file = Files[i]; + using var fileHasher = SHA256.Create(); + byte[] fileHash = fileHasher.ComputeHash(file.GetData()); + manifestHasher.TransformBlock(fileHash, 0, fileHash.Length, fileHash, 0); + } + + manifestHasher.TransformFinalBlock(Array.Empty(), 0, 0); + byte[] manifestHash = manifestHasher.Hash!; + + return Convert.ToBase64String(manifestHash) + .Substring(DefaultBundleIDLength) + .Replace('/', '_'); + } + + /// + /// Constructs a new application host file based on the bundle manifest. + /// + /// The path of the file to write to. + /// The parameters to use for bundling all files into a single executable. + public void WriteUsingTemplate(string outputPath, in BundlerParameters parameters) + { + using var fs = File.Create(outputPath); + WriteUsingTemplate(fs, parameters); + } + + /// + /// Constructs a new application host file based on the bundle manifest. + /// + /// The output stream to write to. + /// The parameters to use for bundling all files into a single executable. + public void WriteUsingTemplate(Stream outputStream, in BundlerParameters parameters) + { + WriteUsingTemplate(new BinaryStreamWriter(outputStream), parameters); + } + + /// + /// Constructs a new application host file based on the bundle manifest. + /// + /// The output stream to write to. + /// The parameters to use for bundling all files into a single executable. + public void WriteUsingTemplate(IBinaryStreamWriter writer, BundlerParameters parameters) + { + var appBinaryEntry = Files.FirstOrDefault(f => f.RelativePath == parameters.ApplicationBinaryPath); + if (appBinaryEntry is null) + throw new ArgumentException($"Application {parameters.ApplicationBinaryPath} does not exist within the bundle."); + + byte[] appBinaryPathBytes = Encoding.UTF8.GetBytes(parameters.ApplicationBinaryPath); + if (appBinaryPathBytes.Length > 1024) + throw new ArgumentException("Application binary path cannot exceed 1024 bytes."); + + if (!parameters.IsArm64Linux) + EnsureAppHostPEHeadersAreUpToDate(ref parameters); + + var appHostTemplateSource = new ByteArrayDataSource(parameters.ApplicationHostTemplate); + long signatureAddress = FindInFile(appHostTemplateSource, BundleSignature); + if (signatureAddress == -1) + throw new ArgumentException("AppHost template does not contain the bundle signature."); + + long appBinaryPathAddress = FindInFile(appHostTemplateSource, AppBinaryPathPlaceholder); + if (appBinaryPathAddress == -1) + throw new ArgumentException("AppHost template does not contain the application binary path placeholder."); + + writer.WriteBytes(parameters.ApplicationHostTemplate); + writer.Offset = writer.Length; + ulong headerAddress = WriteManifest(writer, parameters.IsArm64Linux); + + writer.Offset = (ulong) signatureAddress - sizeof(ulong); + writer.WriteUInt64(headerAddress); + + writer.Offset = (ulong) appBinaryPathAddress; + writer.WriteBytes(appBinaryPathBytes); + if (AppBinaryPathPlaceholder.Length > appBinaryPathBytes.Length) + writer.WriteZeroes(AppBinaryPathPlaceholder.Length - appBinaryPathBytes.Length); + } + + private static void EnsureAppHostPEHeadersAreUpToDate(ref BundlerParameters parameters) + { + PEFile file; + try + { + file = PEFile.FromBytes(parameters.ApplicationHostTemplate); + } + catch (BadImageFormatException) + { + // Template is not a PE file. + return; + } + + bool changed = false; + + // Ensure same Windows subsystem is used (typically required for GUI applications). + if (file.OptionalHeader.SubSystem != parameters.SubSystem) + { + file.OptionalHeader.SubSystem = parameters.SubSystem; + changed = true; + } + + // If the app binary has resources (such as an icon or version info), we need to copy it into the + // AppHost template so that they are also visible from the final packed executable. + if (parameters.Resources is { } directory) + { + // Put original resource directory in a new .rsrc section. + var buffer = new ResourceDirectoryBuffer(); + buffer.AddDirectory(directory); + var rsrc = new PESection(".rsrc", SectionFlags.MemoryRead | SectionFlags.ContentInitializedData); + rsrc.Contents = buffer; + + // Find .reloc section, and insert .rsrc before it if it is present. Otherwise just append to the end. + int sectionIndex = file.Sections.Count - 1; + for (int i = file.Sections.Count - 1; i >= 0; i--) + { + if (file.Sections[i].Name == ".reloc") + { + sectionIndex = i; + break; + } + } + + file.Sections.Insert(sectionIndex, rsrc); + + // Update resource data directory va + size. + file.AlignSections(); + file.OptionalHeader.DataDirectories[(int) DataDirectoryIndex.ResourceDirectory] = new DataDirectory( + buffer.Rva, + buffer.GetPhysicalSize()); + + changed = true; + } + + // Rebuild AppHost PE file if necessary. + if (changed) + { + using var stream = new MemoryStream(); + file.Write(stream); + parameters.ApplicationHostTemplate = stream.ToArray(); + } + } + + /// + /// Writes the manifest to an output stream. + /// + /// The output stream to write to. + /// true if the application host is a Linux ELF binary targeting ARM64. + /// The address of the bundle header. + /// + /// This does not necessarily produce a working executable file, it only writes the contents of the entire manifest, + /// without a host application that invokes the manifest. If you want to produce a runnable executable, use one + /// of the WriteUsingTemplate methods instead. + /// + public ulong WriteManifest(IBinaryStreamWriter writer, bool isArm64Linux) + { + WriteFileContents(writer, isArm64Linux + ? 4096u + : 16u); + + ulong headerAddress = writer.Offset; + WriteManifestHeader(writer); + + return headerAddress; + } + + private void WriteFileContents(IBinaryStreamWriter writer, uint alignment) + { + for (int i = 0; i < Files.Count; i++) + { + var file = Files[i]; + + if (file.Type == BundleFileType.Assembly) + writer.Align(alignment); + + file.Contents.UpdateOffsets(writer.Offset, (uint) writer.Offset); + file.Contents.Write(writer); + } + } + + private void WriteManifestHeader(IBinaryStreamWriter writer) + { + writer.WriteUInt32(MajorVersion); + writer.WriteUInt32(MinorVersion); + writer.WriteInt32(Files.Count); + + BundleID ??= GenerateDeterministicBundleID(); + writer.WriteBinaryFormatterString(BundleID); + + if (MajorVersion >= 2) + { + WriteFileOffsetSizePair(writer, Files.FirstOrDefault(f => f.Type == BundleFileType.DepsJson)); + WriteFileOffsetSizePair(writer, Files.FirstOrDefault(f => f.Type == BundleFileType.RuntimeConfigJson)); + writer.WriteUInt64((ulong) Flags); + } + + WriteFileHeaders(writer); + } + + private void WriteFileHeaders(IBinaryStreamWriter writer) + { + for (int i = 0; i < Files.Count; i++) + { + var file = Files[i]; + + WriteFileOffsetSizePair(writer, file); + + if (MajorVersion >= 6) + writer.WriteUInt64(file.IsCompressed ? file.Contents.GetPhysicalSize() : 0); + + writer.WriteByte((byte) file.Type); + writer.WriteBinaryFormatterString(file.RelativePath); + } + } + + private static void WriteFileOffsetSizePair(IBinaryStreamWriter writer, BundleFile? file) + { + if (file is not null) + { + writer.WriteUInt64(file.Contents.Offset); + writer.WriteUInt64((ulong) file.GetData().Length); + } + else + { + writer.WriteUInt64(0); + writer.WriteUInt64(0); + } + } + + } +} From 788c6b3fb7c12047f3dcacf851df723da4a4b182 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 9 Apr 2022 13:56:32 +0200 Subject: [PATCH 095/184] Add check for apphost file existence. --- .../Bundles/BundleManifestTest.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs b/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs index 0bed69a5a..423509378 100644 --- a/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs +++ b/test/AsmResolver.DotNet.Tests/Bundles/BundleManifestTest.cs @@ -109,11 +109,13 @@ public void MarkFilesAsCompressed() AssertBundlesAreEqual(manifest, newManifest); } - [Theory] + [SkippableTheory()] [InlineData(SubSystem.WindowsCui)] [InlineData(SubSystem.WindowsGui)] public void WriteWithSubSystem(SubSystem subSystem) { + Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); + var manifest = BundleManifest.FromBytes(Properties.Resources.HelloWorld_SingleFile_V6); string appHostTemplatePath = FindAppHostTemplate("6.0"); @@ -251,7 +253,18 @@ private static string FindAppHostTemplate(string sdkVersion) $"Could not find the apphost template for .NET SDK version {sdkVersion}. This is an indication that the test environment does not have this SDK installed."); } - return Path.Combine(sdkVersionPath, "AppHostTemplate", "apphost.exe"); + string fileName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? "apphost.exe" + : "apphost"; + + string finalPath = Path.Combine(sdkVersionPath, "AppHostTemplate", fileName); + if (!File.Exists(finalPath)) + { + throw new InvalidOperationException( + $"Could not find the apphost template for .NET SDK version {sdkVersion}. This is an indication that the test environment does not have this SDK installed."); + } + + return finalPath; } private static void AssertBundlesAreEqual(BundleManifest manifest, BundleManifest newManifest) From fb3516a097021a559c3c6127cba3342e6fd1ec37 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 9 Apr 2022 13:57:30 +0200 Subject: [PATCH 096/184] BUGFIX: Do not include 'shared' in default UNIX dotnet installation paths. Add default macOS dotnet installation folder. --- src/AsmResolver.DotNet/DotNetCorePathProvider.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/AsmResolver.DotNet/DotNetCorePathProvider.cs b/src/AsmResolver.DotNet/DotNetCorePathProvider.cs index 63c7cd756..c01e99357 100644 --- a/src/AsmResolver.DotNet/DotNetCorePathProvider.cs +++ b/src/AsmResolver.DotNet/DotNetCorePathProvider.cs @@ -13,8 +13,9 @@ namespace AsmResolver.DotNet public class DotNetCorePathProvider { private static readonly string[] DefaultDotNetUnixPaths = { - "/usr/share/dotnet/shared", - "/opt/dotnet/shared/" + "/usr/share/dotnet/", + "/usr/local/share/dotnet/", + "/opt/dotnet/" }; private static readonly Regex NetCoreRuntimePattern = new(@"\.NET( Core)? \d+\.\d+\.\d+"); @@ -23,7 +24,7 @@ public class DotNetCorePathProvider static DotNetCorePathProvider() { DefaultInstallationPath = FindDotNetPath(); - Default = new(); + Default = new DotNetCorePathProvider(); } /// @@ -153,8 +154,12 @@ public bool HasRuntimeInstalled(string runtimeName, Version runtimeVersion) private void DetectInstalledRuntimes(string installationDirectory) { installationDirectory = Path.Combine(installationDirectory, "shared"); + if (!Directory.Exists(installationDirectory)) + return; + foreach (string directory in Directory.EnumerateDirectories(installationDirectory)) _installedRuntimes.Add(new DotNetInstallationInfo(directory)); + _installedRuntimes.Sort(); } From 661f0e4fa61f42f38e9a1cb6743963baa3f208fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Apr 2022 14:30:52 +0000 Subject: [PATCH 097/184] Bump System.Text.Json from 6.0.2 to 6.0.3 Bumps [System.Text.Json](https://github.com/dotnet/runtime) from 6.0.2 to 6.0.3. - [Release notes](https://github.com/dotnet/runtime/releases) - [Commits](https://github.com/dotnet/runtime/compare/v6.0.2...v6.0.3) --- updated-dependencies: - dependency-name: System.Text.Json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- src/AsmResolver.DotNet/AsmResolver.DotNet.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj index 522cacb7b..c041214b1 100644 --- a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj +++ b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj @@ -27,7 +27,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + From a9aef95a0416e4d71c7daf9bf4d265000f9625de Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 23 Apr 2022 12:50:18 +0200 Subject: [PATCH 098/184] BUGFIX: Address race condition in LazyRidListRelation.Initialize --- .../Collections/LazyRidListRelation.cs | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/AsmResolver.DotNet/Collections/LazyRidListRelation.cs b/src/AsmResolver.DotNet/Collections/LazyRidListRelation.cs index 8ee9a5c18..5a1f54d1f 100644 --- a/src/AsmResolver.DotNet/Collections/LazyRidListRelation.cs +++ b/src/AsmResolver.DotNet/Collections/LazyRidListRelation.cs @@ -54,22 +54,24 @@ private void Initialize() var tablesStream = _metadata.GetStream(); var associationTable = tablesStream.GetTable(_associationTable); - _memberLists = new Dictionary(); - _memberOwners = new Dictionary(); + var memberLists = new Dictionary(); + var memberOwners = new Dictionary(); for (int i = 0; i < associationTable.Count; i++) { - uint rid = (uint) (i + 1); - InitializeMemberList(_getOwnerRid(rid, associationTable[i]), _getMemberList(rid)); + uint currentRid = (uint) (i + 1); + + uint ownerRid = _getOwnerRid(currentRid, associationTable[i]); + var memberRange = _getMemberList(currentRid); + + memberLists[ownerRid] = memberRange; + foreach (var token in memberRange) + memberOwners[token.Rid] = ownerRid; } - } - } - private void InitializeMemberList(uint ownerRid, MetadataRange memberRange) - { - _memberLists![ownerRid] = memberRange; - foreach (var token in memberRange) - _memberOwners![token.Rid] = ownerRid; + _memberLists = memberLists; + _memberOwners = memberOwners; + } } public MetadataRange GetMemberRange(uint ownerRid) From cfb4f8267ecf08c4bbaf0d3d2265c35fb9655abd Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 23 Apr 2022 13:10:20 +0200 Subject: [PATCH 099/184] Use array for _memberOwnerRids instead of dictionary. --- .../Collections/LazyRidListRelation.cs | 63 ++++++++++++------- .../Serialized/SerializedModuleDefinition.cs | 10 +-- 2 files changed, 45 insertions(+), 28 deletions(-) diff --git a/src/AsmResolver.DotNet/Collections/LazyRidListRelation.cs b/src/AsmResolver.DotNet/Collections/LazyRidListRelation.cs index 5a1f54d1f..763648a44 100644 --- a/src/AsmResolver.DotNet/Collections/LazyRidListRelation.cs +++ b/src/AsmResolver.DotNet/Collections/LazyRidListRelation.cs @@ -15,69 +15,86 @@ internal class LazyRidListRelation private readonly IMetadata _metadata; private readonly TableIndex _associationTable; + private readonly TableIndex _memberTable; private readonly GetOwnerRidDelegate _getOwnerRid; - private readonly GetMemberListDelegate _getMemberList; + private readonly GetMemberListDelegate _getMemberRange; private readonly object _lock = new(); - private IDictionary? _memberLists; - private IDictionary? _memberOwners; + // We use a dictionary here instead of an array, as not all owners might have an association. + // e.g. not all TypeDefs have a PropertyMap assigned to them. + private Dictionary? _memberRanges; + + // We can use an array instead of a dictionary here since all members will have an owner. + // _memberOwnerRids[memberRid - 1] = ownerRid. + private uint[]? _memberOwnerRids; public LazyRidListRelation( IMetadata metadata, + TableIndex memberTable, TableIndex associationTable, GetOwnerRidDelegate getOwnerRid, GetMemberListDelegate getMemberList) { _metadata = metadata ?? throw new ArgumentNullException(nameof(metadata)); _getOwnerRid = getOwnerRid ?? throw new ArgumentNullException(nameof(getOwnerRid)); - _getMemberList = getMemberList ?? throw new ArgumentNullException(nameof(getMemberList)); + _getMemberRange = getMemberList ?? throw new ArgumentNullException(nameof(getMemberList)); _associationTable = associationTable; + _memberTable = memberTable; } - [MemberNotNull(nameof(_memberLists))] - [MemberNotNull(nameof(_memberOwners))] + [MemberNotNull(nameof(_memberRanges))] + [MemberNotNull(nameof(_memberOwnerRids))] private void EnsureIsInitialized() { - if (_memberLists is null || _memberOwners is null) + // Note: This is a hot path, thus we don't lock here until we're absolutely sure we need to initialize. + if (_memberRanges is null || _memberOwnerRids is null) Initialize(); } - [MemberNotNull(nameof(_memberLists))] - [MemberNotNull(nameof(_memberOwners))] + [MemberNotNull(nameof(_memberRanges))] + [MemberNotNull(nameof(_memberOwnerRids))] private void Initialize() { lock (_lock) { - if (_memberLists is not null && _memberOwners is not null) + // Check if some other thread was also initializing it in the mean time. + if (_memberRanges is not null && _memberOwnerRids is not null) return; var tablesStream = _metadata.GetStream(); var associationTable = tablesStream.GetTable(_associationTable); + var memberTable = tablesStream.GetTable(_memberTable); - var memberLists = new Dictionary(); - var memberOwners = new Dictionary(); + // Note: we don't assign the _memberRanges and _memberOwnerRids directly here. + // This is to prevent a very nasty but subtle race condition (and also a data race) from happening, where + // EnsureIsInitialized might return prematurely before the lists are fully initialized. + // See: https://github.com/Washi1337/AsmResolver/issues/299 + var memberRanges = new Dictionary(); + uint[] memberOwnerRids = new uint[memberTable.Count]; for (int i = 0; i < associationTable.Count; i++) { - uint currentRid = (uint) (i + 1); + uint associationRid = (uint) (i + 1); - uint ownerRid = _getOwnerRid(currentRid, associationTable[i]); - var memberRange = _getMemberList(currentRid); + uint ownerRid = _getOwnerRid(associationRid, associationTable[i]); + var memberRange = _getMemberRange(associationRid); - memberLists[ownerRid] = memberRange; - foreach (var token in memberRange) - memberOwners[token.Rid] = ownerRid; + memberRanges[ownerRid] = memberRange; + foreach (var memberToken in memberRange) + memberOwnerRids[memberToken.Rid - 1] = ownerRid; } - _memberLists = memberLists; - _memberOwners = memberOwners; + // Assign in reverse order. This is again to ensure a data race does not happen with the conditions + // happening in EnsureIsInitialized. + _memberOwnerRids = memberOwnerRids; + _memberRanges = memberRanges; } } public MetadataRange GetMemberRange(uint ownerRid) { EnsureIsInitialized(); - return _memberLists.TryGetValue(ownerRid, out var range) + return _memberRanges.TryGetValue(ownerRid, out var range) ? range : MetadataRange.Empty; } @@ -85,8 +102,8 @@ public MetadataRange GetMemberRange(uint ownerRid) public uint GetMemberOwner(uint memberRid) { EnsureIsInitialized(); - return _memberOwners.TryGetValue(memberRid, out uint ownerRid) - ? ownerRid + return memberRid - 1 < _memberOwnerRids.Length + ? _memberOwnerRids[memberRid - 1] : 0; } } diff --git a/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs index ba9e52777..e147de1e8 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs @@ -77,15 +77,15 @@ public SerializedModuleDefinition(IPEImage peImage, ModuleReaderParameters reade readerParameters.PEReaderParameters.FileService)); // Prepare lazy RID lists. - _fieldLists = new LazyRidListRelation(metadata, TableIndex.TypeDef, + _fieldLists = new LazyRidListRelation(metadata, TableIndex.Field, TableIndex.TypeDef, (rid, _) => rid, tablesStream.GetFieldRange); - _methodLists = new LazyRidListRelation(metadata, TableIndex.TypeDef, + _methodLists = new LazyRidListRelation(metadata, TableIndex.Method, TableIndex.TypeDef, (rid, _) => rid, tablesStream.GetMethodRange); - _paramLists = new LazyRidListRelation(metadata, TableIndex.Method, + _paramLists = new LazyRidListRelation(metadata, TableIndex.Param, TableIndex.Method, (rid, _) => rid, tablesStream.GetParameterRange); - _propertyLists = new LazyRidListRelation(metadata, TableIndex.PropertyMap, + _propertyLists = new LazyRidListRelation(metadata, TableIndex.Property, TableIndex.PropertyMap, (_, map) => map.Parent, tablesStream.GetPropertyRange); - _eventLists = new LazyRidListRelation(metadata, TableIndex.EventMap, + _eventLists = new LazyRidListRelation(metadata, TableIndex.Event, TableIndex.EventMap, (_, map) => map.Parent, tablesStream.GetEventRange); } From e888e433220d7f1fedf8053d1d3b8cd897244cbe Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 23 Apr 2022 13:15:48 +0200 Subject: [PATCH 100/184] BUGFIX: MethodDefinition._name should be Utf8String not string. --- src/AsmResolver.DotNet/MethodDefinition.cs | 6 +++--- .../Serialized/SerializedMethodDefinition.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/AsmResolver.DotNet/MethodDefinition.cs b/src/AsmResolver.DotNet/MethodDefinition.cs index 59b6eb989..472d828a7 100644 --- a/src/AsmResolver.DotNet/MethodDefinition.cs +++ b/src/AsmResolver.DotNet/MethodDefinition.cs @@ -27,7 +27,7 @@ public class MethodDefinition : IHasSecurityDeclaration, IManagedEntrypoint { - private readonly LazyVariable _name; + private readonly LazyVariable _name; private readonly LazyVariable _declaringType; private readonly LazyVariable _signature; private readonly LazyVariable _methodBody; @@ -47,7 +47,7 @@ public class MethodDefinition : protected MethodDefinition(MetadataToken token) : base(token) { - _name =new LazyVariable(GetName); + _name =new LazyVariable(GetName); _declaringType = new LazyVariable(GetDeclaringType); _signature = new LazyVariable(GetSignature); _methodBody = new LazyVariable(GetBody); @@ -722,7 +722,7 @@ public bool IsAccessibleFromType(TypeDefinition type) /// /// This method is called upon initialization of the property. /// - protected virtual string? GetName() => null; + protected virtual Utf8String? GetName() => null; /// /// Obtains the declaring type of the method definition. diff --git a/src/AsmResolver.DotNet/Serialized/SerializedMethodDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedMethodDefinition.cs index d000183e1..ed4ed367c 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedMethodDefinition.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedMethodDefinition.cs @@ -40,7 +40,7 @@ public SerializedMethodDefinition(ModuleReaderContext context, MetadataToken tok } /// - protected override string? GetName() + protected override Utf8String? GetName() { return _context.Metadata.TryGetStream(out var stringsStream) ? stringsStream.GetStringByIndex(_row.Name) From 46f4d09c76a9cf45d2a1a32e6c7becbd1437a333 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 23 Apr 2022 13:24:17 +0200 Subject: [PATCH 101/184] Avoid boxing of metadata rowsin TablesStream.GetMemberRange --- .../DotNet/Metadata/Tables/TablesStream.cs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs index 4d903de19..d8f673e46 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/TablesStream.cs @@ -755,7 +755,7 @@ protected TableLayout[] GetTableLayouts() /// The row identifier of the type definition to obtain the fields from. /// The range of metadata tokens. public MetadataRange GetFieldRange(uint typeDefRid) => - GetMemberRange(TableIndex.TypeDef, typeDefRid, 4, + GetMemberRange(TableIndex.TypeDef, typeDefRid, 4, TableIndex.Field, TableIndex.FieldPtr); /// @@ -764,7 +764,7 @@ protected TableLayout[] GetTableLayouts() /// The row identifier of the type definition to obtain the methods from. /// The range of metadata tokens. public MetadataRange GetMethodRange(uint typeDefRid) => - GetMemberRange(TableIndex.TypeDef, typeDefRid, 5, + GetMemberRange(TableIndex.TypeDef, typeDefRid, 5, TableIndex.Method, TableIndex.MethodPtr); /// @@ -773,7 +773,7 @@ protected TableLayout[] GetTableLayouts() /// The row identifier of the method definition to obtain the parameters from. /// The range of metadata tokens. public MetadataRange GetParameterRange(uint methodDefRid) => - GetMemberRange(TableIndex.Method, methodDefRid, 5, + GetMemberRange(TableIndex.Method, methodDefRid, 5, TableIndex.Param, TableIndex.ParamPtr); /// @@ -782,7 +782,7 @@ protected TableLayout[] GetTableLayouts() /// The row identifier of the property map to obtain the properties from. /// The range of metadata tokens. public MetadataRange GetPropertyRange(uint propertyMapRid) => - GetMemberRange(TableIndex.PropertyMap, propertyMapRid, 1, + GetMemberRange(TableIndex.PropertyMap, propertyMapRid, 1, TableIndex.Property, TableIndex.PropertyPtr); /// @@ -791,16 +791,21 @@ protected TableLayout[] GetTableLayouts() /// The row identifier of the event map to obtain the events from. /// The range of metadata tokens. public MetadataRange GetEventRange(uint eventMapRid) => - GetMemberRange(TableIndex.EventMap, eventMapRid, 1, + GetMemberRange(TableIndex.EventMap, eventMapRid, 1, TableIndex.Event, TableIndex.EventPtr); - private MetadataRange GetMemberRange(TableIndex ownerTableIndex, uint ownerRid, int ownerColumnIndex, - TableIndex memberTableIndex, TableIndex redirectTableIndex) + private MetadataRange GetMemberRange( + TableIndex ownerTableIndex, + uint ownerRid, + int ownerColumnIndex, + TableIndex memberTableIndex, + TableIndex redirectTableIndex) + where TOwnerRow : struct, IMetadataRow { int index = (int) (ownerRid - 1); // Check if valid owner RID. - var ownerTable = GetTable(ownerTableIndex); + var ownerTable = GetTable(ownerTableIndex); if (index < 0 || index >= ownerTable.Count) return MetadataRange.Empty; From 0ac259cecd6f060a7832086c6257af881e7c1d04 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 28 Apr 2022 22:05:27 +0200 Subject: [PATCH 102/184] Remove unnecessary lambda. --- src/AsmResolver.DotNet/AssemblyDescriptor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AsmResolver.DotNet/AssemblyDescriptor.cs b/src/AsmResolver.DotNet/AssemblyDescriptor.cs index 87a816029..c8a055dff 100644 --- a/src/AsmResolver.DotNet/AssemblyDescriptor.cs +++ b/src/AsmResolver.DotNet/AssemblyDescriptor.cs @@ -31,7 +31,7 @@ protected AssemblyDescriptor(MetadataToken token) : base(token) { _name = new LazyVariable(GetName); - _culture = new LazyVariable(() => GetCulture()); + _culture = new LazyVariable(GetCulture); Version = new Version(0, 0, 0, 0); } From 9ea4737ee43e9768b4f7f70b0c9f8591341d9b1a Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 28 Apr 2022 22:07:48 +0200 Subject: [PATCH 103/184] Add ModuleDefinition.DefaultImporter. --- src/AsmResolver.DotNet/ModuleDefinition.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/AsmResolver.DotNet/ModuleDefinition.cs b/src/AsmResolver.DotNet/ModuleDefinition.cs index 36b89e058..a6fafd597 100644 --- a/src/AsmResolver.DotNet/ModuleDefinition.cs +++ b/src/AsmResolver.DotNet/ModuleDefinition.cs @@ -54,6 +54,7 @@ public class ModuleDefinition : private readonly LazyVariable _runtimeVersion; private readonly LazyVariable _nativeResources; private IList? _debugData; + private ReferenceImporter? _defaultImporter; /// /// Reads a .NET module from the provided input buffer. @@ -761,6 +762,19 @@ public IMetadataResolver MetadataResolver set => _managedEntrypoint.Value = value; } + /// + /// Gets the default importer instance for this module. + /// + public ReferenceImporter DefaultImporter + { + get + { + if (_defaultImporter is null) + Interlocked.CompareExchange(ref _defaultImporter, new ReferenceImporter(this), null); + return _defaultImporter; + } + } + /// /// Looks up a member by its metadata token. /// From f817113b1be634de74444946a078a550c677f5eb Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 28 Apr 2022 22:25:32 +0200 Subject: [PATCH 104/184] Add CreateTypeReference and CreateMemberReference extension methods. --- .../TypeDescriptorExtensions.cs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/AsmResolver.DotNet/TypeDescriptorExtensions.cs b/src/AsmResolver.DotNet/TypeDescriptorExtensions.cs index 568e90425..a108f6376 100644 --- a/src/AsmResolver.DotNet/TypeDescriptorExtensions.cs +++ b/src/AsmResolver.DotNet/TypeDescriptorExtensions.cs @@ -1,3 +1,5 @@ +using System; +using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.Signatures.Types; namespace AsmResolver.DotNet @@ -100,5 +102,53 @@ public static class TypeDescriptorExtensions { return new GenericInstanceTypeSignature(type.ToTypeDefOrRef(), type.IsValueType, typeArguments); } + + /// + /// Constructs a reference to a type within the provided resolution scope. + /// + /// The scope the type is defined in. + /// The namespace of the type. + /// The name of the type. + /// The constructed reference. + public static TypeReference CreateTypeReference(this IResolutionScope scope, string? ns, string name) + { + return new TypeReference(scope, ns, name); + } + + /// + /// Constructs a reference to a nested type. + /// + /// The enclosing type. + /// The name of the nested type. + /// The constructed reference. + /// + /// Occurs when cannot be used as a declaring type of a type reference. + /// + public static TypeReference CreateTypeReference(this ITypeDefOrRef declaringType, string nestedTypeName) + { + var parent = declaringType switch + { + TypeReference reference => reference, + TypeDefinition definition => definition.ToTypeReference(), + _ => throw new ArgumentOutOfRangeException() + }; + + return new TypeReference(parent, null, nestedTypeName); + } + + /// + /// Constructs a reference to a member declared within the provided parent member. + /// + /// The declaring member. + /// The name of the member to reference. + /// The signature of the member to reference. + /// The constructed reference. + public static MemberReference CreateMemberReference( + this IMemberRefParent parent, + string memberName, + MemberSignature signature) + { + return new MemberReference(parent, memberName, signature); + } } } From 2e0eb0593df39ade0f1a072331808da8fbfdc1bc Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 28 Apr 2022 23:04:17 +0200 Subject: [PATCH 105/184] Add IImportable.ImportWith. --- src/AsmResolver.DotNet/AssemblyDefinition.cs | 4 ++ src/AsmResolver.DotNet/AssemblyDescriptor.cs | 10 +++ src/AsmResolver.DotNet/AssemblyReference.cs | 4 ++ src/AsmResolver.DotNet/EventDefinition.cs | 4 ++ src/AsmResolver.DotNet/ExportedType.cs | 10 +++ src/AsmResolver.DotNet/FieldDefinition.cs | 10 +++ src/AsmResolver.DotNet/FileReference.cs | 11 +++ src/AsmResolver.DotNet/IImportable.cs | 7 ++ src/AsmResolver.DotNet/InvalidTypeDefOrRef.cs | 4 +- src/AsmResolver.DotNet/MemberReference.cs | 5 ++ src/AsmResolver.DotNet/MethodDefinition.cs | 10 +++ src/AsmResolver.DotNet/MethodSpecification.cs | 10 +++ src/AsmResolver.DotNet/ModuleDefinition.cs | 10 +++ src/AsmResolver.DotNet/ModuleReference.cs | 10 +++ src/AsmResolver.DotNet/PropertyDefinition.cs | 4 ++ src/AsmResolver.DotNet/ReferenceImporter.cs | 67 +++++++++++++++++++ .../ReflectionAssemblyDescriptor.cs | 4 ++ .../Signatures/CallingConventionSignature.cs | 10 +++ .../Signatures/FieldSignature.cs | 10 +++ .../GenericInstanceMethodSignature.cs | 12 ++++ .../Signatures/LocalVariablesSignature.cs | 12 ++++ .../Signatures/MethodSignature.cs | 10 +++ .../Signatures/PropertySignature.cs | 10 +++ .../Signatures/Types/TypeSignature.cs | 10 +++ src/AsmResolver.DotNet/TypeDefinition.cs | 10 +++ src/AsmResolver.DotNet/TypeReference.cs | 10 +++ src/AsmResolver.DotNet/TypeSpecification.cs | 10 +++ 27 files changed, 287 insertions(+), 1 deletion(-) diff --git a/src/AsmResolver.DotNet/AssemblyDefinition.cs b/src/AsmResolver.DotNet/AssemblyDefinition.cs index 43fe53c77..fabe500a6 100644 --- a/src/AsmResolver.DotNet/AssemblyDefinition.cs +++ b/src/AsmResolver.DotNet/AssemblyDefinition.cs @@ -225,6 +225,10 @@ protected virtual IList GetModules() /// public override bool IsImportedInModule(ModuleDefinition module) => ManifestModule == module; + /// + public override AssemblyReference ImportWith(ReferenceImporter importer) => + (AssemblyReference) importer.ImportScope(new AssemblyReference(this)); + /// public override AssemblyDefinition Resolve() => this; diff --git a/src/AsmResolver.DotNet/AssemblyDescriptor.cs b/src/AsmResolver.DotNet/AssemblyDescriptor.cs index c8a055dff..5d19e5b42 100644 --- a/src/AsmResolver.DotNet/AssemblyDescriptor.cs +++ b/src/AsmResolver.DotNet/AssemblyDescriptor.cs @@ -217,6 +217,16 @@ public IList CustomAttributes /// public abstract bool IsImportedInModule(ModuleDefinition module); + /// + /// Imports the assembly descriptor using the provided reference importer. + /// + /// The importer object to use. + /// The imported assembly reference. + public abstract AssemblyReference ImportWith(ReferenceImporter importer); + + /// + IImportable IImportable.ImportWith(ReferenceImporter importer) => ImportWith(importer); + /// /// Computes the token of a public key using the provided hashing algorithm. /// diff --git a/src/AsmResolver.DotNet/AssemblyReference.cs b/src/AsmResolver.DotNet/AssemblyReference.cs index f37630de6..d81b2b9c9 100644 --- a/src/AsmResolver.DotNet/AssemblyReference.cs +++ b/src/AsmResolver.DotNet/AssemblyReference.cs @@ -150,6 +150,10 @@ public AssemblyReference(AssemblyDescriptor descriptor) /// public override bool IsImportedInModule(ModuleDefinition module) => Module == module; + /// + public override AssemblyReference ImportWith(ReferenceImporter importer) => + (AssemblyReference) importer.ImportScope(this); + /// public override AssemblyDefinition? Resolve() => Module?.MetadataResolver.AssemblyResolver.Resolve(this); diff --git a/src/AsmResolver.DotNet/EventDefinition.cs b/src/AsmResolver.DotNet/EventDefinition.cs index f319e1270..d21d1036b 100644 --- a/src/AsmResolver.DotNet/EventDefinition.cs +++ b/src/AsmResolver.DotNet/EventDefinition.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -194,6 +195,9 @@ public bool IsImportedInModule(ModuleDefinition module) && (EventType?.IsImportedInModule(module) ?? false); } + /// + IImportable IImportable.ImportWith(ReferenceImporter importer) => throw new NotSupportedException(); + /// /// Obtains the list of custom attributes assigned to the member. /// diff --git a/src/AsmResolver.DotNet/ExportedType.cs b/src/AsmResolver.DotNet/ExportedType.cs index bf83bd24d..323bc800e 100644 --- a/src/AsmResolver.DotNet/ExportedType.cs +++ b/src/AsmResolver.DotNet/ExportedType.cs @@ -152,6 +152,16 @@ public bool IsImportedInModule(ModuleDefinition module) && (Implementation?.IsImportedInModule(module) ?? false); } + /// + /// Imports the exported type using the provided importer object. + /// + /// The reference importer to use. + /// The imported type. + public ExportedType ImportWith(ReferenceImporter importer) => importer.ImportType(this); + + /// + IImportable IImportable.ImportWith(ReferenceImporter importer) => ImportWith(importer); + IMemberDefinition? IMemberDescriptor.Resolve() => Resolve(); /// diff --git a/src/AsmResolver.DotNet/FieldDefinition.cs b/src/AsmResolver.DotNet/FieldDefinition.cs index 585f5ef3d..39b7e5c3e 100644 --- a/src/AsmResolver.DotNet/FieldDefinition.cs +++ b/src/AsmResolver.DotNet/FieldDefinition.cs @@ -393,6 +393,16 @@ public bool IsImportedInModule(ModuleDefinition module) && (Signature?.IsImportedInModule(module) ?? false); } + /// + /// Imports the field using the provided reference importer object. + /// + /// The reference importer to use. + /// The imported field. + public IFieldDescriptor ImportWith(ReferenceImporter importer) => importer.ImportField(this); + + /// + IImportable IImportable.ImportWith(ReferenceImporter importer) => ImportWith(importer); + IMemberDefinition IMemberDescriptor.Resolve() => this; /// diff --git a/src/AsmResolver.DotNet/FileReference.cs b/src/AsmResolver.DotNet/FileReference.cs index ef1f3c225..8de6ba74a 100644 --- a/src/AsmResolver.DotNet/FileReference.cs +++ b/src/AsmResolver.DotNet/FileReference.cs @@ -123,6 +123,17 @@ public IList CustomAttributes /// public bool IsImportedInModule(ModuleDefinition module) => Module == module; + /// + /// Imports the file using the provided reference importer object. + /// + /// The reference importer to use. + /// The imported file reference. + public FileReference ImportWith(ReferenceImporter importer) => + (FileReference) importer.ImportImplementation(this); + + /// + IImportable IImportable.ImportWith(ReferenceImporter importer) => ImportWith(importer); + /// /// Obtains the name of the referenced file. /// diff --git a/src/AsmResolver.DotNet/IImportable.cs b/src/AsmResolver.DotNet/IImportable.cs index 9fdbc2c76..872e578aa 100644 --- a/src/AsmResolver.DotNet/IImportable.cs +++ b/src/AsmResolver.DotNet/IImportable.cs @@ -15,5 +15,12 @@ public interface IImportable /// data or contents (such as a method body) associated to the member. /// bool IsImportedInModule(ModuleDefinition module); + + /// + /// Imports the member using the provided reference importer object. + /// + /// The reference importer to use for importing the object. + /// The imported member. + IImportable ImportWith(ReferenceImporter importer); } } diff --git a/src/AsmResolver.DotNet/InvalidTypeDefOrRef.cs b/src/AsmResolver.DotNet/InvalidTypeDefOrRef.cs index a4fd0d641..3fb25cbb1 100644 --- a/src/AsmResolver.DotNet/InvalidTypeDefOrRef.cs +++ b/src/AsmResolver.DotNet/InvalidTypeDefOrRef.cs @@ -76,6 +76,9 @@ public static InvalidTypeDefOrRef Get(InvalidTypeSignatureError error) /// public bool IsImportedInModule(ModuleDefinition module) => false; + /// + IImportable IImportable.ImportWith(ReferenceImporter importer) => throw new InvalidOperationException(); + IMemberDefinition? IMemberDescriptor.Resolve() => null; TypeDefinition? ITypeDescriptor.Resolve() => null; @@ -87,6 +90,5 @@ public static InvalidTypeDefOrRef Get(InvalidTypeSignatureError error) /// public override string ToString() => ((IFullNameProvider) this).Name!; - } } diff --git a/src/AsmResolver.DotNet/MemberReference.cs b/src/AsmResolver.DotNet/MemberReference.cs index 8254f8a3a..3dba6d1e8 100644 --- a/src/AsmResolver.DotNet/MemberReference.cs +++ b/src/AsmResolver.DotNet/MemberReference.cs @@ -160,6 +160,11 @@ public bool IsImportedInModule(ModuleDefinition module) && (Signature?.IsImportedInModule(module) ?? false); } + /// + public IImportable ImportWith(ReferenceImporter importer) => IsMethod + ? importer.ImportMethod(this) + : importer.ImportField(this); + FieldDefinition? IFieldDescriptor.Resolve() { if (!IsField) diff --git a/src/AsmResolver.DotNet/MethodDefinition.cs b/src/AsmResolver.DotNet/MethodDefinition.cs index 472d828a7..819732b50 100644 --- a/src/AsmResolver.DotNet/MethodDefinition.cs +++ b/src/AsmResolver.DotNet/MethodDefinition.cs @@ -698,6 +698,16 @@ public bool IsImportedInModule(ModuleDefinition module) && (Signature?.IsImportedInModule(module) ?? false); } + /// + /// Imports the method using the provided reference importer object. + /// + /// The reference importer to use. + /// The imported method. + public IMethodDefOrRef ImportWith(ReferenceImporter importer) => importer.ImportMethod(this); + + /// + IImportable IImportable.ImportWith(ReferenceImporter importer) => ImportWith(importer); + IMemberDefinition IMemberDescriptor.Resolve() => this; /// diff --git a/src/AsmResolver.DotNet/MethodSpecification.cs b/src/AsmResolver.DotNet/MethodSpecification.cs index 430c9952e..1c85c399c 100644 --- a/src/AsmResolver.DotNet/MethodSpecification.cs +++ b/src/AsmResolver.DotNet/MethodSpecification.cs @@ -106,6 +106,16 @@ public bool IsImportedInModule(ModuleDefinition module) && (Signature?.IsImportedInModule(module) ?? false); } + /// + /// Imports the method specification using the provided reference importer. + /// + /// The reference importer to use. + /// The imported method specification. + public MethodSpecification ImportWith(ReferenceImporter importer) => importer.ImportMethod(this); + + /// + IImportable IImportable.ImportWith(ReferenceImporter importer) => ImportWith(importer); + IMemberDefinition? IMemberDescriptor.Resolve() => Resolve(); /// diff --git a/src/AsmResolver.DotNet/ModuleDefinition.cs b/src/AsmResolver.DotNet/ModuleDefinition.cs index a6fafd597..0b1a4079c 100644 --- a/src/AsmResolver.DotNet/ModuleDefinition.cs +++ b/src/AsmResolver.DotNet/ModuleDefinition.cs @@ -1121,6 +1121,16 @@ protected IAssemblyResolver CreateAssemblyResolver(IFileService fileService) /// bool IImportable.IsImportedInModule(ModuleDefinition module) => this == module; + /// + /// Imports the module using the provided reference importer object. + /// + /// The reference importer to use. + /// The imported module. + public ModuleReference ImportWith(ReferenceImporter importer) => importer.ImportModule(new ModuleReference(Name)); + + /// + IImportable IImportable.ImportWith(ReferenceImporter importer) => ImportWith(importer); + /// /// Rebuilds the .NET module to a portable executable file and writes it to the file system. /// diff --git a/src/AsmResolver.DotNet/ModuleReference.cs b/src/AsmResolver.DotNet/ModuleReference.cs index 9191d9ad7..1c4cef6d4 100644 --- a/src/AsmResolver.DotNet/ModuleReference.cs +++ b/src/AsmResolver.DotNet/ModuleReference.cs @@ -79,6 +79,16 @@ public IList CustomAttributes /// public bool IsImportedInModule(ModuleDefinition module) => Module == module; + /// + /// Imports the module reference using the provided reference importer object. + /// + /// The reference importer to use. + /// The imported module. + public ModuleReference ImportWith(ReferenceImporter importer) => importer.ImportModule(this); + + /// + IImportable IImportable.ImportWith(ReferenceImporter importer) => ImportWith(importer); + /// /// Obtains the name of the module. /// diff --git a/src/AsmResolver.DotNet/PropertyDefinition.cs b/src/AsmResolver.DotNet/PropertyDefinition.cs index 3df05c38c..325bacd11 100644 --- a/src/AsmResolver.DotNet/PropertyDefinition.cs +++ b/src/AsmResolver.DotNet/PropertyDefinition.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -208,6 +209,9 @@ public bool IsImportedInModule(ModuleDefinition module) && (Signature?.IsImportedInModule(module) ?? false); } + /// + IImportable IImportable.ImportWith(ReferenceImporter importer) => throw new NotSupportedException(); + /// /// Obtains the name of the property definition. /// diff --git a/src/AsmResolver.DotNet/ReferenceImporter.cs b/src/AsmResolver.DotNet/ReferenceImporter.cs index 8af788d7d..eb2bb78db 100644 --- a/src/AsmResolver.DotNet/ReferenceImporter.cs +++ b/src/AsmResolver.DotNet/ReferenceImporter.cs @@ -60,6 +60,27 @@ public IResolutionScope ImportScope(IResolutionScope? scope) }; } + /// + /// Imports an implementation reference. + /// + /// The implementation reference to import. + /// The imported implementation reference. + public IImplementation ImportImplementation(IImplementation? implementation) + { + if (implementation is null) + throw new ArgumentNullException(nameof(implementation)); + if (implementation.IsImportedInModule(TargetModule)) + return implementation; + + return implementation switch + { + AssemblyReference assembly => ImportAssembly(assembly), + ExportedType type => ImportType(type), + FileReference file => ImportFile(file), + _ => throw new ArgumentOutOfRangeException(nameof(implementation)) + }; + } + /// /// Imports a reference to an assembly. /// @@ -83,6 +104,29 @@ protected virtual AssemblyReference ImportAssembly(AssemblyDescriptor assembly) return reference; } + /// + /// Imports a file reference. + /// + /// The file to import. + /// The imported file. + protected virtual FileReference ImportFile(FileReference file) + { + if (file is null) + throw new ArgumentNullException(nameof(file)); + if (file.IsImportedInModule(TargetModule)) + return file; + + var reference = TargetModule.FileReferences.FirstOrDefault(a => a.Name == file.Name); + + if (reference is null) + { + reference = new FileReference(file.Name, file.Attributes); + TargetModule.FileReferences.Add(reference); + } + + return reference; + } + /// /// Imports a reference to a module. /// @@ -181,6 +225,29 @@ protected virtual ITypeDefOrRef ImportType(TypeSpecification type) return new TypeSpecification(ImportTypeSignature(type.Signature)); } + /// + /// Imports a forwarded type. + /// + /// The type to import. + /// The imported type. + public virtual ExportedType ImportType(ExportedType type) + { + if (type is null) + throw new ArgumentNullException(nameof(type)); + if (type.IsImportedInModule(TargetModule)) + return type; + + var result = TargetModule.ExportedTypes.FirstOrDefault(a => _comparer.Equals(a, type)); + + if (result is null) + { + result = new ExportedType(ImportImplementation(type.Implementation), type.Namespace, type.Name); + TargetModule.ExportedTypes.Add(result); + } + + return result; + } + /// /// Imports the given type signature into the target module. /// diff --git a/src/AsmResolver.DotNet/ReflectionAssemblyDescriptor.cs b/src/AsmResolver.DotNet/ReflectionAssemblyDescriptor.cs index d192bf2ba..082c7f483 100644 --- a/src/AsmResolver.DotNet/ReflectionAssemblyDescriptor.cs +++ b/src/AsmResolver.DotNet/ReflectionAssemblyDescriptor.cs @@ -35,6 +35,10 @@ public ReflectionAssemblyDescriptor(ModuleDefinition parentModule, AssemblyName /// public override bool IsImportedInModule(ModuleDefinition module) => false; + /// + public override AssemblyReference ImportWith(ReferenceImporter importer) => + (AssemblyReference) importer.ImportScope(new AssemblyReference(this)); + /// public override bool IsCorLib => Name is not null && KnownCorLibs.KnownCorLibNames.Contains(Name); diff --git a/src/AsmResolver.DotNet/Signatures/CallingConventionSignature.cs b/src/AsmResolver.DotNet/Signatures/CallingConventionSignature.cs index 4ed13994d..12d04b0e2 100644 --- a/src/AsmResolver.DotNet/Signatures/CallingConventionSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/CallingConventionSignature.cs @@ -151,5 +151,15 @@ public bool IsSentinel /// public abstract bool IsImportedInModule(ModuleDefinition module); + + /// + /// Imports the signature using the provided reference importer object. + /// + /// The reference importer to us. + /// The imported signature. + protected abstract CallingConventionSignature ImportWithInternal(ReferenceImporter importer); + + /// + IImportable IImportable.ImportWith(ReferenceImporter importer) => ImportWithInternal(importer); } } diff --git a/src/AsmResolver.DotNet/Signatures/FieldSignature.cs b/src/AsmResolver.DotNet/Signatures/FieldSignature.cs index 9b742200a..d5df8bf4a 100644 --- a/src/AsmResolver.DotNet/Signatures/FieldSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/FieldSignature.cs @@ -94,5 +94,15 @@ protected override void WriteContents(BlobSerializationContext context) context.Writer.WriteByte((byte) Attributes); FieldType.Write(context); } + + /// + /// Imports the field signature using the provided reference importer object. + /// + /// The reference importer to us. + /// The imported field signature. + public FieldSignature ImportWith(ReferenceImporter importer) => importer.ImportFieldSignature(this); + + /// + protected override CallingConventionSignature ImportWithInternal(ReferenceImporter importer) => ImportWith(importer); } } diff --git a/src/AsmResolver.DotNet/Signatures/GenericInstanceMethodSignature.cs b/src/AsmResolver.DotNet/Signatures/GenericInstanceMethodSignature.cs index 240bd396e..2e6286418 100644 --- a/src/AsmResolver.DotNet/Signatures/GenericInstanceMethodSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/GenericInstanceMethodSignature.cs @@ -103,6 +103,18 @@ public override bool IsImportedInModule(ModuleDefinition module) return true; } + /// + /// Imports the generic method instantiation signature using the provided reference importer object. + /// + /// The reference importer to us. + /// The imported signature. + public GenericInstanceMethodSignature ImportWith(ReferenceImporter importer) => + importer.ImportGenericInstanceMethodSignature(this); + + /// + protected override CallingConventionSignature ImportWithInternal(ReferenceImporter importer) => + ImportWith(importer); + /// public override string ToString() { diff --git a/src/AsmResolver.DotNet/Signatures/LocalVariablesSignature.cs b/src/AsmResolver.DotNet/Signatures/LocalVariablesSignature.cs index e8c8de393..2376fd95c 100644 --- a/src/AsmResolver.DotNet/Signatures/LocalVariablesSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/LocalVariablesSignature.cs @@ -80,6 +80,18 @@ public override bool IsImportedInModule(ModuleDefinition module) return true; } + /// + /// Imports the local variables signature using the provided reference importer object. + /// + /// The reference importer to us. + /// The imported signature. + public LocalVariablesSignature ImportWith(ReferenceImporter importer) => + importer.ImportLocalVariablesSignature(this); + + /// + protected override CallingConventionSignature ImportWithInternal(ReferenceImporter importer) => + ImportWith(importer); + /// protected override void WriteContents(BlobSerializationContext context) { diff --git a/src/AsmResolver.DotNet/Signatures/MethodSignature.cs b/src/AsmResolver.DotNet/Signatures/MethodSignature.cs index 14787822f..cc6962a1e 100644 --- a/src/AsmResolver.DotNet/Signatures/MethodSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/MethodSignature.cs @@ -215,5 +215,15 @@ public override string ToString() return $"{prefix}{fullName} *{genericsString}({parameterTypesString})"; } + + /// + /// Imports the method signature using the provided reference importer object. + /// + /// The reference importer to us. + /// The imported signature. + public MemberSignature ImportWith(ReferenceImporter importer) => importer.ImportMethodSignature(this); + + /// + protected override CallingConventionSignature ImportWithInternal(ReferenceImporter importer) => ImportWith(importer); } } diff --git a/src/AsmResolver.DotNet/Signatures/PropertySignature.cs b/src/AsmResolver.DotNet/Signatures/PropertySignature.cs index 864e4d201..5883cad30 100644 --- a/src/AsmResolver.DotNet/Signatures/PropertySignature.cs +++ b/src/AsmResolver.DotNet/Signatures/PropertySignature.cs @@ -128,5 +128,15 @@ public override string ToString() return $"{prefix}{ReturnType.FullName} *{parameterTypesString}"; } + + /// + /// Imports the property signature using the provided reference importer object. + /// + /// The reference importer to us. + /// The imported signature. + public PropertySignature ImportWith(ReferenceImporter importer) => importer.ImportPropertySignature(this); + + /// + protected override CallingConventionSignature ImportWithInternal(ReferenceImporter importer) => ImportWith(importer); } } diff --git a/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs index f5419ddfe..389411e30 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/TypeSignature.cs @@ -379,6 +379,16 @@ public TypeSignature InstantiateGenericTypes(GenericContext context) /// public abstract bool IsImportedInModule(ModuleDefinition module); + /// + /// Imports the type signature using the provided reference importer object. + /// + /// The reference importer to us. + /// The imported type. + public TypeSignature ImportWith(ReferenceImporter importer) => importer.ImportTypeSignature(this); + + /// + IImportable IImportable.ImportWith(ReferenceImporter importer) => ImportWith(importer); + /// /// Visit the current type signature using the provided visitor. /// diff --git a/src/AsmResolver.DotNet/TypeDefinition.cs b/src/AsmResolver.DotNet/TypeDefinition.cs index d1757a8f8..ce14eb497 100644 --- a/src/AsmResolver.DotNet/TypeDefinition.cs +++ b/src/AsmResolver.DotNet/TypeDefinition.cs @@ -683,6 +683,16 @@ public TypeSignature ToTypeSignature() /// public bool IsImportedInModule(ModuleDefinition module) => Module == module; + /// + /// Imports the type definition using the provided reference importer object. + /// + /// The reference importer to use. + /// The imported type. + public ITypeDefOrRef ImportWith(ReferenceImporter importer) => importer.ImportType(this); + + /// + IImportable IImportable.ImportWith(ReferenceImporter importer) => ImportWith(importer); + /// public bool IsAccessibleFromType(TypeDefinition type) { diff --git a/src/AsmResolver.DotNet/TypeReference.cs b/src/AsmResolver.DotNet/TypeReference.cs index 113a8892d..0b1332fe9 100644 --- a/src/AsmResolver.DotNet/TypeReference.cs +++ b/src/AsmResolver.DotNet/TypeReference.cs @@ -139,6 +139,16 @@ public TypeSignature ToTypeSignature() /// public bool IsImportedInModule(ModuleDefinition module) => Module == module; + /// + /// Imports the type reference using the provided reference importer object. + /// + /// The reference importer to use. + /// The imported type. + public ITypeDefOrRef ImportWith(ReferenceImporter importer) => importer.ImportType(this); + + /// + IImportable IImportable.ImportWith(ReferenceImporter importer) => ImportWith(importer); + /// public TypeDefinition? Resolve() => Module?.MetadataResolver.ResolveType(this); diff --git a/src/AsmResolver.DotNet/TypeSpecification.cs b/src/AsmResolver.DotNet/TypeSpecification.cs index d268bf85a..f62c45293 100644 --- a/src/AsmResolver.DotNet/TypeSpecification.cs +++ b/src/AsmResolver.DotNet/TypeSpecification.cs @@ -98,6 +98,16 @@ public IList CustomAttributes /// public bool IsImportedInModule(ModuleDefinition module) => Signature?.IsImportedInModule(module) ?? false; + /// + /// Imports the type specification using the provided reference importer object. + /// + /// The reference importer to use. + /// The imported type. + public TypeSpecification ImportWith(ReferenceImporter importer) => (TypeSpecification) importer.ImportType(this); + + /// + IImportable IImportable.ImportWith(ReferenceImporter importer) => ImportWith(importer); + /// public TypeDefinition? Resolve() => Module?.MetadataResolver.ResolveType(this); From 337a030df0704477049e44c5d373d90819cc70c5 Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 1 May 2022 19:18:39 +0200 Subject: [PATCH 106/184] Update docs on default importer. --- docs/dotnet/importing.rst | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/docs/dotnet/importing.rst b/docs/dotnet/importing.rst index 24e4e21ad..694c3f7de 100644 --- a/docs/dotnet/importing.rst +++ b/docs/dotnet/importing.rst @@ -5,14 +5,25 @@ Reference Importing .NET modules use entries in the TypeRef or MemberRef tables to reference types or members from external assemblies. Importing references into the current module therefore form a key role when creating new- or modifying existing .NET modules. When a member is not imported into the current module, a ``MemberNotImportedException`` will be thrown when you are trying to create a PE image or write the module to the disk. -AsmResolver provides the ``ReferenceImporter`` class that does most of the heavy lifting. +AsmResolver provides the ``ReferenceImporter`` class that does most of the heavy lifting. Obtaining an instance of ``ReferenceImporter`` can be done in two ways. -All samples in this document assume there is an instance of ``ReferenceImporter`` created using the following code: +Either instantiate one yourself: .. code-block:: csharp + ModuleDefinition module = ... var importer = new ReferenceImporter(module); +Or obtain the default instance that comes with every ``ModuleDefinition`` object. This avoids allocating new reference importers every time. + +.. code-block:: csharp + + ModuleDefinition module = ... + var importer = module.DefaultImporter; + + +The example snippets that will follow in this articule assume that there is such a ``ReferenceImporter`` object instantiated using either of these two methods, and is stored in an ``importer`` variable. + Importing metadata members -------------------------- @@ -124,9 +135,6 @@ This is a common mistake when trying to import using metadata provided by ``Syst .. code-block:: csharp - var targetModule = ModuleDefinition.FromFile(...); - var importer = new ReferenceImporter(targetModule); - var reference = importer.ImportType(typeof(DateTime)); // `reference` will target `[mscorlib] System.DateTime` when running on .NET Framework, and `[System.Runtime] System.DateTime` when running on .NET Core. From c43f2013a2546efb4a0704addf346911eeffba28 Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 1 May 2022 19:22:01 +0200 Subject: [PATCH 107/184] Extract default importer instantiation to protected virtual method. --- src/AsmResolver.DotNet/ModuleDefinition.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/AsmResolver.DotNet/ModuleDefinition.cs b/src/AsmResolver.DotNet/ModuleDefinition.cs index a6fafd597..2eefa1f2e 100644 --- a/src/AsmResolver.DotNet/ModuleDefinition.cs +++ b/src/AsmResolver.DotNet/ModuleDefinition.cs @@ -770,7 +770,7 @@ public ReferenceImporter DefaultImporter get { if (_defaultImporter is null) - Interlocked.CompareExchange(ref _defaultImporter, new ReferenceImporter(this), null); + Interlocked.CompareExchange(ref _defaultImporter, GetDefaultImporter(), null); return _defaultImporter; } } @@ -1062,6 +1062,15 @@ public TypeDefinition GetOrCreateModuleType() /// protected virtual IList GetDebugData() => new List(); + /// + /// Obtains the default reference importer assigned to this module. + /// + /// The importer. + /// + /// This method is called upon initialization of the property. + /// + protected virtual ReferenceImporter GetDefaultImporter() => new(this); + /// /// Detects the runtime that this module targets. /// From b27b36bd9e3e2d935217661819def29a8e174e89 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 5 May 2022 16:30:38 +0200 Subject: [PATCH 108/184] Add NativeLocalSymbol. --- .../Code/Native/NativeLocalSymbol.cs | 25 ++++++++ .../Code/Native/NativeMethodBodySerializer.cs | 10 ++- .../Code/Native/NativeMethodBodyTest.cs | 64 ++++++++++++++++++- 3 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 src/AsmResolver.DotNet/Code/Native/NativeLocalSymbol.cs diff --git a/src/AsmResolver.DotNet/Code/Native/NativeLocalSymbol.cs b/src/AsmResolver.DotNet/Code/Native/NativeLocalSymbol.cs new file mode 100644 index 000000000..b53ff657f --- /dev/null +++ b/src/AsmResolver.DotNet/Code/Native/NativeLocalSymbol.cs @@ -0,0 +1,25 @@ +namespace AsmResolver.DotNet.Code.Native; + +public class NativeLocalSymbol : ISymbol +{ + public NativeLocalSymbol(NativeMethodBody body, int offset) + { + Body = body; + Offset = offset; + } + + public NativeMethodBody Body + { + get; + } + + public int Offset + { + get; + } + + /// + public ISegmentReference? GetReference() => Body.Address is not null + ? new RelativeReference(Body.Address, Offset) + : null; +} diff --git a/src/AsmResolver.DotNet/Code/Native/NativeMethodBodySerializer.cs b/src/AsmResolver.DotNet/Code/Native/NativeMethodBodySerializer.cs index 2c8c879b0..d57f759a3 100644 --- a/src/AsmResolver.DotNet/Code/Native/NativeMethodBodySerializer.cs +++ b/src/AsmResolver.DotNet/Code/Native/NativeMethodBodySerializer.cs @@ -25,7 +25,7 @@ public ISegmentReference SerializeMethodBody(MethodBodySerializationContext cont { // Import symbol. var fixup = nativeMethodBody.AddressFixups[i]; - var symbol = provider.ImportSymbol(fixup.Symbol); + var symbol = TransformSymbol(segment, provider, fixup.Symbol); // Create new fixup with imported symbol. segment.AddressFixups.Add(new AddressFixup(fixup.Offset, fixup.Type, symbol)); @@ -43,5 +43,13 @@ public ISegmentReference SerializeMethodBody(MethodBodySerializationContext cont return segment.ToReference(); } + + protected virtual ISymbol TransformSymbol(CodeSegment result, INativeSymbolsProvider provider, ISymbol symbol) + { + if (symbol is NativeLocalSymbol local) + return new Symbol(result.ToReference(local.Offset)); + + return provider.ImportSymbol(symbol); + } } } diff --git a/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs b/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs index 8248382d7..291c1787a 100644 --- a/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs +++ b/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs @@ -6,21 +6,30 @@ using AsmResolver.PE; using AsmResolver.PE.Code; using AsmResolver.PE.DotNet; +using AsmResolver.PE.DotNet.Cil; using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; using AsmResolver.PE.File.Headers; using AsmResolver.PE.Imports; +using AsmResolver.Tests.Runners; using Xunit; namespace AsmResolver.DotNet.Tests.Code.Native { - public class NativeMethodBodyTest + public class NativeMethodBodyTest : IClassFixture { + private TemporaryDirectoryFixture _fixture; + + public NativeMethodBodyTest(TemporaryDirectoryFixture fixture) + { + _fixture = fixture; + } + private static NativeMethodBody CreateDummyBody(bool isVoid, bool is32Bit) { var module = ModuleDefinition.FromBytes(Properties.Resources.TheAnswer_NetFx); - module.Attributes &= DotNetDirectoryFlags.ILOnly; + module.Attributes &= ~DotNetDirectoryFlags.ILOnly; if (is32Bit) { module.PEKind = OptionalHeaderMagic.Pe32; @@ -243,5 +252,56 @@ public void ReadNativeMethodShouldResultInReferenceWithRightContents() reference.CreateReader().ReadBytes(newBuffer, 0, newBuffer.Length); Assert.Equal(body.Code, newBuffer); } + + [Fact] + public void NativeBodyWithLocalSymbols() + { + // Create native body. + var body = CreateDummyBody(false, true); + body.Code = new byte[] + { + /* 00: */ 0xB8, 0x00, 0x00, 0x00, 0x00, // mov eax, message + /* 05: */ 0xc3, // ret + + // message: + 0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x2c, 0x00, 0x20, 0x00, // "Hello, " + 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x6c, 0x00, 0x64, 0x00, 0x21, 0x00, 0x00, 0x00 // "world!." + }; + + // Define local symbol. + var messageSymbol = new NativeLocalSymbol(body, 6); + + // Fixup address in mov instruction. + body.AddressFixups.Add(new AddressFixup(1, AddressFixupType.Absolute32BitAddress, messageSymbol)); + + // Update main to call native method, convert the returned pointer to a String, and write to stdout. + var module = body.Owner.Module; + var stringConstructor = new MemberReference( + module!.CorLibTypeFactory.String.Type, + ".ctor", + MethodSignature.CreateInstance( + module.CorLibTypeFactory.Void, + module.CorLibTypeFactory.Char.MakePointerType()) + ); + var writeLine = new MemberReference( + new TypeReference(module, module.CorLibTypeFactory.CorLibScope, "System", "Console"), + "WriteLine", + MethodSignature.CreateStatic( + module.CorLibTypeFactory.Void, + module.CorLibTypeFactory.String) + ); + + var instructions = module.ManagedEntrypointMethod!.CilMethodBody!.Instructions; + instructions.Clear(); + instructions.Add(CilOpCodes.Call, body.Owner); + instructions.Add(CilOpCodes.Newobj, stringConstructor); + instructions.Add(CilOpCodes.Call, writeLine); + instructions.Add(CilOpCodes.Ret); + + // Verify. + _fixture + .GetRunner() + .RebuildAndRun(module, "StringPointer.exe", $"Hello, world!{Environment.NewLine}"); + } } } From a0989cd2cd894f05d09b8cd8d45412475c585e83 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 5 May 2022 16:37:22 +0200 Subject: [PATCH 109/184] Add failing instance field import test. --- .../ReferenceImporterTest.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs index 83ec26082..ce0517a88 100644 --- a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs +++ b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs @@ -4,6 +4,7 @@ using System.Linq; using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.Signatures.Types; +using AsmResolver.DotNet.TestCases.Fields; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; using Xunit; @@ -464,5 +465,26 @@ public void ImportFullyImportedFunctionPointerTypeShouldResultInSameInstance() var newInstance = Assert.IsAssignableFrom(imported); Assert.Same(signature, newInstance); } + + [Fact] + public void ImportInstanceFieldByReflectionShouldConstructValidFieldSignature() + { + // https://github.com/Washi1337/AsmResolver/issues/307 + + var module = ModuleDefinition.FromFile(typeof(SingleField).Assembly.Location); + var field = module.GetAllTypes() + .First(t => t.Name == nameof(SingleField)) + .Fields + .First(f => f.Name == nameof(SingleField.IntField)); + + var fieldInfo = typeof(SingleField).GetField(nameof(SingleField.IntField))!; + + var importer = new ReferenceImporter(module); + var imported = importer.ImportField(fieldInfo); + var resolved = imported.Resolve(); + + Assert.NotNull(resolved); + Assert.Equal(field, resolved); + } } } From 006107df13b7dc9b36789553dd526dd7f69ca343 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 5 May 2022 16:40:48 +0200 Subject: [PATCH 110/184] BUGFIX: Do not set HasThis in field signature when importing by Reflection. --- src/AsmResolver.DotNet/ReferenceImporter.cs | 4 +--- test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/AsmResolver.DotNet/ReferenceImporter.cs b/src/AsmResolver.DotNet/ReferenceImporter.cs index 8af788d7d..d000816b8 100644 --- a/src/AsmResolver.DotNet/ReferenceImporter.cs +++ b/src/AsmResolver.DotNet/ReferenceImporter.cs @@ -521,9 +521,7 @@ public MemberReference ImportField(FieldInfo field) ? ImportType(field.DeclaringType) : TargetModule.GetModuleType(); - var signature = new FieldSignature(field.IsStatic ? 0 : CallingConventionAttributes.HasThis, - ImportTypeSignature(field.FieldType)); - + var signature = new FieldSignature(ImportTypeSignature(field.FieldType)); return new MemberReference(scope, field.Name, signature); } diff --git a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs index ce0517a88..fd2880cde 100644 --- a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs +++ b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs @@ -484,7 +484,7 @@ public void ImportInstanceFieldByReflectionShouldConstructValidFieldSignature() var resolved = imported.Resolve(); Assert.NotNull(resolved); - Assert.Equal(field, resolved); + Assert.Equal(field, Assert.IsAssignableFrom(resolved), Comparer); } } } From d7e11c89b6808ddd618ee7db29155467bf88b6d6 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 5 May 2022 17:07:23 +0200 Subject: [PATCH 111/184] Add relocs for 64-bit absolute addresses within native method bodies. --- .../Code/Native/NativeLocalSymbol.cs | 6 ++-- .../Code/Native/NativeMethodBodySerializer.cs | 20 ++++++++---- .../Code/Native/NativeMethodBodyTest.cs | 32 +++++++++++++------ 3 files changed, 39 insertions(+), 19 deletions(-) diff --git a/src/AsmResolver.DotNet/Code/Native/NativeLocalSymbol.cs b/src/AsmResolver.DotNet/Code/Native/NativeLocalSymbol.cs index b53ff657f..1da4db56b 100644 --- a/src/AsmResolver.DotNet/Code/Native/NativeLocalSymbol.cs +++ b/src/AsmResolver.DotNet/Code/Native/NativeLocalSymbol.cs @@ -2,7 +2,7 @@ namespace AsmResolver.DotNet.Code.Native; public class NativeLocalSymbol : ISymbol { - public NativeLocalSymbol(NativeMethodBody body, int offset) + public NativeLocalSymbol(NativeMethodBody body, uint offset) { Body = body; Offset = offset; @@ -13,13 +13,13 @@ public NativeMethodBody Body get; } - public int Offset + public uint Offset { get; } /// public ISegmentReference? GetReference() => Body.Address is not null - ? new RelativeReference(Body.Address, Offset) + ? new RelativeReference(Body.Address, (int) Offset) : null; } diff --git a/src/AsmResolver.DotNet/Code/Native/NativeMethodBodySerializer.cs b/src/AsmResolver.DotNet/Code/Native/NativeMethodBodySerializer.cs index d57f759a3..0da8635fa 100644 --- a/src/AsmResolver.DotNet/Code/Native/NativeMethodBodySerializer.cs +++ b/src/AsmResolver.DotNet/Code/Native/NativeMethodBodySerializer.cs @@ -31,13 +31,19 @@ public ISegmentReference SerializeMethodBody(MethodBodySerializationContext cont segment.AddressFixups.Add(new AddressFixup(fixup.Offset, fixup.Type, symbol)); // Add base relocation when necessary. - // TODO: keep architecture into account.. - if (fixup.Type == AddressFixupType.Absolute32BitAddress) + switch (fixup.Type) { - var relocation = new BaseRelocation( - RelocationType.HighLow, - segment.ToReference((int) fixup.Offset)); - provider.RegisterBaseRelocation(relocation); + case AddressFixupType.Absolute32BitAddress: + provider.RegisterBaseRelocation(new BaseRelocation( + RelocationType.HighLow, + segment.ToReference((int) fixup.Offset))); + break; + + case AddressFixupType.Absolute64BitAddress: + provider.RegisterBaseRelocation(new BaseRelocation( + RelocationType.Dir64, + segment.ToReference((int) fixup.Offset))); + break; } } @@ -47,7 +53,7 @@ public ISegmentReference SerializeMethodBody(MethodBodySerializationContext cont protected virtual ISymbol TransformSymbol(CodeSegment result, INativeSymbolsProvider provider, ISymbol symbol) { if (symbol is NativeLocalSymbol local) - return new Symbol(result.ToReference(local.Offset)); + return new Symbol(result.ToReference((int) local.Offset)); return provider.ImportSymbol(symbol); } diff --git a/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs b/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs index 291c1787a..5052f8346 100644 --- a/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs +++ b/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using AsmResolver.DotNet.Code.Native; @@ -253,29 +254,42 @@ public void ReadNativeMethodShouldResultInReferenceWithRightContents() Assert.Equal(body.Code, newBuffer); } - [Fact] - public void NativeBodyWithLocalSymbols() + [Theory] + [InlineData( + true, + new byte[] {0xB8, 0x00, 0x00, 0x00, 0x00}, // mov eax, message + 1u, AddressFixupType.Absolute32BitAddress, + 6u)] + [InlineData( + false, + new byte[] {0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // mov rax, message + 2u, AddressFixupType.Absolute64BitAddress, + 11u)] + public void NativeBodyWithLocalSymbols(bool is32Bit, byte[] movInstruction, uint fixupOffset, AddressFixupType fixupType, uint symbolOffset) { // Create native body. - var body = CreateDummyBody(false, true); - body.Code = new byte[] + var code = new List(movInstruction); + code.AddRange(new byte[] { - /* 00: */ 0xB8, 0x00, 0x00, 0x00, 0x00, // mov eax, message - /* 05: */ 0xc3, // ret + 0xc3, // ret // message: 0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x2c, 0x00, 0x20, 0x00, // "Hello, " 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x6c, 0x00, 0x64, 0x00, 0x21, 0x00, 0x00, 0x00 // "world!." - }; + }); + + var body = CreateDummyBody(false, is32Bit); + body.Code = code.ToArray(); // Define local symbol. - var messageSymbol = new NativeLocalSymbol(body, 6); + var messageSymbol = new NativeLocalSymbol(body, symbolOffset); // Fixup address in mov instruction. - body.AddressFixups.Add(new AddressFixup(1, AddressFixupType.Absolute32BitAddress, messageSymbol)); + body.AddressFixups.Add(new AddressFixup(fixupOffset, fixupType, messageSymbol)); // Update main to call native method, convert the returned pointer to a String, and write to stdout. var module = body.Owner.Module; + body.Owner!.Signature!.ReturnType = body.Owner.Module!.CorLibTypeFactory.IntPtr; var stringConstructor = new MemberReference( module!.CorLibTypeFactory.String.Type, ".ctor", From 7f32b952c4034999c97d54c15b34de753135d44f Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 5 May 2022 17:15:58 +0200 Subject: [PATCH 112/184] Add xmldoc for native local symbols, extract protected virtual methods in NativeMethodBodySerializer. --- .../Code/Native/NativeLocalSymbol.cs | 53 ++++++++++++------- .../Code/Native/NativeMethodBodySerializer.cs | 52 ++++++++++++------ 2 files changed, 69 insertions(+), 36 deletions(-) diff --git a/src/AsmResolver.DotNet/Code/Native/NativeLocalSymbol.cs b/src/AsmResolver.DotNet/Code/Native/NativeLocalSymbol.cs index 1da4db56b..bec589b08 100644 --- a/src/AsmResolver.DotNet/Code/Native/NativeLocalSymbol.cs +++ b/src/AsmResolver.DotNet/Code/Native/NativeLocalSymbol.cs @@ -1,25 +1,40 @@ -namespace AsmResolver.DotNet.Code.Native; - -public class NativeLocalSymbol : ISymbol +namespace AsmResolver.DotNet.Code.Native { - public NativeLocalSymbol(NativeMethodBody body, uint offset) + /// + /// Represents a symbol within a native method body. + /// + public class NativeLocalSymbol : ISymbol { - Body = body; - Offset = offset; - } + /// + /// Creates a new native local symbol. + /// + /// The body that defines this symbol. + /// The offset relative to the start of the method body. + public NativeLocalSymbol(NativeMethodBody body, uint offset) + { + Body = body; + Offset = offset; + } - public NativeMethodBody Body - { - get; - } + /// + /// Gets the body that this symbol is defined in. + /// + public NativeMethodBody Body + { + get; + } - public uint Offset - { - get; - } + /// + /// Gets the offset of the symbol, relative to the start of the method body. + /// + public uint Offset + { + get; + } - /// - public ISegmentReference? GetReference() => Body.Address is not null - ? new RelativeReference(Body.Address, (int) Offset) - : null; + /// + public ISegmentReference? GetReference() => Body.Address is not null + ? new RelativeReference(Body.Address, (int) Offset) + : null; + } } diff --git a/src/AsmResolver.DotNet/Code/Native/NativeMethodBodySerializer.cs b/src/AsmResolver.DotNet/Code/Native/NativeMethodBodySerializer.cs index 0da8635fa..e4253b717 100644 --- a/src/AsmResolver.DotNet/Code/Native/NativeMethodBodySerializer.cs +++ b/src/AsmResolver.DotNet/Code/Native/NativeMethodBodySerializer.cs @@ -12,7 +12,7 @@ public class NativeMethodBodySerializer : IMethodBodySerializer /// public ISegmentReference SerializeMethodBody(MethodBodySerializationContext context, MethodDefinition method) { - if (!(method.MethodBody is NativeMethodBody nativeMethodBody)) + if (method.MethodBody is not NativeMethodBody nativeMethodBody) return SegmentReference.Null; var provider = context.SymbolsProvider; @@ -25,32 +25,50 @@ public ISegmentReference SerializeMethodBody(MethodBodySerializationContext cont { // Import symbol. var fixup = nativeMethodBody.AddressFixups[i]; - var symbol = TransformSymbol(segment, provider, fixup.Symbol); + var symbol = FinalizeSymbol(segment, provider, fixup.Symbol); // Create new fixup with imported symbol. segment.AddressFixups.Add(new AddressFixup(fixup.Offset, fixup.Type, symbol)); // Add base relocation when necessary. - switch (fixup.Type) - { - case AddressFixupType.Absolute32BitAddress: - provider.RegisterBaseRelocation(new BaseRelocation( - RelocationType.HighLow, - segment.ToReference((int) fixup.Offset))); - break; - - case AddressFixupType.Absolute64BitAddress: - provider.RegisterBaseRelocation(new BaseRelocation( - RelocationType.Dir64, - segment.ToReference((int) fixup.Offset))); - break; - } + AddBaseRelocations(segment, provider, fixup); } return segment.ToReference(); } - protected virtual ISymbol TransformSymbol(CodeSegment result, INativeSymbolsProvider provider, ISymbol symbol) + /// + /// Registers base relocations for the provided address fixup, if required. + /// + /// The code segment that is being constructed. + /// The object responsible for providing symbols referenced by the native method body. + /// The fixup to build base relocations for. + protected virtual void AddBaseRelocations(CodeSegment segment, INativeSymbolsProvider provider, AddressFixup fixup) + { + switch (fixup.Type) + { + case AddressFixupType.Absolute32BitAddress: + provider.RegisterBaseRelocation(new BaseRelocation( + RelocationType.HighLow, + segment.ToReference((int) fixup.Offset))); + break; + + case AddressFixupType.Absolute64BitAddress: + provider.RegisterBaseRelocation(new BaseRelocation( + RelocationType.Dir64, + segment.ToReference((int) fixup.Offset))); + break; + } + } + + /// + /// Ensures the right symbol is used within the method body. + /// + /// The code segment that is being constructed. + /// The object responsible for providing symbols referenced by the native method body. + /// The symbol to reference. + /// The symbol. + protected virtual ISymbol FinalizeSymbol(CodeSegment result, INativeSymbolsProvider provider, ISymbol symbol) { if (symbol is NativeLocalSymbol local) return new Symbol(result.ToReference((int) local.Offset)); From dbb1272db28c825f4170f7d32c6b0f7624fb7e7b Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 5 May 2022 17:32:52 +0200 Subject: [PATCH 113/184] Update docs on new native local symbols API. --- docs/dotnet/unmanaged-method-bodies.rst | 60 +++++++++++++++++++------ 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/docs/dotnet/unmanaged-method-bodies.rst b/docs/dotnet/unmanaged-method-bodies.rst index dee5d9c5e..cca069ecc 100644 --- a/docs/dotnet/unmanaged-method-bodies.rst +++ b/docs/dotnet/unmanaged-method-bodies.rst @@ -20,7 +20,7 @@ Allowing native code in modules To make the CLR treat the output file as a mixed mode application, the ``ILOnly`` flag needs to be unset: -.. code-block:: csharp +.. code-block:: csharp ModuleDefinition module = ... module.Attributes &= ~DotNetDirectoryFlags.ILOnly; @@ -92,14 +92,20 @@ The contents of a native method body can be set through the ``Code`` property. T .. note:: - + Since native method bodies are platform dependent, AsmResolver does not provide a standard way to encode these instructions. To construct the byte array that you need for a particular implementation of a method body, consider using a third-party assembler or assembler library. -References to external symbols ------------------------------- +Symbols and Address Fixups +-------------------------- + +In a lot of cases, native method bodies that references symbols (such as imported functions) require direct addresses to be referenced within its instructions. Since the addresses of these symbols are not known yet upon creating a ``NativeMethodBody``, it is not possible to encode such an operand. To support these kinds of references regardless, AsmResolver can be instructed to apply address fixups just before writing the body to the disk. These are essentially small pieces of information that tell AsmResolver that at a particular offset the bytes should be replaced with a reference to a symbol in the final PE. This can be applied to any object that implements ``ISymbol``. In the following, two of the most commonly used symbols will be discussed. -In a lot of cases, methods require making calls to functions defined in external libraries and native method bodies are no exception. In the PE file format, these kinds of symbols are often put into the imports directory. This is essentially a table of names that the Windows PE loader will go through, look up the actual address of each name, and put it in the import address table. Typically, when a piece of code is meant to make a call to an external function, the code will make an indirect call to an entry stored in this table. In x86 64-bit, using nasm syntax, a call to the ``puts`` function might look like the following snippet: + +Imported Symbols +~~~~~~~~~~~~~~~~ + +In the PE file format, symbols from external modules are often imported by placing an entry into the imports directory. This is essentially a table of names that the Windows PE loader will go through, look up the actual address of each name, and put it in the import address table. Typically, when a piece of code is meant to make a call to an external function, the code will make an indirect call to an entry stored in this table. In x86 64-bit, using nasm syntax, a call to the ``puts`` function might look like the following snippet: .. code-block:: csharp @@ -108,10 +114,6 @@ In a lot of cases, methods require making calls to functions defined in external call qword [rel puts] ... -Since the import directory is not constructed yet when we are operating on the abstraction level of a ``ModuleDefinition``, the address of the import address entry is still unknown. Therefore, it is not possible to encode an operand like the one in the call instruction of the above example. - -To support these kinds of references in native method bodies regardless, it is possible to instruct AsmResolver to apply address fixups just before writing the body to the disk. These are essentially small pieces of information that tell AsmResolver that at a particular offset the bytes should be replaced with a reference to a symbol in the final PE. - Consider the following example x86 64-bit code, that is printing the text ``Hello from the unmanaged world!`` to the standard output stream using the ``puts`` function. .. code-block:: csharp @@ -135,16 +137,16 @@ Consider the following example x86 64-bit code, that is printing the text ``Hell 0x67, 0x65, 0x64, 0x20, 0x77, 0x6f, 0x72, // "ged wor" 0x6c, 0x64, 0x21, 0x00 // "ld!" }; - -Notice how the operand of the call instruction is left at zero (`0x00`) bytes. To let AsmResolver know that these 4 bytes are to be replaced by an address to an entry in the import address table, we first create a new instance of ``ImportedSymbol``, representing the ``puts`` symbol: + +Notice how the operand of the ``call`` instruction is left at zero (``0x00``) bytes. To let AsmResolver know that these 4 bytes are to be replaced by an address to an entry in the import address table, we first create a new instance of ``ImportedSymbol``, representing the ``puts`` symbol: .. code-block:: csharp var ucrtbased = new ImportedModule("ucrtbased.dll"); var puts = new ImportedSymbol(0x4fc, "puts"); ucrtbased.Symbols.Add(puts); - + We can then add it as a fixup to the method body: @@ -155,12 +157,44 @@ We can then add it as a fixup to the method body: )); +Local Symbols +~~~~~~~~~~~~~ + +If a native body is supposed to process or return some data that is defined within the body itself, the ``NativeLocalSymbol`` class can be used instead. + +Consider the following example x86 32-bit snippet, that returns the virtual address of a string. + +.. code-block:: csharp + + 0xB8, 0x00, 0x00, 0x00, 0x00 // mov eax, message + 0xc3, // ret + + // message (unicode): + 0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x2c, 0x00, 0x20, 0x00, // "Hello, " + 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x6c, 0x00, 0x64, 0x00, 0x21, 0x00, 0x00, 0x00 // "world!." + + +Notice how the operand of the ``mov`` instruction is left at zero (``0x00``) bytes. To let AsmResolver know that these 4 bytes are to be replaced by the actual virtual address to ``message``, we can define a local symbol and register an address fixup in the following manner: + +.. code-block:: csharp + + var message = new NativeLocalSymbol(body, offset: 0x6); + body.AddressFixups.Add(new AddressFixup( + 0x1, AddressFixupType.Absolute32BitAddress, message + )); + + +Fixup Types +~~~~~~~~~~~ + The type of fixup that is required will depend on the architecture and instruction that is used. Below an overview: +--------------------------+-----------------------------------------------------------------------+---------------------------------+ | Fixup type | Description | Example instructions | +==========================+=======================================================================+=================================+ -| ``Absolute32BitAddress`` | The operand is an absolute virtual address | ``call dword [address]`` | +| ``Absolute32BitAddress`` | The operand is a 32-bit absolute virtual address | ``call dword [address]`` | ++--------------------------+-----------------------------------------------------------------------+---------------------------------+ +| ``Absolute64BitAddress`` | The operand is a 64-bit absolute virtual address | ``mov rax, address`` | +--------------------------+-----------------------------------------------------------------------+---------------------------------+ | ``Relative32BitAddress`` | The operand is an address relative to the current instruction pointer | ``call qword [rip+offset]`` | +--------------------------+-----------------------------------------------------------------------+---------------------------------+ From 2d2ef3b999d533fc87d3cbd131b6d25a4ff36655 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 5 May 2022 17:42:28 +0200 Subject: [PATCH 114/184] Bump version numbers in appveyor and Build.props. --- Directory.Build.props | 2 +- appveyor.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 648c5ce2f..c1dc47b04 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,7 +7,7 @@ https://github.com/Washi1337/AsmResolver git 10 - 4.10.0 + 4.11.0 diff --git a/appveyor.yml b/appveyor.yml index fe19b7d43..91e0e257f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,7 +4,7 @@ - master image: Visual Studio 2022 - version: 4.10.0-master-build.{build} + version: 4.11.0-master-build.{build} configuration: Release skip_commits: @@ -33,7 +33,7 @@ - development image: Visual Studio 2022 - version: 4.10.0-dev-build.{build} + version: 4.11.0-dev-build.{build} configuration: Release skip_commits: From 1d1e0981be2ad762607cc8c8a617823ac6dec0ee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 May 2022 14:34:57 +0000 Subject: [PATCH 115/184] Bump xunit.runner.visualstudio from 2.4.3 to 2.4.5 Bumps [xunit.runner.visualstudio](https://github.com/xunit/visualstudio.xunit) from 2.4.3 to 2.4.5. - [Release notes](https://github.com/xunit/visualstudio.xunit/releases) - [Commits](https://github.com/xunit/visualstudio.xunit/commits) --- updated-dependencies: - dependency-name: xunit.runner.visualstudio dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj | 2 +- test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj | 2 +- test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj | 2 +- .../AsmResolver.PE.Win32Resources.Tests.csproj | 2 +- test/AsmResolver.Tests/AsmResolver.Tests.csproj | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj b/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj index a29c5c0d5..0532fbaf9 100644 --- a/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj +++ b/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj @@ -11,7 +11,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive 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 97ca135a4..2fc3c08cf 100644 --- a/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj +++ b/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj @@ -11,7 +11,7 @@ - + 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 a34649959..944b5a804 100644 --- a/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj +++ b/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj @@ -19,7 +19,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive 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 f827592b7..429cbde0a 100644 --- a/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj +++ b/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj @@ -10,7 +10,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/AsmResolver.Tests/AsmResolver.Tests.csproj b/test/AsmResolver.Tests/AsmResolver.Tests.csproj index a7428e961..07b8b090d 100644 --- a/test/AsmResolver.Tests/AsmResolver.Tests.csproj +++ b/test/AsmResolver.Tests/AsmResolver.Tests.csproj @@ -10,7 +10,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From ae14702a4b692734d300d90760a81f886d30c4ad Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 6 May 2022 20:01:54 +0200 Subject: [PATCH 116/184] Update member signature ToString methods. --- .../Signatures/FieldSignature.cs | 2 ++ .../Signatures/MemberSignature.cs | 8 -------- .../Signatures/MethodSignature.cs | 19 ++++++++++++++++--- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/AsmResolver.DotNet/Signatures/FieldSignature.cs b/src/AsmResolver.DotNet/Signatures/FieldSignature.cs index 9b742200a..0489fb217 100644 --- a/src/AsmResolver.DotNet/Signatures/FieldSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/FieldSignature.cs @@ -94,5 +94,7 @@ protected override void WriteContents(BlobSerializationContext context) context.Writer.WriteByte((byte) Attributes); FieldType.Write(context); } + /// + public override string ToString() => FieldType.FullName; } } diff --git a/src/AsmResolver.DotNet/Signatures/MemberSignature.cs b/src/AsmResolver.DotNet/Signatures/MemberSignature.cs index 2d5e412d1..e785171d8 100644 --- a/src/AsmResolver.DotNet/Signatures/MemberSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/MemberSignature.cs @@ -29,13 +29,5 @@ protected TypeSignature MemberReturnType /// public override bool IsImportedInModule(ModuleDefinition module) => MemberReturnType.IsImportedInModule(module); - - /// - public override string ToString() - { - string prefix = HasThis ? "instance " : string.Empty; - string fullName = MemberReturnType?.FullName ?? TypeSignature.NullTypeToString; - return $"{prefix}{fullName}"; - } } } diff --git a/src/AsmResolver.DotNet/Signatures/MethodSignature.cs b/src/AsmResolver.DotNet/Signatures/MethodSignature.cs index 14787822f..39f1d80c8 100644 --- a/src/AsmResolver.DotNet/Signatures/MethodSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/MethodSignature.cs @@ -207,13 +207,26 @@ protected override void WriteContents(BlobSerializationContext context) public override string ToString() { string prefix = HasThis ? "instance " : string.Empty; - string fullName = ReturnType?.FullName ?? TypeSignature.NullTypeToString; + string fullName = ReturnType.FullName; + string genericsString = GenericParameterCount > 0 ? $"<{string.Join(", ", new string('?', GenericParameterCount))}>" : string.Empty; - string parameterTypesString = string.Join(", ", ParameterTypes) + (IsSentinel ? ", ..." : string.Empty); - return $"{prefix}{fullName} *{genericsString}({parameterTypesString})"; + string parameterTypesString = string.Join(", ", ParameterTypes); + + string sentinelSuffix; + if (IsSentinel) + { + sentinelSuffix = ParameterTypes.Count > 0 + ? ", ..." + : " ...";} + else + { + sentinelSuffix = string.Empty; + } + + return $"{prefix}{fullName} *{genericsString}({parameterTypesString}{sentinelSuffix})"; } } } From 827e5903aec5a8651548289ef4f4f9613487e4f4 Mon Sep 17 00:00:00 2001 From: Washi Date: Tue, 10 May 2022 19:33:00 +0200 Subject: [PATCH 117/184] Warn for limited usage of NativeLocalSymbol in docs. --- docs/dotnet/unmanaged-method-bodies.rst | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/dotnet/unmanaged-method-bodies.rst b/docs/dotnet/unmanaged-method-bodies.rst index cca069ecc..c5edc20d3 100644 --- a/docs/dotnet/unmanaged-method-bodies.rst +++ b/docs/dotnet/unmanaged-method-bodies.rst @@ -80,7 +80,7 @@ In the following sections, we will briefly go over each of them. Writing native code ------------------- -The contents of a native method body can be set through the ``Code`` property. This is a ``byte[]`` that represents the raw code stream to be executed. Below an example of a simple method body written in x86 64-bit assembly code, that returns the constant ``0x1337``: +The contents of a native method body can be set through the ``Code`` property. This is a ``byte[]`` that represents the raw code stream to be executed. Below an example of a simple method body written in x86 64-bit assembly code, that returns the constant ``1337``: .. code-block:: csharp @@ -99,7 +99,7 @@ The contents of a native method body can be set through the ``Code`` property. T Symbols and Address Fixups -------------------------- -In a lot of cases, native method bodies that references symbols (such as imported functions) require direct addresses to be referenced within its instructions. Since the addresses of these symbols are not known yet upon creating a ``NativeMethodBody``, it is not possible to encode such an operand. To support these kinds of references regardless, AsmResolver can be instructed to apply address fixups just before writing the body to the disk. These are essentially small pieces of information that tell AsmResolver that at a particular offset the bytes should be replaced with a reference to a symbol in the final PE. This can be applied to any object that implements ``ISymbol``. In the following, two of the most commonly used symbols will be discussed. +In a lot of cases, native method bodies that references symbols (such as imported functions) require direct addresses to be referenced within its instructions. Since the addresses of these symbols are not known yet upon creating a ``NativeMethodBody``, it is not possible to encode such an operand directly in the ``Code`` byte array. To support these kinds of references regardless, AsmResolver can be instructed to apply address fixups just before writing the body to the disk. These instructions are essentially small pieces of information that tell AsmResolver that at a particular offset the bytes should be replaced with a reference to a symbol in the final PE. This can be applied to any object that implements ``ISymbol``. In the following, two of the most commonly used symbols will be discussed. Imported Symbols @@ -160,7 +160,7 @@ We can then add it as a fixup to the method body: Local Symbols ~~~~~~~~~~~~~ -If a native body is supposed to process or return some data that is defined within the body itself, the ``NativeLocalSymbol`` class can be used instead. +If a native body is supposed to process or return some data that is defined within the body itself, the ``NativeLocalSymbol`` class can be used. Consider the following example x86 32-bit snippet, that returns the virtual address of a string. @@ -184,10 +184,15 @@ Notice how the operand of the ``mov`` instruction is left at zero (``0x00``) byt )); +.. warning:: + + The ``NativeLocalSymbol`` can only be used within the code of the native method body itself. This is due to the fact that these types of symbols are not processed further after serializing a ``NativeMethodBody`` to a ``CodeSegment`` by the default method body serializer. + + Fixup Types ~~~~~~~~~~~ -The type of fixup that is required will depend on the architecture and instruction that is used. Below an overview: +The type of fixup that is required will depend on the architecture and instruction that is used. Below an overview of all fixups that AsmResolver is able to apply: +--------------------------+-----------------------------------------------------------------------+---------------------------------+ | Fixup type | Description | Example instructions | From 83320abd18c87c19fa6a8fae74eb5657fe223417 Mon Sep 17 00:00:00 2001 From: Washi Date: Tue, 10 May 2022 19:36:04 +0200 Subject: [PATCH 118/184] Add NativeLocalSymbol.ToString() --- src/AsmResolver.DotNet/Code/Native/NativeLocalSymbol.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/AsmResolver.DotNet/Code/Native/NativeLocalSymbol.cs b/src/AsmResolver.DotNet/Code/Native/NativeLocalSymbol.cs index bec589b08..8d165fe41 100644 --- a/src/AsmResolver.DotNet/Code/Native/NativeLocalSymbol.cs +++ b/src/AsmResolver.DotNet/Code/Native/NativeLocalSymbol.cs @@ -36,5 +36,8 @@ public uint Offset public ISegmentReference? GetReference() => Body.Address is not null ? new RelativeReference(Body.Address, (int) Offset) : null; + + /// + public override string ToString() => $"{Body.Owner.SafeToString()}+{Offset:X}"; } } From 74d061487a9d9d635dbf01f278d3949049c3a98d Mon Sep 17 00:00:00 2001 From: Washi Date: Tue, 10 May 2022 20:43:22 +0200 Subject: [PATCH 119/184] Overload ImportWith methods with more specific return type. --- src/AsmResolver.DotNet/ITypeDefOrRef.cs | 7 +++++++ src/AsmResolver.DotNet/InvalidTypeDefOrRef.cs | 3 +++ src/AsmResolver.DotNet/MemberReference.cs | 13 ++++++++++--- src/AsmResolver.DotNet/TypeSpecification.cs | 2 +- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/AsmResolver.DotNet/ITypeDefOrRef.cs b/src/AsmResolver.DotNet/ITypeDefOrRef.cs index bf80f8481..a81bc335b 100644 --- a/src/AsmResolver.DotNet/ITypeDefOrRef.cs +++ b/src/AsmResolver.DotNet/ITypeDefOrRef.cs @@ -28,5 +28,12 @@ public interface ITypeDefOrRef : ITypeDescriptor, IMemberRefParent, IHasCustomAt { get; } + + /// + /// Imports the type using the provided reference importer object. + /// + /// The reference importer to use for importing the type. + /// The imported type. + new ITypeDefOrRef ImportWith(ReferenceImporter importer); } } diff --git a/src/AsmResolver.DotNet/InvalidTypeDefOrRef.cs b/src/AsmResolver.DotNet/InvalidTypeDefOrRef.cs index 3fb25cbb1..aa0dc667d 100644 --- a/src/AsmResolver.DotNet/InvalidTypeDefOrRef.cs +++ b/src/AsmResolver.DotNet/InvalidTypeDefOrRef.cs @@ -76,6 +76,9 @@ public static InvalidTypeDefOrRef Get(InvalidTypeSignatureError error) /// public bool IsImportedInModule(ModuleDefinition module) => false; + /// + ITypeDefOrRef ITypeDefOrRef.ImportWith(ReferenceImporter importer) => throw new InvalidOperationException(); + /// IImportable IImportable.ImportWith(ReferenceImporter importer) => throw new InvalidOperationException(); diff --git a/src/AsmResolver.DotNet/MemberReference.cs b/src/AsmResolver.DotNet/MemberReference.cs index 3dba6d1e8..69a274884 100644 --- a/src/AsmResolver.DotNet/MemberReference.cs +++ b/src/AsmResolver.DotNet/MemberReference.cs @@ -160,10 +160,17 @@ public bool IsImportedInModule(ModuleDefinition module) && (Signature?.IsImportedInModule(module) ?? false); } + /// + /// Imports the member using the provided reference importer object. + /// + /// The reference importer to use for importing the object. + /// The imported member. + public MemberReference ImportWith(ReferenceImporter importer) => IsMethod + ? (MemberReference) importer.ImportMethod(this) + : (MemberReference) importer.ImportField(this); + /// - public IImportable ImportWith(ReferenceImporter importer) => IsMethod - ? importer.ImportMethod(this) - : importer.ImportField(this); + IImportable IImportable.ImportWith(ReferenceImporter importer) => ImportWith(importer); FieldDefinition? IFieldDescriptor.Resolve() { diff --git a/src/AsmResolver.DotNet/TypeSpecification.cs b/src/AsmResolver.DotNet/TypeSpecification.cs index f62c45293..e8d805b4d 100644 --- a/src/AsmResolver.DotNet/TypeSpecification.cs +++ b/src/AsmResolver.DotNet/TypeSpecification.cs @@ -103,7 +103,7 @@ public IList CustomAttributes /// /// The reference importer to use. /// The imported type. - public TypeSpecification ImportWith(ReferenceImporter importer) => (TypeSpecification) importer.ImportType(this); + public ITypeDefOrRef ImportWith(ReferenceImporter importer) => (TypeSpecification) importer.ImportType(this); /// IImportable IImportable.ImportWith(ReferenceImporter importer) => ImportWith(importer); From da448c60eddbdc3f71869aa8e0036356c15b19ed Mon Sep 17 00:00:00 2001 From: Washi Date: Tue, 10 May 2022 20:43:35 +0200 Subject: [PATCH 120/184] Update docs on new fluent syntax. --- docs/dotnet/importing.rst | 95 ++++++++++++++++++++++++++++++++------- docs/dotnet/methods.rst | 34 -------------- 2 files changed, 79 insertions(+), 50 deletions(-) delete mode 100644 docs/dotnet/methods.rst diff --git a/docs/dotnet/importing.rst b/docs/dotnet/importing.rst index 24e4e21ad..56d99eb94 100644 --- a/docs/dotnet/importing.rst +++ b/docs/dotnet/importing.rst @@ -14,8 +14,8 @@ All samples in this document assume there is an instance of ``ReferenceImporter` var importer = new ReferenceImporter(module); -Importing metadata members --------------------------- +Importing existing metadata members +----------------------------------- Metadata members from external modules can be imported using the ``ReferenceImporter`` class using one of the following members: @@ -59,18 +59,28 @@ Below an example of how to import a type definition called ``SomeType``: ITypeDefOrRef importedType = importer.ImportType(typeToImport); -Importing type signatures -------------------------- +These types also implement the ``IImportable`` interface. This means you can also use the ``member.ImportWith`` method instead: + +.. code-block:: csharp + + ModuleDefinition externalModule = ModuleDefinition.FromFile(...); + TypeDefinition typeToImport = externalModule.TopLevelTypes.First(t => t.Name == "SomeType"); + + ITypeDefOrRef importedType = typeToImport.ImportWith(importer); + + +Importing existing type signatures +---------------------------------- Type signatures can also be imported using the ``ReferenceImporter`` class, but these should be imported using the ``ImportTypeSignature`` method instead. -.. note:: +.. note:: If a corlib type signature is imported, the appropriate type from the ``CorLibTypeFactory`` of the target module will be selected, regardless of whether CorLib versions are compatible with each other. -Importing using reflection --------------------------- +Importing using System.Reflection +--------------------------------- Types and members can also be imported by passing on an instance of various ``System.Reflection`` classes. @@ -90,22 +100,75 @@ Types and members can also be imported by passing on an instance of various ``Sy | ``FieldInfo`` | ``ImportScope`` | ``MemberReference`` | +---------------------------+------------------------+----------------------+ - -There is limited support for importing compound types. Types that can be imported through reflection include: +There is limited support for importing complex types. Types that can be imported through reflection include: - Pointer types. - By-reference types. -- Array types: - - If an array contains only one dimension, a ``SzArrayTypeSignature`` is returned. Otherwise a ``ArrayTypeSignature`` is created. +- Array types (If an array contains only one dimension, a ``SzArrayTypeSignature`` is returned. Otherwise a ``ArrayTypeSignature`` is created). - Generic parameters. - Generic type instantiations. -Instantiations of generic methods are supported. +Instantiations of generic methods are also supported. + + +Creating new references using Fluent Syntax +------------------------------------------- + +Member references can also be created and imported without having direct access to its member definition or ``System.Reflection`` instance. It is possible to create new instances of ``TypeReference`` and ``MemberReference`` using the constructors, but the preferred way is to use the factory methods that allow for a more fluent syntax. Below an example on how to create a fully imported reference to ``void System.Console.WriteLine(string)``: + +.. code-block:: csharp + + var factory = module.CorLibTypeFactory; + var importedMethod = factory.CorLibScope + .CreateTypeReference("System", "Console") + .CreateMemberReference("WriteLine", + MethodSignature.CreateStatic(factory.Void, factory.String)) + .ImportWith(importer); + + // importedMethod now references "void System.Console.WriteLine(string)" + +Generic type instantiations can also be created using ``MakeGenericInstanceType``: + +.. code-block:: csharp + + ModuleDefinition module = ... + + var factory = module.CorLibTypeFactory; + var importedMethod = factory.CorLibScope + .CreateTypeReference("System.Collections.Generic", "List`1") + .MakeGenericInstanceType(factory.Int32) + .ToTypeDefOrRef() + .CreateMemberReference("Add", + MethodSignature.CreateInstance( + factory.Void, + new GenericParameterSignature(GenericParameterType.Type, 0))) + .ImportWith(importer); + + // importedMethod now references "System.Collections.Generic.List`1.Add(!0)" + + +Similarly, generic method instantiations can be constructed using ``MakeGenericInstanceMethod``: + +.. code-block:: csharp + + ModuleDefinition module = ... + + var arrayType = module.CorLibTypeFactory.CorLibScope + .CreateTypeReference("System", "Array") + .ImportWith(importer); + + var emptyMethod = arrayType.Resolve().Methods.Single(m => m.Name == "Empty" && m.Parameters.Count == 0); + + var importedMethod = emptyMethod + .MakeGenericInstanceMethod(moduleDefinition.CorLibTypeFactory.Int32) + .ImportWith(importer); + + // importedMethod now references "!0[] System.Array.Empty()" .. _dotnet-importer-common-caveats: -Common Caveats using the Importer +Common Caveats using the Importer --------------------------------- Caching and reuse of instances @@ -116,9 +179,9 @@ The default implementation of ``ReferenceImporter`` does not maintain a cache. E Importing cross-framework versions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The ``ReferenceImporter`` does not support importing across different versions of the target framework. Members are being imported as-is, and are not automatically adjusted to conform with other versions of a library. +The ``ReferenceImporter`` does not support importing across different versions of the target framework. Members are being imported as-is, and are not automatically adjusted to conform with other versions of a library. -As a result, trying to import from for example a library part of the .NET Framework into a module targeting .NET Core or vice versa has a high chance of producing an invalid .NET binary that cannot be executed by the runtime. For example, attempting to import a reference to ``[System.Runtime] System.DateTime`` into a module targeting .NET Framework will result in a new reference targeting a .NET Core library (``System.Runtime``) as opposed to the appropriate .NET Framework library (``mscorlib``). +As a result, trying to import from for example a library part of the .NET Framework into a module targeting .NET Core or vice versa has a high chance of producing an invalid .NET binary that cannot be executed by the runtime. For example, attempting to import a reference to ``[System.Runtime] System.DateTime`` into a module targeting .NET Framework will result in a new reference targeting a .NET Core library (``System.Runtime``) as opposed to the appropriate .NET Framework library (``mscorlib``). This is a common mistake when trying to import using metadata provided by ``System.Reflection``. For example, if the host application that uses AsmResolver targets .NET Core but the input file is targeting .NET Framework, then you will run in the exact issue described in the above. @@ -132,4 +195,4 @@ This is a common mistake when trying to import using metadata provided by ``Syst // `reference` will target `[mscorlib] System.DateTime` when running on .NET Framework, and `[System.Runtime] System.DateTime` when running on .NET Core. -Therefore, always make sure you are importing from a .NET module that is compatible with the target .NET module. \ No newline at end of file +Therefore, always make sure you are importing from a .NET module that is compatible with the target .NET module. diff --git a/docs/dotnet/methods.rst b/docs/dotnet/methods.rst deleted file mode 100644 index 30f8dec4e..000000000 --- a/docs/dotnet/methods.rst +++ /dev/null @@ -1,34 +0,0 @@ -Methods -=============== - -Non-Generic Methods on Generic Types ------------------------------------------- - -This section covers referencing methods such as ``System.Collections.Generic.List`1.Add``. They can be referenced with the ``MemberReference`` class. - -.. code-block:: csharp - - var corlibScope = moduleDefinition.CorLibTypeFactory.CorLibScope; - - var listTypeReference = new TypeReference(corlibScope, "System.Collections.Generic", "List`1"); - - var listOfInt32 = listTypeReference.MakeGenericInstanceType(moduleDefinition.CorLibTypeFactory.Int32); - - var addMethodDefinition = listTypeReference.Resolve().Methods.Single(m => m.Name == "Add" && m.Parameters.Count == 1); - - var reference = new MemberReference(listOfInt32.ToTypeDefOrRef(), addMethodDefinition.Name, addMethodDefinition.Signature); - -Generic Methods on Non-Generic Types ------------------------------------------- - -This section covers referencing methods such as ``System.Array.Empty``. They can be referenced with the ``MethodSpecification`` class via the ``MakeGenericInstanceMethod`` extension method on ``IMethodDefOrRef``. - -.. code-block:: csharp - - var corlibScope = moduleDefinition.CorLibTypeFactory.CorLibScope; - - var arrayTypeReference = new TypeReference(corlibScope, "System", "Array"); - - var emptyMethodDefinition = arrayTypeReference.Resolve().Methods.Single(m => m.Name == "Empty" && m.Parameters.Count == 0); - - var reference = emptyMethodDefinition.MakeGenericInstanceMethod(moduleDefinition.CorLibTypeFactory.Int32); From b12957d4cdecdb0626c97598cdfcca26c57281a4 Mon Sep 17 00:00:00 2001 From: Washi Date: Tue, 10 May 2022 20:50:06 +0200 Subject: [PATCH 121/184] BUGFIX: MethodSignature.CreateXXX should set IsGeneric flag when providing non-zero generic param count. --- docs/dotnet/importing.rst | 23 ++++----- .../Signatures/MethodSignature.cs | 16 +++++-- .../Signatures/MethodSignatureTest.cs | 47 +++++++++++++++++++ 3 files changed, 69 insertions(+), 17 deletions(-) create mode 100644 test/AsmResolver.DotNet.Tests/Signatures/MethodSignatureTest.cs diff --git a/docs/dotnet/importing.rst b/docs/dotnet/importing.rst index 56d99eb94..f3c9acd2e 100644 --- a/docs/dotnet/importing.rst +++ b/docs/dotnet/importing.rst @@ -121,8 +121,8 @@ Member references can also be created and imported without having direct access var factory = module.CorLibTypeFactory; var importedMethod = factory.CorLibScope .CreateTypeReference("System", "Console") - .CreateMemberReference("WriteLine", - MethodSignature.CreateStatic(factory.Void, factory.String)) + .CreateMemberReference("WriteLine", MethodSignature.CreateStatic( + factory.Void, factory.String)) .ImportWith(importer); // importedMethod now references "void System.Console.WriteLine(string)" @@ -138,10 +138,9 @@ Generic type instantiations can also be created using ``MakeGenericInstanceType` .CreateTypeReference("System.Collections.Generic", "List`1") .MakeGenericInstanceType(factory.Int32) .ToTypeDefOrRef() - .CreateMemberReference("Add", - MethodSignature.CreateInstance( - factory.Void, - new GenericParameterSignature(GenericParameterType.Type, 0))) + .CreateMemberReference("Add", MethodSignature.CreateInstance( + factory.Void, + new GenericParameterSignature(GenericParameterType.Type, 0))) .ImportWith(importer); // importedMethod now references "System.Collections.Generic.List`1.Add(!0)" @@ -153,14 +152,12 @@ Similarly, generic method instantiations can be constructed using ``MakeGenericI ModuleDefinition module = ... - var arrayType = module.CorLibTypeFactory.CorLibScope + var factory = module.CorLibTypeFactory; + var importedMethod = factory.CorLibScope .CreateTypeReference("System", "Array") - .ImportWith(importer); - - var emptyMethod = arrayType.Resolve().Methods.Single(m => m.Name == "Empty" && m.Parameters.Count == 0); - - var importedMethod = emptyMethod - .MakeGenericInstanceMethod(moduleDefinition.CorLibTypeFactory.Int32) + .CreateMemberReference("Empty", MethodSignature.CreateStatic( + new GenericParameterSignature(GenericParameterType.Method, 0).MakeSzArrayType(), 1)) + .MakeGenericInstanceMethod(factory.String) .ImportWith(importer); // importedMethod now references "!0[] System.Array.Empty()" diff --git a/src/AsmResolver.DotNet/Signatures/MethodSignature.cs b/src/AsmResolver.DotNet/Signatures/MethodSignature.cs index cc6962a1e..bc9799de9 100644 --- a/src/AsmResolver.DotNet/Signatures/MethodSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/MethodSignature.cs @@ -65,7 +65,7 @@ public static MethodSignature CreateStatic(TypeSignature returnType, params Type /// The signature. public static MethodSignature CreateStatic(TypeSignature returnType, int genericParameterCount, params TypeSignature[] parameterTypes) { - return new MethodSignature(0, returnType, parameterTypes) + return new MethodSignature(genericParameterCount > 0 ? CallingConventionAttributes.Generic : 0, returnType, parameterTypes) { GenericParameterCount = genericParameterCount }; @@ -89,7 +89,7 @@ public static MethodSignature CreateStatic(TypeSignature returnType, IEnumerable /// The signature. public static MethodSignature CreateStatic(TypeSignature returnType, int genericParameterCount, IEnumerable parameterTypes) { - return new MethodSignature(0, returnType, parameterTypes) + return new MethodSignature(genericParameterCount > 0 ? CallingConventionAttributes.Generic : 0, returnType, parameterTypes) { GenericParameterCount = genericParameterCount }; @@ -121,7 +121,11 @@ public static MethodSignature CreateInstance(TypeSignature returnType, params Ty /// The signature. public static MethodSignature CreateInstance(TypeSignature returnType, int genericParameterCount, params TypeSignature[] parameterTypes) { - return new MethodSignature(CallingConventionAttributes.HasThis, returnType, parameterTypes) + var attributes = genericParameterCount > 0 + ? CallingConventionAttributes.HasThis | CallingConventionAttributes.Generic + : CallingConventionAttributes.HasThis; + + return new MethodSignature(attributes, returnType, parameterTypes) { GenericParameterCount = genericParameterCount }; @@ -145,7 +149,11 @@ public static MethodSignature CreateInstance(TypeSignature returnType, IEnumerab /// The signature. public static MethodSignature CreateInstance(TypeSignature returnType, int genericParameterCount, IEnumerable parameterTypes) { - return new MethodSignature(CallingConventionAttributes.HasThis, returnType, parameterTypes) + var attributes = genericParameterCount > 0 + ? CallingConventionAttributes.HasThis | CallingConventionAttributes.Generic + : CallingConventionAttributes.HasThis; + + return new MethodSignature(attributes, returnType, parameterTypes) { GenericParameterCount = genericParameterCount }; diff --git a/test/AsmResolver.DotNet.Tests/Signatures/MethodSignatureTest.cs b/test/AsmResolver.DotNet.Tests/Signatures/MethodSignatureTest.cs new file mode 100644 index 000000000..0b271d859 --- /dev/null +++ b/test/AsmResolver.DotNet.Tests/Signatures/MethodSignatureTest.cs @@ -0,0 +1,47 @@ +using AsmResolver.DotNet.Signatures; +using Xunit; + +namespace AsmResolver.DotNet.Tests.Signatures +{ + public class MethodSignatureTest + { + private readonly ModuleDefinition _module; + + public MethodSignatureTest() + { + _module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + } + + [Fact] + public void MakeInstanceShouldHaveHasThisFlagSet() + { + var signature = MethodSignature.CreateInstance(_module.CorLibTypeFactory.Void); + Assert.True(signature.HasThis); + Assert.False(signature.IsGeneric); + } + + [Fact] + public void MakeStaticShouldNotHaveHasThisFlagSet() + { + var signature = MethodSignature.CreateStatic(_module.CorLibTypeFactory.Void); + Assert.False(signature.HasThis); + Assert.False(signature.IsGeneric); + } + + [Fact] + public void MakeGenericInstanceShouldHaveHasThisAndGenericFlagSet() + { + var signature = MethodSignature.CreateInstance(_module.CorLibTypeFactory.Void, 1); + Assert.True(signature.HasThis); + Assert.True(signature.IsGeneric); + } + + [Fact] + public void MakeGenericStaticShouldNotHaveHasThisAndGenericFlagSet() + { + var signature = MethodSignature.CreateStatic(_module.CorLibTypeFactory.Void, 1); + Assert.False(signature.HasThis); + Assert.True(signature.IsGeneric); + } + } +} From bffed664253f3eb1a1247aa913f4b6d2bf5e17df Mon Sep 17 00:00:00 2001 From: Washi Date: Tue, 10 May 2022 20:59:01 +0200 Subject: [PATCH 122/184] BUGFIX: Method.FullName should include generic parameters. --- src/AsmResolver.DotNet/FullNameGenerator.cs | 9 ++++++++- src/AsmResolver.DotNet/MethodDefinition.cs | 9 ++++++++- src/AsmResolver.DotNet/MethodSpecification.cs | 2 +- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/AsmResolver.DotNet/FullNameGenerator.cs b/src/AsmResolver.DotNet/FullNameGenerator.cs index e04c42aa2..906bae332 100644 --- a/src/AsmResolver.DotNet/FullNameGenerator.cs +++ b/src/AsmResolver.DotNet/FullNameGenerator.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.Signatures.Types; @@ -36,6 +37,12 @@ public static string GetFieldFullName(string? name, ITypeDescriptor? declaringTy /// The full name public static string GetMethodFullName(string? name, ITypeDescriptor? declaringType, MethodSignature? signature) { + if (signature?.GenericParameterCount > 0) + { + return GetMethodFullName(name, declaringType, signature, + Enumerable.Repeat("?", signature.GenericParameterCount)); + } + string returnTypeString = signature?.ReturnType.FullName ?? TypeSignature.NullTypeToString; string parameterTypesString = GetParameterTypesString(signature); @@ -54,7 +61,7 @@ public static string GetMethodFullName(string? name, ITypeDescriptor? declaringT /// The type arguments. /// The full name public static string GetMethodFullName(string? name, ITypeDescriptor? declaringType, MethodSignature? signature, - IEnumerable typeArguments) + IEnumerable typeArguments) { string returnTypeString = signature?.ReturnType.FullName ?? TypeSignature.NullTypeToString; string parameterTypesString = GetParameterTypesString(signature); diff --git a/src/AsmResolver.DotNet/MethodDefinition.cs b/src/AsmResolver.DotNet/MethodDefinition.cs index 819732b50..4a32048be 100644 --- a/src/AsmResolver.DotNet/MethodDefinition.cs +++ b/src/AsmResolver.DotNet/MethodDefinition.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Threading; using AsmResolver.Collections; using AsmResolver.DotNet.Code; @@ -100,7 +101,13 @@ public MethodDefinition(string? name, MethodAttributes attributes, MethodSignatu } /// - public string FullName => FullNameGenerator.GetMethodFullName(Name, DeclaringType, Signature); + public string FullName => FullNameGenerator.GetMethodFullName( + Name, + DeclaringType, + Signature, + GenericParameters.Count > 0 + ? GenericParameters.Select(x => x.Name?.Value ?? NullName) + : Enumerable.Empty()); /// /// Gets or sets the attributes associated to the method. diff --git a/src/AsmResolver.DotNet/MethodSpecification.cs b/src/AsmResolver.DotNet/MethodSpecification.cs index 1c85c399c..31ca1226e 100644 --- a/src/AsmResolver.DotNet/MethodSpecification.cs +++ b/src/AsmResolver.DotNet/MethodSpecification.cs @@ -73,7 +73,7 @@ public MethodSpecification(IMethodDefOrRef? method, GenericInstanceMethodSignatu Name, DeclaringType, Method?.Signature, - Signature?.TypeArguments ?? Enumerable.Empty()); + Signature?.TypeArguments.Select(x => x.FullName) ?? Enumerable.Empty()); /// public ModuleDefinition? Module => Method?.Module; From c352a55c025a3088f0b951ebbc6e945ebf7b4cec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 May 2022 14:39:33 +0000 Subject: [PATCH 123/184] Bump System.Text.Json from 6.0.3 to 6.0.4 Bumps [System.Text.Json](https://github.com/dotnet/runtime) from 6.0.3 to 6.0.4. - [Release notes](https://github.com/dotnet/runtime/releases) - [Commits](https://github.com/dotnet/runtime/compare/v6.0.3...v6.0.4) --- updated-dependencies: - dependency-name: System.Text.Json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- src/AsmResolver.DotNet/AsmResolver.DotNet.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj index c041214b1..456ce3119 100644 --- a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj +++ b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj @@ -27,7 +27,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + From 97e08cc4917f23253acf23e58eabbe1662699d96 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 May 2022 14:39:39 +0000 Subject: [PATCH 124/184] Bump Microsoft.NET.Test.Sdk from 17.1.0 to 17.2.0 Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.1.0 to 17.2.0. - [Release notes](https://github.com/microsoft/vstest/releases) - [Commits](https://github.com/microsoft/vstest/compare/v17.1.0...v17.2.0) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj | 2 +- test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj | 2 +- test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj | 2 +- .../AsmResolver.PE.Win32Resources.Tests.csproj | 2 +- test/AsmResolver.Tests/AsmResolver.Tests.csproj | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj b/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj index 0532fbaf9..5a428afc5 100644 --- a/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj +++ b/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj @@ -9,7 +9,7 @@ - + all 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 2fc3c08cf..ba56b3821 100644 --- a/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj +++ b/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj @@ -9,7 +9,7 @@ - + all diff --git a/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj b/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj index 944b5a804..2e5602e90 100644 --- a/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj +++ b/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj @@ -16,7 +16,7 @@ - + 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 429cbde0a..c6ae10b54 100644 --- a/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj +++ b/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj @@ -8,7 +8,7 @@ - + all diff --git a/test/AsmResolver.Tests/AsmResolver.Tests.csproj b/test/AsmResolver.Tests/AsmResolver.Tests.csproj index 07b8b090d..82f9cb9c6 100644 --- a/test/AsmResolver.Tests/AsmResolver.Tests.csproj +++ b/test/AsmResolver.Tests/AsmResolver.Tests.csproj @@ -8,7 +8,7 @@ - + all From e8586370a8ec7aa374299054bc2f983950052e24 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 11 May 2022 17:14:02 +0200 Subject: [PATCH 125/184] Fix typo in example code. --- docs/dotnet/importing.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/dotnet/importing.rst b/docs/dotnet/importing.rst index ff108d980..7b1bb8dd2 100644 --- a/docs/dotnet/importing.rst +++ b/docs/dotnet/importing.rst @@ -17,7 +17,7 @@ Either instantiate one yourself: Or obtain the default instance that comes with every ``ModuleDefinition`` object. This avoids allocating new reference importers every time. .. code-block:: csharp - + ModuleDefinition module = ... var importer = module.DefaultImporter; @@ -25,8 +25,8 @@ Or obtain the default instance that comes with every ``ModuleDefinition`` object The example snippets that will follow in this articule assume that there is such a ``ReferenceImporter`` object instantiated using either of these two methods, and is stored in an ``importer`` variable. -Importing existing metadata members ------------------------------------ +Importing existing members +-------------------------- Metadata members from external modules can be imported using the ``ReferenceImporter`` class using one of the following members: @@ -122,8 +122,8 @@ There is limited support for importing complex types. Types that can be imported Instantiations of generic methods are also supported. -Creating new references using Fluent Syntax -------------------------------------------- +Creating new references +----------------------- Member references can also be created and imported without having direct access to its member definition or ``System.Reflection`` instance. It is possible to create new instances of ``TypeReference`` and ``MemberReference`` using the constructors, but the preferred way is to use the factory methods that allow for a more fluent syntax. Below an example on how to create a fully imported reference to ``void System.Console.WriteLine(string)``: @@ -171,7 +171,7 @@ Similarly, generic method instantiations can be constructed using ``MakeGenericI .MakeGenericInstanceMethod(factory.String) .ImportWith(importer); - // importedMethod now references "!0[] System.Array.Empty()" + // importedMethod now references "!0[] System.Array.Empty()" .. _dotnet-importer-common-caveats: From 292e86d8219195c1b2621137f7fec4469702b4f5 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 11 May 2022 17:33:18 +0200 Subject: [PATCH 126/184] Add nullable operators in importer tests. --- .../ReferenceImporterTest.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs index fd2880cde..d7c2cdc0d 100644 --- a/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs +++ b/test/AsmResolver.DotNet.Tests/ReferenceImporterTest.cs @@ -74,7 +74,7 @@ public void ImportTypeDefFromDifferentModuleShouldReturnTypeRef() var assembly = new AssemblyDefinition("ExternalAssembly", new Version(1, 2, 3, 4)); assembly.Modules.Add(new ModuleDefinition("ExternalAssembly.dll")); var definition = new TypeDefinition("SomeNamespace", "SomeName", TypeAttributes.Public); - assembly.ManifestModule.TopLevelTypes.Add(definition); + assembly.ManifestModule!.TopLevelTypes.Add(definition); var result = _importer.ImportType(definition); @@ -103,7 +103,7 @@ public void ImportNestedTypeShouldImportParentType() Assert.Equal(nested, result, Comparer); Assert.Equal(_module, result.Module); - Assert.Equal(_module, result.DeclaringType.Module); + Assert.Equal(_module, result.DeclaringType?.Module); } [Fact] @@ -146,7 +146,7 @@ public void ImportSimpleTypeFromReflectionShouldResultInTypeRef() Assert.IsAssignableFrom(result); Assert.Equal(type.FullName, result.FullName); - Assert.Equal(type.Assembly.GetName().Name, result.Scope.Name); + Assert.Equal(type.Assembly.GetName().Name, result.Scope?.Name); } [Fact] @@ -224,11 +224,11 @@ public void ImportMethodFromSameModuleShouldResultInSameInstance() [Fact] public void ImportMethodFromGenericTypeThroughReflectionShouldIncludeGenericParamSig() { - var method = typeof(List).GetMethod("Add"); + var method = typeof(List).GetMethod("Add")!; var result = _importer.ImportMethod(method); - Assert.IsAssignableFrom(result.Signature.ParameterTypes[0]); + Assert.IsAssignableFrom(result.Signature?.ParameterTypes[0]); var genericParameter = (GenericParameterSignature) result.Signature.ParameterTypes[0]; Assert.Equal(0, genericParameter.Index); Assert.Equal(GenericParameterType.Type, genericParameter.ParameterType); @@ -238,7 +238,7 @@ public void ImportMethodFromGenericTypeThroughReflectionShouldIncludeGenericPara public void ImportGenericMethodFromReflectionShouldResultInMethodSpec() { var method = typeof(Enumerable) - .GetMethod("Empty") + .GetMethod("Empty")! .MakeGenericMethod(typeof(string)); var result = _importer.ImportMethod(method); @@ -249,7 +249,7 @@ public void ImportGenericMethodFromReflectionShouldResultInMethodSpec() Assert.Equal(new TypeSignature[] { _module.CorLibTypeFactory.String - }, specification.Signature.TypeArguments, Comparer); + }, specification.Signature?.TypeArguments, Comparer); } [Fact] @@ -288,13 +288,13 @@ public void ImportFieldFromSameModuleShouldResultInSameInstance() [Fact] public void ImportFieldFromReflectionShouldResultInMemberRef() { - var field = typeof(string).GetField("Empty"); + var field = typeof(string).GetField("Empty")!; var result = _importer.ImportField(field); Assert.Equal(field.Name, result.Name); - Assert.Equal(field.DeclaringType.FullName, result.DeclaringType.FullName); - Assert.Equal(field.FieldType.FullName, ((FieldSignature) result.Signature).FieldType.FullName); + Assert.Equal(field.DeclaringType!.FullName, result.DeclaringType?.FullName); + Assert.Equal(field.FieldType.FullName, ((FieldSignature) result.Signature)?.FieldType.FullName); } [Fact] From 84a79bf8b0ec0eb0cb47249e1a050fc7546df9ac Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 18 May 2022 20:01:53 +0200 Subject: [PATCH 127/184] Add capacity constructor parameters and properties to LazyList and relation classes. --- src/AsmResolver/Collections/LazyList.cs | 28 ++++++++++++++++++- .../Collections/OneToManyRelation.cs | 23 +++++++++++++-- .../Collections/OneToOneRelation.cs | 23 +++++++++++++-- .../Collections/OwnedCollection.cs | 11 ++++++++ 4 files changed, 80 insertions(+), 5 deletions(-) diff --git a/src/AsmResolver/Collections/LazyList.cs b/src/AsmResolver/Collections/LazyList.cs index 90b584a75..0dd0cc0ce 100644 --- a/src/AsmResolver/Collections/LazyList.cs +++ b/src/AsmResolver/Collections/LazyList.cs @@ -12,7 +12,24 @@ namespace AsmResolver.Collections [DebuggerDisplay("Count = {" + nameof(Count) + "}")] public abstract class LazyList : IList { - private readonly List _items = new(); + 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] @@ -39,6 +56,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; diff --git a/src/AsmResolver/Collections/OneToManyRelation.cs b/src/AsmResolver/Collections/OneToManyRelation.cs index 570768c7e..30df87146 100644 --- a/src/AsmResolver/Collections/OneToManyRelation.cs +++ b/src/AsmResolver/Collections/OneToManyRelation.cs @@ -11,8 +11,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. 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. /// From 808ccbc1245ec7ce739c03f27af51ef72e4da4b5 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 18 May 2022 20:03:58 +0200 Subject: [PATCH 128/184] Use initial number of elements as capacity in serialized metadata member collections. --- .../Collections/MethodSemanticsCollection.cs | 10 +++++ .../Serialized/SerializedGenericParameter.cs | 7 ++-- .../Serialized/SerializedMethodDefinition.cs | 10 +++-- .../SerializedModuleDefinition.Metadata.cs | 42 ++++++++++--------- .../Serialized/SerializedModuleDefinition.cs | 33 ++++++++------- .../SerializedPropertyDefinition.cs | 9 ++-- .../Serialized/SerializedTypeDefinition.cs | 20 +++++---- 7 files changed, 78 insertions(+), 53 deletions(-) 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/Serialized/SerializedGenericParameter.cs b/src/AsmResolver.DotNet/Serialized/SerializedGenericParameter.cs index 841c47adc..3d876c1d5 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedGenericParameter.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedGenericParameter.cs @@ -58,10 +58,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/SerializedMethodDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedMethodDefinition.cs index ed4ed367c..c35f28b9e 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedMethodDefinition.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedMethodDefinition.cs @@ -81,9 +81,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 +109,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/SerializedModuleDefinition.Metadata.cs b/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.Metadata.cs index f7e13d09f..e81e548aa 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.Metadata.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.Metadata.cs @@ -59,7 +59,7 @@ private void EnsureTypeDefinitionTreeInitialized() return typeDefTree; } - internal IEnumerable GetNestedTypeRids(uint enclosingTypeRid) + internal ICollection GetNestedTypeRids(uint enclosingTypeRid) { EnsureTypeDefinitionTreeInitialized(); return _typeDefTree.GetValues(enclosingTypeRid); @@ -107,8 +107,8 @@ private void InitializeMethodSemantics() 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 ICollection GetMethodSemantics(MetadataToken owner) { EnsureMethodSemanticsInitialized(); return _semantics.GetValues(owner); @@ -155,7 +155,7 @@ private void EnsureConstantsInitialized() 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); @@ -199,7 +199,7 @@ private void EnsureCustomAttributesInitialized() 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); @@ -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); @@ -252,7 +253,7 @@ private void EnsureSecurityDeclarationsInitialized() 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); @@ -296,7 +298,7 @@ private void EnsureGenericParametersInitialized() 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); @@ -331,7 +333,7 @@ private void EnsureGenericParameterConstrainsInitialized() var tablesStream = DotNetDirectory.Metadata!.GetStream(); 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); @@ -366,7 +368,7 @@ private void EnsureInterfacesInitialized() var tablesStream = DotNetDirectory.Metadata!.GetStream(); 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); @@ -401,7 +403,7 @@ private void EnsureMethodImplementationsInitialized() var tablesStream = DotNetDirectory.Metadata!.GetStream(); 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); @@ -430,7 +432,7 @@ private void EnsureClassLayoutsInitialized() var tablesStream = DotNetDirectory.Metadata!.GetStream(); 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); @@ -460,7 +462,7 @@ private void EnsureImplementationMapsInitialized() 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); @@ -495,7 +497,7 @@ private void EnsureFieldRvasInitialized() var tablesStream = DotNetDirectory.Metadata!.GetStream(); 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); @@ -525,7 +527,7 @@ private void EnsureFieldMarshalsInitialized() 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); @@ -571,7 +573,7 @@ private void EnsureFieldLayoutsInitialized() var tablesStream = DotNetDirectory.Metadata!.GetStream(); 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..5ed4f0d4a 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs @@ -204,12 +204,18 @@ protected override IList GetTopLevelTypes() { EnsureTypeDefinitionTreeInitialized(); - var types = new OwnedCollection(this); - var typeDefTable = ReaderContext.Metadata .GetStream() .GetTable(TableIndex.TypeDef); + int nestedTypeCount = ReaderContext.Metadata + .GetStream() + .GetTable(TableIndex.NestedClass) + .Count; + + var types = new OwnedCollection(this, + typeDefTable.Count - nestedTypeCount); + for (int i = 0; i < typeDefTable.Count; i++) { uint rid = (uint) i + 1; @@ -226,12 +232,12 @@ protected override IList GetTopLevelTypes() /// protected override IList GetAssemblyReferences() { - var result = new OwnedCollection(this); - 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,12 +251,12 @@ protected override IList GetAssemblyReferences() /// protected override IList GetModuleReferences() { - var result = new OwnedCollection(this); - var table = ReaderContext.Metadata .GetStream() .GetTable(TableIndex.ModuleRef); + var result = new OwnedCollection(this, table.Count); + for (int i = 0; i < table.Count; i++) { var token = new MetadataToken(TableIndex.ModuleRef, (uint) i + 1); @@ -264,12 +270,12 @@ protected override IList GetModuleReferences() /// protected override IList GetFileReferences() { - var result = new OwnedCollection(this); - var table = ReaderContext.Metadata .GetStream() .GetTable(TableIndex.File); + var result = new OwnedCollection(this, table.Count); + for (int i = 0; i < table.Count; i++) { var token = new MetadataToken(TableIndex.File, (uint) i + 1); @@ -283,12 +289,12 @@ protected override IList GetFileReferences() /// protected override IList GetResources() { - var result = new OwnedCollection(this); - var table = ReaderContext.Metadata .GetStream() .GetTable(TableIndex.ManifestResource); + var result = new OwnedCollection(this, table.Count); + for (int i = 0; i < table.Count; i++) { var token = new MetadataToken(TableIndex.ManifestResource, (uint) i + 1); @@ -302,12 +308,12 @@ protected override IList GetResources() /// protected override IList GetExportedTypes() { - var result = new OwnedCollection(this); - var table = ReaderContext.Metadata .GetStream() .GetTable(TableIndex.ExportedType); + var result = new OwnedCollection(this, table.Count); + for (int i = 0; i < table.Count; i++) { var token = new MetadataToken(TableIndex.ExportedType, (uint) i + 1); @@ -350,12 +356,11 @@ protected override IList GetExportedTypes() 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/SerializedPropertyDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedPropertyDefinition.cs index 9739939fd..b1f1fa227 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; @@ -70,11 +71,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/SerializedTypeDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedTypeDefinition.cs index 9b87e76e3..2ee5afe6b 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; @@ -67,9 +68,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 +108,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 +127,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 +145,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 +163,13 @@ protected override IList GetInterfaces() /// protected override IList GetMethodImplementations() { - var result = new List(); - var tablesStream = _context.Metadata.GetStream(); 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); From 46f4326c6da12891a524694f6f60a87e6bf4acc8 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 18 May 2022 20:40:14 +0200 Subject: [PATCH 129/184] Set capacities for several type and member signature collection properties before populating them. --- .../Signatures/CustomAttributeSignature.cs | 22 +++++++++---------- .../GenericInstanceMethodSignature.cs | 15 +++++++++++-- .../Signatures/MethodSignature.cs | 6 +++-- .../Signatures/MethodSignatureBase.cs | 16 +++++++------- .../Signatures/Types/ArrayTypeSignature.cs | 4 ++-- .../Types/FunctionPointerTypeSignature.cs | 2 +- .../Types/GenericInstanceTypeSignature.cs | 12 +++++----- 7 files changed, 44 insertions(+), 33 deletions(-) diff --git a/src/AsmResolver.DotNet/Signatures/CustomAttributeSignature.cs b/src/AsmResolver.DotNet/Signatures/CustomAttributeSignature.cs index 1676016d9..15d4834fb 100644 --- a/src/AsmResolver.DotNet/Signatures/CustomAttributeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/CustomAttributeSignature.cs @@ -12,6 +12,8 @@ namespace AsmResolver.DotNet.Signatures /// public class CustomAttributeSignature : ExtendableBlobSignature { + private readonly List _fixedArguments; + private readonly List _namedArguments; private const ushort CustomAttributeSignaturePrologue = 0x0001; /// @@ -35,18 +37,20 @@ public class CustomAttributeSignature : ExtendableBlobSignature // Read fixed arguments. var parameterTypes = ctor.Signature?.ParameterTypes ?? Array.Empty(); + result._fixedArguments.Capacity = parameterTypes.Count; for (int i = 0; i < parameterTypes.Count; i++) { var argument = CustomAttributeArgument.FromReader(context, parameterTypes[i], ref reader); - result.FixedArguments.Add(argument); + result._fixedArguments.Add(argument); } // Read named arguments. ushort namedArgumentCount = reader.ReadUInt16(); + result._namedArguments.Capacity = namedArgumentCount; for (int i = 0; i < namedArgumentCount; i++) { var argument = CustomAttributeNamedArgument.FromReader(context, ref reader); - result.NamedArguments.Add(argument); + result._namedArguments.Add(argument); } return result; @@ -73,25 +77,19 @@ 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); } /// /// Gets a collection of fixed arguments that are passed onto the constructor of the attribute. /// - public IList FixedArguments - { - get; - } + public IList FixedArguments => _fixedArguments; /// /// Gets a collection of values that are assigned to fields and/or members of the attribute class. /// - public IList NamedArguments - { - get; - } + public IList NamedArguments => _namedArguments; /// public override string ToString() diff --git a/src/AsmResolver.DotNet/Signatures/GenericInstanceMethodSignature.cs b/src/AsmResolver.DotNet/Signatures/GenericInstanceMethodSignature.cs index 2e6286418..291a07c25 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. /// diff --git a/src/AsmResolver.DotNet/Signatures/MethodSignature.cs b/src/AsmResolver.DotNet/Signatures/MethodSignature.cs index 67d530c14..818452aa6 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) 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/Types/ArrayTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/ArrayTypeSignature.cs index 193142038..e81140d88 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)) diff --git a/src/AsmResolver.DotNet/Signatures/Types/FunctionPointerTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/FunctionPointerTypeSignature.cs index ff2acc2c8..1fe273fa5 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/FunctionPointerTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/FunctionPointerTypeSignature.cs @@ -47,7 +47,7 @@ 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); diff --git a/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs b/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs index 7ed057d8b..e05310b12 100644 --- a/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs +++ b/src/AsmResolver.DotNet/Signatures/Types/GenericInstanceTypeSignature.cs @@ -10,6 +10,8 @@ namespace AsmResolver.DotNet.Signatures.Types /// public class GenericInstanceTypeSignature : TypeSignature, IGenericArgumentsProvider { + private readonly List _typeArguments; + internal new static GenericInstanceTypeSignature FromReader(in BlobReadContext context, ref BinaryStreamReader reader) { var genericType = TypeSignature.FromReader(context, ref reader); @@ -21,8 +23,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; } @@ -53,7 +56,7 @@ public GenericInstanceTypeSignature(ITypeDefOrRef genericType, bool isValueType) IEnumerable typeArguments) { GenericType = genericType; - TypeArguments = new List(typeArguments); + _typeArguments = new List(typeArguments); IsValueType = isValueType; } @@ -72,10 +75,7 @@ public ITypeDefOrRef GenericType /// /// Gets a collection of type arguments used to instantiate the generic type. /// - public IList TypeArguments - { - get; - } + public IList TypeArguments => _typeArguments; /// public override string? Name From 78cd5479de1a663bfa67270c1cd9fc526adfc1c8 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 18 May 2022 21:08:14 +0200 Subject: [PATCH 130/184] Add public setter to RefList.Capacity. --- src/AsmResolver/Collections/RefList.cs | 20 +++++++-- .../Collections/RefListTest.cs | 42 +++++++++++++++++++ 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/src/AsmResolver/Collections/RefList.cs b/src/AsmResolver/Collections/RefList.cs index f454fe6ff..b37971f8f 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."); + + EnsureEnoughCapacity(value); + IncrementVersion(); + } + } /// /// Gets a number indicating the current version of the list. @@ -248,7 +262,7 @@ private void AssertIsValidIndex(int index) private void EnsureEnoughCapacity(int requiredCount) { - if (_items.Length >= requiredCount) + if (_items.Length >= requiredCount) return; int newCapacity = _items.Length == 0 ? 1 : _items.Length * 2; 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() { From cc0fe349a9a44fc80eb5ae14cb5133aa24cf4bb1 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 18 May 2022 21:08:55 +0200 Subject: [PATCH 131/184] Add IMetadataTableBuffer.EnsureCapacity and use it in the .net dir buffer. --- .../DotNetDirectoryBuffer.MemberTree.cs | 24 +++++++++++++++++++ .../Tables/DistinctMetadataTableBuffer.cs | 10 ++++++++ .../Metadata/Tables/IMetadataTableBuffer.cs | 6 +++++ .../Tables/UnsortedMetadataTableBuffer.cs | 10 ++++++++ .../DotNet/Metadata/Tables/MetadataTable.cs | 9 +++++++ 5 files changed, 59 insertions(+) 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/Metadata/Tables/DistinctMetadataTableBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Tables/DistinctMetadataTableBuffer.cs index 2fba58233..2a7ecdef5 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 bcf1b8bfd..66a6c1836 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/UnsortedMetadataTableBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Tables/UnsortedMetadataTableBuffer.cs index e94a81d44..8e9d4395f 100644 --- a/src/AsmResolver.DotNet/Builder/Metadata/Tables/UnsortedMetadataTableBuffer.cs +++ b/src/AsmResolver.DotNet/Builder/Metadata/Tables/UnsortedMetadataTableBuffer.cs @@ -36,6 +36,13 @@ public UnsortedMetadataTableBuffer(MetadataTable table) set => _entries[(int) (rid - 1)] = value; } + /// + public void EnsureCapacity(int capacity) + { + if (_entries.Capacity < capacity) + _entries.Capacity = capacity; + } + /// public ref TRow GetRowRef(uint rid) => ref _entries.GetElementRef((int)(rid - 1)); @@ -49,6 +56,9 @@ public virtual MetadataToken Add(in TRow row) /// 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.PE/DotNet/Metadata/Tables/MetadataTable.cs b/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataTable.cs index 2b8f01739..94983d7d9 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataTable.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Tables/MetadataTable.cs @@ -88,6 +88,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. From 80f5b35ed258ec7ff25c59e6dcf30e8eb31d3de8 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 18 May 2022 21:13:19 +0200 Subject: [PATCH 132/184] Rename RefList.EnsureEnoughCapacity to EnsureCapacity. --- src/AsmResolver/Collections/RefList.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/AsmResolver/Collections/RefList.cs b/src/AsmResolver/Collections/RefList.cs index b37971f8f..4fa091140 100644 --- a/src/AsmResolver/Collections/RefList.cs +++ b/src/AsmResolver/Collections/RefList.cs @@ -59,7 +59,7 @@ public int Capacity if (value < _count) throw new ArgumentException("Capacity must be equal or larger than the current number of elements in the list."); - EnsureEnoughCapacity(value); + EnsureCapacity(value); IncrementVersion(); } } @@ -141,7 +141,7 @@ public int Capacity /// The element. public void Add(in T item) { - EnsureEnoughCapacity(_count + 1); + EnsureCapacity(_count + 1); _items[_count] = item; _count++; IncrementVersion(); @@ -208,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); @@ -260,9 +260,9 @@ private void AssertIsValidIndex(int index) throw new IndexOutOfRangeException(); } - private void EnsureEnoughCapacity(int requiredCount) + private void EnsureCapacity(int requiredCount) { - if (_items.Length >= requiredCount) + if (_items.Length >= requiredCount) return; int newCapacity = _items.Length == 0 ? 1 : _items.Length * 2; From 213a4d4a1b718ec7bb55fc9ca9c2d1597da31095 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 18 May 2022 22:04:18 +0200 Subject: [PATCH 133/184] Add MemoryStreamWriterPool. --- src/AsmResolver/IO/BinaryStreamWriter.cs | 82 +++++++++++--------- src/AsmResolver/IO/MemoryStreamWriterPool.cs | 67 ++++++++++++++++ 2 files changed, 111 insertions(+), 38 deletions(-) create mode 100644 src/AsmResolver/IO/MemoryStreamWriterPool.cs 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/MemoryStreamWriterPool.cs b/src/AsmResolver/IO/MemoryStreamWriterPool.cs new file mode 100644 index 000000000..bc3e6fb03 --- /dev/null +++ b/src/AsmResolver/IO/MemoryStreamWriterPool.cs @@ -0,0 +1,67 @@ +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. + /// + 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 readonly struct RentedWriter : IDisposable + { + 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; + } + + /// + /// Gets the data that was written to the temporary stream. + /// + /// + public byte[] GetData() => ((MemoryStream) Writer.BaseStream).ToArray(); + + /// + public void Dispose() => Pool.Return(Writer); + } + } +} From fab723d07e8220395d5570f3eb81557f243243cb Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 18 May 2022 22:04:37 +0200 Subject: [PATCH 134/184] Let blob stream buffer use a writer pool for serializing blob signatures. --- .../Builder/Metadata/Blob/BlobStreamBuffer.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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()); } /// From abaeb6bab70f9820a31d3b038337e506065202e3 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 18 May 2022 22:26:11 +0200 Subject: [PATCH 135/184] Let CilmethodBodySerializer use rented binary stream writers. --- .../Code/Cil/CilMethodBodySerializer.cs | 23 +++++++++---------- src/AsmResolver/IO/MemoryStreamWriterPool.cs | 3 +++ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/AsmResolver.DotNet/Code/Cil/CilMethodBodySerializer.cs b/src/AsmResolver.DotNet/Code/Cil/CilMethodBodySerializer.cs index 023e04f9e..48026a8a6 100644 --- a/src/AsmResolver.DotNet/Code/Cil/CilMethodBodySerializer.cs +++ b/src/AsmResolver.DotNet/Code/Cil/CilMethodBodySerializer.cs @@ -15,6 +15,8 @@ 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. @@ -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) { @@ -137,35 +138,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/IO/MemoryStreamWriterPool.cs b/src/AsmResolver/IO/MemoryStreamWriterPool.cs index bc3e6fb03..b784ffb62 100644 --- a/src/AsmResolver/IO/MemoryStreamWriterPool.cs +++ b/src/AsmResolver/IO/MemoryStreamWriterPool.cs @@ -8,6 +8,9 @@ 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(); From 77bffdc694071f446cdd05144341b8eaddf8dc08 Mon Sep 17 00:00:00 2001 From: xxx Date: Fri, 20 May 2022 14:51:42 +0300 Subject: [PATCH 136/184] Update SignatureComparer.TypeDefOrRef.cs --- .../Signatures/SignatureComparer.TypeDefOrRef.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeDefOrRef.cs b/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeDefOrRef.cs index cf0f19cc9..5e4e343d2 100644 --- a/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeDefOrRef.cs +++ b/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeDefOrRef.cs @@ -43,8 +43,11 @@ public int GetHashCode(ITypeDescriptor obj) private bool SimpleTypeEquals(ITypeDescriptor x, ITypeDescriptor y) { + // Namespace can be null if It is a nested type so we need to check declaring type too + if (x.DeclaringType?.Name != y.DeclaringType?.Name) + return false; // Check the basic properties first. - if (!x.IsTypeOf(y.Namespace, y.Name)) + else if (!x.IsTypeOf(y.Namespace, y.Name)) return false; // If scope matches, it is a perfect match. From 9d3e0316ca984a513f3e6063989e86d660927585 Mon Sep 17 00:00:00 2001 From: xxx Date: Sat, 21 May 2022 18:15:03 +0300 Subject: [PATCH 137/184] Update --- .../SignatureComparer.ResolutionScope.cs | 2 +- .../Signatures/SignatureComparer.TypeDefOrRef.cs | 14 ++++---------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/AsmResolver.DotNet/Signatures/SignatureComparer.ResolutionScope.cs b/src/AsmResolver.DotNet/Signatures/SignatureComparer.ResolutionScope.cs index 0f91206d9..5eedef741 100644 --- a/src/AsmResolver.DotNet/Signatures/SignatureComparer.ResolutionScope.cs +++ b/src/AsmResolver.DotNet/Signatures/SignatureComparer.ResolutionScope.cs @@ -85,7 +85,7 @@ public bool Equals(AssemblyDescriptor? x, AssemblyDescriptor? y) return versionMatch && x.Name == y.Name - && x.Culture == y.Culture + && (x.Culture == y.Culture || (Utf8String.IsNullOrEmpty(x.Culture) && Utf8String.IsNullOrEmpty(y.Culture))) && Equals(x.GetPublicKeyToken(), y.GetPublicKeyToken()); } diff --git a/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeDefOrRef.cs b/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeDefOrRef.cs index 5e4e343d2..0c2a1bf36 100644 --- a/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeDefOrRef.cs +++ b/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeDefOrRef.cs @@ -43,21 +43,15 @@ public int GetHashCode(ITypeDescriptor obj) private bool SimpleTypeEquals(ITypeDescriptor x, ITypeDescriptor y) { - // Namespace can be null if It is a nested type so we need to check declaring type too - if (x.DeclaringType?.Name != y.DeclaringType?.Name) - return false; // Check the basic properties first. - else if (!x.IsTypeOf(y.Namespace, y.Name)) + if (!x.IsTypeOf(y.Namespace, y.Name)) return false; - // If scope matches, it is a perfect match. if (Equals(x.Scope, y.Scope)) return true; - - // If scope does not match, it can still be a reference to an exported type. - return x.Resolve() is { } definition1 - && y.Resolve() is { } definition2 - && Equals(definition1.Module!.Assembly, definition2.Module!.Assembly); + if (!Equals(x.Module, y.Module)) + return x.Resolve() is { } definition1 && y.Resolve() is { } definition2 && Equals(definition1.Module!.Assembly, definition2.Module!.Assembly); + return false; } /// From 7a0db0700f51b1b51a72c5fd53770bfa9a762894 Mon Sep 17 00:00:00 2001 From: xxx Date: Tue, 24 May 2022 12:28:16 +0300 Subject: [PATCH 138/184] Signature Comparer Test --- .../Signatures/SignatureComparerTest.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/AsmResolver.DotNet.Tests/Signatures/SignatureComparerTest.cs b/test/AsmResolver.DotNet.Tests/Signatures/SignatureComparerTest.cs index 80e67f070..892c1ce7f 100644 --- a/test/AsmResolver.DotNet.Tests/Signatures/SignatureComparerTest.cs +++ b/test/AsmResolver.DotNet.Tests/Signatures/SignatureComparerTest.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.Signatures.Types; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; @@ -114,5 +115,35 @@ public void MatchPropertySignature() Assert.Equal(signature1, signature2, _comparer); } + + [Fact] + public void MatchNestedTypes() + { + var nestedTypes = ModuleDefinition.FromModule(typeof(SignatureComparerTest).Assembly.ManifestModule).GetAllTypes().FirstOrDefault(c => c.Name == "SignatureComparerTest").NestedTypes.FirstOrDefault(); + var firstType = nestedTypes.NestedTypes[0].NestedTypes[0]; + var secondType = nestedTypes.NestedTypes[1].NestedTypes[0]; + Assert.NotEqual(firstType, secondType, _comparer); + } + public class NestedTypes + { + public class FirstType + { + public class TypeWithCommonName + { + public string stringValue { get; set; } + public bool boolValue { get; set; } + } + + } + public class SecondType + { + + public class TypeWithCommonName + { + public int intValue { get; set; } + public byte byteValue { get; set; } + } + } + } } } From ce3f2125226efeebcfe81eff01c13c9f8bdef37c Mon Sep 17 00:00:00 2001 From: xxx Date: Wed, 25 May 2022 19:54:51 +0300 Subject: [PATCH 139/184] declaring type check --- .../Signatures/SignatureComparer.TypeDefOrRef.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeDefOrRef.cs b/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeDefOrRef.cs index 0c2a1bf36..c7ae62584 100644 --- a/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeDefOrRef.cs +++ b/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeDefOrRef.cs @@ -46,11 +46,14 @@ private bool SimpleTypeEquals(ITypeDescriptor x, ITypeDescriptor y) // Check the basic properties first. if (!x.IsTypeOf(y.Namespace, y.Name)) return false; + + if (x.DeclaringType != null && y.DeclaringType != null && !Equals(x.DeclaringType, y.DeclaringType)) + return false; + + var scopeCheck = Equals(x.Scope, y.Scope); // If scope matches, it is a perfect match. - if (Equals(x.Scope, y.Scope)) + if (scopeCheck || (!scopeCheck && x.Resolve() is { } definition1 && y.Resolve() is { } definition2 && Equals(definition1.Module!.Assembly, definition2.Module!.Assembly))) return true; - if (!Equals(x.Module, y.Module)) - return x.Resolve() is { } definition1 && y.Resolve() is { } definition2 && Equals(definition1.Module!.Assembly, definition2.Module!.Assembly); return false; } From 6721728dabfc46abb642bb1d644f648cf9daa9cf Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 26 May 2022 09:32:25 +0200 Subject: [PATCH 140/184] Update tests to include declaringtype tests. --- .../SignatureComparer.TypeDefOrRef.cs | 16 +++-- .../Resources/ActualLibrary.dll | Bin 2048 -> 2048 bytes .../Resources/ForwarderLibrary.dll | Bin 1536 -> 2048 bytes .../Resources/ForwarderRefTest.exe | Bin 2048 -> 2048 bytes .../Signatures/SignatureComparerTest.cs | 56 ++++++++++++++---- .../DotNet/TypeForwarding/ActualLibrary.dll | Bin 2048 -> 2048 bytes .../DotNet/TypeForwarding/ActualLibrary.il | 32 +++++++++- .../TypeForwarding/ForwarderLibrary.dll | Bin 1536 -> 2048 bytes .../DotNet/TypeForwarding/ForwarderLibrary.il | 17 +++++- .../TypeForwarding/ForwarderRefTest.exe | Bin 2048 -> 2048 bytes .../DotNet/TypeForwarding/ForwarderRefTest.il | 9 ++- 11 files changed, 108 insertions(+), 22 deletions(-) diff --git a/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeDefOrRef.cs b/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeDefOrRef.cs index c7ae62584..83de4c287 100644 --- a/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeDefOrRef.cs +++ b/src/AsmResolver.DotNet/Signatures/SignatureComparer.TypeDefOrRef.cs @@ -47,13 +47,19 @@ private bool SimpleTypeEquals(ITypeDescriptor x, ITypeDescriptor y) if (!x.IsTypeOf(y.Namespace, y.Name)) return false; - if (x.DeclaringType != null && y.DeclaringType != null && !Equals(x.DeclaringType, y.DeclaringType)) - return false; - - var scopeCheck = Equals(x.Scope, y.Scope); // If scope matches, it is a perfect match. - if (scopeCheck || (!scopeCheck && x.Resolve() is { } definition1 && y.Resolve() is { } definition2 && Equals(definition1.Module!.Assembly, definition2.Module!.Assembly))) + if (Equals(x.Scope, y.Scope)) return true; + + // It can still be an exported type, we need to resolve the type then and check if the definitions match. + if (!Equals(x.Module, y.Module)) + { + return x.Resolve() is { } definition1 + && y.Resolve() is { } definition2 + && Equals(definition1.Module!.Assembly, definition2.Module!.Assembly) + && Equals(definition1.DeclaringType, definition2.DeclaringType); + } + return false; } diff --git a/test/AsmResolver.DotNet.Tests/Resources/ActualLibrary.dll b/test/AsmResolver.DotNet.Tests/Resources/ActualLibrary.dll index 09112a3f8294238544619ec7d7184d017aaf57f6..ffcf8958c6b3e709135a766a485ce9a6f8eac33a 100644 GIT binary patch literal 2048 zcmeHHO-oc^6n^gbfgfpKV7jSSCLt6CQx_!?lNx5ssBqIFg1j^HqBri`D|hZl(<1sH zC=hDZB8X_;!bPOCs!col3E`qee;|l9({t|J$r%)(XcZlO&pFR|&U@~8zb1X{DH;GY za+J%!Cap*^)cdQuq&rW%>%^PZt=>%|wbgrh*)FIR-&^vnyvkax>jf&Km0xs~?W)OH zQ{}y!PIPp%os3G)T>w%>408idGS#nbBi7q)v{#ynRidBcw2C9iAxhDZlfFf&7K;SY zoCYKx0*Au?9UDQJM0v!zX2soGCxHWdGy6vs&{C7pLVU3XCjxydApRnoPcB?Q)HhKm z3Rq0|+VQec4@rh4rzMm>oD4= zyQxJaTeN{!w7k9%x<_J$eiNA(?h`FUr~$&|BGg#NRqyS`#yk#k=A2?0o?#V7iLORy z4Lz)xt3%Sm1NNq9cgstLsc&%rDe0-eWf=BCaDnDE?! z=V)B_BDhllo z!)YBXdpVea71-GbSpHHmuiXHZIdxp!N5g*2G)kD@9kEd0-v9*9K+_MXb2Z#3M(Y@p z85W0)EN5wY%kbn(=Id(?{hg{ke~c!t*B`CvDkoNO&?R1y?Mdk`(6GH8=TAZRbVAWBpuCKOVLm@R5y)~cXImw{F- z3WCU%22r$*mh}(%2ki=?MQ8MO;LQ0R=X^}vU3Z@yYa4fsoOv4LrHI+c+C>DImt!m_ z!z{}uLfJu*IQW{*BW!@iqCbzE!43Se+I9;t2OPf0caY34{ zv`iY;gxv}!GP7}-_r$&OoqG=mxJbayKoow8C#c#+%`N<|0lh5`aKM8Oa@)J=Ww;_1 zwKRr9I+`M8Cr9O|deP!^#b5}<&asFtINRaNX1K>!AXQVARSd&`{I-SUpf)16v`}iM zP~FXMFBI1+`O02v%O+amw;@ucugAIDo9VZe*rgBp!~SYEF!6Ch3yeO&y7gql%B8WU furR}ZZbamhJ<$}7GII%_ll1a<)8qJL=BV=zXK_MP diff --git a/test/AsmResolver.DotNet.Tests/Resources/ForwarderLibrary.dll b/test/AsmResolver.DotNet.Tests/Resources/ForwarderLibrary.dll index 67599b738782c196ab239b26b09818aeea4f5fdb..a0376b4790ea0f279e83f545adfcd325e4024d54 100644 GIT binary patch delta 312 zcmZqRX%Lvup%kIrpTqzK4;Yvfxf!_`7+8P;Ob|gkrHQM;Sf3~|FoaJwWR#h#$QaM% z0+LezVwTBWjM|KBleaN?Nh&BoGYDAzGC zhyX=1KqRhPXVjz>D1}Me>5(^DxXqh~h$()gO@>Ql}O$Cr7gFg^+0kHxQGcd#g zF)I)&05L>Ph=F0UDRYp$Z>3*qaY<^5b53G$F@tX;SX; BHj4lN delta 241 zcmZn=XyBR9p~SazRXhU}JYZl_xrQNrxTGjEFTEJZWUv6L?S6t#2Rd+Vs?pOOr&btL%nG$*j5w75JeDM>&7N1D(+HhDNTA2 z1*I+{J@#69>OsL%Zl#|569f;Qy!Z!r@taY^S>}7c_q~~UyR)@;Exxy9y4fEef}IXA z7yOyF;wfNE4u}rvh*DbxrqWS0}uoyOF>zc@ir2 zsQOxX?5lt zAsfPk5Y7A~@gT~}5e-LQW~bVppKY_>i`yTmTIUtpfzxITrR0Tq9JPT)TJN77w1h83 H&>y)52fR_F delta 470 zcmY+BO-lk%6o%hBU*!-s_8}UgGZthPX4J+6ArMLmGs4;kE)t?mm`V%j!-AqBqTz1( z1wn0Fv}x0({0r?`w2j&XEh;*9!bN8|ocDdsJ@?+3X&4RT{7CU$UKj1t0CU?{%j#Vi znCD|Gz%5o}J$#Stn(xF!%uq^DQ8s}&31H+YHNK(7%~+L)7Lfrx8k8c$^=Pa@r5vG# z9_3roK;n}o`ged5@e83BYn8S2)hbDR5icj&W7Gn5?nfD^|_aM2?= zevHzzg(>K^)jh<;4*z1sapID#Fi->dhn(j%WtQJ7p;WRN+RWBN>3y}C>im8^gKUB+ zV9ZSyV_mNH+j?QiKk c.Name == "SignatureComparerTest").NestedTypes.FirstOrDefault(); - var firstType = nestedTypes.NestedTypes[0].NestedTypes[0]; - var secondType = nestedTypes.NestedTypes[1].NestedTypes[0]; + var nestedTypes = ModuleDefinition.FromFile(typeof(SignatureComparerTest).Assembly.Location) + .GetAllTypes().First(t => t.Name == nameof(SignatureComparerTest)) + .NestedTypes.First(t => t.Name == nameof(NestedTypes)); + + var firstType = nestedTypes.NestedTypes + .First(t => t.Name == nameof(NestedTypes.FirstType)).NestedTypes + .First(t => t.Name == nameof(NestedTypes.FirstType.TypeWithCommonName)); + var secondType = nestedTypes.NestedTypes + .First(t => t.Name == nameof(NestedTypes.SecondType)).NestedTypes + .First(t => t.Name == nameof(NestedTypes.SecondType.TypeWithCommonName)); + Assert.NotEqual(firstType, secondType, _comparer); } - public class NestedTypes + + [Fact] + public void MatchForwardedNestedTypes() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.ForwarderRefTest); + var forwarder = ModuleDefinition.FromBytes(Properties.Resources.ForwarderLibrary).Assembly!; + var library = ModuleDefinition.FromBytes(Properties.Resources.ActualLibrary).Assembly!; + + module.MetadataResolver.AssemblyResolver.AddToCache(forwarder, forwarder); + module.MetadataResolver.AssemblyResolver.AddToCache(library, library); + forwarder.ManifestModule!.MetadataResolver.AssemblyResolver.AddToCache(library, library); + + var referencedTypes = module.ManagedEntrypointMethod!.CilMethodBody!.Instructions + .Where(i => i.OpCode.Code == CilCode.Call) + .Select(i => ((IMethodDefOrRef) i.Operand!).DeclaringType) + .Where(t => t.Name == "MyNestedClass") + .ToArray(); + + var type1 = referencedTypes[0]!; + var type2 = referencedTypes[1]!; + + var resolvedType1 = type1.Resolve(); + var resolvedType2 = type2.Resolve(); + + Assert.Equal(type1, resolvedType1, _comparer); + Assert.Equal(type2, resolvedType2, _comparer); + + Assert.NotEqual(type1, type2, _comparer); + Assert.NotEqual(type1, resolvedType2, _comparer); // Fails + Assert.NotEqual(type2, resolvedType1, _comparer); // Fails + } + + private class NestedTypes { public class FirstType { public class TypeWithCommonName { - public string stringValue { get; set; } - public bool boolValue { get; set; } } } public class SecondType { - public class TypeWithCommonName { - public int intValue { get; set; } - public byte byteValue { get; set; } } } } diff --git a/test/TestBinaries/DotNet/TypeForwarding/ActualLibrary.dll b/test/TestBinaries/DotNet/TypeForwarding/ActualLibrary.dll index 09112a3f8294238544619ec7d7184d017aaf57f6..ffcf8958c6b3e709135a766a485ce9a6f8eac33a 100644 GIT binary patch literal 2048 zcmeHHO-oc^6n^gbfgfpKV7jSSCLt6CQx_!?lNx5ssBqIFg1j^HqBri`D|hZl(<1sH zC=hDZB8X_;!bPOCs!col3E`qee;|l9({t|J$r%)(XcZlO&pFR|&U@~8zb1X{DH;GY za+J%!Cap*^)cdQuq&rW%>%^PZt=>%|wbgrh*)FIR-&^vnyvkax>jf&Km0xs~?W)OH zQ{}y!PIPp%os3G)T>w%>408idGS#nbBi7q)v{#ynRidBcw2C9iAxhDZlfFf&7K;SY zoCYKx0*Au?9UDQJM0v!zX2soGCxHWdGy6vs&{C7pLVU3XCjxydApRnoPcB?Q)HhKm z3Rq0|+VQec4@rh4rzMm>oD4= zyQxJaTeN{!w7k9%x<_J$eiNA(?h`FUr~$&|BGg#NRqyS`#yk#k=A2?0o?#V7iLORy z4Lz)xt3%Sm1NNq9cgstLsc&%rDe0-eWf=BCaDnDE?! z=V)B_BDhllo z!)YBXdpVea71-GbSpHHmuiXHZIdxp!N5g*2G)kD@9kEd0-v9*9K+_MXb2Z#3M(Y@p z85W0)EN5wY%kbn(=Id(?{hg{ke~c!t*B`CvDkoNO&?R1y?Mdk`(6GH8=TAZRbVAWBpuCKOVLm@R5y)~cXImw{F- z3WCU%22r$*mh}(%2ki=?MQ8MO;LQ0R=X^}vU3Z@yYa4fsoOv4LrHI+c+C>DImt!m_ z!z{}uLfJu*IQW{*BW!@iqCbzE!43Se+I9;t2OPf0caY34{ zv`iY;gxv}!GP7}-_r$&OoqG=mxJbayKoow8C#c#+%`N<|0lh5`aKM8Oa@)J=Ww;_1 zwKRr9I+`M8Cr9O|deP!^#b5}<&asFtINRaNX1K>!AXQVARSd&`{I-SUpf)16v`}iM zP~FXMFBI1+`O02v%O+amw;@ucugAIDo9VZe*rgBp!~SYEF!6Ch3yeO&y7gql%B8WU furR}ZZbamhJ<$}7GII%_ll1a<)8qJL=BV=zXK_MP diff --git a/test/TestBinaries/DotNet/TypeForwarding/ActualLibrary.il b/test/TestBinaries/DotNet/TypeForwarding/ActualLibrary.il index 34e8a8e9e..2fb8b287a 100644 --- a/test/TestBinaries/DotNet/TypeForwarding/ActualLibrary.il +++ b/test/TestBinaries/DotNet/TypeForwarding/ActualLibrary.il @@ -6,12 +6,38 @@ } .class public abstract sealed MyClass - extends [mscorlib] System.Object + extends [mscorlib] System.Object { .method public static hidebysig void StaticMethod(class IMyModel argument) { ldarg.0 call instance void IMyModel::MyMethod() - ret + ret } -} \ No newline at end of file + + .class nested public abstract sealed MyNestedClass + extends [mscorlib] System.Object + { + .method public static hidebysig void NestedStaticMethod() + { + ldstr "MyClass+MyNestedClass::NestedStaticMethod" + call void [mscorlib] System.Console::WriteLine(string) + ret + } + } +} + +.class public abstract sealed MyClass2 + extends [mscorlib] System.Object +{ + .class nested public abstract sealed MyNestedClass + extends [mscorlib] System.Object + { + .method public static hidebysig void NestedStaticMethod() + { + ldstr "MyClass2+MyNestedClass::NestedStaticMethod" + call void [mscorlib] System.Console::WriteLine(string) + ret + } + } +} diff --git a/test/TestBinaries/DotNet/TypeForwarding/ForwarderLibrary.dll b/test/TestBinaries/DotNet/TypeForwarding/ForwarderLibrary.dll index 67599b738782c196ab239b26b09818aeea4f5fdb..a0376b4790ea0f279e83f545adfcd325e4024d54 100644 GIT binary patch delta 312 zcmZqRX%Lvup%kIrpTqzK4;Yvfxf!_`7+8P;Ob|gkrHQM;Sf3~|FoaJwWR#h#$QaM% z0+LezVwTBWjM|KBleaN?Nh&BoGYDAzGC zhyX=1KqRhPXVjz>D1}Me>5(^DxXqh~h$()gO@>Ql}O$Cr7gFg^+0kHxQGcd#g zF)I)&05L>Ph=F0UDRYp$Z>3*qaY<^5b53G$F@tX;SX; BHj4lN delta 241 zcmZn=XyBR9p~SazRXhU}JYZl_xrQNrxTGjEFTEJZWUv6L?S6t#2Rd+Vs?pOOr&btL%nG$*j5w75JeDM>&7N1D(+HhDNTA2 z1*I+{J@#69>OsL%Zl#|569f;Qy!Z!r@taY^S>}7c_q~~UyR)@;Exxy9y4fEef}IXA z7yOyF;wfNE4u}rvh*DbxrqWS0}uoyOF>zc@ir2 zsQOxX?5lt zAsfPk5Y7A~@gT~}5e-LQW~bVppKY_>i`yTmTIUtpfzxITrR0Tq9JPT)TJN77w1h83 H&>y)52fR_F delta 470 zcmY+BO-lk%6o%hBU*!-s_8}UgGZthPX4J+6ArMLmGs4;kE)t?mm`V%j!-AqBqTz1( z1wn0Fv}x0({0r?`w2j&XEh;*9!bN8|ocDdsJ@?+3X&4RT{7CU$UKj1t0CU?{%j#Vi znCD|Gz%5o}J$#Stn(xF!%uq^DQ8s}&31H+YHNK(7%~+L)7Lfrx8k8c$^=Pa@r5vG# z9_3roK;n}o`ged5@e83BYn8S2)hbDR5icj&W7Gn5?nfD^|_aM2?= zevHzzg(>K^)jh<;4*z1sapID#Fi->dhn(j%WtQJ7p;WRN+RWBN>3y}C>im8^gKUB+ zV9ZSyV_mNH+j?QiKk Date: Thu, 26 May 2022 09:47:42 +0200 Subject: [PATCH 141/184] BUGFIX: Only include chevrons in full name if method has type arguments. --- src/AsmResolver.DotNet/FullNameGenerator.cs | 15 +++++++++++---- .../MethodDefinitionTest.cs | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/AsmResolver.DotNet/FullNameGenerator.cs b/src/AsmResolver.DotNet/FullNameGenerator.cs index 906bae332..fe2409c38 100644 --- a/src/AsmResolver.DotNet/FullNameGenerator.cs +++ b/src/AsmResolver.DotNet/FullNameGenerator.cs @@ -60,16 +60,23 @@ public static string GetMethodFullName(string? name, ITypeDescriptor? declaringT /// The signature of the method. /// The type arguments. /// The full name - public static string GetMethodFullName(string? name, ITypeDescriptor? declaringType, MethodSignature? signature, + public static string GetMethodFullName( + string? name, + ITypeDescriptor? declaringType, + MethodSignature? signature, IEnumerable typeArguments) { string returnTypeString = signature?.ReturnType.FullName ?? TypeSignature.NullTypeToString; string parameterTypesString = GetParameterTypesString(signature); - string typeArgumentsString = string.Join(", ", typeArguments); + + string[] argumentNames = typeArguments.ToArray(); + string typeArgumentsString = argumentNames.Length>0 + ? $"<{string.Join(", ", argumentNames)}>" + : string.Empty; return declaringType is null - ? $"{returnTypeString} {name}<{typeArgumentsString}>({parameterTypesString})" - : $"{returnTypeString} {declaringType}::{name}<{typeArgumentsString}>({parameterTypesString})"; + ? $"{returnTypeString} {name}{typeArgumentsString}({parameterTypesString})" + : $"{returnTypeString} {declaringType}::{name}{typeArgumentsString}({parameterTypesString})"; } /// diff --git a/test/AsmResolver.DotNet.Tests/MethodDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/MethodDefinitionTest.cs index 695089208..cb3e996f2 100644 --- a/test/AsmResolver.DotNet.Tests/MethodDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Tests/MethodDefinitionTest.cs @@ -6,6 +6,7 @@ using AsmResolver.DotNet.Code.Cil; using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.TestCases.Events; +using AsmResolver.DotNet.TestCases.Generics; using AsmResolver.DotNet.TestCases.Methods; using AsmResolver.DotNet.TestCases.Properties; using AsmResolver.PE.DotNet; @@ -500,5 +501,22 @@ public void FillUpExportGapsWithDummyExports() Assert.Equal(ordinal1, image.Exports.BaseOrdinal); } + + [Theory] + [InlineData("NonGenericMethodInNonGenericType", + "System.Void AsmResolver.DotNet.TestCases.Generics.NonGenericType::NonGenericMethodInNonGenericType()")] + [InlineData("GenericMethodInNonGenericType", + "System.Void AsmResolver.DotNet.TestCases.Generics.NonGenericType::GenericMethodInNonGenericType()")] + [InlineData("GenericMethodWithConstraints", + "System.Void AsmResolver.DotNet.TestCases.Generics.NonGenericType::GenericMethodWithConstraints()")] + public void MethodFullNameTests(string methodName, string expectedFullName) + { + var module = ModuleDefinition.FromFile(typeof(NonGenericType).Assembly.Location); + var method = module + .TopLevelTypes.First(t => t.Name == nameof(NonGenericType)) + .Methods.First(m => m.Name == methodName); + + Assert.Equal(expectedFullName, method.FullName); + } } } From 7b6fa435be1ffcfb4e0e3e047f3adbb6ce2b3ddc Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 26 May 2022 09:49:44 +0200 Subject: [PATCH 142/184] Update issue templates. --- .github/ISSUE_TEMPLATE/bug_report.md | 27 --------- .github/ISSUE_TEMPLATE/bug_report.yaml | 61 +++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 ------- .github/ISSUE_TEMPLATE/feature_request.yaml | 36 ++++++++++++ .github/ISSUE_TEMPLATE/other.yaml | 8 +++ 5 files changed, 105 insertions(+), 47 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yaml delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yaml create mode 100644 .github/ISSUE_TEMPLATE/other.yaml diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index e95f10964..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -name: Bug report -about: Create a report to help improve AsmResolver -title: '' -labels: bug -assignees: '' - ---- - -## Describe the bug -A clear and concise description of what the bug is. - -## To Reproduce -Steps to reproduce the behavior. Preferably with sample code and sample input/output files. - -## Expected behavior -A clear and concise description of what you expected to happen. - -## Screenshots -If applicable, add screenshots to help explain your problem. - -## Platform - - OS: [e.g. Windows 10, Linux] - - AsmResolver Version: [e.g. 4.5.1] - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml new file mode 100644 index 000000000..6548e9f62 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -0,0 +1,61 @@ +name: Bug Report +description: File a bug or crash report +labels: ["bug"] +body: + - type: input + id: asmresolver_version + attributes: + label: AsmResolver Version + placeholder: 4.11.0 + validations: + required: true + - type: input + id: dotnet_version + attributes: + label: .NET Version + placeholder: .NET 6.0 + - type: dropdown + attributes: + label: Operating System + options: + - Windows + - Linux + - MacOS + - Other + validations: + required: true + - type: textarea + id: description + attributes: + label: Describe the Bug + description: A clear and concise description of what the bug is. + validations: + required: true + - type: textarea + id: how_to_reproduce + attributes: + label: How To Reproduce + description: The steps on how to reproduce the bug. Preferably with sample code and/or sample input files. + validations: + required: true + - type: textarea + id: expected_behavior + attributes: + label: Expected Behavior + description: Describe the result that you expect to get after performing the steps. + validations: + required: true + - type: textarea + id: actual_behavior + attributes: + label: Actual Behavior + description: Describe the actual behavior that you observed after performing the steps. + validations: + required: true + - type: textarea + id: context + attributes: + label: Additional Context + description: Any other information that may help us fix the issue goes here. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 3e0c05e63..000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for AsmResolver -title: '' -labels: enhancement -assignees: '' - ---- - -## Problem Description -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -## Proposal -A clear and concise description of what you want to happen. - -## Alternatives -A clear and concise description of any alternative solutions or features you've considered. - -## Additional context -Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml new file mode 100644 index 000000000..679f68408 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -0,0 +1,36 @@ +name: Feature Request +description: Suggest a new feature +labels: ["enhancement"] +body: + - type: textarea + id: description + attributes: + label: Problem Description + description: A clear and concise description of what the problem is. + placeholder: | + Example: "I'm always frustrated when [...]", or "I often need to [...]" + validations: + required: true + - type: textarea + id: proposal + attributes: + label: Proposal + description: | + A clear and concise description of what you want to happen. You can be relatively open-ended, but including + syntax suggestions or pseudo code is appreciated as it may help conveying your idea. + validations: + required: true + - type: textarea + id: alternatives + attributes: + label: Alternatives + description: A clear and concise description of any alternative solutions or features you've considered. + validations: + required: false + - type: textarea + id: context + attributes: + label: Additional Context + description: Add any other context or screenshots about the feature request here. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/other.yaml b/.github/ISSUE_TEMPLATE/other.yaml new file mode 100644 index 000000000..b0da1e372 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/other.yaml @@ -0,0 +1,8 @@ +name: Other +description: Something else? +body: + - type: textarea + id: description + attributes: + label: Description + description: Please describe your issue. From 471bfb20b0467926aa5684a50e31366f4e18784b Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 26 May 2022 09:50:24 +0200 Subject: [PATCH 143/184] Bump version --- AsmResolver.sln | 1 + Directory.Build.props | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/AsmResolver.sln b/AsmResolver.sln index 526ac85f6..5b61c298f 100644 --- a/AsmResolver.sln +++ b/AsmResolver.sln @@ -82,6 +82,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution CONTRIBUTING.md = CONTRIBUTING.md LICENSE.md = LICENSE.md README.md = README.md + Directory.Build.props = Directory.Build.props EndProjectSection EndProject Global diff --git a/Directory.Build.props b/Directory.Build.props index c1dc47b04..134a35eb3 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,7 +7,7 @@ https://github.com/Washi1337/AsmResolver git 10 - 4.11.0 + 4.11.1 From b5d0ae3ce242c77af91be61e66fba728be0faa29 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 26 May 2022 15:09:12 +0200 Subject: [PATCH 144/184] Make RentedWriter a ref struct to avoid it being used outside of temporary scope. Add tests. --- src/AsmResolver/IO/MemoryStreamWriterPool.cs | 27 +++- .../IO/MemoryStreamReaderPoolTest.cs | 136 ++++++++++++++++++ 2 files changed, 158 insertions(+), 5 deletions(-) create mode 100644 test/AsmResolver.Tests/IO/MemoryStreamReaderPoolTest.cs diff --git a/src/AsmResolver/IO/MemoryStreamWriterPool.cs b/src/AsmResolver/IO/MemoryStreamWriterPool.cs index b784ffb62..14a3ef691 100644 --- a/src/AsmResolver/IO/MemoryStreamWriterPool.cs +++ b/src/AsmResolver/IO/MemoryStreamWriterPool.cs @@ -33,12 +33,15 @@ public RentedWriter Rent() /// /// Represents a single instance of a that is rented by a writer pool. /// - public readonly struct RentedWriter : IDisposable + public ref struct RentedWriter { + private bool _isDisposed = false; + private readonly BinaryStreamWriter _writer; + internal RentedWriter(MemoryStreamWriterPool pool, BinaryStreamWriter writer) { Pool = pool; - Writer = writer; + _writer = writer; } /// @@ -54,7 +57,12 @@ public MemoryStreamWriterPool Pool /// public BinaryStreamWriter Writer { - get; + get + { + if (_isDisposed) + throw new ObjectDisposedException(nameof(Writer)); + return _writer; + } } /// @@ -63,8 +71,17 @@ public BinaryStreamWriter Writer /// public byte[] GetData() => ((MemoryStream) Writer.BaseStream).ToArray(); - /// - public void Dispose() => Pool.Return(Writer); + /// + /// Returns the stream writer to the pool. + /// + public void Dispose() + { + if (_isDisposed) + return; + + Pool.Return(Writer); + _isDisposed = true; + } } } } 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); + } + } + } +} From 007a1065ff932876c9e2321eeeac9eab1a0c0283 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 26 May 2022 15:57:33 +0200 Subject: [PATCH 145/184] Bump dev version to 5.0.0 --- Directory.Build.props | 2 +- appveyor.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 134a35eb3..128e21272 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,7 +7,7 @@ https://github.com/Washi1337/AsmResolver git 10 - 4.11.1 + 5.0.0 diff --git a/appveyor.yml b/appveyor.yml index 91e0e257f..a350fd134 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -33,7 +33,7 @@ - development image: Visual Studio 2022 - version: 4.11.0-dev-build.{build} + version: 5.0.0-dev-build.{build} configuration: Release skip_commits: From 7b41c968787eb28992287ac3d9a30382c756c9e5 Mon Sep 17 00:00:00 2001 From: Salim Abdelaziz <53277521+N78750469@users.noreply.github.com> Date: Sun, 29 May 2022 17:24:09 +0100 Subject: [PATCH 146/184] Fix ExpandMacro. --- src/AsmResolver.DotNet/Code/Cil/CilInstructionCollection.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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; From e095e55e69f351e33f80e778c2c7023cbee08dea Mon Sep 17 00:00:00 2001 From: Washi Date: Tue, 14 Jun 2022 16:15:39 +0200 Subject: [PATCH 147/184] Add DataSourceSlice. --- src/AsmResolver/IO/DataSourceSlice.cs | 68 ++++++++++++++++++ .../IO/DataSourceSliceTest.cs | 70 +++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 src/AsmResolver/IO/DataSourceSlice.cs create mode 100644 test/AsmResolver.Tests/IO/DataSourceSliceTest.cs 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/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)); + } + } +} From 0d86462555b264a7d54fb16233987b5222713cc9 Mon Sep 17 00:00:00 2001 From: Washi Date: Tue, 14 Jun 2022 17:25:25 +0200 Subject: [PATCH 148/184] Add basic MSF 7.0 file read support. --- AsmResolver.sln | 30 +++++ .../AsmResolver.Symbols.WindowsPdb.csproj | 26 ++++ .../Msf/MsfFile.cs | 120 +++++++++++++++++ .../Msf/MsfStream.cs | 65 +++++++++ .../Msf/MsfStreamDataSource.cs | 109 +++++++++++++++ .../Msf/SerializedMsfFile.cs | 127 ++++++++++++++++++ ...smResolver.Symbols.WindowsPdb.Tests.csproj | 42 ++++++ .../Msf/MsfStreamDataSourceTest.cs | 81 +++++++++++ .../Properties/Resources.Designer.cs | 55 ++++++++ .../Properties/Resources.resx | 24 ++++ .../Resources/.gitignore | 1 + .../Resources/SimpleDll.pdb | Bin 0 -> 823296 bytes 12 files changed, 680 insertions(+) create mode 100644 src/AsmResolver.Symbols.WindowsPdb/AsmResolver.Symbols.WindowsPdb.csproj create mode 100644 src/AsmResolver.Symbols.WindowsPdb/Msf/MsfFile.cs create mode 100644 src/AsmResolver.Symbols.WindowsPdb/Msf/MsfStream.cs create mode 100644 src/AsmResolver.Symbols.WindowsPdb/Msf/MsfStreamDataSource.cs create mode 100644 src/AsmResolver.Symbols.WindowsPdb/Msf/SerializedMsfFile.cs create mode 100644 test/AsmResolver.Symbols.WindowsPdb.Tests/AsmResolver.Symbols.WindowsPdb.Tests.csproj create mode 100644 test/AsmResolver.Symbols.WindowsPdb.Tests/Msf/MsfStreamDataSourceTest.cs create mode 100644 test/AsmResolver.Symbols.WindowsPdb.Tests/Properties/Resources.Designer.cs create mode 100644 test/AsmResolver.Symbols.WindowsPdb.Tests/Properties/Resources.resx create mode 100644 test/AsmResolver.Symbols.WindowsPdb.Tests/Resources/.gitignore create mode 100644 test/AsmResolver.Symbols.WindowsPdb.Tests/Resources/SimpleDll.pdb diff --git a/AsmResolver.sln b/AsmResolver.sln index 5b61c298f..cee77d8fe 100644 --- a/AsmResolver.sln +++ b/AsmResolver.sln @@ -85,6 +85,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Directory.Build.props = Directory.Build.props EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsmResolver.Symbols.WindowsPdb", "src\AsmResolver.Symbols.WindowsPdb\AsmResolver.Symbols.WindowsPdb.csproj", "{9E311832-D0F2-42CA-84DD-9A91B88F0287}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsmResolver.Symbols.WindowsPdb.Tests", "test\AsmResolver.Symbols.WindowsPdb.Tests\AsmResolver.Symbols.WindowsPdb.Tests.csproj", "{AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -423,6 +427,30 @@ 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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -460,6 +488,8 @@ 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} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3302AC79-6D23-4E7D-8C5F-C0C7261044D0} diff --git a/src/AsmResolver.Symbols.WindowsPdb/AsmResolver.Symbols.WindowsPdb.csproj b/src/AsmResolver.Symbols.WindowsPdb/AsmResolver.Symbols.WindowsPdb.csproj new file mode 100644 index 000000000..1f1099b04 --- /dev/null +++ b/src/AsmResolver.Symbols.WindowsPdb/AsmResolver.Symbols.WindowsPdb.csproj @@ -0,0 +1,26 @@ + + + + AsmResolver + Windows PDB models for the AsmResolver executable file inspection toolsuite. + windows pdb symbols + enable + net6.0;netcoreapp3.1;netstandard2.0 + true + + + + true + bin\Debug\netstandard2.0\AsmResolver.Symbols.WindowsPdb.xml + + + + true + bin\Release\netstandard2.0\AsmResolver.xml + + + + + + + diff --git a/src/AsmResolver.Symbols.WindowsPdb/Msf/MsfFile.cs b/src/AsmResolver.Symbols.WindowsPdb/Msf/MsfFile.cs new file mode 100644 index 000000000..058ea7824 --- /dev/null +++ b/src/AsmResolver.Symbols.WindowsPdb/Msf/MsfFile.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.WindowsPdb.Msf; + +/// +/// Models a file that is in the Microsoft Multi-Stream Format (MSF). +/// +public class MsfFile +{ + // 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 + }; + + 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 (_blockSize is 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(ByteArrayDataSource.CreateReader(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 List(); +} diff --git a/src/AsmResolver.Symbols.WindowsPdb/Msf/MsfStream.cs b/src/AsmResolver.Symbols.WindowsPdb/Msf/MsfStream.cs new file mode 100644 index 000000000..cd8115dfa --- /dev/null +++ b/src/AsmResolver.Symbols.WindowsPdb/Msf/MsfStream.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.WindowsPdb.Msf; + +/// +/// Represents a single stream in an Multi-Stream Format (MSF) file. +/// +public class MsfStream +{ + /// + /// 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; + Blocks = 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. + public MsfStream(IDataSource contents, IEnumerable blocks) + { + Contents = contents; + Blocks = blocks.ToArray(); + } + + /// + /// 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 Blocks + { + get; + } + + /// + /// 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.WindowsPdb/Msf/MsfStreamDataSource.cs b/src/AsmResolver.Symbols.WindowsPdb/Msf/MsfStreamDataSource.cs new file mode 100644 index 000000000..08e43ef2d --- /dev/null +++ b/src/AsmResolver.Symbols.WindowsPdb/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.WindowsPdb.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.WindowsPdb/Msf/SerializedMsfFile.cs b/src/AsmResolver.Symbols.WindowsPdb/Msf/SerializedMsfFile.cs new file mode 100644 index 000000000..21d488bf9 --- /dev/null +++ b/src/AsmResolver.Symbols.WindowsPdb/Msf/SerializedMsfFile.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.WindowsPdb.Msf; + +/// +/// Provides an implementation for an MSF file version, 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 uint _originalBlockSize; + + private readonly IDataSource?[] _blocks; + private readonly uint _directoryByteCount; + private readonly int _blockMapIndex; + + /// + /// 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) + { + // Check MSF header. + byte[] signature = new byte[BigMsfSignature.Length]; + int count = reader.ReadBytes(signature, 0, signature.Length); + if (count != BigMsfSignature.Length || !ByteArrayEqualityComparer.Instance.Equals(signature, BigMsfSignature)) + throw new BadImageFormatException("File does not start with a valid or supported MSF file signature."); + + // BlockSize property also validates, so no need to do it again. + BlockSize = _originalBlockSize = reader.ReadUInt32(); + + // 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. + int freeBlockMapIndex = reader.ReadInt32(); + if (freeBlockMapIndex is not (1 or 2)) + throw new BadImageFormatException($"Free block map index must be 1 or 2, but was {freeBlockMapIndex}."); + + int blockCount = reader.ReadInt32(); + _blocks = new IDataSource?[blockCount]; + + _directoryByteCount = reader.ReadUInt32(); + reader.Offset += sizeof(uint); + _blockMapIndex = reader.ReadInt32(); + + _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 * _originalBlockSize), + _originalBlockSize); + + 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(_blockMapIndex); + var indicesReader = new BinaryStreamReader(indicesBlock, indicesBlock.BaseAddress, 0, + GetBlockCount(_directoryByteCount) * sizeof(uint)); + + // Access the Stream Directory stream. + var directoryStream = CreateStreamFromIndicesReader(ref indicesReader, _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 List(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, _originalBlockSize, blocks); + return new MsfStream(dataSource, indices); + } + + private uint GetBlockCount(uint streamSize) => (streamSize + _originalBlockSize - 1) / _originalBlockSize; +} diff --git a/test/AsmResolver.Symbols.WindowsPdb.Tests/AsmResolver.Symbols.WindowsPdb.Tests.csproj b/test/AsmResolver.Symbols.WindowsPdb.Tests/AsmResolver.Symbols.WindowsPdb.Tests.csproj new file mode 100644 index 000000000..ff11f109f --- /dev/null +++ b/test/AsmResolver.Symbols.WindowsPdb.Tests/AsmResolver.Symbols.WindowsPdb.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.WindowsPdb.Tests/Msf/MsfStreamDataSourceTest.cs b/test/AsmResolver.Symbols.WindowsPdb.Tests/Msf/MsfStreamDataSourceTest.cs new file mode 100644 index 000000000..95430985d --- /dev/null +++ b/test/AsmResolver.Symbols.WindowsPdb.Tests/Msf/MsfStreamDataSourceTest.cs @@ -0,0 +1,81 @@ +using System; +using System.Linq; +using AsmResolver.IO; +using AsmResolver.Symbols.WindowsPdb.Msf; +using Xunit; + +namespace AsmResolver.Symbols.WindowsPdb.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(b, 0)); + } + + [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.WindowsPdb.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.Symbols.WindowsPdb.Tests/Properties/Resources.Designer.cs new file mode 100644 index 000000000..ccd260a2d --- /dev/null +++ b/test/AsmResolver.Symbols.WindowsPdb.Tests/Properties/Resources.Designer.cs @@ -0,0 +1,55 @@ +//------------------------------------------------------------------------------ +// +// 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.WindowsPdb.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.WindowsPdb.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)); + } + } + } +} diff --git a/test/AsmResolver.Symbols.WindowsPdb.Tests/Properties/Resources.resx b/test/AsmResolver.Symbols.WindowsPdb.Tests/Properties/Resources.resx new file mode 100644 index 000000000..64f81d46b --- /dev/null +++ b/test/AsmResolver.Symbols.WindowsPdb.Tests/Properties/Resources.resx @@ -0,0 +1,24 @@ + + + + + + + + + + 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 + + diff --git a/test/AsmResolver.Symbols.WindowsPdb.Tests/Resources/.gitignore b/test/AsmResolver.Symbols.WindowsPdb.Tests/Resources/.gitignore new file mode 100644 index 000000000..bd46a47b5 --- /dev/null +++ b/test/AsmResolver.Symbols.WindowsPdb.Tests/Resources/.gitignore @@ -0,0 +1 @@ +!*.pdb diff --git a/test/AsmResolver.Symbols.WindowsPdb.Tests/Resources/SimpleDll.pdb b/test/AsmResolver.Symbols.WindowsPdb.Tests/Resources/SimpleDll.pdb new file mode 100644 index 0000000000000000000000000000000000000000..2a5f3d4adbf687f01302a87ab125db03da2a52bf GIT binary patch literal 823296 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

&h-E<4_FNFUX!JeV6;PDPZOKoA^ z?rN}e&2s1q*0*xXiEs67tgT84b^DI+A+b-ZtyU2IVqJAmA~tq7J!<8MEs&}a^J2q{ zhX|6pQ(KiVAM(<}db>7`NetNjzKb2>`nuN)L^@CdUg!g_%@fi0o@9Y<%ANF+}clIX} z{D_a;BWQN>8H{84_f6FtLaG^aHcxRRAqNL`UY)>2 zGEO(~75b2J{;z~wX-NJ0Wqj{K{I4PA>ssnCY^KchTOkKR49|7ip7FfVxAohCF(N)^ zUkqzm$9}M^pOaG(ztFovCC)HQ2%FJfsqO#|w4I@x@*P1Rpcg?5sQtp+>kc|#Wf@P- z`#_n`em`P||NL^aN}BQ=7(ZzW>0i|UCeZIQ6L7h(4*OP-v9q7=w0`&wBi_#7T)Mx( zXbSHOQLHldf|DO7VQ9(mE%(WXSPSyK=2pagLU@6;4e+-oFXMRm?+(@o@5J&9qTQFL z@qaK~YL@KCex*rM0{Vr)LdZNOK;YV2?*Dqp} zC+s^Rqgmg|fFFZi{~BaSaZyY&j|VxmAY}q;q-S^p{ewR4?(7v;9_MwpbGfr&XN6_Y zw-`FkSDW}F{N70A@b32uGUNy&q66zzXgUuqsGLdi(4Ncyad{|As0aY}V< z@;=64D11+_Z{Fy2WMFF$6P)QB8!ozs(~~8O#&{28-~D373Z+Gyd_*WhKUmt}`T1Tu zKfG7CPQ)DuzxQSPJ{c=qKYJK2+@US>p zE6o9o^GrY;+Enzz(Ta;Haf-$s(S`P2`6{n0q5rhCfXu9e+f1RxyZj35Nuy{1kGRgY z@!i$cc9-vct-`(b{rVleAY<4M2u$*J__iK{tO;_rA``s;H>muLc_CzWD&gOZvV~A7s<3wf?-^aUXd#td9>` zANOa|_DL9jIhwBj#&<4PI(_ZAoUxC6A^Ksqhp~Ui+Rfg7xVR-I3u7J67OQ2Lugi!t z;5N<=@7rD&A4HQS83N(}AqqxoDr@=?Yme_OedkuD#aPx%S4#|%i+!l?++-QeDO_10 zN|r$yiijArp^tN-{3!ZL){1Re`r#NN`puEsWM!Pysb-ZJ?ml93b$9S6k5!b+S3;P3 zTRB@HcL#qI-@8BXNsbm(W=y8BI6rRhpUfhRBF|QxSv`v|^O+FQoah!BQx(hfoLz?2 zviB5%cbBHJ+UbX*4QHG8UygBRYI+9c*5&o>_^x-cVn}P={Y>%%{&tjm=VQRj5P9_0 z46KUuyB`-=sfD+#)Oi);F+(bk5u0t=P=7qYXB9d`&58cmk42TkcRG16)A2T;v>k^W zB12rwYp4}kPQ2`(6zyf>A~^zZ3>TU;C}SjihgZqmtdijU4fgK(2zVX%m*80! z^Lo303`Tf9*}cg;9?)g1AW>DjYj&hR0X#BBPK?XiyXbLZr8>_1%a7WD=>}-jtJ7+|8$jc+5SGz2Yw8{Vq?hTb-?8q1?h`HsGX7z z%Bz81IL}|RKDycaC3w;&El;P5^~Mw=NC?Y52M*c%jt*Si&+)%l{}1Q%A4uSJPN9_E z=J0=88*jF~Gb)D=Z-?D3oD(G~eM0`kx7{Fht`dqx@2KBI8>#q88h^;%`z|EQ zru_r*e?SY;W~t=XbaaK^!LQLQBHAl#fn}5}Q)cs9D0hFb&xy4g-h%6I*<2co(ogw1wlKw%ian|SjBpGB7c=v{5Jz{%JXtV*tg4OknVIES zSt~zg>+U(bWGga`d-kd0_QJS@A)nFxa>DvI*F@Pbqvl4Cv9V#BldG&Tt=g5 z*%_{EG%mwQ)jnTn*wfrohcwOk(kem;{IrGCVI=yRa`eN z@Ae?M*y{6*I0ab;6fH0d>Zc{2hc(`zY}A&E@yj{q`yCtvdwZj0L`L!PFSNfpg_uYj z0QFd}?Zyuo9mi~Cv>}2f!1n&K3d>!XDkXFL!lhsY)B| z*4FN}eLv>P;Hq{dpu@#*4XAz&%EqHvcy00W2)2o}SBhp=mf>sz$2Zw_obQC&`zd%C z@#eal*IM0nlg6k)WA*kLRozhDxH(K>oIQ8CG>n-=RnUELK5FdlY$KR4E{hG1LM zj6uP}JC2MvcCKfjt6T4x?@FTuS4C_pd`Mpu5>0+r1J9*6Xl(1aZLiq5nf2oc$f51a zSH}C9h4bxRN7}3>z7ep@sbOm!@-hgqvAn*cV`(~_g`RTxW-f!qh=KvR0o|I-yLcUn zOx-~?#s&R8Sabiiw13g{iLbU+mb@+VrEp&|nwva{b&Vg3oV|!F^!P4Pn`|*wh~ex@ zqF3&=yL+?n{Mn*Xd;$qcc5rT-9)@^-RaD+;ch~lKt3e}gyF}an(qx%5=H~k9Xl{E2 zG}whr&?h4tk!-$;?wl2w3x&qhU0`Zrv!%H4erK=g!;9S6W?ULUMtJ0&7Wi)3oBTF- zZ*xM3?F>VgzMMj^)zEhj++9E$%oRXc0_C~<-y_h-{SMmY+v#`tzfbDFzY_0Z_wP%1 zH|rZbwDF`1yi;e#x*Yhp`W(r$%SE4sKMdXsct597%Ih;I1;mi)I~e-QO?J8<9Y|W( zfU!1+a@=Pa^ULX{5ZAklKuF)ZEMk5tTi3Hmy0> zeK1RH0H}+S6n=~;U@zpzc%5bddMX5OP|sw0lZ~!#og<% zoQSZ2RE)<_z~JAwx4G2WFv?4lInm4f0@nF(Do!S4inJs8^I+ZXhIAV#jtAYMLVEsX4j6WRLN@v@}=p;oy|tDsi-E!IM?$--kX< z2JP5s6nlT#h5S{3hf`7fe6Q4UGfBg;&AVaU$_KLUPX49qvLS&pYO50)JeO|$|3@$; ztj*kv*fUI2t9^;DGK-`i$|w3|V~fzJR;FQ^ZQf}}eX1EX@^{V((jt)F!j>(i1j z3=-dE%lb_Lq4#2`I9n7imWpMa%PqBeM=#KIK5KL;X%mBiVb1ghTol?A{Kof>XYWVn zmE**!<_3n9|YP6%NWL=H)lO9CPQsyd#*q=NU}FOOL5~t*V#HB+N$L zDrdVS9Q-TUkGI{;bsMrd^WTStbk6+uz@72G3wJ$r;}>lo5KrLvHZW^&)?IHyPB`NY z@TnW#D})b4%PZF;l!@AHvVG)fS-B=giV8alJlv(}dwWOGfxx;`c6rtxmOe z8s#aBov;>U(04_vcubGax_fR(V0>C@4ri}ad| zxJWBl%XD0%%QCho4=I0P8*Hm|d>q~TBMcoJ=V zwHuE>WB%xkHXhdo*B#72;htche7(hLnysEs#%fZ4tou!9cHl|r4mK@FZkCTSqb_>m z9(%U!u`P@@gLiRsHR!iARjD!$S*^qzYq7uQoQK_{vdoixKgaqe50=q)GVd)2+hpau z`2uDZ`F5XY?amneNyh=oiZM2d{c#*?SYO+ccNzB(b3P$1hC$RgZ zdqH65DNr-|&bnAnfsZqgAkFlvE=7x^&u0xs&UPhDM->OliFi?@nMW;8o;z>n((?noJYQ`Dw!Hf8^DyiG zi(oUybtheT2ss@cU3iI&>lJpcIF2Zysu!f2YPwC0??`+PT5dg@BDjF5K7@3GWrzJ+ zL+Kf5!xsi)g`7}#(0FYQ;u-kWAqzSZBturF@}~S^%s}_!Hm1AWYbr!e(rE5AS_VPV0;A?+ndEJwnX_7C$eWw@)T6;%z@zrZpwm;*569@a zHwVzh4Djy$@YX0_gsq)I#8cl!_Dfmw%Q61UFF5Xn zbD2fxPR><7KS7)GTleYUIVjqG`_9CEQ)F{5(bo{~+)fyxLxlLI17#ih8$x=ryRr^- zS!PAu1De}h1omQV#%?Jmm-z=%J@? zKf);IZ$ZGm#w!l}5YL309V`X(0;9g+C}Y?`y|jr;=xwvTC43n0=4H49KupR4*TymA zC1*IAt8K{ep;Dpzh4<(AU=D~Cy(Pkp)yP^wQO@S)mz1)fEV83lr8 z?!k)fLB|V?5+$Gs8kIAiCt@q@JOP(s5#AY(z*xty@xiNheOas+c6UTJM0$52m=Ab7 zrxS2u1e1-aazIn)SvZt~Rh99(eL|iz9gBc6XV^CLaV_>;1&nDr7!&ALu2gjLmE#Ca zP^`{bc&-Cfl<_z@eHw>cFYeP?&_B+P!^th_NlpUidUq9uzYIJY|4p4nKV7mDhd?O| z>qUeoo^$!dG8gWRhV6|sFJnQrL@~pC&H3_vG3yLD8-E~s!^Puj>@M|?g2~Pj>w~xE zd#KWDZ+dykMciq_k%WZwaO&Aed*%Y%;a%czXED2!#-xBUk9P>}hYJq~okYj@8=w!s z+_XrU7aMSbri!GA+}HI-nUQP+ihxBk#Z0RXL@=%KWM6(Vua&Wdb2n>Z41YK)p$@R$ z*IaEM>8HQZrb3ytA7uzho`=_1u4Pw(_z7Xv)v42T0c+zuw@ zs_F`kNb6y=o&0i)KkscXvE&+f#a9sGL-Ii5eg}g%0~^!{_)BQ&RkJKs`Ss0fqd)G+ z$81l&!{U%3mP;ZAfVQoV0%Q$cY*-}L;&j<`68qOT8+-;lkovIr*tdPFKMU5ajJPHf z5<3<9oP5dPO};Ho6lYy@1+Edlxow|svcndrUgGdR-w_WsPSIzGw!!*4a+B}!!|H3l z>DZ?s{yE;8eSiB+`Y%q*GcF!O`BX{D@Ys`exnrO2*l+4PA%70Xckc78?3s!B;sPJ8 zW`DqSiJtsa{zd=?kI;Q{AR;hRe+oWzNA6*+6Y`p`!hX&FFxLC*Uk95X>*em~FO@Sa z1l98JkMoYHG2T`Yxs58;t zLXZ|#k#majLy2;01Jo0*A6D~y4JPVoJ8?BT_h|37L_OGEo$t2bf3Y&YS8CxR^z9wa zt*5O@?O+~9a_TobFk9tA4qn(`D}DQr?*({6>Zl#CuiivHt~ZjXH;I_SaV3YNGl@F1 zaat3j{r1(ix052S#}f5w*DxS)OmpqbCdy}^Ri0=qLpSBhUy~>=Z*KSYq=GBoPL$7d z8WZuiPo~NQ?T#K}Y~PS5Z!!jYwY8KS|GN`)G{hn|0d>}RlE}GU!v2K7L&P7(#m0v@ zqX64_U!65BNCaoAO+b`V6>dgF7a>^A7Vp3nI~z_9c3;Fl1f4V)Eq^no*Ba!h>n+JEQxCQp(5BN-aR!`(v1Ce(7U$0jnlej(sTs5bqFun0g1=lj zvNDi)Qe?D8yk9oFk7u`SyFnNpTv%Qj)(^M#9=GhY z6>S^5kbpC0?_*_8?-FJ1$wjM>OYkD5kNy#%|Gb|zFa~5Xhx6v*#B9Zk6&AV@!HQR&tC*rciL0H`0VvbMvF34UR!DfN~wHijpRRu3ByFEfuF?c@g zz!QPjDcc|%d~Q1nerHOLcGQ2b{z`5P!~XjiPI<|3XJ2RZoG{hjL5LI3@>9s4-P4od z*YLELZ^toB&Ul#G*RZ`uJnSLydA|$$b}v}Nv5dGTLJ|67n#ehs&s;ls!KlD(3&COt5d3{h z;>`PK&br}(aWH;ehq-JzTRK%MQ@YA&G17cEpW1G3xg&NgZr|ZOfxz@IN*BR}noYCp z&%i)2XQVGfJ4<%Hzap3qX=-2(3uzT??O@#|PryVR+MCzI?D=*g|2Q0-FV`T@gNXr6 zKWj7SpYXe4c)ff+h9gF$tIx|9b~>>Q-&bP1u(NX&i1dYX0RY{Cb~znIt%0k3wpzPH z7hDGMLEf6Wk4amIlI0R(w$y}dFOEn1Rp@WgS1Y@AaIK24QZ_q6GwxFZBf?tJW`J`} z%TDSw2nl&LSC9{{S!pttA?tzHNW2YwJ^4rZ3(r`+w5%jm@4-jRPcJPON!2_1606tu z=Jpj64{q@z;GIl<=4*hX?9uA-WS1#kKAt!q$Sp!fA#pB5U(07`r;A(pGMIzq9#R)! z{%+q>pQaHW!72yeQkUk=Hu1&!q>82miC>}>7d+v_uaQ6_?AyTWMZ)rwM8Ip z_}btsoq{hjRA_0vS`r2n++ntd$6#;x@#Nhmaje;|>ZHnBbaE-tNt}nyf`ESSfGj>xHUA_i~Tfr-T56bV#7H-g9aTUC!oioyf zA&XDJe;-AB3hpibdpOAR#NWp(&lAhZ100u_%sXOl$#eC#0GD#-A{q@+=-e2+$#rzbH7+B;BQ_l-f{V$XqO zvPH&io5hm3?n4+xa6i5sz_&^J?kOy@nRe?xWvurm=%403gA}MkArHq*{)=tBJs1b} zlf9Z}>v%BY(&)dS_o*QL=DgKa4{w&lsc3aB293tNiVLMZ?w4-~_RcW&2KNqq8n~{{ zk-f8>uot!%pCZa&ol&gEQCoZW8#o8=qwB?T@@rVIZi}xK+w;7KzCFOtapB7v)heS) z2Ju|ToF$+bIrTxjZxIc!-2!Fb+y)OOu^RRg?;*XrSx4zzv)x4)1>x6fTcu97yDMCm z=`UE1sZTa>n}I%v|M(90p_q?(C&tV0va0wo-iqqchGQwR3o&Hu&Fr@;?TV3)ySFjB zejU#lIitiG&Kt5xAj7HcYs|Lrt^i*y&nI52vz3eG>b%?65^v35=$P+te)|uBUJv&A zCTmIO^G(ZQGRo0r_S?{`2dF_nL}}!1mJ#(q@jdt5yKUbkb$hh2+1y<3L5LtsMa(bo zJu!zx3ro4*_>N|NJa`rF^fW<$;85S!8{3Qu>+%M?Rp`S|;XMH!q1$sN7qWO@=<1h3 zpTE{xU+z;>feeD7yCcELr~(6!(|BX=>?(sG!5yI$th*-N~aPejYxFeQ<-V&2!0sKq++f_6SC zOm=gUw)3b*c@FJA{#7vlQ_h~dhK%9(uZjS*4?Y|m1Onp%!Fl%H1(m@mZc5NcQJvWx zl>o~3+`I3yy<33Jg?o_vCoH8p(78Zwfh5uro=4}#vLTd-ge)lJcYG%fQ`Uj7J+w6C zi%~y~`QGD_N5!Yu)Zzr+i;wjRN|Zf{dvPSH!yeD9)|zl#hiZj78uqQ_rS09!D%$_A zqK%W8ZTPUV(w5fCY0FoQ&+@5AhMIUYb3CVs!>kbp^F2d+__U-tWzL6VcqHe0KLl%s zeLV{MqF!ca@!TOD&#vPeWE}k4?s+=)<@X2saw_ntoT)XL(@o!tePNLnRX86Dc5hZ) z_F#@&J(cp?nSA$w{XTxb?c<#GB+@Oj@Y3hkp9&_U(PZ+FMQVM_cB88n6Fg zv}Gackub~h(v8YF0sV)-htWZHx5bk~_7}_eE==2BlFuT)_rU=7v{yoC&KkxDb<>3V zw+Z+4N^-!A3Nn{m8j;Y;df8_ZiWW#Ce#mg~fZ-xbPDVZ&@%#`_y0`_yI0SRi_do%2 z`cN>ZA=0_x4EQ`s9sEk9fiiZ|N%G%!V2`^$=Odw_J&0;++-~(Qi;mARlrw#gX zoj^Tkm!X94jrg*WL|MG=8@K~bf`3WR$Gv}8e#*-G#%VZVoztij_pPz-X@68W7{2Ct`B6JBj&_hIfJYf9 zE$Eo7tHI{h1*q#|FbE(dAcWxCV3V(1(wr&wy9V79LfUA(w_2>|kDyx0oq~`X6<~!+Hp3U_Yv*T^IrkK zv|A&IGTq~K)hav`vB1=qx!@&8m{VDq)eV)z3EfzskN5P`VZ^P>^S%2)CKDxx~W0`F& zUVx#-e2uw{!Kjcq!ES{lk><5(Ofa{96_1-=wmz?B_Y>-T61-&t7q;|w&}gUqANVo+ zs*PbZN5(;1K(5FbS>o4Hz*_t|zHxt!KfyLbqTlL-44PTA9G+FCkt{>+PNoveIBAa+ zpF_q<7Kq&+q^0m{m-Ed%LE8Bn!8pV|U}k!1LP-%ezxiAwCflWF0 z9AQUb75AIO0oMJu3>R-s@O_`Y?*#4hvwSBAzg6Ff-wx&)`c7CTWA8h$fEnlPCnqZj zd|51lMhR*^z71%odlRSR+%M2Cariri!(5t#{h%D{Y&rDz^)PGa_pF^aB<|MhQwVIg z4Mm0U3d)1;^}(=DBnBdRPS$}PzQ}gnZ^-AC79^~X()bb7fsM>YYpd<$9E9By(Mw*1 zdz=q>^FK(e18MABy#XVlDHzANEgyhn|NXh~(kdVCZBDfz69{DDgHdm@xWj02Ixm%z z_cp0PCXnMM40{|A{4^4odGGi#+&$2uWS2qSxLLf)498uaww)=q3EAx?)c2WXko|8i zq{fQ#zFE84?p|(bTtzoF_yVho5hSoHMMU;n?p<`eANzs4CtZHYmA@9{r%^6)K#s)j zUcw&$oN_<`+qelo!}i*%t=$bR;y4ID3-~x}Os)j&i~xQP@JbJ!-lk_F?8Si1Av{PI z@%&)cAbX1NhXDt#xx0J?I)57r;V%JvcD=WJxriV@t$+qec1ASzolmSoa z%*8g}^$fP@B$L`U`AfdT|0Eb|w*E{~i#Q{_n^1|2x|p4lnUem&Di2BP5@$++0g4Qq$Kx#WSIZ|+|j-05)sO6I=Eum&6L#qDLWhPN}Iw9l1oE-fOs_~Ir~ zTqf>ISH7`~9I7krZRKl;8{VIPE$wqHNLv;cm%Bap!3qpn7q@qpcwWfG<`v4ED9h>R zXI+SSz8Gj0sj7LkY}GbZ`PIm(;BoaghLdbL7tR!LPBAgh5!9_-u#*D&_WsV=dqw7$ zNzdtnA&GkX(%xhg`F8)_+NJJR^;_mMs9{07>UJDh1Y(_3$CX)+cFfEjD*8LtW8TT@ zSye@NLLc^6*Y%GWi}MrHlTp8<8MPK;`|W)yU{Z)TcNlB>#)9sSd`G(U!&<0H*1K8@ z#`0T2ufWM&h}W}?d@Q5rnDKg?zJlk3wCn1+7Dp1W?f&Fl zfVW>5210&e{9NaLJmd!^2&vY1e^>*wsOx+e`ge01!(QgUv)__QORd>rbILjM`8WIi zRiw`Yw`~q``?VcZk+z*}$1^aeC{oS3m;%@tyBl!ovzfCdgzuvEgxMC5)^c5*iMsA@ zDrLbYbKh@ZEW?O$Aan)8MKhV6>%MgAK)4S(12_i(h5cw-!ku7O}HPxN@y&r;-TTryYybf#P9zwIwEv%$LAWg03CfAotY? z_5;Ux!q-8EjLH|pB4qhUqC7eOldjChXJ32#oJy6;*TEc4qkIB!uxaUnDA<17X8@;_ z9o`#C~$|9#IbohT5XV#R`?^oxjqZ7K6$(j;xtCQZoD0fCU= zrVXS?$WU7J!HR$g2q+*t$b$olI3Nl*fdeWJf+7mytcdf3Xi=P%-}|$NbI-Z=+_Wgq z^Zfqsyk2eY*?XP6*Is+=wb$Nz?X|YlqowO$EXJeJr`ILYCBvIdPw$$Zu|e5ewqb3l zJ0Rl8xeU|&t3kp)+=ozTsuu3w;tSNfJ z#|Tq>V)2wMzSD7wM-ZyS2egwFwm;j&%Yg2FL3^j`?hxLuL*1Q7A^@~5D>^gCDj(x< z4lJC@UG8!fb>)Mg%MQOzxa!?GSenmx$kwyGe$m*LX%0IW zJSD3&ZWxjj236u>ZpCHS`vvPx&iI)P25>%2hws0tsD^sTLQ?V~DP--zbD&`0)( zhl5La@TGy7Uqj2X=Jn13Vv1wa9m4sx{*|$>{y2pCa@1FHKG6hY2W`at=DUi0$6_`1 zcNqFEnda+UQtj$I#p}rj*al|%N{bS{(gJNY|0erFd+)?_$G+J42p0Xc?}F+cEv2sT z1RdI%SUHImutc*?ql1t4sW@ zE-dl8dT@!~)k710R~Jy;q71*OOW6x9^_zN3@KY+Mt%-RJKdvdisVcK3!#}I$KMd!s zJXSjz!OJ$pm!-63a0_YD=O9sZbTt|NY4i4REW>u%&vz4cBY`K?ha0V*D_0=4HM%nC z?p?c|^wv|`Z}u{}H2N*OYfbnvvG%pOlE}DrwxTUgR(s=RFMpLsWIoI$8gQ zPEXR9tnxPhI?Gg>UtSH6f1K^G;(51ptO%pfYBaoqq43)Pg9*Q>WV}64%&=p(6F_-p z-1cSFuI%$l*6Km!*4TZ7`f2-si>ie)h@l|`j<@5%+5CnpJw@G6Hez^?knV3ydE}*! z>oHb$ln*Xq(4~BENluA2_23hprA41fz6CxuK-lXFskCOwwSRE=TlD*l&l9^1>SpMy zd{6u3;3L{=A7A6cxcC5dV_n028|Alxa|Wjq*|-9U^p2j*h5lyANH?y0K8FhWYVF{} zMuHG{d-rsncGj+8Id=UO5@IR?HG32ileAGUGnL)YJmM? zWp@jEJ1z3Z<_-emD^;cG;JVz?BMa9Blv%3J$tR=kQ;eVMPe$b;jfqA5jwFh@s{ z&SA~aw4%|jz_6c>$44p=ES|zvE&KjULXWe1VH$%G_4Fj_v3G;eWgD8Co7((3hf!Hf zp$6H)nRrePIF{SOiI@8*TjM?0bX3!3%_$4`OtM9PWz$g*9Pk7ByUoP0SAmcQ*HR z#s9gS=-_`x#oznopCY!0ulj78 zUjV+Te;hl|@ZFeAA4|oyaAR_FkKCi5>HB%y!l@3?EWMTE_tg{oJ;eY(CA&VAKrKx* zRx;+uRn1|3)GtrN-O&F`Zy~>v#c&rNl^uZ3y`RMKG z=Qb13dEI%G`k#3r<{iDyF`L*T)y`WJhXYPtoVB0Q`-X7lwqk5#a+J?4FP=wzbNqRV z)P(Sr<`jMopbwb+Ys6VDUU%x7SEagku*>(}{j!d$u3(gGl!D!&$A zQO{ORi(FEK+L^|RiMD>NxiZFQ$NJTsjacH=c{@=r2mDy6luL<0Us+%=S;pI+2XHco;^9`o1lwcT5T(`|DlVz^-jA?%URbi?^^Bd z&D)!nP8ppFqK5JB_ka#$;xK02;$!`|(EBvAZb|GXg7=xc2cIy?pTql-1bh|mM`dHxy=f%)18C&33QGP~t}KRquvA`1k2r5hezRKlqP(w1A`16x8Qe6vDGg$+b`&cQ zwz6yXt7r#HDDPgN5o2=o9iDDdX`l*y_Vx1mYQ8bVpK;y`gsqFkAo+8R%~QU{ncMc} z>?KuN>CS#;K2=uW3%gZ>(q?`9l$MoBrwsAMwbhORo8{YZU}S88Ga5qFB9*0;hVIp#=DU9Gq}0| zZG;<*?hfe=tR_3A`pVPrtscjUYx#QSQZc=(?pos2WjfWD^>$yDY}UfOfQ`q*Rsv;* zcdapHSB#l#e}~O+dd&ewW4Moy3~mgwOrVdQt%QCD|0DWMoVnOmLhsmLTU!b7!3=oy zrfL%XvzMuFUcr7-8zgh5Vu^Q;ceU@23hN7AiEyZ(KLZ@c%g zh(8MS{P9A+&Xkhp@2-Nwn`vs|i| zaY~oZQT($O#uD(SLZsbdJ6#u(L%QEB`oKTz=ex5yoK51<#=0R_qn*Uh%(89I9?RC= z(KG29jtN@binKgArr&$39sM3l-4pFyvov8ZAsIf+kmgp^R$pa|Gv2 z@I%_}O2Y&{Y4f!J*ea#3S(+fY<3Z)kRi1EY+9YGwxXyV>U)NR`8t2YFE?I+pgz%ZK zy!GXIDz933bwgv^@sD+Vr7!g9&dCyPkG&qDyn~fTkgX8UE{vr^m2LxVrSC^CQn~_d z*`B8Pqx8BCJBO(3SFvV4L20Ogqa#cMe&1YWoT2m;J)1TW-c~w$ zz@vUrk~D88XhmicaL3w%@%-C)H@2g>`wYh-^0Y_xbuK zc$;Ph8paZF+D2Hr&opCwZR~ajS_%%|L>R13jNpLG;;yv$W&lT$E`EE^7rObrx+D&^ z&RaEZK2Xg^{;82J-&V`~nqKiHk}U4o z6Aw^$&5K1$rCg8eM||16z7P6fqVOz=eb@v)!n%O84Evnm|G3nb%@lCd*Y0Jwi%kx@ zSpPp5dLaSAdVK+v%TH+RlhJSQi|^&HG3Cn!D)vz++o+5EA;Iy z9$Wc-!w$i0A80?VTn}pOKuJS)32Q_D=IMGj_((4)+SC@;u%GmK7iE?k>nY*%Dm!*@ zhV#j=v;QnUwCxkme%j;oDc?s28a=xC(aKSZ-GFuCJ*>0Ho#}iMmjF1~rSGdu>%_*w zSjBwBZtUsj5N3FM0LOGAlSmZxv%al%Wv4R}bsy}?F3Xp$AzAX7Y6|fWe2mI$EvDj| zpNWK+2HIl{y5v^m)a(oR2W{^4>BHIm<*(Wm_1e1iby(-vmAJ-XYKLnlfn%KR^L5Kc z!<|Q__batbvbV{Dn@nir&Ly+q>ev!!AU25PvEWMmWQOv*Jp+Iau5_O?z}0rNp@(1| z&_t|MS9D9QwRNc6w`zJ73*avi}-j;vgoz{xbiY0Wjr$)5TVkuB!RxerT6T z1-E3&%!2USgFVc@zMt4%7(Z)bWHh;P=y}-fD>{4ZWVZPKCtRqgGT45OPEK?d`Zf;Z zqiln}!*&t-U2_dyz!PU8575qeX1tG`aBh&f@i)MJ=6o|&7(fl)Xw5ra`YiZ*p7#hgmcJ+2h}Uk$K2?UEzPw53d1%I7r~F@hf%?P4-b>0qtKI8N7^~Zfb7~OF z``Pob)1}3mwJnJ`&aZIeR7%jeeGt4fp3h9_zW@6+&oZp2Wb-R~=c#OdW$RqY=2te( zK2IzZuLWh>+(y?I#xUSk&Zc1eThuET0z>pSEIBa!O#{Wcr|g`IF!kOX*p(rB;6pR) zfu+ldCmw?K;X(dd>tF7k%kxDzOp=LkNQ&j%`IPHqaIWg@iFlX~Q(lnG`}uQn?S+=l z%m2D>BJs|nzP_-B^IOIZ(!||lwGlwvK3tBrejV8G#OK{UbOO#d>bxaFCjDE$d-%J6_o5Cl!+f(EI)LA|^mm*4eZRLM$hKRQ4-)iY!VtB$*RFT# z&QwxN`|tWPb%v2a*Pvb`_D^a%ns;~;Jpn^a!u>H{&v9PYPi`iv|?!M=*IDm z$x(SSmR(d>gVKg}Sy7C8_@7h@Z);d#%hkS-_I`g#dU&jD#R^;oHo}q?Zx%k`9h@tk zfpUby7hE3FwqSFumm+*zmO&}5?|x3b8YAIuqS|ASX{I*<% zu`aIE+*WF^9_lX5rA66$Xf$7LKv%8Dl})^dSWMwN@$eJiAUYgEe@I@H+CaC|#izkd z_BY?{#n{x{O`7Jc5Eu`q+wZHQ-%wt7Qt=B}tt)b&q6phqh9?|j$6Qw`JYgwN&B8@T zQz7M7nDenDJ7NjFOh+(sIzat$+0Oem%7& z{2%ne`A_)R+*$vb3yk*r(mpY@o|A0-Gwloyx=QitNRmOi%N$F_Q)w`%p3)7a@5_63 zWb1)AEAbQQewO1K+gHNb!nv#K6whQF-5mhMbxrei zRou%fwFeBnRhsVUJKdM}WJ>ozk9Stn@m}!)qdAFmmSYn(wWz!64rasH*a%xDHduPt zTh)A}`!~yQz&*BAH*H+ZVa?ad(;Ou~BR1NzwQ?!WYGs5r?FWs8m`1O+eZDHNH=cENp$3fD0#5B$$h+8r=$r@OClFHpP(H5(7cDA*o zk6PmdYLHz`@@$oB&l@lvS2z{)lA!P6Q#V|nh||!IdxLFGfSaRxNjA~NM`AccdnfP- zc1mt*FbE99yGyY6WXnwCuTyvHn>fc|7cG6uD=UxEB#WQtc~-wK@@MARy7oV3_d3s) zANKgO?%}HcxNmPgdWlwgpJz5Y&sCnz_rnj>eh1P$9t_L=+^Q@m~H zVjq`Z)y;&1M&idDb(HAFx}+a=_}y<{-(>G5mHS*NHCRY4LNi?4+rgK$D0LYnZsLpE z%ca#=e)&g=e~bQCMCWSgJWcj31OpnGn27iEl)j6>HF8G!bhB)7d~0q0nF=5@zHwy8 zulw}P4Aa5oD7W9VAAK?-;s0s@`7Um!3p7(46CtLY?zI=^9i8>Km~!d#xp53R7HZbJ zT5%jIW|#Y>m?wRISNd=N1dq&5_<|`VnjLjJpy?7%Q&;5Vc)X{!TDxryAJHQ+1N8;M zqAN{%QV~hj2|SRcVTLR6=W51oVheK$0NcOeF1hB`QlDH(%ks&!Ab~@fPpI#A#&H{IM2nc{JSF&dzI8~m9oqQ{rY9?f3F)=RdAYWFttnB6- zVh!g#tgky+40-3vZeq{OipxDX#?j3Q>SJzd^%W9&n9>yMx!|TyJ0FPLitX$SQsi&g zolPF14FA9jkV6qp!hw9^9SlUHX6yt+BklcY4`C&HIN_-_-NQrUn_c|eL3tCf_ubNI zlyj0lFEZGjjVMZ(TU!MqG;b%D%4^uNu?G_ulnxo9!prU)$;06I+7)3l{n?SvYZ|89 z&1@tW^YGu~>a}4fy@1h41M*zu?h^8YDuNyVWMQ$zYkJ7*WK?GT zi?yM|nmQGhCTL4#`E&;51-oY5h1Y&=HRZ~#d69UHK3}ZA+otdvz}${Q_~r(pswPH7 zNQ!y)2wzV2{ztbW7djg^4DZ_I*+5S3I1%)l@|E*FZyxE(KP8{k$wqL*r~q|NCcXw6 zf@y}dPVU%Y!!tJ67wtncls0a?JPwVF7TO2BoxJioOY$a%i)UZ!l;@mJ)(&rAnxx-? zo^q=4IIlQ+QZt`RS9xbB-8%&ptkU|GCKCql7cP{sQRzB%(`~dw*+WVnuOoy~a5-A6 zchu&sOWkdf&O4#>u|7GLTsGqO%2oFTN+*IK0Tln(yvtd|4%MaKmYikr%caoldb_R?3H zKO^W^!}sA~jk%(|w!R5l1?yeGO`oYGYIS;CIX!e0Hnl0}-8Nd0>WZ2Q!nbFcZ_o9< zJ(6RkcU)b2(tbe1EAHN?&KWjaA51}xC{KGo#K=NZEvC!}>mI^z{mdqr6|ba9-(1vT4GdsykXM zEi#)Xh_q_QyKL3CuNlX}(B5nwkX7zupcT^~+}%zYQkN!LT3qC97<3+^7z$gOMn=QA zo6?Af(6eP3r&MlvZE;X~mEY^Jn@Ndp7b=%225L4JteT`(vQ2*k1dIQT$lDtg@Wbc4YaqWt~lBt&-iIt<9t*Uj~TE<+wnJW zK?R-8?NZjYF30`A`j%y_%^j)bMa68(SK{wh+9KUE`;3uY*_IX=voX(jEisuwDJtV+ zUq-gABzI_AYdaTni_5N7!khoLW6Juu`HrXGjy5fuysc$2#h20L%c!uA!*N)=kK=yx zT?IL1y(EP7xbCNPh4b009AE!BU%%$=jCo_7LOXQVVTIA9(@~;KIZBkL`|?ilzm}1NX{FmvE3VyObz4fIX!NN zTQA7A6D=J3HS7JbE6A_~q$0!Ei(+h&8s_vlVN55|s*ppn6Ily~qr1sX z^%!8ft3}JY>Q+H^Z?at}6Kvgjy}a)*Ht1;Nw9MbxD8AZn#QVLzjlq}7xyVYjM}K9< zQ?%dVL`SfEQ}EyPdL2M|CwJnTSfs{nCnj&>$i#RYN5RoZaTJPa`%nk5d3(|M$ItTX z5~bmrsCSbKZfgDQY9C=kf=<{XgzGkr6T=&=s<5Bp`deP`n0E0tnoxQ&vts%T`nIaw z7cwtO*03I~>S3=p_*5VAFctL4U^d%|NIj*Yoggk50hD$C6yq}-(%%<(-0XCDr0M%Ra8at;Uz zs9>9%|1qV{q5N|Tt?uZ+&s{15JL<81&h&y^j!Sd*2<;kzguRSi0^522Z2PWRy_hy; z)52zpi}k7f(s32$ANE{b3}b(wo!F$x%82QC^<3AdhsS$Pb**jdrQ|Is?h+D=rvW-^ z?=j}y8fr6Vh1zcPpbQkO{S+JaT zHS3TPE5@z{hvW1txQ6z2Zf4nXW@P2dLYjKyD{Uryps~N1exT?;wib3;6%sPxg(o|c zP9Vl@)||7fgCl4!wES;?H3qj(m&Ty%AIjw&d$4%u=hVEhsk2)nOK*LqrLeX6m*e9a+HPk+f4 zvLDMXueNu5WM~q~j83NZm?Qsf{nlajO8OM|%{z>8x4L!MD4!k!56Rk0G@^TPd3+$M>{l(~ONTPB4~D;sDJ06u92@@yzIT={~L$K`(ja zr}%{{3d7hL_O%nzV{E+Oigh+|>3|5Ca5eA@8??-~rD6QQ8u?LSo=*0&w8W&&mYqj& zvK4(jZCn5aP#=&@$@6fxCEGe+uiNz4O;~UHcnRN3Gl}#>X9>q}9(HMhe&`-$cudKy zwiO8o@O_gV3*ih_{iet5EVSdMNSDgnTRU4>D%g=wv16Pe#FTyFL24?O?)u}B)VXkF z=u%vCgfY@LG8CMuXPpU`!AywLu&J3$zRE<`H5wJXSY{WwoRUs@4mu9G z{L}xNwi0>G-NWcgcU6bYcly9o9UNlQ7K=~YPB$Oa*>&s& zzxONt;r>l@U8235t=OIYEMZsLoU*Z53Sim1Eb)JxfA&r3L224ZUgV&`^$8ka-|;_9 zgZ~qGA^lK%pnuiqhx>TC++f>Y+*goZCYt$hvhkfl(i0NecE6uMZ5Ic?KbKBo+JPhf|#NhB`rV^6M ze-=1quSNg4abxdwWDOXXvC~)$v;LFpcvqS1NR2#u)*u+hx%GSH7|mGy=jQBhiyiIt zAi??XL!Ome?=Jg3&8*V1B*O%U?v>br z820xP+~L~)-m-6f?|8hGZ;VZ|bH*IjG4CU-W+!E4#*(G7=%da*_&N6o<}c_yGP<>G zqg*qI24UWCRP-iv9#8g|nIkriDdd>0Gz56Xa)@Vc4|^egBdUn)Gicc%U5@hRDbIJ6 zEdErM(yNQ<+8`Ydj#==du_A*r|%cVd1aq(*Z7)+PI0THx7w~a&i}XL zE3Fr%=_vUx9q6_MzR9x(KM$8qnYQms6O&w-B^FZoJ1rfw{!8xf>7JS9nRI^#o+{-L z=}_=fhjrhiuJq$j>6}GkVu*;f#vC>??qkbW8N8F=sj|*b@mB@jDe_g>=fus~g)^3! z(T5U!vFM-V9fx|5Fj>7(0`V;RxU|$WQg%Z2IoQ ziN4#}IjU41*(Oaj1kwOIJpvy2zvBPrvi~y1jAYM86LkM)WshT}%FBRM{!UX}F+(!X zO)*e%R%+a$>0DA%W7o$YryW`!SFrKI7OAsoS#V|@-u$)G&JE}uH7zp>DJXx45$lSuuQG%<=fYZ+3 zAUAvjsqrSbc@EBw(^H@m(_8fEL|?M6kuu^-2DH=(wzrm>8phM-bKWjScA}=t4x3Z< z6`aK5F~nVwwsa~@jOq1RPcQ9h4ES=-&n!2-)spb(W6ia#pIUnX7u&B6#r#Z#?cr$6 z-UZv~&tlyE+vB!86CavnZTn!ly2j2{cUdzuwmiqv?(>QE&i8kxf*->L^dtA>kgSt^ z?v{Y#7uiee&Ri>Z2e?EK`%Rm)x((@L#3%MVGhy`_?z4w`7QE{&eI1+G6I>1m>ll?e zSLs7N=xXjP8ek{Wph-4uy?=Ng(V(umw#mDl&dP}U>p@T3u!kmkYQEIJOk06PF~s z?N#Ze%CcO_6TQFc>0P1Eoh=H1$mC`y!|o)V~FFsfnUc zR05$EC)dB>>&d1Il+%6oe#^l$DN zCQ6qzIxc#P^Sm-zT?W%IEMI@#ufW+hpUW%Q)(wq}HRx8ZLVk&NegL18l#f^cz5@Pb zTnWC8A6mGvviM)bez)ry_l|$hsF9ewX5rNH(pm361V$z3e?Jkl~yKfi(DstJ^+2Am^CLor0oey!Ikzw&unLqPo%KkB9esFV6 zLtE3cuz%b!v~SNZeBHXM0+FIQ)}M`|ll5`X*}8&T(n)`K#T3doBopUGtcAnn68kf5 z>l0Jc&$YENju9VQ+oY184!=k8d8`g|_k{4_C@ZmJ;mZ&C3Uk@i|7RP8|(;yV9{WBi*Z=9{v*S zLFby~J>ed&#*loA{{fuB(nH60*7#S;?JH& zyW>kX0&wES%8YK7|6>J~mZFYF02@zNw!8n}+wjay>?Ot* zhD*E?6lswG0bQ14@;~y?-lz<=@QHUzOZ?Q)V97}BljX4QJKWjcLFf3b2ndSg?Y4Vb(4I})HZX;lckTQ0qmd%)o_3wQ_sk3_pOp74ElrHeI>eV%L0)>^Y;J3; z<*w9DaOaee)GC@B>g&|+r@Tzc)^{6qHM+j5sTobQi#u-}u_9iU?#q6eFI#p2|KxoA zug%k0Ur#+h*SFS5HhPB_;mx;aiEmFfeZt*0?Gv_k>F(hQ+ua%IzPzQryraB4aPgKJ zhKB~nCpO`eD8x2ay7doH5{GFGbd8V$M^R3ASnnq9>?H1&6OqfuMV|ry%v_u?-RsJk zHJkA0Qy2?3#kiGXgLk5TDnBaCR{&3+moLp*Ru~#N$4Bz?FxXOH9xV`*Vgy%ZTJ*T~ zxqNOqW`a1J1k;>ZMzPHfrb0OmET(mv+QK+UqAS{{jCIg76BXZ9!JJwFCP_!ZonclN zCNc{vwyVDqjK}#*^siC#;2Q z_^mNiX)bn~A8tBNIxmiW2WUUcWl*{sOUrzj8l&@ktd;CB8a7vIyXv_-9iQR&!sAp* zR#^?c+)8D6uh_v2x|1eBD+;N;tQ83yU+U-El_`DD-fPv4upJ*-dQZb{{7mOkuT9}7 z!}?!&_+Z4}V$mKKt{qChFdwInw&HcO9K%ukv(itgxo)tZn1Q zFWbp24eSe)xoE7IO^J?QPX;HAfeL!AEx_$1t8hYCayICgD>ExDx5JlP0dJ>~qRiTJ z%!=+|GxDiHE z?n{f#hnr~`>+1<_RCW*QVg`!En)uC@ldC$Fa)Qh{jo*h*PIkLp^9b>~WKr?}w>+7W zUH^Aep744oHTL??N&4mjgt6Bj<`bdzDG;JHV20I3&%U;;Vg)2#~is2C% z342g|qqqX@oANxxHna8k@J>Pg=sU$Wv)?81B!lEHqelS#K`(9Lx_3KrU5=U@8z)n0 zDvmQh2`~xCBa_{;N%>H!oKKnR|32^#owN5YRx2FHdVjWJk6C;e3X5aKTwiv z!}LDaG!UO-tDRdrF-Hk@E2VkuYijMBta23-)y2hi^R&{cbvI&Uc!NCp6ay1DT$PWH zJ=|*eIJ!1~nj6c?TV&SQl;wk4CUF>OaaH;;reTY$X?#dE3$t=^Q|wbvF!HCT*u4F# zSsQFFG~xop+C)iC$p_xqYKVUt@f{?GH9IS(G?p`X+3g}G_xO@2HE!IEQ?JI|N%o%J zw#cF2b|5<%R#>lS4b2VuD}Lo~WIi*f_$HYpXsKj>P939IhYa?0YmWYnFvAOq??^c2X z(waE08I!w@&G4NobB@xzv1CZwO=-2mBg0!ajZBUg%h+A%oD6NT7`bxyP#VLcFg7}d zUuM6+VtJMLuFBOhG@zo;`_z^_Z5_0b{R_NFa;s3vSh23Xl+VzSvuQFlAe3pp-CKE` zg|SWYp(Whihc<7n`%ao>^T-BVpC|hGdCK#t7Cn#4J}zB-zjb_~fa_~Vo97qPoi%9! z|9Q$|X=UkN%(XbYKzYk1*`KAmMm7;yz1XgO6M41$w17RFF)>(zUN20*h$=~3=O^-3 zOhTW;cl#ystpA<(V@LD-6Zy4$ee${IVmbTT)h{nfz&L88Ky3SnjqvWk;D$+h1CzuQww_N8(l?%PslH3_#fuX#wto_N2Pg7Y zjrC4!;50jo&zB_f9M9MqY|GRyhm_^V4YGam(z5&_cj)^=6ZxVZuANeRbyzCjhM@|B z*p`2IGQXgC2Hs83{bh;#j=qhBO+7^j)xRi_UlbQdxjCNl!0Z=uQ#ZwqvRC>jw#PY2|Ag!uRu$g6Z(~LKVt6N-zqz**dnN zXL#^j%QT6;K3e%MTzUzB8z09g-^&Ohsf<9)4H!XzR_7Q&8^%`74y4)E}%T^J1Si z+I-w_6lC@$N!m9!+A}m>s!uctj$06QQ-G?zm4Yh{u6Fdhk>MeNZKQ^?Ytt&hdRbNh z(9*D3aGp;aHZxpcD8a*R-uxf}vMavGs5hKM;N(hiP z<}g&}GM%hbaGpN3!vyKGU_~D8vbY2nEL*JxHe8sf8ybX`3B1-!376J!)}~=$9K~u0 zvzRM6{=(CHUCB3SRa_k&ZyxL&Beq}>-}Py@H5hP?B%wr`Q_?UkljDPZiGpmuy&?@$ zKhig;qpD<47VgwEoOQlqRl!7`*b(E~aatO_$Zi$n^yz7MsRWxf+7wKW{#3&)9^HcT z{n%dL;Zz4N4~sL58#`yDVH<|~N44FWkcJMfCk^KoU!p<+QuOQP-AP-b7H^p{Wz{08X2~tq-4aajzXdw*DMcIJ3fvIs>6_!aMd>hiEkW1icN^c zx9^t?7LU}N8;=#LV1N~@Ca!Wfmcxo7%qdAoN5etE+A;1MnHV!?mgb=mSn33wIsto@ ztRq(xm$8r=5lon38pR{tBihit7Y4mPd24tL_`);MVUUFsK-Z_cQc1ydaEGO zU_@}9-?HFJ=YY`!yyso7GN<|B>_q*m3Iu!$YMSq-u|$4x@T4H^co?^Ess04W>LxKQ zCNg1kt%vU3+OoVhLI24qVQaaq0~1Q|PJFYu{F~*-tc}7J%wjyw5nN15@sVA1+fF$N zzoJ{%+t41dmLA8$qmTM89oSYw(Y<3;USV_w`<=ZuTWQvII9 zR=+2qv#qt&#eL#e`tAau?cTe9ujAGd-N$qm_W10=CM-wvCJobq)7y#tlW*7x7u=JbaKTIMj}C z;QqOV+Zi0#BeSqOi^C#=#Og0?l-qMt`;T#VLs8@;?aKh;wqV_aUtIR>-2CC;E0^17 z;?|(vY#M~o%L>>2qHt)#XUWJ=3}0Gkyf3*9zjl4pud5AdpyHnYsqP*R5Dr%RE-U>$ z->c!1@SLHzoA~Lr;TRGJw|3(YLlJnt-(}xhFXPJ?!+Af3&j){OP6510U&XD>%*ubU@lkC=n!n8VlL&WPcHK`)$h-ETV0L09K4&3Lo0)`+O@Uq4XYJ_Go13c zXebr(d>yjsP`|5=Z^pl!&FEgXZ{6=`FZHwgy~{p{-!FV>`|YW*;En<8$phuzVdU{{J;LUE998w>ge3I{kSwIL3K1>DW2gvx%w9YP;8k855IA9c1hnxJF~+ z@XYb#O)=dKm+)fY)eqN!llr8>IeN-kr2H7YVs>2q^}hV%Ain@_h2OB#1oKspeN1ON<=M8jJqQ#5c zWD3>Mui5AG4lT}1iFoORelSzW%%LdJ_H7<-{l3QgKFHRWWIwsKr700B#+@E#r2DdO z@@1cw+2`Iq!AHulW_nx*u{M!1GYq$(vqPus{8t{AcX?bk$Nht`2%Y2ap)7B3@!2^7 zYiP&95g+p6Z_OLJbfL@OZK$FI$?0lqYi;ke^J&gxmf-;HfpwD$`qE*xYmSZl9Q$r? z$@(;rA0<*@MKeG7fOLmV<%2&t*ckKM8Qq7X-yri__5aeF5&$M_deae zw=b_-eDqOAAr}NQ!!>9cLRMviXT zci2AKH3b{nOxwIUHV+)TyHYe5ccZ*Zh~E z1JUK)&ZFE;BRkLYz-^ufvSonvq``e1VrJN=T=Df+RJX2lx-aiTzPt*uyQ`&x7?A{~ z>1t-oW0TU}+0~ZN{wnAD^6&8FSColCV-*RD1tSv6GAPmMZXfn#o}NuxH{XSOQi)h! zELDTCxfgB~f#SM8>g%dNA9tGH8XH{`WF~dVwQ@c+XiR_Hm!HiiLG^aVK;deV(~~OX z`Eu{}uU7G64`lK%_yAR!0n z%oxhbT-Mt)bt^KEyFOkakFwNRvyU)X9_cI1w#jvvREm7Nu z>?~?_biCh}8}35tPNU+q>7VR=f=!Y1ml;U;F9;r-zv#cKFopti)i*TPu4i(p?doiG z!@oF2jm#U91liuTX`G^>1;rHG%i@$T| z^&vesKk<9Nr?!7PrSIFo>~;;!s$%ciyK z^uf~h+xC6=DIxzm_PyTfSgPZ@_Pw^T&9}>~)ok03&^PK6SZVtAfJ^jAT}Gd{w3-?l zt5kbD&)D{;4f?j|t)^!~sYeu2_-_9cZx`+LzHf0v4uVSrE|2q`QC=d#h%4{ORC(!j z?r*`3I6CanIa;nmvzck?h6+8ylcWC3QuO&9aC1`Wb@qmRU5~prAL#1t)3^8F`D7@> z0(_cp=W2gdXL>BAb-VC>%8ijn>rR}_IoZMkBDVsOx5~GA>q<~ zWOW*)!Tw&p{UwGQgk2rnUyEt+w^r=g@&@BNZkO_F(_nvJ-rwzBt;HoS?{9H=leWD5 zJ^%lmbgeId4(xx_(i%s*!*p-%0{-EHBu6dJ09({qNukjpZ!xgS)884a2qK#oRps?} zoAR8W8_kEJpJ1K79+_P{?DQDc#d!hD?4kSroT&qvE^YaFMI9H|eLf)=E zPkp1k)QGG{o(F(`X6}T(k-me6aakLYJbb^1+|!H<$ZVqJ``xHhthCs}w}5XLyZ*bVY9F5$`g?&d z!}Vd#%=2Zupad@Tb)K@OQP&GSoZe@j@9MpsG2wpaszl#BG!Q;7Dk-a)cj0YWp0W-| zmUWRU>%fw47W!|*cUr0bKmRVg^{ze4z>BK+ub$uX@4T7Y>c|_6%7q zSZe85QQu>~ub9_Si0_8#JA5^9&0r8l(pj-ia0&xG>}Loo9gK>11vihoo$e4DGmOGA zHY?6jK-ck&BSY~VBDnc6oIUXua3)-}X{HE{iUtO^xN9y>zNw6bw0+CqrpZmk18z&S zfXl($aUYXmgS$YTS^lAl`6%;nj+{o_(jcZe_s?Bk?Z5y=_?W8{fSQikQszZ7c;% zl-U+w1B}BcrdelczQZccU+3~uH2DEz=u~^3Nv4k*M1+{1HahtD$`UuKntCR%N*dc* zMvI}!uw}Fvb+FA`vCi8e8qC6Py`IG(SI>p3E>=LYW6^$qoG)~%u9 z&5O|F;Txunl;$$}~A^Y=TLb<0;|4y?EzJ{ZGQ z8dydA?~GwBLX|#<%l;U)P2&@Ge7BC*7Z}hO@fG?#2ia|!?Y-zPi4bR4U^QWX^&7;paC%-;^|E+wn_0EU*Bf*&&6(1l zi}R(^&kpftbp|~W$MfB_Yq-d2{m-vECY-C`ldmMNqocu}%SBmvOPAPluzXow?V5Gq z;Li6-%WPP^oCA;*K7W_2Z+)H9v+|ByZ1>z$c6nRaV&o`JdF5?c-N5Oj#i_h4%5(dZ zlKaPTwx+!p(Q+yDP0*{U8NX8MyR}P8Q|T(cm9DEU3Hs4|UZjWYgKjPL<{{+p@6i1} z&6-$xiTde5t1nJ7#{iquoi@&uzl7XgtT)Xhut$?$`TObSc=EL#_O#K_3L}+eSfZG|YD%q)Yv!tS*9rJGP9(Ddftk}%Tp?&+BS$4g;JmAd&Q@o{TU)sLs z^El-v500?YT)_?xW9?*e@Lgm04tVk1v#q~^onc%~VR-W(_swO*2>CfCs#^Y>vq9n6jc&UBO;ro*IC4iq5UYGn!DbyoFuC}YIZOH+oqByVO+M-Kb31xOm{af z+e-S;%E!!cS*fLIDrbFKGbP3q_re{o%iX-2ddf#_X z&{cRyUaSw_CFxpTQU>3_PWIx4`Uf|6 zPq-S~Z_2A+E0Cho4rH1mS2+-+7v=3~0ha{bg1kMQyh`#`_=$eq;rk5wl~(tv1$uc488fcJ}P_$CBCee{}8& zb8MKW2H>fw=BYC+|5rsD61*4d@mZ7238k$`6EN$O8ZR?7KWGof?HzZw4RMOEeNZ;v ztW42398BQBdGFvz@8p2v4Exj4{vFRke<;D$X)ynjJ>a#RK>xFLK>smM|Di;?pB4Rm zi*}+HV*Ts0&w_pxw37b?I!-NL0)2;fK;O$feMfehzEih&XXrc?<`v=sBy_RziNx{$ zlpuCogtf=np{*Hn;TU<9=0f47F*qK+N6^M=E31e7^SEgb;XrMw@MxWoYSF%4tdf{5~G|7V4a7hVd|Fwu=^d@YtHbquMO&mIVUdar(vvajh}7up2w0u^t=#t9yw( z@$K3H3|J;{kCcX3bD*lExsVq1Ah3obFwRIu~Rdy&#HU)ZQi(}^I z&Fk#@Q``5z^5SZKhrU$*Yd`DFq%mu1?^Y3Jy9dTbHp!lXi>~aZaum}uWcGaCjNG-r zofm(s@*?vV(&YOgzA#h|jPj;K@yK*p-rUt36#bWnyOFN)O}b-TY3DpIdm(`u;us`|`ZxcyVLmW?;o<@8z$?#QU5)r;3MXE>GKh1)N@9_Bv9xg9kELoOXJi^5J@DOK{7&-S-MnXg z{|Wznd0tT#W4mQ#@yCHx-+xknsq<6(E&Hy7?}Ss}yU*}D#vQ&3xT}oPynt8WyU+5i z>i%4)6TVB975MJWWwJGIKFv4jj&Qtm59wBJ#{J>==w8y(e3Z`#c4uVge*>qdl;6Z7 z;uE;ZX{-*3N0h{OVL;mlV{ok+fH?$$}$0MZ0^}rt$@hjmmwO4dd zJ9O7dWqgf)yHamB`>J$bj#;ZXK$G|7$}>>R+}xq{O%l(*d-7#43uW|v>eih8Fn{B2 zbaSxzDrsrn+S@!znx6BDz{tA2&DTk%Ds#@p9&aO;GvBaj-STU|+@H_s+nwRdZ~8h) z`BHPT=JId(`~+VXu@O(kZ$*CG!FL`hCeZ@r2V;zsGyv zOXYo+f8yE4_^U_qPU(T?ALnd@7j_ZNvL;F-lXaR#k^J_jQxSip-CxFwF#dEXaxnKMI1kV*w3UaW7Z&pt| z*K(%Ov$cD;a8CD7VR*yD#v=QbWOz=r>!sg-N8m&KPV(V)-a~&X@3;IDA3nujJ>o;9 z2R{5gzmt6UH1Ap8|G|G>o>%0xFz@~jSoQNC^_M#T#NV>-N_b2-1s?k=zhm6tv4FeE zD9sCa1s?kw->UAvhdSZ0WLbg7ZuUI(9pI$zNss$8zio$>(B7>xX5{`tzT>x20Jnzu z2l=JEr*C$K_nrYxPbu$-e>4wc)N7wo-Ydlzaq4g90uN&d-GPv|SyDNWHw!YOr z`y|#$W%ZWupS}tFw+Cg#IKY3Q?JA=*FSIN0UzP9yu{}LLntPLF1^&xgQ_tZW@tP;i0BL&8DTm z`XZ*OqciG@mjI>XES+KN3;lZ_=TJJMQvc`<*RhnV`HJ| zpYxrYE1vpU|AT^yifIMiFI~;KFzvUt)b*6>EwF~U7qGcNhhfS)IjX-C!B{w$@stBV zT#IWS{tIK${swQ^0XRJS?Hzt+<6PLHewFW82ySs`EaHHH@+7;!;ziMW?NX0hBApuH zP0>ylv*S9Un|_D5Nh>MyChpp;;;;HabXVM@X8$fe)1!U}X9W5t2@iUvl{Co=!8@7Z zUoIb3xnD%gO-=_=35OQjXd$(b`!<+`~j&_}QV-jx-ZJ`eBs+R-=I z$IIUgENtySRsg5phx_}k4t}~Bu-_MXe;P7A$?tkU%F7D18EzdI9OJMF9+13H8uDUD z=TG4sdBiO=!<_+lwtJ@iPTy*->BbTGmcfZ`ci!O5kXyoXZvrYzHqi5 z`u}|D2+vH@nL63GRiu-sP4`W*=#>Vn^sU-`0d=Ym)wXiFr`xvKw@vyhwhPWj_#kdE zJda;US<}p@Dz`j-5$`k21OZlbT}swfGGBM}5KqiC<_`UIWAF7UgJA~(~5p% zQ};-9|7d10&tU-%OCD-`%qiyi`=pLnR9BV!8ut?FwTSR`-AB0sS|za#yr}fK22+}z zO_o2!12edna%F|3MRzYZC2)o~7;kr}|uc zSuPu%Jzr`ZU7MiAb)FW(DY?=!!AX5Dv8`gcBAEa!HVxqN{u}~`g+AUBVlWjYJ|CwBO4JHqxkv+t*~$2slJrmc~Za9SWoID|3#VC(;lZ!cDHxd zb=Nhn>gZerFF3u@rLAd~n_e78TQB4;kNa{KzO}u*tF2S6>grahJ^GG&lFB0r?rL1h zHn-w~SYb@7<`JfTrZv|JI+ragJG0%QZ zo7FdoR9G22G+$U<{{po+|Gc;Jy~gG=bRXsEUA!IU&>MIcZA3Fs?VY^;`}sEo{!M{@ zQ{dke_%{XqO@V(?;NKMZHwFGp0b@>Pqv^XVu=^S$w(H)UliZazCqKqs@GX&f>9??5 zV3+pwd*@8`4|3+#mk`zbA9?f0?Z$lOb2&5pC&s+$)W|&YT4P>&1hz0Q%bOQ3V-NI! zyxDbS-t2WxWNtn^Z!Y;WcHqr9^Sa;U%+KGLH{ejrug>UM?>$Jo3rshWI0uIe@h|D3EvHqAAnI9jPH^+T5Z@vwq?RrVheCogQ=4$Bi zwXfvNm!=u>(}g+nn(Dmy&xXjn>=!w+?ZuHf`xs-6|2b=$-$dpH@H=2pWFCV~n?98{ zH@=vChiCGp;X3TaACJuMPR^T;H{{H^%Z+(Hb)EDLV_x(QY}h`RHy2!;H=7HQ+2eb8 z^KN59iGKM~vzJ zZDfxATHZW)bKd;NtMcZx-@?Z4)|~m{Un29`et|fzwWZUnfukqTu!^+^f+{ZpO!=46X3}sz8#qle>P|KdmZ%soiVR| z)R;efi@1`Ld9&<%@E*&XW6m_@zNd*Y<_#Tr^Mwl{ z^F)NB$X7>Z*6}%W(o1k#Njtwp`E$P;nP1!pueV0#I~#K5LUu9EZ8PRSzLGaxcjV2H zA2#MRcx0dZ^5)?;=gjQa<;ZzX~3M zCMQF??TnMIIeF86FXNm#{`!KvdF)hU?m3^nuYrF5K_rwzXk#NdT#++BZjQ`-S8$eb zGroP`&9`z!@mj{r`>>0-<;R=}Jz>nf&u5(dE;5^ck~debiOg57r0?f)+EyEx2m5p8 zhYRwihjBdfJ|YD@oHIxLH8LMq9hr`LW6u0S-n^`v_S_tqPrNZQH^HZe{F3_b$eFi& z624?ybiFlaKJe?vd=a_vrkn7q@B+r}=OR;uj5+qzjNe=G=8t;=2aXTiL|d0frZCPp zU6M1a4$7M!!XGDhN9NmCBHu2~nbY@$mwu2phwZ{%{QdCMl{s_y8F_OS{r%tIdG*os z4Ltc9@}f%_z*RZZbU@x*`Yy)pBF>{f0X+QurD0<>|21b0|GqJujMsfm&YADt5Sf+8 zx|h+u*M2Z>p16~K{Rl9JM&_f)uELk{=B>Bn%>CW?Mw|zI>LYW+*YoD?8**kLa^t6r zgWeg&Y=J)xJTqrL4PPGzeHZ>ZXD<2zWiN@$``G|_W@Fy`cpv!n3S&NYE;6f!aeoOi z_^O;a^C9{XdG%^|vF2@gbL|(6ncO{RUis~u`7M3<)sGspH)H!I`0<-}z*849*6FX? zXm9UT&>1=MrY9qF=kMtkc=z}7BlCUwup0V4@k(R>vhzhH2Mgc&Csz z*Srkb2OgKt%$X0rFft9)|M6WSvjm*yL#MqNGrxQ(K45Rlnd|AptKXV8kG}=kb}RKl zt2+-h<}b`&ckC9K&mceVyDMi-MkbBZ9|ib*26bKAY|KUPjm%{)ip(WXF$Y{keA|(n z`Nd}<)6;LvEiI8*`v%IK#$M@x$h&8-NqD(2Kj??I-eb)77=LY)I|PbL@V3^OaXdW_%!To@NX^1C3t(H{d_SJLBwEHSo-vj5+b0%qQUV`zM$i;rYWD zFuTbBo;ED60#Q{I(`{Q$F z$@}u=vwU;KoXETYdHyBl^WVY?OE1cq-)&^BpxnM!a+df_X!%rRZaWEiIU{dQJDsta z%b6L>^RN4aF$0Y4_Q!MPCpWS;iyhM2AI+N&Y-Wrx7T&WyGPCLX++@zY{CCKjL1WsG zFPoptnNR$V`TkLGUun$E%(1WdZe(_)-wZOKb`fLyEc$@4`2JsUf8xf-ypw+T;Mbu0 zKhSZQA1~Vjo#`Uxg5!Coo*yknuEL9t9!T_y`;EDKkDR&h`^KC_UtIoWbbw14qix7@ z=Ia}2`#s3MJ&|$eFG3dl0si<(L_bC5CCp8?!wbI}H|F#3%pdn-o&x7HnR`A>yZ%Jo zd;UE#4;BLK-h2SQ_~84oDrz-m9lUWrJafUqy!l=p z8G$aa%OUug+sHrU)?pVL^OfC{*%$bMbgQt-jw|yR*KgN8B z96jL6@b87l8*sh``S5xA@&(AquYJLo_w3DWYsS(5^WB59u!o+(d~{?E{$#u!k~5!W z?w@g5WZwFlyqV8;*8{)h7UCjb8=3RJf(}6+{p`)?mXEGWG#v$A%B1Lv4x7G0@?W|A3AUp`Y%~e8pU_+xL-8jKy#M(wIL#W6W1)GmkJ< zANgZs7QC5x23dbQeDWCmyXs?%3uMy)-^`n#E$|O>^!3Q*f3%>xw4-O7i9WDT&Rp@` zoO$=X*sATp-I#@Z1D$_;53>7OexuJ_dQ{HbIe~2hG=JBDdGjUYQSM8~%!7=13p{qs zVa9BQ)(_3in{DuA-@TFf7<$za$lnF$!E3KZwj4~KGY_xXJ8ynO-yTi5m(bqt{fzm7 zel%}G<}gHp7v zf>&Ao%#V_9|MfFtS}xCNC~tm3f3EvRWIlIl&b;MA=>8~bKfc14Uu}bq^w+oE0Bx!3y_A2`$MfbB z#~`PE%edYnGC!=P5BT@}b8_ZKjLZAKk4_EWKJzg2fp7E7Vef6to1guKbA|UaF2L=B z|CKYZ`UU#P6*+S%?P~cRFz~^$gOO{|gO2sOy!q{i(NB(L>@zO$ zzis9+4_3p^W0&CtdVbCvZzA&%a9iAA%we68xpF9HdcOzV8Hab$FOOXZFZ~FaelT;= zjp$qN$eCyQnY+=aXG7~dz`#j_SaoT>T zG1VW;nagIewmArWm^t!=jFT4~nll^WzrP+G zz*5 zvhpQ=gxW) zHg1gDWjE%{?pGO8Gr)X)RnA;8!JKuTG1owYp(i49o<>KGu&AV>r`v&wo z>iOG|v=@H6@@8Z^^0%G2>V&b#oQv#vaw8`C(CWp=s66%T@git>19ETIoVoF9(1pIZ zYcuvz$dfLoez{gueU<03P;3HtAX&I%tcVqSjNEc*RE z@^U$|brw7WpS}wjvgECi`Rh>5Jo@vzc_(w+6BomWl+y@*--^r_{6l0$FG21y4_}gp zF5f{moy>_O@~e%0`4VHY?=1N9Ev#`LMvsS%*L{SwFnZz@55TWC&>xKV)6hfjfCp~{ zpSRvc`~J$ft}*6|$ih0t=6mL`KX86z-p~m$o%R-`s;DzI%DMl94rUv)5g#ZQ_0nSbuP1DSsvbt4Xbjjnj`FC)`?3p|ab@!Y?_qxaCJ*DyAK z`v$tykyoNmFt6Xpc<-Z)9gOn@uVwAe{AK2%cQKzYZ)Y4L^p?B`{SF?v>O1JYtSK+P z3BCFuW2)b7%*t<3-_z_7AP0{p|2Aa&iLLP5ci5lV3Xkm}OH=Co6zf^^fqv4@XP#Kb zxcDt{e#0Kfps%4X{RegJ$NK#M^wbS`^Ti90y^Qe}9!)>~m_3K@GQSs)L&tyzW8qS0 zb_0C;QOZAbf8-xLaBdg##5!a@ee?8wIdkO|=%RNZtE-vcf&EGc^5WXaY+>$QJsO!W zoX@zr0=s?YhdbYiuHJ*(ZqA#nKSy>vhCclR`fyLy*NmV2;jIyP`{J>jxvG&k#rMF2 z9muF7nBzaqdh03d`j~H?_$WA!f-`-18+^DY^Uq_SL^goO%T8r1znppd2xQQjoH-I4 zc1Oo-hp*3u_wJ&M_wLKwa8KSWME+LOnfuaj@0`Ql#i@DI^og8#>DwZ+{uj^z+8x6D za^RD!0Zu|DZb4UF4!^-ie|#oq{y+BKGrp>7+y7okkPgzTK3;mpZ5`G!8 zjXC<9d#(NW!Y;R{SM?{p>`ok203EB$vz4*czF^mW#7>Q5pNM~&VW)Du60!eX>`s3t<4Rv->mGRqJaer?ec*(XaX_%b)W3Ym8Gj}BVjlGyvsW%o#skLK z0#BEBbv8~C`_{mQm89n6RmaJwxQTk=R&)_wp2Ii%z`pPQ0YA^&Ly^0|M+t{xx@jzeFcSu$E%P zBDb-(8>ltZbul(wAtyh8&G%$p@%W41$geLjcX$_tpNN|4Y~~bu&b#QO$Ev%y?zPx#E7vlu&0$SjY ziJgbXVn?INjV@qYKO>)PNesRZoAVQLzrirtVn1IgK@N}H8`z}{9gx?2&PKT-#3smF zS9EWCsFN|i1%AQ{d)Sb%uM=xOX3oS^#nFM^I`jU*HTYI^)B`)bn}46k$$rAeFIMxx zcNgsFVkEpmU9yRjv751mFz3hc^L1j%HuSZ>EwMlQHvv24!anUn7Dsnu&#>dyJ*j1( zKdrETNu!8UMqH;fr8@d01sBN|gT?dNQ~fc@W_hd4MG8&Hf~@Gvs&&D@DgQsHyx zY;@-kHIzfn#)#77Zuo=g9k74csXZ0Z&G)#;GoDzt9eLj_-N$?u z9;c3oJxM}ex(viVErp-h)HT)74 zWKW(hKz|3YC&=d1+xR>9(zCLY@gnl|3v#giBDF94Y%;!lCi`!P_r0(=pMH$*szFH0 zns1%K7gnV++m(Hrj-^5PxA$@-Zf~+lwQ+gPn|g>$taz z-TDa`tos#yXBaj*0QKU4EZj#?2r&syPRoX*Di#BypYXZgN&Ds}bGiP?zz z_7dC7eN5a!eebgr@`6fE#>>d!ReW^MuE;3+m!krC2Xkw19v_Q8di@jj1Q~c0yV!d; zIz0*9Ko_s(B$ix3F2}n17AKDChmFPF?@psnY?U2bei&K$gmzya5#69>Lu_;BF!9)4 zXX9tqRAxQj9jb>c%*978buu<)GmI{a$uHRV{Z){|WllyiaiUX4^08QA(H1;gf>_`+ z_&U z)7iK{t>R=~>NW6nNHu(vGh@-mH~5xa*qGeIDYIkG_H{&0-$B3dkKKqzp5!LiK^EiL zoBLf@6LQ|w4;uzwvttu>WB1CTr@N?ajhRNgQJVT*xU;br+3><1^yy1JLQJ{>TU7%e zl7BUOi~l@_e`rr!^>{StMjzzG2b)w8d%p21W0oU6YKR=SN9X$B!`QC{ zcd)_ur*5TPj7R9?*u_r9d3=TQ3+UWzXXBSg)Fv8G3t&$9uVc&5>Cv^Z_jR3&vSLS& zzarT8iJ$UZ6>_Zm@H9s(u?%t8L+oA={AOA+Yyz>GuOD`gn5h-9&G(;Ri;hy`pGzL= zgfB){cV2WcK85G69w&E2&gWxKelE)S1^V(ndh$EA>lU^?oN@MJE5C{4>=r*d_+|3W z2b5=ymb&CC8?v5Wlg^%h_?ly;9g!uUfbfXdR zVBtU)Bl!e=q$c&e+r(bmvC}_MqiagchhK`yi%&$Z0*Pz7B4>m8&=>NW7|0nivNtP^ z7?$;2%T2AK9dV4Cv*C|Cx!JG*ZzFr9h;PuzyH%+%e}U~n?w2BK_t~e7_?5RB6EhMo zeT;2?h#f2mk2<7L*DQ_vVatB`j+*&IYD&bHzGcY2$}#pzV$XF>#%yfJe(Xa2N%+Ur z)aPG^U-&@3JLu_M>_>O0!4rFSCjXd$9T|bmEJtl0doc;!8gz!3HHveZUBsE}48Qjrf5TthLjMXkChj8caQ8$vL&+JiN7W=*IHEn=oGdvNBPU+OHZ8yOtPI*KmhOxTXynL$1gf$YqpKJyMW>21Vnr`aoP zRz7?~c5Gxb;=#h-5J&hr8}+a~^}#ldrY40QIgG8p$5^-V$$OB6nGx8$rO3i|7o$WS zd`oNe`x^00HN%*{0v&h_-;E#Wki@yfNhf15KCSOHzUTE2TJ*b3VdC2mV(V7eFyfCI zthfFY>S8-2mUA)c(D!lnuLFHw#x~x^#*DzfOo117u#G$6(~qomIezCe_OK-SS#E`k zQ5xN8j&HAC349uM>r;FcWA~Ydo^7NKOq{x7GkG9CU4!_Z@p{hqYv5;Rz{70BAMmgW zb1cK2Y-PMM-MMG58N1OLyU>8T>l*6E=InV^`|97^6;de#9CL?E2Tl@C)z-wYuTg;D0eenl}L(A&$jnV-)wqe?O~vMJK-RD!J&STC zoJf3xFa4H%@%`D!nEw`Mso3oy9>f~N(Z`wZXYi}iB$kDM^8$KXZzvyJ7Hg__j_}ln))LI5P8Asje^B4543-)Xb-*5W~Uoe}T7r7f4 zj{VLH~jD$=QKU=d&CNxBSa|!M1)ud<>u0|3ZK0MaMP9wvaK1x)I?^B8!d@jWBGFI;fyg5yOf0O*w47RhzGwu$1{20Prx=)6J4z*~(_okPcyMMs9^X?J+x8iL z4qv+H0Wlu?5i*2Y{X=TrpOTl@I1ju@+){`$iXiTo;O}2Y_wUW)j>cMSV>`pxjNdFj zmilv3Igc=bNI`v|*?olRozi!k=WJ={h3j=d}HWJKeOKgE7_xMvt^YLN4n$B(rlUPg}7 zh@U)r6SGC*JIUE@Po)-Di+ajR1HG8ztJeC*Y}Wrnc@J0Cz?*{3-6`7$vE z{O)#}+~;@l_a8Z%eH+;&9@>cRwcLnbBQCt?Nqn^*TY{gNPd`iXe`VfAX3)P+`yn%J zosG7^j6DK*Jx<(@47T3xY}6uVC~%c}<0)#PIh>3~ZhT*axN2^0>iQ$u2YBBPnY(is zeHcw03Y%VV7V=Nt7dQ4p&}xf5^0r_IFQac^V8$#tvK<}+;bgS+Sf{-bjZ@)dFmJF+#)7ynMY5b-my^Gn1& z`06q%@UP1`k9VbR$sTSd&RDdVdRQKK%)WaOcWwN}1?PwzAI6hF+7U~kLrY6?ro0

6d$;(GH9G1O zg&g>nF`q%HX?G4dz)24`&7Yxo4Qp$`+dC-4SmRi)UA$=rowjJPqxxY+!y-w-pi zZ=)-_7_KiP)97TaH;@hXC_A>pXBKCSK76h+uMYUwa_B%G{P`hdhd8CoW$vK8gdf1i zeRmL_T!S(9!dvWLBW$G~dl$p+uB@ll=j;>y;OFnDTP>xIL%i}~Wnx%(-w>a%=|k@5 zpkKAtQQG;HpU8f3#+q1wx-oiOqb#-RImAiWi6i5w-QYh?4#gKeB1U+NdeaqR1M-45 zjvK~+W!R&(JgWhl>e`&W;P=*r@MmYZb8weE`-DBq?rdy_Pap8{YJsi8)op=yZ(;ZL zo#Bjvwx4z693G$i6@GMuA93xwoH0K{cXy&M>_t1`)D6d+jQ6Tj2Vw1}i4*D&2R$9? zWQ0z0GI}ybyZ5O-o#M=S3%<8F=TMKy-^y}MmXCW6_<-x^@rc^gzm5@C>?NlsPH2vN z__&eNffd_A?Rx){0{i+KG3D39r^xH#_t5M8oUN?nJJIu*D=|bBY;Mhi)F)U+ z&;|U>C)6mqaz@44w~VLejV(AGjQSfB!i-8+OL`5cR^`g|)NqMVzSd^mjH!CUYm`ZD*tM zZ^SCtj-$2UnVnemAbBt{cd;6GCEg^*X$`-xQZp=ujYKBu6ARUDOr0tf8*q_(Srf<& z`Vgn2!Bcc#CH5(fysTOpcH#&*_!RDY)Fj?Pci+FxeUvunH9oT#arV6^&Z*IhbEo-< zEc9rGz0JqD6}IPOQ*0*tm~@+1dH`oX^N5eTaaRmmQIkBOKY47<*2Lg9@nPu9rr)Ur zl;La+pHl4+^`O$!#2M4A5jCm-=zV?e4S4f!VuJ&hIafihq<*mDMeJV$K8^htfuBo6 zPS&NekDqXEh8-G(k1HICem1}!yvd!1zVI3uZ^Kw6S!;#u^n=Zr6zOb?-Ac~%3bE@^ zbblA|%8Q)KA%mxox$(r;(b)QR__&Wsk^4B)#{+Z%{d*so*jNC&g?^4kR_0&h{A@RO zS)w?D%R$VA?j(1iuhzsVg^64IunRY+Bm7QYvky7CNetx7-Jtu_ZRqRuy5vgNi7$r2 z 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 literal 0 HcmV?d00001 From a2d11f728cf58806f93c80b066c50c3ee5ee451b Mon Sep 17 00:00:00 2001 From: Washi Date: Tue, 14 Jun 2022 17:35:33 +0200 Subject: [PATCH 149/184] Make MsfFile.Streams an owned collection. --- .../Msf/MsfFile.cs | 3 +- .../Msf/MsfStream.cs | 28 +++++++++++++++---- .../Msf/SerializedMsfFile.cs | 3 +- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/AsmResolver.Symbols.WindowsPdb/Msf/MsfFile.cs b/src/AsmResolver.Symbols.WindowsPdb/Msf/MsfFile.cs index 058ea7824..b2399185e 100644 --- a/src/AsmResolver.Symbols.WindowsPdb/Msf/MsfFile.cs +++ b/src/AsmResolver.Symbols.WindowsPdb/Msf/MsfFile.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Threading; +using AsmResolver.Collections; using AsmResolver.IO; namespace AsmResolver.Symbols.WindowsPdb.Msf; @@ -116,5 +117,5 @@ public MsfFile(uint blockSize) /// /// This method is called upon initialization of the property. /// - protected virtual IList GetStreams() => new List(); + protected virtual IList GetStreams() => new OwnedCollection(this); } diff --git a/src/AsmResolver.Symbols.WindowsPdb/Msf/MsfStream.cs b/src/AsmResolver.Symbols.WindowsPdb/Msf/MsfStream.cs index cd8115dfa..2c9576116 100644 --- a/src/AsmResolver.Symbols.WindowsPdb/Msf/MsfStream.cs +++ b/src/AsmResolver.Symbols.WindowsPdb/Msf/MsfStream.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using AsmResolver.Collections; using AsmResolver.IO; namespace AsmResolver.Symbols.WindowsPdb.Msf; @@ -8,7 +9,7 @@ namespace AsmResolver.Symbols.WindowsPdb.Msf; ///

/// Represents a single stream in an Multi-Stream Format (MSF) file. /// -public class MsfStream +public class MsfStream : IOwnedCollectionElement { /// /// Creates a new MSF stream with the provided contents. @@ -26,18 +27,33 @@ public MsfStream(byte[] data) public MsfStream(IDataSource contents) { Contents = contents; - Blocks = Array.Empty(); + 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. - public MsfStream(IDataSource contents, IEnumerable blocks) + /// The original block indices that this MSF stream was based on. + public MsfStream(IDataSource contents, IEnumerable originalBlockIndices) { Contents = contents; - Blocks = blocks.ToArray(); + 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; } /// @@ -52,7 +68,7 @@ public IDataSource Contents /// /// Gets a collection of block indices that this stream was based of (if available). /// - public IReadOnlyList Blocks + public IReadOnlyList OriginalBlockIndices { get; } diff --git a/src/AsmResolver.Symbols.WindowsPdb/Msf/SerializedMsfFile.cs b/src/AsmResolver.Symbols.WindowsPdb/Msf/SerializedMsfFile.cs index 21d488bf9..a7c98e903 100644 --- a/src/AsmResolver.Symbols.WindowsPdb/Msf/SerializedMsfFile.cs +++ b/src/AsmResolver.Symbols.WindowsPdb/Msf/SerializedMsfFile.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Threading; +using AsmResolver.Collections; using AsmResolver.IO; namespace AsmResolver.Symbols.WindowsPdb.Msf; @@ -93,7 +94,7 @@ protected override IList GetStreams() streamSizes[i] = directoryReader.ReadUInt32(); // Construct streams. - var result = new List(streamCount); + var result = new OwnedCollection(this, streamCount); for (int i = 0; i < streamCount; i++) { // A size of 0xFFFFFFFF indicates the stream does not exist. From d708bf30f7190b4837cc2c8abd8335547d4b4190 Mon Sep 17 00:00:00 2001 From: Washi Date: Tue, 14 Jun 2022 20:25:26 +0200 Subject: [PATCH 150/184] Add simple MSF reconstruction support. --- .../Msf/Builder/FreeBlockMap.cs | 38 ++++ .../Msf/Builder/IMsfFileBuilder.cs | 14 ++ .../Msf/Builder/MsfFileBuffer.cs | 203 ++++++++++++++++++ .../Msf/Builder/SequentialMsfFileBuilder.cs | 56 +++++ .../Msf/MsfFile.cs | 48 +++-- .../Msf/MsfStream.cs | 28 ++- .../Msf/MsfSuperBlock.cs | 132 ++++++++++++ .../Msf/SerializedMsfFile.cs | 44 ++-- .../Msf/MsfFileTest.cs | 27 +++ .../Msf/MsfStreamDataSourceTest.cs | 2 +- 10 files changed, 542 insertions(+), 50 deletions(-) create mode 100644 src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/FreeBlockMap.cs create mode 100644 src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/IMsfFileBuilder.cs create mode 100644 src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/MsfFileBuffer.cs create mode 100644 src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/SequentialMsfFileBuilder.cs create mode 100644 src/AsmResolver.Symbols.WindowsPdb/Msf/MsfSuperBlock.cs create mode 100644 test/AsmResolver.Symbols.WindowsPdb.Tests/Msf/MsfFileTest.cs diff --git a/src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/FreeBlockMap.cs b/src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/FreeBlockMap.cs new file mode 100644 index 000000000..4339d4daa --- /dev/null +++ b/src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/FreeBlockMap.cs @@ -0,0 +1,38 @@ +using System.Collections; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.WindowsPdb.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.WindowsPdb/Msf/Builder/IMsfFileBuilder.cs b/src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/IMsfFileBuilder.cs new file mode 100644 index 000000000..d47687e0a --- /dev/null +++ b/src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/IMsfFileBuilder.cs @@ -0,0 +1,14 @@ +namespace AsmResolver.Symbols.WindowsPdb.Msf.Builder; + +/// +/// Provides members for constructing new MSF files. +/// +public interface IMsfFileBuilder +{ + /// + /// Reconstructs a new writable MSF file from an instance of . + /// + /// The file to reconstruct. + /// The reconstructed buffer. + MsfFileBuffer CreateFile(MsfFile file); +} diff --git a/src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/MsfFileBuffer.cs b/src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/MsfFileBuffer.cs new file mode 100644 index 000000000..2a3d072af --- /dev/null +++ b/src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/MsfFileBuffer.cs @@ -0,0 +1,203 @@ +using System; +using System.Collections.Generic; +using System.IO; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.WindowsPdb.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(); + private readonly List _blocks = new(); + + /// + /// 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, + }; + + 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) + { + while (_blocks.Count <= blockIndex) + _blocks.Add(null); + + _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); + } + + byte[] bytes = contents.ToArray(); + return new MsfStream(bytes); + } + + /// + /// 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); + + byte[] bytes = contents.ToArray(); + return new MsfStream(bytes); + } + + /// + 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.WindowsPdb/Msf/Builder/SequentialMsfFileBuilder.cs b/src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/SequentialMsfFileBuilder.cs new file mode 100644 index 000000000..d34a9d0f1 --- /dev/null +++ b/src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/SequentialMsfFileBuilder.cs @@ -0,0 +1,56 @@ +namespace AsmResolver.Symbols.WindowsPdb.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++) + { + buffer.InsertBlock(currentIndex, stream, j); + + // Move to next available block, and skip over the FPMs. + currentIndex++; + if (currentIndex % 4096 == 1) + currentIndex += 2; + } + } +} diff --git a/src/AsmResolver.Symbols.WindowsPdb/Msf/MsfFile.cs b/src/AsmResolver.Symbols.WindowsPdb/Msf/MsfFile.cs index b2399185e..5e73e5d84 100644 --- a/src/AsmResolver.Symbols.WindowsPdb/Msf/MsfFile.cs +++ b/src/AsmResolver.Symbols.WindowsPdb/Msf/MsfFile.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using System.IO; using System.Threading; using AsmResolver.Collections; using AsmResolver.IO; +using AsmResolver.Symbols.WindowsPdb.Msf.Builder; namespace AsmResolver.Symbols.WindowsPdb.Msf; @@ -11,21 +13,6 @@ namespace AsmResolver.Symbols.WindowsPdb.Msf; /// public class MsfFile { - // 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 - }; - private uint _blockSize; private IList? _streams; @@ -40,7 +27,7 @@ public uint BlockSize get => _blockSize; set { - if (_blockSize is 512 or 1024 or 2048 or 4096) + if (value is not (512 or 1024 or 2048 or 4096)) { throw new ArgumentOutOfRangeException( nameof(value), @@ -118,4 +105,33 @@ public MsfFile(uint blockSize) /// 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.WindowsPdb/Msf/MsfStream.cs b/src/AsmResolver.Symbols.WindowsPdb/Msf/MsfStream.cs index 2c9576116..942bf0fab 100644 --- a/src/AsmResolver.Symbols.WindowsPdb/Msf/MsfStream.cs +++ b/src/AsmResolver.Symbols.WindowsPdb/Msf/MsfStream.cs @@ -44,7 +44,7 @@ public MsfStream(IDataSource contents, IEnumerable originalBlockIndices) /// /// Gets the parent MSF file that this stream is embedded in. /// - public MsfFile Parent + public MsfFile? Parent { get; private set; @@ -73,6 +73,32 @@ 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. /// diff --git a/src/AsmResolver.Symbols.WindowsPdb/Msf/MsfSuperBlock.cs b/src/AsmResolver.Symbols.WindowsPdb/Msf/MsfSuperBlock.cs new file mode 100644 index 000000000..91dfe778d --- /dev/null +++ b/src/AsmResolver.Symbols.WindowsPdb/Msf/MsfSuperBlock.cs @@ -0,0 +1,132 @@ +using System; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.WindowsPdb.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.WindowsPdb/Msf/SerializedMsfFile.cs b/src/AsmResolver.Symbols.WindowsPdb/Msf/SerializedMsfFile.cs index a7c98e903..12e14f6ea 100644 --- a/src/AsmResolver.Symbols.WindowsPdb/Msf/SerializedMsfFile.cs +++ b/src/AsmResolver.Symbols.WindowsPdb/Msf/SerializedMsfFile.cs @@ -7,7 +7,7 @@ namespace AsmResolver.Symbols.WindowsPdb.Msf; /// -/// Provides an implementation for an MSF file version, read from an input file. +/// 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. @@ -15,11 +15,9 @@ namespace AsmResolver.Symbols.WindowsPdb.Msf; public class SerializedMsfFile : MsfFile { private readonly BinaryStreamReader _reader; - private readonly uint _originalBlockSize; + private readonly MsfSuperBlock _originalSuperBlock; private readonly IDataSource?[] _blocks; - private readonly uint _directoryByteCount; - private readonly int _blockMapIndex; /// /// Interprets an input stream as an MSF file version 7.0. @@ -28,28 +26,10 @@ public class SerializedMsfFile : MsfFile /// Occurs when the MSF file is malformed. public SerializedMsfFile(BinaryStreamReader reader) { - // Check MSF header. - byte[] signature = new byte[BigMsfSignature.Length]; - int count = reader.ReadBytes(signature, 0, signature.Length); - if (count != BigMsfSignature.Length || !ByteArrayEqualityComparer.Instance.Equals(signature, BigMsfSignature)) - throw new BadImageFormatException("File does not start with a valid or supported MSF file signature."); - - // BlockSize property also validates, so no need to do it again. - BlockSize = _originalBlockSize = reader.ReadUInt32(); - - // 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. - int freeBlockMapIndex = reader.ReadInt32(); - if (freeBlockMapIndex is not (1 or 2)) - throw new BadImageFormatException($"Free block map index must be 1 or 2, but was {freeBlockMapIndex}."); - - int blockCount = reader.ReadInt32(); - _blocks = new IDataSource?[blockCount]; - - _directoryByteCount = reader.ReadUInt32(); - reader.Offset += sizeof(uint); - _blockMapIndex = reader.ReadInt32(); + _originalSuperBlock = MsfSuperBlock.FromReader(ref reader); + BlockSize = _originalSuperBlock.BlockSize; + _blocks = new IDataSource?[_originalSuperBlock.BlockCount]; _reader = reader; } @@ -60,8 +40,8 @@ private IDataSource GetBlock(int index) // 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 * _originalBlockSize), - _originalBlockSize); + _reader.DataSource.BaseAddress + (ulong) (index * _originalSuperBlock.BlockSize), + _originalSuperBlock.BlockSize); Interlocked.CompareExchange(ref _blocks[index], block, null); } @@ -73,12 +53,12 @@ private IDataSource GetBlock(int index) protected override IList GetStreams() { // Get the block indices of the Stream Directory stream. - var indicesBlock = GetBlock(_blockMapIndex); + var indicesBlock = GetBlock((int) _originalSuperBlock.DirectoryMapIndex); var indicesReader = new BinaryStreamReader(indicesBlock, indicesBlock.BaseAddress, 0, - GetBlockCount(_directoryByteCount) * sizeof(uint)); + GetBlockCount(_originalSuperBlock.DirectoryByteCount) * sizeof(uint)); // Access the Stream Directory stream. - var directoryStream = CreateStreamFromIndicesReader(ref indicesReader, _directoryByteCount); + var directoryStream = CreateStreamFromIndicesReader(ref indicesReader, _originalSuperBlock.DirectoryByteCount); var directoryReader = directoryStream.CreateReader(); // Stream Directory format is as follows: @@ -120,9 +100,9 @@ private MsfStream CreateStreamFromIndicesReader(ref BinaryStreamReader indicesRe blocks[i] = GetBlock(indices[i]); // Construct stream. - var dataSource = new MsfStreamDataSource(streamSize, _originalBlockSize, blocks); + var dataSource = new MsfStreamDataSource(streamSize, _originalSuperBlock.BlockSize, blocks); return new MsfStream(dataSource, indices); } - private uint GetBlockCount(uint streamSize) => (streamSize + _originalBlockSize - 1) / _originalBlockSize; + private uint GetBlockCount(uint streamSize) => (streamSize + _originalSuperBlock.BlockSize - 1) / _originalSuperBlock.BlockSize; } diff --git a/test/AsmResolver.Symbols.WindowsPdb.Tests/Msf/MsfFileTest.cs b/test/AsmResolver.Symbols.WindowsPdb.Tests/Msf/MsfFileTest.cs new file mode 100644 index 000000000..80298ad77 --- /dev/null +++ b/test/AsmResolver.Symbols.WindowsPdb.Tests/Msf/MsfFileTest.cs @@ -0,0 +1,27 @@ +using System.IO; +using System.Linq; +using AsmResolver.Symbols.WindowsPdb.Msf; +using Xunit; + +namespace AsmResolver.Symbols.WindowsPdb.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.WindowsPdb.Tests/Msf/MsfStreamDataSourceTest.cs b/test/AsmResolver.Symbols.WindowsPdb.Tests/Msf/MsfStreamDataSourceTest.cs index 95430985d..86bd38693 100644 --- a/test/AsmResolver.Symbols.WindowsPdb.Tests/Msf/MsfStreamDataSourceTest.cs +++ b/test/AsmResolver.Symbols.WindowsPdb.Tests/Msf/MsfStreamDataSourceTest.cs @@ -16,7 +16,7 @@ public void EmptyStream() byte[] buffer = new byte[0x1000]; int readCount = source.ReadBytes(0, buffer, 0, buffer.Length); Assert.Equal(0, readCount); - Assert.All(buffer, b => Assert.Equal(b, 0)); + Assert.All(buffer, b => Assert.Equal(0, b)); } [Theory] From 1098369fe4e0862d2bcb91f0cb6d3f66e789e6b4 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 15 Jun 2022 15:50:15 +0200 Subject: [PATCH 151/184] Enable nupkg generation for WindowsPdb project. --- .../AsmResolver.Symbols.WindowsPdb.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/AsmResolver.Symbols.WindowsPdb/AsmResolver.Symbols.WindowsPdb.csproj b/src/AsmResolver.Symbols.WindowsPdb/AsmResolver.Symbols.WindowsPdb.csproj index 1f1099b04..b0b3df765 100644 --- a/src/AsmResolver.Symbols.WindowsPdb/AsmResolver.Symbols.WindowsPdb.csproj +++ b/src/AsmResolver.Symbols.WindowsPdb/AsmResolver.Symbols.WindowsPdb.csproj @@ -7,6 +7,7 @@ enable net6.0;netcoreapp3.1;netstandard2.0 true + true From 6c0eeb3322bec8e4e37f66a33b1bac389988e99a Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 15 Jun 2022 15:50:59 +0200 Subject: [PATCH 152/184] Inline some variables, clarify some docs and remove some redundant code. --- .../Msf/Builder/IMsfFileBuilder.cs | 2 +- .../Msf/Builder/MsfFileBuffer.cs | 14 ++++++++------ .../Msf/Builder/SequentialMsfFileBuilder.cs | 18 ++++++++++++------ .../Msf/SerializedMsfFile.cs | 6 ++++-- .../Msf/MsfStreamDataSourceTest.cs | 4 +--- 5 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/IMsfFileBuilder.cs b/src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/IMsfFileBuilder.cs index d47687e0a..f340944a1 100644 --- a/src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/IMsfFileBuilder.cs +++ b/src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/IMsfFileBuilder.cs @@ -6,7 +6,7 @@ namespace AsmResolver.Symbols.WindowsPdb.Msf.Builder; public interface IMsfFileBuilder { /// - /// Reconstructs a new writable MSF file from an instance of . + /// Reconstructs a new writable MSF file buffer from an instance of . /// /// The file to reconstruct. /// The reconstructed buffer. diff --git a/src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/MsfFileBuffer.cs b/src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/MsfFileBuffer.cs index 2a3d072af..79ff2afc1 100644 --- a/src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/MsfFileBuffer.cs +++ b/src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/MsfFileBuffer.cs @@ -11,8 +11,8 @@ namespace AsmResolver.Symbols.WindowsPdb.Msf.Builder; public class MsfFileBuffer : SegmentBase { private readonly Dictionary _blockIndices = new(); - private readonly List _freeBlockMaps = new(); - private readonly List _blocks = new(); + private readonly List _freeBlockMaps = new(2); + private readonly List _blocks; /// /// Creates a new empty MSF file buffer. @@ -28,6 +28,8 @@ public MsfFileBuffer(uint blockSize) BlockCount = 3, }; + _blocks = new List((int) blockSize); + InsertBlock(0, SuperBlock); var fpm = GetOrCreateFreeBlockMap(1, out _); InsertBlock(2, null); @@ -90,9 +92,11 @@ public void InsertBlock(int blockIndex, MsfStream stream, int chunkIndex) 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; } @@ -156,8 +160,7 @@ public MsfStream CreateStreamDirectory(IList streams) writer.WriteInt32(index); } - byte[] bytes = contents.ToArray(); - return new MsfStream(bytes); + return new MsfStream(contents.ToArray()); } /// @@ -177,8 +180,7 @@ public MsfStream CreateStreamDirectoryMap(MsfStream streamDirectory) foreach (int index in indices) writer.WriteInt32(index); - byte[] bytes = contents.ToArray(); - return new MsfStream(bytes); + return new MsfStream(contents.ToArray()); } /// diff --git a/src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/SequentialMsfFileBuilder.cs b/src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/SequentialMsfFileBuilder.cs index d34a9d0f1..35195f776 100644 --- a/src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/SequentialMsfFileBuilder.cs +++ b/src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/SequentialMsfFileBuilder.cs @@ -43,14 +43,20 @@ private static void AddStream(MsfFileBuffer buffer, MsfStream stream, ref int cu { int blockCount = stream.GetRequiredBlockCount(buffer.SuperBlock.BlockSize); - for (int j = 0; j < blockCount; j++) + for (int j = 0; j < blockCount; j++, currentIndex++) { - buffer.InsertBlock(currentIndex, stream, j); + // Skip over any of the FPM indices. + switch (currentIndex % 4096) + { + case 1: + currentIndex += 2; + break; + case 2: + currentIndex++; + break; + } - // Move to next available block, and skip over the FPMs. - currentIndex++; - if (currentIndex % 4096 == 1) - currentIndex += 2; + buffer.InsertBlock(currentIndex, stream, j); } } } diff --git a/src/AsmResolver.Symbols.WindowsPdb/Msf/SerializedMsfFile.cs b/src/AsmResolver.Symbols.WindowsPdb/Msf/SerializedMsfFile.cs index 12e14f6ea..025780e57 100644 --- a/src/AsmResolver.Symbols.WindowsPdb/Msf/SerializedMsfFile.cs +++ b/src/AsmResolver.Symbols.WindowsPdb/Msf/SerializedMsfFile.cs @@ -16,7 +16,6 @@ public class SerializedMsfFile : MsfFile { private readonly BinaryStreamReader _reader; private readonly MsfSuperBlock _originalSuperBlock; - private readonly IDataSource?[] _blocks; /// @@ -104,5 +103,8 @@ private MsfStream CreateStreamFromIndicesReader(ref BinaryStreamReader indicesRe return new MsfStream(dataSource, indices); } - private uint GetBlockCount(uint streamSize) => (streamSize + _originalSuperBlock.BlockSize - 1) / _originalSuperBlock.BlockSize; + private uint GetBlockCount(uint streamSize) + { + return (streamSize + _originalSuperBlock.BlockSize - 1) / _originalSuperBlock.BlockSize; + } } diff --git a/test/AsmResolver.Symbols.WindowsPdb.Tests/Msf/MsfStreamDataSourceTest.cs b/test/AsmResolver.Symbols.WindowsPdb.Tests/Msf/MsfStreamDataSourceTest.cs index 86bd38693..d8ce31566 100644 --- a/test/AsmResolver.Symbols.WindowsPdb.Tests/Msf/MsfStreamDataSourceTest.cs +++ b/test/AsmResolver.Symbols.WindowsPdb.Tests/Msf/MsfStreamDataSourceTest.cs @@ -28,9 +28,7 @@ public void StreamWithOneBlock(int blockSize, int actualSize) for (int i = 0; i < blockSize; i++) block[i] = (byte) (i & 0xFF); - var source = new MsfStreamDataSource((ulong) actualSize, (uint) blockSize, new[] { - block - }); + var source = new MsfStreamDataSource((ulong) actualSize, (uint) blockSize, new[] {block}); byte[] buffer = new byte[0x1000]; int readCount = source.ReadBytes(0, buffer, 0, buffer.Length); From a4d119417bbe0b6be693858ff25cf6d3e84eb468 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 16 Jun 2022 17:28:36 +0200 Subject: [PATCH 153/184] Change version to 5.0.0-beta.1 --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 128e21272..da8160f3a 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,7 +7,7 @@ https://github.com/Washi1337/AsmResolver git 10 - 5.0.0 + 5.0.0-beta.1 From d30aa97559bf72d7cf2d756a7d25b8e704580f51 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 16 Jun 2022 17:28:53 +0200 Subject: [PATCH 154/184] Shorten WindowsPdb to Pdb. --- AsmResolver.sln | 4 ++-- src/.gitignore | 2 ++ .../AsmResolver.Symbols.Pdb.csproj} | 0 .../Msf/Builder/FreeBlockMap.cs | 2 +- .../Msf/Builder/IMsfFileBuilder.cs | 2 +- .../Msf/Builder/MsfFileBuffer.cs | 2 +- .../Msf/Builder/SequentialMsfFileBuilder.cs | 2 +- .../Msf/MsfFile.cs | 4 ++-- .../Msf/MsfStream.cs | 2 +- .../Msf/MsfStreamDataSource.cs | 2 +- .../Msf/MsfSuperBlock.cs | 2 +- .../Msf/SerializedMsfFile.cs | 2 +- .../AsmResolver.Symbols.Pdb.Tests.csproj} | 2 +- .../Msf/MsfFileTest.cs | 4 ++-- .../Msf/MsfStreamDataSourceTest.cs | 4 ++-- .../Properties/Resources.Designer.cs | 20 +++++++++--------- .../Properties/Resources.resx | 0 .../Resources/.gitignore | 0 .../Resources/SimpleDll.pdb | Bin 19 files changed, 29 insertions(+), 27 deletions(-) create mode 100644 src/.gitignore rename src/{AsmResolver.Symbols.WindowsPdb/AsmResolver.Symbols.WindowsPdb.csproj => AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj} (100%) rename src/{AsmResolver.Symbols.WindowsPdb => AsmResolver.Symbols.Pdb}/Msf/Builder/FreeBlockMap.cs (94%) rename src/{AsmResolver.Symbols.WindowsPdb => AsmResolver.Symbols.Pdb}/Msf/Builder/IMsfFileBuilder.cs (88%) rename src/{AsmResolver.Symbols.WindowsPdb => AsmResolver.Symbols.Pdb}/Msf/Builder/MsfFileBuffer.cs (99%) rename src/{AsmResolver.Symbols.WindowsPdb => AsmResolver.Symbols.Pdb}/Msf/Builder/SequentialMsfFileBuilder.cs (97%) rename src/{AsmResolver.Symbols.WindowsPdb => AsmResolver.Symbols.Pdb}/Msf/MsfFile.cs (97%) rename src/{AsmResolver.Symbols.WindowsPdb => AsmResolver.Symbols.Pdb}/Msf/MsfStream.cs (98%) rename src/{AsmResolver.Symbols.WindowsPdb => AsmResolver.Symbols.Pdb}/Msf/MsfStreamDataSource.cs (98%) rename src/{AsmResolver.Symbols.WindowsPdb => AsmResolver.Symbols.Pdb}/Msf/MsfSuperBlock.cs (98%) rename src/{AsmResolver.Symbols.WindowsPdb => AsmResolver.Symbols.Pdb}/Msf/SerializedMsfFile.cs (98%) rename test/{AsmResolver.Symbols.WindowsPdb.Tests/AsmResolver.Symbols.WindowsPdb.Tests.csproj => AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj} (92%) rename test/{AsmResolver.Symbols.WindowsPdb.Tests => AsmResolver.Symbols.Pdb.Tests}/Msf/MsfFileTest.cs (87%) rename test/{AsmResolver.Symbols.WindowsPdb.Tests => AsmResolver.Symbols.Pdb.Tests}/Msf/MsfStreamDataSourceTest.cs (96%) rename test/{AsmResolver.Symbols.WindowsPdb.Tests => AsmResolver.Symbols.Pdb.Tests}/Properties/Resources.Designer.cs (88%) rename test/{AsmResolver.Symbols.WindowsPdb.Tests => AsmResolver.Symbols.Pdb.Tests}/Properties/Resources.resx (100%) rename test/{AsmResolver.Symbols.WindowsPdb.Tests => AsmResolver.Symbols.Pdb.Tests}/Resources/.gitignore (100%) rename test/{AsmResolver.Symbols.WindowsPdb.Tests => AsmResolver.Symbols.Pdb.Tests}/Resources/SimpleDll.pdb (100%) diff --git a/AsmResolver.sln b/AsmResolver.sln index cee77d8fe..90430c976 100644 --- a/AsmResolver.sln +++ b/AsmResolver.sln @@ -85,9 +85,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Directory.Build.props = Directory.Build.props EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsmResolver.Symbols.WindowsPdb", "src\AsmResolver.Symbols.WindowsPdb\AsmResolver.Symbols.WindowsPdb.csproj", "{9E311832-D0F2-42CA-84DD-9A91B88F0287}" +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.WindowsPdb.Tests", "test\AsmResolver.Symbols.WindowsPdb.Tests\AsmResolver.Symbols.WindowsPdb.Tests.csproj", "{AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}" +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 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution 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.Symbols.WindowsPdb/AsmResolver.Symbols.WindowsPdb.csproj b/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj similarity index 100% rename from src/AsmResolver.Symbols.WindowsPdb/AsmResolver.Symbols.WindowsPdb.csproj rename to src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj diff --git a/src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/FreeBlockMap.cs b/src/AsmResolver.Symbols.Pdb/Msf/Builder/FreeBlockMap.cs similarity index 94% rename from src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/FreeBlockMap.cs rename to src/AsmResolver.Symbols.Pdb/Msf/Builder/FreeBlockMap.cs index 4339d4daa..dea1712e0 100644 --- a/src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/FreeBlockMap.cs +++ b/src/AsmResolver.Symbols.Pdb/Msf/Builder/FreeBlockMap.cs @@ -1,7 +1,7 @@ using System.Collections; using AsmResolver.IO; -namespace AsmResolver.Symbols.WindowsPdb.Msf.Builder; +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. diff --git a/src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/IMsfFileBuilder.cs b/src/AsmResolver.Symbols.Pdb/Msf/Builder/IMsfFileBuilder.cs similarity index 88% rename from src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/IMsfFileBuilder.cs rename to src/AsmResolver.Symbols.Pdb/Msf/Builder/IMsfFileBuilder.cs index f340944a1..b51dcfe0b 100644 --- a/src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/IMsfFileBuilder.cs +++ b/src/AsmResolver.Symbols.Pdb/Msf/Builder/IMsfFileBuilder.cs @@ -1,4 +1,4 @@ -namespace AsmResolver.Symbols.WindowsPdb.Msf.Builder; +namespace AsmResolver.Symbols.Pdb.Msf.Builder; /// /// Provides members for constructing new MSF files. diff --git a/src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/MsfFileBuffer.cs b/src/AsmResolver.Symbols.Pdb/Msf/Builder/MsfFileBuffer.cs similarity index 99% rename from src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/MsfFileBuffer.cs rename to src/AsmResolver.Symbols.Pdb/Msf/Builder/MsfFileBuffer.cs index 79ff2afc1..865715037 100644 --- a/src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/MsfFileBuffer.cs +++ b/src/AsmResolver.Symbols.Pdb/Msf/Builder/MsfFileBuffer.cs @@ -3,7 +3,7 @@ using System.IO; using AsmResolver.IO; -namespace AsmResolver.Symbols.WindowsPdb.Msf.Builder; +namespace AsmResolver.Symbols.Pdb.Msf.Builder; /// /// Represents a mutable buffer for building up a new MSF file. diff --git a/src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/SequentialMsfFileBuilder.cs b/src/AsmResolver.Symbols.Pdb/Msf/Builder/SequentialMsfFileBuilder.cs similarity index 97% rename from src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/SequentialMsfFileBuilder.cs rename to src/AsmResolver.Symbols.Pdb/Msf/Builder/SequentialMsfFileBuilder.cs index 35195f776..ca02da2a9 100644 --- a/src/AsmResolver.Symbols.WindowsPdb/Msf/Builder/SequentialMsfFileBuilder.cs +++ b/src/AsmResolver.Symbols.Pdb/Msf/Builder/SequentialMsfFileBuilder.cs @@ -1,4 +1,4 @@ -namespace AsmResolver.Symbols.WindowsPdb.Msf.Builder; +namespace AsmResolver.Symbols.Pdb.Msf.Builder; /// /// Provides an implementation of the that places all blocks of every stream in sequence, diff --git a/src/AsmResolver.Symbols.WindowsPdb/Msf/MsfFile.cs b/src/AsmResolver.Symbols.Pdb/Msf/MsfFile.cs similarity index 97% rename from src/AsmResolver.Symbols.WindowsPdb/Msf/MsfFile.cs rename to src/AsmResolver.Symbols.Pdb/Msf/MsfFile.cs index 5e73e5d84..16aff99f9 100644 --- a/src/AsmResolver.Symbols.WindowsPdb/Msf/MsfFile.cs +++ b/src/AsmResolver.Symbols.Pdb/Msf/MsfFile.cs @@ -4,9 +4,9 @@ using System.Threading; using AsmResolver.Collections; using AsmResolver.IO; -using AsmResolver.Symbols.WindowsPdb.Msf.Builder; +using AsmResolver.Symbols.Pdb.Msf.Builder; -namespace AsmResolver.Symbols.WindowsPdb.Msf; +namespace AsmResolver.Symbols.Pdb.Msf; /// /// Models a file that is in the Microsoft Multi-Stream Format (MSF). diff --git a/src/AsmResolver.Symbols.WindowsPdb/Msf/MsfStream.cs b/src/AsmResolver.Symbols.Pdb/Msf/MsfStream.cs similarity index 98% rename from src/AsmResolver.Symbols.WindowsPdb/Msf/MsfStream.cs rename to src/AsmResolver.Symbols.Pdb/Msf/MsfStream.cs index 942bf0fab..f5e871498 100644 --- a/src/AsmResolver.Symbols.WindowsPdb/Msf/MsfStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Msf/MsfStream.cs @@ -4,7 +4,7 @@ using AsmResolver.Collections; using AsmResolver.IO; -namespace AsmResolver.Symbols.WindowsPdb.Msf; +namespace AsmResolver.Symbols.Pdb.Msf; /// /// Represents a single stream in an Multi-Stream Format (MSF) file. diff --git a/src/AsmResolver.Symbols.WindowsPdb/Msf/MsfStreamDataSource.cs b/src/AsmResolver.Symbols.Pdb/Msf/MsfStreamDataSource.cs similarity index 98% rename from src/AsmResolver.Symbols.WindowsPdb/Msf/MsfStreamDataSource.cs rename to src/AsmResolver.Symbols.Pdb/Msf/MsfStreamDataSource.cs index 08e43ef2d..fa0e96cef 100644 --- a/src/AsmResolver.Symbols.WindowsPdb/Msf/MsfStreamDataSource.cs +++ b/src/AsmResolver.Symbols.Pdb/Msf/MsfStreamDataSource.cs @@ -4,7 +4,7 @@ using System.Runtime.CompilerServices; using AsmResolver.IO; -namespace AsmResolver.Symbols.WindowsPdb.Msf; +namespace AsmResolver.Symbols.Pdb.Msf; /// /// Implements a data source for a single MSF stream that pulls data from multiple (fragmented) blocks. diff --git a/src/AsmResolver.Symbols.WindowsPdb/Msf/MsfSuperBlock.cs b/src/AsmResolver.Symbols.Pdb/Msf/MsfSuperBlock.cs similarity index 98% rename from src/AsmResolver.Symbols.WindowsPdb/Msf/MsfSuperBlock.cs rename to src/AsmResolver.Symbols.Pdb/Msf/MsfSuperBlock.cs index 91dfe778d..cba31d1c8 100644 --- a/src/AsmResolver.Symbols.WindowsPdb/Msf/MsfSuperBlock.cs +++ b/src/AsmResolver.Symbols.Pdb/Msf/MsfSuperBlock.cs @@ -1,7 +1,7 @@ using System; using AsmResolver.IO; -namespace AsmResolver.Symbols.WindowsPdb.Msf; +namespace AsmResolver.Symbols.Pdb.Msf; /// /// Represents the first block in a Multi-Stream Format (MSF) file. diff --git a/src/AsmResolver.Symbols.WindowsPdb/Msf/SerializedMsfFile.cs b/src/AsmResolver.Symbols.Pdb/Msf/SerializedMsfFile.cs similarity index 98% rename from src/AsmResolver.Symbols.WindowsPdb/Msf/SerializedMsfFile.cs rename to src/AsmResolver.Symbols.Pdb/Msf/SerializedMsfFile.cs index 025780e57..5570f188d 100644 --- a/src/AsmResolver.Symbols.WindowsPdb/Msf/SerializedMsfFile.cs +++ b/src/AsmResolver.Symbols.Pdb/Msf/SerializedMsfFile.cs @@ -4,7 +4,7 @@ using AsmResolver.Collections; using AsmResolver.IO; -namespace AsmResolver.Symbols.WindowsPdb.Msf; +namespace AsmResolver.Symbols.Pdb.Msf; /// /// Provides an implementation for an MSF file that is read from an input file. diff --git a/test/AsmResolver.Symbols.WindowsPdb.Tests/AsmResolver.Symbols.WindowsPdb.Tests.csproj b/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj similarity index 92% rename from test/AsmResolver.Symbols.WindowsPdb.Tests/AsmResolver.Symbols.WindowsPdb.Tests.csproj rename to test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj index ff11f109f..18d5ed468 100644 --- a/test/AsmResolver.Symbols.WindowsPdb.Tests/AsmResolver.Symbols.WindowsPdb.Tests.csproj +++ b/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj @@ -36,7 +36,7 @@ - + diff --git a/test/AsmResolver.Symbols.WindowsPdb.Tests/Msf/MsfFileTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Msf/MsfFileTest.cs similarity index 87% rename from test/AsmResolver.Symbols.WindowsPdb.Tests/Msf/MsfFileTest.cs rename to test/AsmResolver.Symbols.Pdb.Tests/Msf/MsfFileTest.cs index 80298ad77..b17cd4b36 100644 --- a/test/AsmResolver.Symbols.WindowsPdb.Tests/Msf/MsfFileTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Msf/MsfFileTest.cs @@ -1,9 +1,9 @@ using System.IO; using System.Linq; -using AsmResolver.Symbols.WindowsPdb.Msf; +using AsmResolver.Symbols.Pdb.Msf; using Xunit; -namespace AsmResolver.Symbols.WindowsPdb.Tests.Msf; +namespace AsmResolver.Symbols.Pdb.Tests.Msf; public class MsfFileTest { diff --git a/test/AsmResolver.Symbols.WindowsPdb.Tests/Msf/MsfStreamDataSourceTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Msf/MsfStreamDataSourceTest.cs similarity index 96% rename from test/AsmResolver.Symbols.WindowsPdb.Tests/Msf/MsfStreamDataSourceTest.cs rename to test/AsmResolver.Symbols.Pdb.Tests/Msf/MsfStreamDataSourceTest.cs index d8ce31566..c61fa8dc1 100644 --- a/test/AsmResolver.Symbols.WindowsPdb.Tests/Msf/MsfStreamDataSourceTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Msf/MsfStreamDataSourceTest.cs @@ -1,10 +1,10 @@ using System; using System.Linq; using AsmResolver.IO; -using AsmResolver.Symbols.WindowsPdb.Msf; +using AsmResolver.Symbols.Pdb.Msf; using Xunit; -namespace AsmResolver.Symbols.WindowsPdb.Tests.Msf; +namespace AsmResolver.Symbols.Pdb.Tests.Msf; public class MsfStreamDataSourceTest { diff --git a/test/AsmResolver.Symbols.WindowsPdb.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.Symbols.Pdb.Tests/Properties/Resources.Designer.cs similarity index 88% rename from test/AsmResolver.Symbols.WindowsPdb.Tests/Properties/Resources.Designer.cs rename to test/AsmResolver.Symbols.Pdb.Tests/Properties/Resources.Designer.cs index ccd260a2d..b115b2929 100644 --- a/test/AsmResolver.Symbols.WindowsPdb.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Properties/Resources.Designer.cs @@ -7,34 +7,34 @@ // //------------------------------------------------------------------------------ -namespace AsmResolver.Symbols.WindowsPdb.Tests.Properties { +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.WindowsPdb.Tests.Properties.Resources", typeof(Resources).Assembly); + 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 { @@ -44,7 +44,7 @@ internal class Resources { resourceCulture = value; } } - + internal static byte[] SimpleDllPdb { get { object obj = ResourceManager.GetObject("SimpleDllPdb", resourceCulture); diff --git a/test/AsmResolver.Symbols.WindowsPdb.Tests/Properties/Resources.resx b/test/AsmResolver.Symbols.Pdb.Tests/Properties/Resources.resx similarity index 100% rename from test/AsmResolver.Symbols.WindowsPdb.Tests/Properties/Resources.resx rename to test/AsmResolver.Symbols.Pdb.Tests/Properties/Resources.resx diff --git a/test/AsmResolver.Symbols.WindowsPdb.Tests/Resources/.gitignore b/test/AsmResolver.Symbols.Pdb.Tests/Resources/.gitignore similarity index 100% rename from test/AsmResolver.Symbols.WindowsPdb.Tests/Resources/.gitignore rename to test/AsmResolver.Symbols.Pdb.Tests/Resources/.gitignore diff --git a/test/AsmResolver.Symbols.WindowsPdb.Tests/Resources/SimpleDll.pdb b/test/AsmResolver.Symbols.Pdb.Tests/Resources/SimpleDll.pdb similarity index 100% rename from test/AsmResolver.Symbols.WindowsPdb.Tests/Resources/SimpleDll.pdb rename to test/AsmResolver.Symbols.Pdb.Tests/Resources/SimpleDll.pdb From 410b90d2fbc84370ace0c5d601470cfb7cd56224 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 16 Jun 2022 20:34:55 +0200 Subject: [PATCH 155/184] Basic read support PDB info stream. --- .../Metadata/Info/InfoStream.cs | 124 ++++++++++++++++++ .../Metadata/Info/InfoStreamVersion.cs | 19 +++ .../Metadata/Info/PdbFeature.cs | 28 ++++ .../Metadata/Info/SerializedInfoStream.cs | 83 ++++++++++++ .../Metadata/PdbHashTable.cs | 36 +++++ .../Metadata/Info/InfoStreamTest.cs | 30 +++++ 6 files changed, 320 insertions(+) create mode 100644 src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStream.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStreamVersion.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Metadata/Info/PdbFeature.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Metadata/Info/SerializedInfoStream.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Metadata/PdbHashTable.cs create mode 100644 test/AsmResolver.Symbols.Pdb.Tests/Metadata/Info/InfoStreamTest.cs 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..182981fe1 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStream.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +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 +{ + 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; + } + + /// + /// 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; + } + + /// + /// 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() + { + return sizeof(uint) // Version + + sizeof(uint) // Signature + + sizeof(uint) // Aage + + 16 // UniqueId + ; + } + + /// + public override void Write(IBinaryStreamWriter writer) + { + throw new NotImplementedException(); + } +} 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..361d41e07 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStreamVersion.cs @@ -0,0 +1,19 @@ +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, +} 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..506a9ae86 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Info/SerializedInfoStream.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Metadata.Info; + +/// +/// Models an PDB info stream by pulling 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); + byte[] rawData = stringReader.ReadBytesUntil(0); + + Utf8String keyString; + if (rawData.Length == 0) + { + keyString = Utf8String.Empty; + } + else + { + // Trim off null terminator byte if its present. + int actualLength = rawData.Length; + if (rawData[actualLength - 1] == 0) + actualLength--; + + keyString = new Utf8String(rawData, 0, actualLength); + } + + return (keyString, (int) value); + }); + + _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/PdbHashTable.cs b/src/AsmResolver.Symbols.Pdb/Metadata/PdbHashTable.cs new file mode 100644 index 000000000..942b1ddd1 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/PdbHashTable.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using AsmResolver.IO; + +namespace AsmResolver.Symbols.Pdb.Metadata; + +internal static class PdbHashTable +{ + public static Dictionary FromReader( + ref BinaryStreamReader reader, + Func mapper) + where TKey : notnull + { + uint size = reader.ReadUInt32(); + uint capacity = reader.ReadUInt32(); + + uint presentWordCount = reader.ReadUInt32(); + reader.RelativeOffset += presentWordCount * sizeof(uint); + + uint deletedWordCount = reader.ReadUInt32(); + reader.RelativeOffset += deletedWordCount * sizeof(uint); + + var result = new Dictionary(); + for (int i = 0; i < size; i++) + { + (uint rawKey, uint rawValue) = (reader.ReadUInt32(), reader.ReadUInt32()); + var (key, value) = mapper(rawKey, rawValue); + result.Add(key, value); + } + + uint lastNi = reader.ReadUInt32(); + + return result; + } +} 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..97e8827cf --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Info/InfoStreamTest.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using AsmResolver.Symbols.Pdb.Metadata.Info; +using AsmResolver.Symbols.Pdb.Msf; +using Xunit; + +namespace AsmResolver.Symbols.Pdb.Tests.Metadata.Info; + +public class InfoStreamTest +{ + [Fact] + public void Read() + { + var file = MsfFile.FromBytes(Properties.Resources.SimpleDllPdb); + var infoStream = InfoStream.FromReader(file.Streams[1].CreateReader()); + + Assert.Equal(InfoStreamVersion.VC70, infoStream.Version); + Assert.Equal(1u, infoStream.Age); + Assert.Equal(Guid.Parse("205dc366-d8f8-4175-8e06-26dd76722df5"), infoStream.UniqueId); + Assert.Equal(new Dictionary + { + ["/UDTSRCLINEUNDONE"] = 48, + ["/src/headerblock"] = 46, + ["/LinkInfo"] = 5, + ["/TMCache"] = 6, + ["/names"] = 12 + }, infoStream.StreamIndices); + Assert.Equal(new[] {PdbFeature.VC140}, infoStream.Features); + } +} From 8e4de816830345222616fe5da8883c8c7799d74c Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 18 Jun 2022 14:31:49 +0200 Subject: [PATCH 156/184] Add write support for pdb hash tables and info stream. --- .../Metadata/Info/InfoStream.cs | 39 +++++- .../Metadata/Info/SerializedInfoStream.cs | 2 + .../Metadata/PdbHash.cs | 49 +++++++ .../Metadata/PdbHashTable.cs | 123 +++++++++++++++++- .../Metadata/Info/InfoStreamTest.cs | 14 +- .../Metadata/PdbHashTest.cs | 18 +++ 6 files changed, 239 insertions(+), 6 deletions(-) create mode 100644 src/AsmResolver.Symbols.Pdb/Metadata/PdbHash.cs create mode 100644 test/AsmResolver.Symbols.Pdb.Tests/Metadata/PdbHashTest.cs diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStream.cs index 182981fe1..7d3e07c55 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStream.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Threading; using AsmResolver.IO; @@ -119,6 +120,42 @@ public override uint GetPhysicalSize() /// public override void Write(IBinaryStreamWriter writer) { - throw new NotImplementedException(); + // 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. + // 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. + StreamIndices.WriteAsPdbHashTable(writer, + str => (ushort) PdbHash.ComputeV1(str), + (key, value) => (stringOffsets[key], (uint) value)); + + // last NI, safe to put always zero. + writer.WriteUInt32(0); + + // Write feature codes. + foreach (var feature in Features) + writer.WriteUInt32((uint) feature); } } diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Info/SerializedInfoStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Info/SerializedInfoStream.cs index 506a9ae86..53f774848 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Info/SerializedInfoStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Info/SerializedInfoStream.cs @@ -62,6 +62,8 @@ public SerializedInfoStream(BinaryStreamReader reader) return (keyString, (int) value); }); + uint lastNi = hashTableReader.ReadUInt32(); // Unused. + _featureOffset = hashTableReader.Offset; 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 index 942b1ddd1..7125fed86 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/PdbHashTable.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/PdbHashTable.cs @@ -5,8 +5,22 @@ namespace AsmResolver.Symbols.Pdb.Metadata; -internal static class PdbHashTable +/// +/// 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) @@ -29,8 +43,111 @@ internal static class PdbHashTable result.Add(key, value); } - uint lastNi = reader.ReadUInt32(); - return result; } + + /// + /// Serializes a dictionary to a PDB hash table. + /// + /// 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 + { + // "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 <= dictionary.Count; i++) + { + // Reference implementation allows only 67% of the capacity to be used. + uint maxLoad = capacity * 2 / 3 + 1; + if (i >= maxLoad) + capacity = 2 * maxLoad; + } + + // Define buckets. + uint[] keys = new uint[capacity]; + uint[] values = new uint[capacity]; + var present = new BitArray((int) capacity, false); + + // Fill in buckets. + foreach (var item in dictionary) + { + uint hash = hasher(item.Key); + (uint key, uint value) = mapper(item.Key, item.Value); + + uint index = hash % capacity; + while (present.Get((int) index)) + index = (index + 1) % capacity; + + keys[index] = key; + values[index] = value; + present.Set((int) index, true); + } + + return new HashTableInfo(capacity, keys, values, present); + } + + private readonly struct HashTableInfo + { + public readonly uint Capacity; + public readonly uint[] Keys; + public readonly uint[] Values; + public readonly BitArray Present; + + public HashTableInfo(uint capacity, uint[] keys, uint[] values, BitArray present) + { + Capacity = capacity; + Keys = keys; + Values = values; + Present = present; + } + } + } diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Info/InfoStreamTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Info/InfoStreamTest.cs index 97e8827cf..8c348e1f4 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Info/InfoStreamTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Info/InfoStreamTest.cs @@ -1,5 +1,7 @@ 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; @@ -8,11 +10,19 @@ namespace AsmResolver.Symbols.Pdb.Tests.Metadata.Info; public class InfoStreamTest { - [Fact] - public void Read() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ReadWrite(bool rebuild) { var file = MsfFile.FromBytes(Properties.Resources.SimpleDllPdb); var infoStream = InfoStream.FromReader(file.Streams[1].CreateReader()); + if (rebuild) + { + using var stream = new MemoryStream(); + infoStream.Write(new BinaryStreamWriter(stream)); + infoStream = InfoStream.FromReader(ByteArrayDataSource.CreateReader(stream.ToArray())); + } Assert.Equal(InfoStreamVersion.VC70, infoStream.Version); Assert.Equal(1u, infoStream.Age); 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)); + } +} From 8375199c9f2db75ac14cf4f0e54aa7858ab7a9f1 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 18 Jun 2022 14:41:44 +0200 Subject: [PATCH 157/184] Add BinaryStreamReader.ReadBytesUntil overload that can strip off the delimeter. --- .../Strings/SerializedStringsStream.cs | 18 ++------ .../Metadata/Info/SerializedInfoStream.cs | 21 +++------ src/AsmResolver/IO/BinaryStreamReader.cs | 43 +++++++++++++------ 3 files changed, 39 insertions(+), 43 deletions(-) diff --git a/src/AsmResolver.PE/DotNet/Metadata/Strings/SerializedStringsStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Strings/SerializedStringsStream.cs index 55d2f9299..78532dfb1 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Strings/SerializedStringsStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Strings/SerializedStringsStream.cs @@ -65,21 +65,11 @@ 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); + byte[] rawData = stringsReader.ReadBytesUntil(0, false); - 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 = rawData.Length != 0 + ? new Utf8String(rawData) + : Utf8String.Empty; _cachedStrings[index] = value; } diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Info/SerializedInfoStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Info/SerializedInfoStream.cs index 53f774848..2cec0ebba 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Info/SerializedInfoStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Info/SerializedInfoStream.cs @@ -42,22 +42,11 @@ public SerializedInfoStream(BinaryStreamReader reader) var result = PdbHashTable.FromReader(ref hashTableReader, (key, value) => { var stringReader = stringsReader.ForkRelative(key); - byte[] rawData = stringReader.ReadBytesUntil(0); - - Utf8String keyString; - if (rawData.Length == 0) - { - keyString = Utf8String.Empty; - } - else - { - // Trim off null terminator byte if its present. - int actualLength = rawData.Length; - if (rawData[actualLength - 1] == 0) - actualLength--; - - keyString = new Utf8String(rawData, 0, actualLength); - } + byte[] rawData = stringReader.ReadBytesUntil(0, false); + + var keyString = rawData.Length != 0 + ? new Utf8String(rawData) + : Utf8String.Empty; return (keyString, (int) value); }); diff --git a/src/AsmResolver/IO/BinaryStreamReader.cs b/src/AsmResolver/IO/BinaryStreamReader.cs index 74088ce4b..7a48e29be 100644 --- a/src/AsmResolver/IO/BinaryStreamReader.cs +++ b/src/AsmResolver/IO/BinaryStreamReader.cs @@ -328,18 +328,45 @@ 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) + { + bool shouldReadExtra = false; + var lookahead = Fork(); while (lookahead.RelativeOffset < lookahead.Length) { byte b = lookahead.ReadByte(); if (b == delimeter) + { + if (!includeDelimeterInReturn) + { + lookahead.RelativeOffset--; + shouldReadExtra = true; + } break; + } } byte[] buffer = new byte[lookahead.RelativeOffset - RelativeOffset]; ReadBytes(buffer, 0, buffer.Length); + + if (shouldReadExtra) + ReadByte(); + return buffer; } @@ -347,17 +374,7 @@ public byte[] ReadBytesUntil(byte delimeter) /// Reads a null-terminated ASCII string from the input stream. /// /// The read ASCII string, excluding the null terminator. - public string ReadAsciiString() - { - byte[] data = ReadBytesUntil(0); - int length = data.Length; - - // Exclude trailing 0 byte. - if (data[data.Length - 1] == 0) - length--; - - return Encoding.ASCII.GetString(data, 0, length); - } + public string ReadAsciiString() => Encoding.ASCII.GetString(ReadBytesUntil(0, false)); /// /// Reads a zero-terminated Unicode string from the stream. From bc5ce143fdaa8551ac8c60dec2afdfb252c1c9f2 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 18 Jun 2022 15:37:22 +0200 Subject: [PATCH 158/184] Add basic read support for DBI stream header. --- .../AsmResolver.Symbols.Pdb.csproj | 1 + .../Metadata/Dbi/DbiAttributes.cs | 30 ++++ .../Metadata/Dbi/DbiStream.cs | 148 ++++++++++++++++++ .../Metadata/Dbi/DbiStreamVersion.cs | 15 ++ .../Metadata/Dbi/SerializedDbiStream.cs | 51 ++++++ .../Metadata/Info/InfoStream.cs | 9 +- .../Metadata/Info/InfoStreamVersion.cs | 1 + .../Metadata/Info/SerializedInfoStream.cs | 2 +- .../Metadata/Dbi/DbiStreamTest.cs | 20 +++ .../Metadata/Info/InfoStreamTest.cs | 2 +- 10 files changed, 275 insertions(+), 4 deletions(-) create mode 100644 src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiAttributes.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStreamVersion.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs create mode 100644 test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs diff --git a/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj b/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj index b0b3df765..168f407cc 100644 --- a/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj +++ b/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj @@ -21,6 +21,7 @@ + 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..f615a3545 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs @@ -0,0 +1,148 @@ +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; + + /// + /// 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 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; + } + + /// + /// 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); + + /// + public override uint GetPhysicalSize() + { + throw new System.NotImplementedException(); + } + + /// + public override void Write(IBinaryStreamWriter writer) + { + throw new System.NotImplementedException(); + } +} 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/SerializedDbiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs new file mode 100644 index 000000000..5bb1043d5 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs @@ -0,0 +1,51 @@ +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 uint _moduleInfoSize; + private readonly uint _sectionContributionSize; + private readonly uint _sectionMapSize; + private readonly uint _sourceInfoSize; + private readonly uint _typeServerMapSize; + private readonly uint _optionalDebugHeaderSize; + private readonly uint _ecSize; + + /// + /// 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(); + PublicStreamIndex = reader.ReadUInt16(); + PdbDllVersion = reader.ReadUInt16(); + SymbolRecordStreamIndex = reader.ReadUInt16(); + PdbDllRbld = reader.ReadUInt16(); + + _moduleInfoSize = reader.ReadUInt32(); + _sectionContributionSize = reader.ReadUInt32(); + _sectionMapSize = reader.ReadUInt32(); + _sourceInfoSize = reader.ReadUInt32(); + _typeServerMapSize = reader.ReadUInt32(); + + MfcTypeServerIndex = reader.ReadUInt32(); + + _optionalDebugHeaderSize = reader.ReadUInt32(); + _ecSize = reader.ReadUInt32(); + + Attributes = (DbiAttributes) reader.ReadUInt16(); + Machine = (MachineType) reader.ReadUInt16(); + + _ = reader.ReadUInt32(); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStream.cs index 7d3e07c55..00fede54c 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStream.cs @@ -11,6 +11,11 @@ namespace AsmResolver.Symbols.Pdb.Metadata.Info; /// public class InfoStream : SegmentBase { + /// + /// Gets the default fixed MSF stream index for the PDB Info stream. + /// + public const int StreamIndex = 1; + private IDictionary? _streamIndices; private IList? _features; @@ -24,7 +29,7 @@ public InfoStreamVersion Version { get; set; - } + } = InfoStreamVersion.VC70; /// /// Gets or sets the 32-bit UNIX time-stamp of the PDB file. @@ -42,7 +47,7 @@ public uint Age { get; set; - } + } = 1; /// /// Gets or sets the unique identifier assigned to the PDB file. diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStreamVersion.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStreamVersion.cs index 361d41e07..d377f5ba1 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStreamVersion.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStreamVersion.cs @@ -16,4 +16,5 @@ public enum InfoStreamVersion VC80 = 20030901, VC110 = 20091201, VC140 = 20140508, +#pragma warning restore CS1591 } diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Info/SerializedInfoStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Info/SerializedInfoStream.cs index 2cec0ebba..8ce5c3b21 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Info/SerializedInfoStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Info/SerializedInfoStream.cs @@ -5,7 +5,7 @@ namespace AsmResolver.Symbols.Pdb.Metadata.Info; /// -/// Models an PDB info stream by pulling its data from an input stream. +/// Implements an PDB info stream that pulls its data from an input stream. /// public class SerializedInfoStream : InfoStream { 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..e1bca57a4 --- /dev/null +++ b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs @@ -0,0 +1,20 @@ +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 +{ + [Fact] + public void Read() + { + var file = MsfFile.FromBytes(Properties.Resources.SimpleDllPdb); + var dbiStream = DbiStream.FromReader(file.Streams[DbiStream.StreamIndex].CreateReader()); + + Assert.Equal(1u, dbiStream.Age); + Assert.Equal(DbiAttributes.None, dbiStream.Attributes); + Assert.Equal(MachineType.I386, dbiStream.Machine); + } +} diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Info/InfoStreamTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Info/InfoStreamTest.cs index 8c348e1f4..ff78a7b81 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Info/InfoStreamTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Info/InfoStreamTest.cs @@ -16,7 +16,7 @@ public class InfoStreamTest public void ReadWrite(bool rebuild) { var file = MsfFile.FromBytes(Properties.Resources.SimpleDllPdb); - var infoStream = InfoStream.FromReader(file.Streams[1].CreateReader()); + var infoStream = InfoStream.FromReader(file.Streams[InfoStream.StreamIndex].CreateReader()); if (rebuild) { using var stream = new MemoryStream(); From 3ef199a1c9241dd210d0f1fa6b6cc6ab7212ecc9 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 18 Jun 2022 16:56:30 +0200 Subject: [PATCH 159/184] Add read support for dbi module descriptors. --- .../Metadata/Dbi/DbiStream.cs | 23 ++ .../Metadata/Dbi/ModuleDescriptor.cs | 207 ++++++++++++++++++ .../Dbi/ModuleDescriptorAttributes.cs | 25 +++ .../Metadata/Dbi/SectionContribution.cs | 124 +++++++++++ .../Metadata/Dbi/SerializedDbiStream.cs | 56 +++-- .../Metadata/Dbi/DbiStreamTest.cs | 46 +++- .../Msf/MsfFileTest.cs | 1 + 7 files changed, 467 insertions(+), 15 deletions(-) create mode 100644 src/AsmResolver.Symbols.Pdb/Metadata/Dbi/ModuleDescriptor.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Metadata/Dbi/ModuleDescriptorAttributes.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionContribution.cs diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs index f615a3545..feb56f542 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; +using System.Threading; using AsmResolver.IO; using AsmResolver.PE.File.Headers; @@ -13,6 +15,8 @@ public class DbiStream : SegmentBase /// public const int StreamIndex = 3; + private IList? _modules; + /// /// Gets or sets the version signature assigned to the DBI stream. /// @@ -127,6 +131,25 @@ public MachineType Machine 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; + } + } + + /// + /// Obtains the list of modules + /// + /// + protected virtual IList GetModules() => new List(); + /// /// Reads a single DBI stream from the provided input stream. /// 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..968777a1d --- /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 = new Utf8String(reader.ReadBytesUntil(0, false)); + result.ObjectFileName = new Utf8String(reader.ReadBytesUntil(0, false)); + 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(char) * 2 // Padding + + sizeof(uint) // Unused2 + + sizeof(uint) // SourceFileNameIndex + + sizeof(uint) // PdbFilePathNameIndex + + (uint) (ModuleName?.ByteCount ?? 0) + 1 // ModuleName + + (uint) (ObjectFileName?.ByteCount ?? 0) + 1 // ObjectFileName + ; + } + + /// + 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..713f251b9 --- /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..00b684dd5 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionContribution.cs @@ -0,0 +1,124 @@ +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 +{ + /// + /// 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() + { + return 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 + ; + } + + /// + 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/SerializedDbiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs index 5bb1043d5..93def65e3 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using AsmResolver.IO; using AsmResolver.PE.File.Headers; @@ -8,13 +9,13 @@ namespace AsmResolver.Symbols.Pdb.Metadata.Dbi; /// public class SerializedDbiStream : DbiStream { - private readonly uint _moduleInfoSize; - private readonly uint _sectionContributionSize; - private readonly uint _sectionMapSize; - private readonly uint _sourceInfoSize; - private readonly uint _typeServerMapSize; - private readonly uint _optionalDebugHeaderSize; - private readonly uint _ecSize; + 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. @@ -32,20 +33,47 @@ public SerializedDbiStream(BinaryStreamReader reader) SymbolRecordStreamIndex = reader.ReadUInt16(); PdbDllRbld = reader.ReadUInt16(); - _moduleInfoSize = reader.ReadUInt32(); - _sectionContributionSize = reader.ReadUInt32(); - _sectionMapSize = reader.ReadUInt32(); - _sourceInfoSize = reader.ReadUInt32(); - _typeServerMapSize = reader.ReadUInt32(); + uint moduleInfoSize = reader.ReadUInt32(); + uint sectionContributionSize = reader.ReadUInt32(); + uint sectionMapSize = reader.ReadUInt32(); + uint sourceInfoSize = reader.ReadUInt32(); + uint typeServerMapSize = reader.ReadUInt32(); MfcTypeServerIndex = reader.ReadUInt32(); - _optionalDebugHeaderSize = reader.ReadUInt32(); - _ecSize = 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; } } diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs index e1bca57a4..d5fe61bee 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs @@ -1,3 +1,4 @@ +using System.Linq; using AsmResolver.PE.File.Headers; using AsmResolver.Symbols.Pdb.Metadata.Dbi; using AsmResolver.Symbols.Pdb.Msf; @@ -8,7 +9,7 @@ namespace AsmResolver.Symbols.Pdb.Tests.Metadata.Dbi; public class DbiStreamTest { [Fact] - public void Read() + public void ReadHeader() { var file = MsfFile.FromBytes(Properties.Resources.SimpleDllPdb); var dbiStream = DbiStream.FromReader(file.Streams[DbiStream.StreamIndex].CreateReader()); @@ -17,4 +18,47 @@ public void Read() Assert.Equal(DbiAttributes.None, dbiStream.Attributes); Assert.Equal(MachineType.I386, dbiStream.Machine); } + + [Fact] + public void ReadModuleNames() + { + var file = MsfFile.FromBytes(Properties.Resources.SimpleDllPdb); + var dbiStream = DbiStream.FromReader(file.Streams[DbiStream.StreamIndex].CreateReader()); + + 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)); + } } diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Msf/MsfFileTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Msf/MsfFileTest.cs index b17cd4b36..e7c589c70 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Msf/MsfFileTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Msf/MsfFileTest.cs @@ -1,5 +1,6 @@ using System.IO; using System.Linq; +using AsmResolver.IO; using AsmResolver.Symbols.Pdb.Msf; using Xunit; From 09d5166860fad57d5db218a228d43e0d4b5caddf Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 19 Jun 2022 22:19:30 +0200 Subject: [PATCH 160/184] Add read support section contributions to DBI header. --- .../Metadata/Dbi/DbiStream.cs | 30 +++++++++++++++-- .../Metadata/Dbi/SectionContribution.cs | 1 + .../Dbi/SectionContributionStreamVersion.cs | 17 ++++++++++ .../Metadata/Dbi/SerializedDbiStream.cs | 18 +++++++++++ .../Metadata/Dbi/DbiStreamTest.cs | 32 +++++++++++++++++++ 5 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionContributionStreamVersion.cs diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs index feb56f542..5af75e2fd 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs @@ -16,6 +16,7 @@ public class DbiStream : SegmentBase public const int StreamIndex = 3; private IList? _modules; + private IList? _sectionContributions; /// /// Gets or sets the version signature assigned to the DBI stream. @@ -145,11 +146,36 @@ public IList Modules } /// - /// Obtains the list of 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; + } + } + + /// + /// 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(); + /// /// Reads a single DBI stream from the provided input stream. /// diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionContribution.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionContribution.cs index 00b684dd5..35953c208 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionContribution.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionContribution.cs @@ -1,3 +1,4 @@ +using System.Text; using AsmResolver.IO; using AsmResolver.PE.File.Headers; 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/SerializedDbiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs index 93def65e3..dd3c1b42e 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs @@ -76,4 +76,22 @@ protected override IList GetModules() 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; + } } diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs index d5fe61bee..280266228 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs @@ -61,4 +61,36 @@ public void ReadModuleNames() "* Linker *", }, dbiStream.Modules.Select(m => m.ModuleName?.Value)); } + + [Fact] + public void ReadSectionContributions() + { + var file = MsfFile.FromBytes(Properties.Resources.SimpleDllPdb); + var dbiStream = DbiStream.FromReader(file.Streams[DbiStream.StreamIndex].CreateReader()); + + 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))); + } } From f9828030787f6a97a8af4a230b4a39b4089a9b62 Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 19 Jun 2022 23:52:12 +0200 Subject: [PATCH 161/184] Add read support section mappings in DBI stream. --- .../Metadata/Dbi/DbiStream.cs | 28 ++++ .../Dbi/ModuleDescriptorAttributes.cs | 2 +- .../Metadata/Dbi/SectionMap.cs | 128 ++++++++++++++++++ .../Metadata/Dbi/SectionMapAttributes.cs | 42 ++++++ .../Metadata/Dbi/SerializedDbiStream.cs | 16 +++ .../Metadata/Dbi/DbiStreamTest.cs | 20 +++ 6 files changed, 235 insertions(+), 1 deletion(-) create mode 100644 src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionMap.cs create mode 100644 src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionMapAttributes.cs diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs index 5af75e2fd..4a0e490fe 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs @@ -17,6 +17,7 @@ public class DbiStream : SegmentBase private IList? _modules; private IList? _sectionContributions; + private IList? _sectionMaps; /// /// Gets or sets the version signature assigned to the DBI stream. @@ -158,6 +159,24 @@ public IList 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; + + } + } + /// /// Obtains the list of module descriptors. /// @@ -176,6 +195,15 @@ public IList SectionContributions /// 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(); + /// /// Reads a single DBI stream from the provided input stream. /// diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/ModuleDescriptorAttributes.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/ModuleDescriptorAttributes.cs index 713f251b9..5b6a018c0 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/ModuleDescriptorAttributes.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/ModuleDescriptorAttributes.cs @@ -14,7 +14,7 @@ public enum ModuleDescriptorAttributes : ushort Dirty = 1, /// - /// Indicates the module contains Edit & Continue information. + /// Indicates the module contains Edit & Continue information. /// EC = 2, 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..6c42bb300 --- /dev/null +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionMap.cs @@ -0,0 +1,128 @@ +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 +{ + /// + /// 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() + { + return sizeof(ushort) // Attributes + + sizeof(ushort) // Ovl + + sizeof(ushort) // Group + + sizeof(ushort) // Frame + + sizeof(ushort) // SectionName + + sizeof(ushort) // ClassName + + sizeof(uint) // Offset + + sizeof(uint) // SectionLength + ; + } + + /// + 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 index dd3c1b42e..6e0219b57 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs @@ -94,4 +94,20 @@ protected override IList GetSectionContributions() return result; } + + /// + protected override IList GetSectionMaps() + { + var result = new List(); + + var reader = _sectionMapReader.Fork(); + + ushort count = reader.ReadUInt16(); + ushort logCount = reader.ReadUInt16(); + + for (int i = 0; i < count; i++) + result.Add(SectionMap.FromReader(ref reader)); + + return result; + } } diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs index 280266228..45804e09e 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs @@ -93,4 +93,24 @@ public void ReadSectionContributions() (24, 0), (10, 0), (14, 0), (2, 0), (31, 0), (3, 0), (3, 0) }, dbiStream.SectionContributions.Select(x => (x.ModuleIndex, x.DataCrc))); } + + [Fact] + public void ReadSectionMap() + { + var file = MsfFile.FromBytes(Properties.Resources.SimpleDllPdb); + var dbiStream = DbiStream.FromReader(file.Streams[DbiStream.StreamIndex].CreateReader()); + + 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))); + } } From 3573e6360c524dc423aa2dd9720423da7105887e Mon Sep 17 00:00:00 2001 From: Washi Date: Tue, 21 Jun 2022 11:43:30 +0200 Subject: [PATCH 162/184] Add basic read support for typeservermap substream. --- .../Metadata/Dbi/DbiStream.cs | 477 +++++++++--------- .../Metadata/Dbi/SerializedDbiStream.cs | 233 ++++----- 2 files changed, 372 insertions(+), 338 deletions(-) diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs index 4a0e490fe..926f1d91c 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs @@ -1,225 +1,252 @@ -using System.Collections.Generic; -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; - - /// - /// 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 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; - - } - } - - /// - /// 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(); - - /// - /// 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); - - /// - public override uint GetPhysicalSize() - { - throw new System.NotImplementedException(); - } - - /// - public override void Write(IBinaryStreamWriter writer) - { - throw new System.NotImplementedException(); - } -} +using System.Collections.Generic; +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 _typeServerMap; + + public DbiStream() + { + _typeServerMap = new LazyVariable(GetTypeServerMap); + } + + /// + /// 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 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 of this sub stream is unknown. + /// + public ISegment TypeServerMap + { + get => _typeServerMap.Value; + set => _typeServerMap.Value = value; + } + + /// + /// 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? GetTypeServerMap() => null; + + /// + public override uint GetPhysicalSize() + { + throw new System.NotImplementedException(); + } + + /// + public override void Write(IBinaryStreamWriter writer) + { + throw new System.NotImplementedException(); + } +} diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs index 6e0219b57..6fcc68cc4 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs @@ -1,113 +1,120 @@ -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(); - 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 result = new List(); - - var reader = _sectionMapReader.Fork(); - - ushort count = reader.ReadUInt16(); - ushort logCount = reader.ReadUInt16(); - - for (int i = 0; i < count; i++) - result.Add(SectionMap.FromReader(ref reader)); - - return result; - } -} +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(); + 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 result = new List(); + + var reader = _sectionMapReader.Fork(); + + ushort count = reader.ReadUInt16(); + ushort logCount = reader.ReadUInt16(); + + for (int i = 0; i < count; i++) + result.Add(SectionMap.FromReader(ref reader)); + + return result; + } + + /// + protected override ISegment? GetTypeServerMap() + { + var reader = _typeServerMapReader; + return reader.ReadSegment(reader.Length); + } +} From 0f6032c71f67044877004288d70c9d802249a7bd Mon Sep 17 00:00:00 2001 From: Washi Date: Tue, 21 Jun 2022 11:46:47 +0200 Subject: [PATCH 163/184] Add basic read support for ec substream. --- .../Metadata/Dbi/DbiStream.cs | 41 +++++++++++++++---- .../Metadata/Dbi/SerializedDbiStream.cs | 17 ++++++-- 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs index 926f1d91c..6ebd15df3 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs @@ -18,11 +18,13 @@ public class DbiStream : SegmentBase private IList? _modules; private IList? _sectionContributions; private IList? _sectionMaps; - private readonly LazyVariable _typeServerMap; + private readonly LazyVariable _typeServerMapStream; + private readonly LazyVariable _ecStream; public DbiStream() { - _typeServerMap = new LazyVariable(GetTypeServerMap); + _typeServerMapStream = new LazyVariable(GetTypeServerMapStream); + _ecStream = new LazyVariable(GetECStream); } /// @@ -187,12 +189,26 @@ public IList SectionMaps /// Gets or sets the contents of the type server map sub stream. /// /// - /// The exact purpose of this sub stream is unknown. + /// The exact purpose and layout of this sub stream is unknown, hence this property exposes the stream as + /// a raw segment. /// - public ISegment TypeServerMap + public ISegment TypeServerMapStream { - get => _typeServerMap.Value; - set => _typeServerMap.Value = value; + 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; } /// @@ -234,9 +250,18 @@ public ISegment TypeServerMap /// /// The contents of the sub stream. /// - /// This method is called upon initialization of the property. + /// 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? GetTypeServerMap() => null; + protected virtual ISegment? GetECStream() => null; /// public override uint GetPhysicalSize() diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs index 6fcc68cc4..3d6bad38c 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs @@ -112,9 +112,20 @@ protected override IList GetSectionMaps() } /// - protected override ISegment? GetTypeServerMap() + protected override ISegment? GetTypeServerMapStream() { - var reader = _typeServerMapReader; - return reader.ReadSegment(reader.Length); + 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; } } From fea51df9f864755bf3af353bc44444a7cf94088d Mon Sep 17 00:00:00 2001 From: Washi Date: Tue, 21 Jun 2022 19:58:15 +0200 Subject: [PATCH 164/184] Add read support for source file collections. --- .../Metadata/Dbi/DbiStream.cs | 32 ++++++++++++- .../Metadata/Dbi/SerializedDbiStream.cs | 48 +++++++++++++++++++ .../Metadata/Dbi/SourceFileCollection.cs | 37 ++++++++++++++ .../Metadata/Dbi/DbiStreamTest.cs | 45 +++++++++++++++++ 4 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SourceFileCollection.cs diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs index 6ebd15df3..05495f5b4 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs @@ -20,7 +20,11 @@ public class DbiStream : SegmentBase private IList? _sectionMaps; private readonly LazyVariable _typeServerMapStream; private readonly LazyVariable _ecStream; + private IList? _sourceFiles; + /// + /// Creates a new empty DBI stream. + /// public DbiStream() { _typeServerMapStream = new LazyVariable(GetTypeServerMapStream); @@ -181,7 +185,6 @@ public IList SectionMaps if (_sectionMaps is null) Interlocked.CompareExchange(ref _sectionMaps, GetSectionMaps(), null); return _sectionMaps; - } } @@ -211,6 +214,24 @@ public ISegment ECStream 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; + } + } + /// /// Reads a single DBI stream from the provided input stream. /// @@ -263,6 +284,15 @@ public ISegment ECStream /// 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(); + /// public override uint GetPhysicalSize() { diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs index 3d6bad38c..8e1650274 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs @@ -128,4 +128,52 @@ protected override IList GetSectionMaps() ? reader.ReadSegment(reader.Length) : null; } + + /// + protected override IList GetSourceFiles() + { + var result = new List(); + + 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. + 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(new Utf8String(nameReader.ReadBytesUntil(0, false))); + } + + result.Add(files); + } + + 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/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs index 45804e09e..85c4cd836 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using AsmResolver.PE.File.Headers; using AsmResolver.Symbols.Pdb.Metadata.Dbi; @@ -113,4 +114,48 @@ public void ReadSectionMap() m.Attributes, m.LogicalOverlayNumber, m.Group, m.Frame, m.SectionName, m.ClassName, m.Offset, m.SectionLength))); } + + [Fact] + public void ReadSourceFiles() + { + var file = MsfFile.FromBytes(Properties.Resources.SimpleDllPdb); + var dbiStream = DbiStream.FromReader(file.Streams[DbiStream.StreamIndex].CreateReader()); + + 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); + } } From 5f700acdac3bf8186b5fe5afb1fd4d530878aa49 Mon Sep 17 00:00:00 2001 From: Washi Date: Tue, 21 Jun 2022 20:10:33 +0200 Subject: [PATCH 165/184] Add read support extra dbg stream indices. --- .../Metadata/Dbi/DbiStream.cs | 23 +++++++++++++++++++ .../Metadata/Dbi/SerializedDbiStream.cs | 19 +++++++++++---- .../Metadata/Dbi/DbiStreamTest.cs | 11 +++++++++ 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs index 05495f5b4..9744d852e 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs @@ -21,6 +21,7 @@ public class DbiStream : SegmentBase private readonly LazyVariable _typeServerMapStream; private readonly LazyVariable _ecStream; private IList? _sourceFiles; + private IList? _extraStreamIndices; /// /// Creates a new empty DBI stream. @@ -232,6 +233,19 @@ public IList 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. /// @@ -293,6 +307,15 @@ public IList SourceFiles /// 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() { diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs index 8e1650274..4c6fd2632 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs @@ -98,13 +98,12 @@ protected override IList GetSectionContributions() /// protected override IList GetSectionMaps() { - var result = new List(); - 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)); @@ -132,8 +131,6 @@ protected override IList GetSectionMaps() /// protected override IList GetSourceFiles() { - var result = new List(); - var reader = _sourceInfoReader.Fork(); ushort moduleCount = reader.ReadUInt16(); @@ -158,6 +155,7 @@ protected override IList GetSourceFiles() 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]); @@ -176,4 +174,17 @@ protected override IList GetSourceFiles() 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/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs index 85c4cd836..179173850 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs @@ -158,4 +158,15 @@ public void ReadSourceFiles() }, firstThreeActualFileLists); } + + [Fact] + public void ReadExtraDebugIndices() + { + var file = MsfFile.FromBytes(Properties.Resources.SimpleDllPdb); + var dbiStream = DbiStream.FromReader(file.Streams[DbiStream.StreamIndex].CreateReader()); + Assert.Equal(new ushort[] + { + 0x7, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xB, 0xFFFF, 0xFFFF, 0xFFFF, 0xD, 0xFFFF + }, dbiStream.ExtraStreamIndices); + } } From 6f169b5742a7f57eaa8bd938591a91347de2bb63 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 24 Jun 2022 16:01:23 +0200 Subject: [PATCH 166/184] Add write support for DBI stream. --- .../Metadata/Dbi/DbiStream.cs | 253 ++++++++++- .../Metadata/Dbi/ModuleDescriptor.cs | 414 +++++++++--------- .../Metadata/Dbi/SectionContribution.cs | 253 +++++------ .../Metadata/Dbi/SectionMap.cs | 259 +++++------ .../Metadata/Dbi/DbiStreamTest.cs | 368 ++++++++-------- 5 files changed, 907 insertions(+), 640 deletions(-) diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs index 9744d852e..e9180fb4d 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Threading; using AsmResolver.IO; using AsmResolver.PE.File.Headers; @@ -18,8 +20,8 @@ public class DbiStream : SegmentBase private IList? _modules; private IList? _sectionContributions; private IList? _sectionMaps; - private readonly LazyVariable _typeServerMapStream; - private readonly LazyVariable _ecStream; + private readonly LazyVariable _typeServerMapStream; + private readonly LazyVariable _ecStream; private IList? _sourceFiles; private IList? _extraStreamIndices; @@ -28,8 +30,8 @@ public class DbiStream : SegmentBase /// public DbiStream() { - _typeServerMapStream = new LazyVariable(GetTypeServerMapStream); - _ecStream = new LazyVariable(GetECStream); + _typeServerMapStream = new LazyVariable(GetTypeServerMapStream); + _ecStream = new LazyVariable(GetECStream); } /// @@ -196,7 +198,7 @@ public IList SectionMaps /// The exact purpose and layout of this sub stream is unknown, hence this property exposes the stream as /// a raw segment. /// - public ISegment TypeServerMapStream + public ISegment? TypeServerMapStream { get => _typeServerMapStream.Value; set => _typeServerMapStream.Value = value; @@ -209,7 +211,7 @@ public ISegment TypeServerMapStream /// The exact purpose and layout of this sub stream is unknown, hence this property exposes the stream as /// a raw segment. /// - public ISegment ECStream + public ISegment? ECStream { get => _ecStream.Value; set => _ecStream.Value = value; @@ -319,12 +321,247 @@ public IList ExtraStreamIndices /// public override uint GetPhysicalSize() { - throw new System.NotImplementedException(); + 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) { - throw new System.NotImplementedException(); + 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/ModuleDescriptor.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/ModuleDescriptor.cs index 968777a1d..5fee61c3e 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/ModuleDescriptor.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/ModuleDescriptor.cs @@ -1,207 +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 = new Utf8String(reader.ReadBytesUntil(0, false)); - result.ObjectFileName = new Utf8String(reader.ReadBytesUntil(0, false)); - 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(char) * 2 // Padding - + sizeof(uint) // Unused2 - + sizeof(uint) // SourceFileNameIndex - + sizeof(uint) // PdbFilePathNameIndex - + (uint) (ModuleName?.ByteCount ?? 0) + 1 // ModuleName - + (uint) (ObjectFileName?.ByteCount ?? 0) + 1 // ObjectFileName - ; - } - - /// - 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 ?? "?"; -} +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 = new Utf8String(reader.ReadBytesUntil(0, false)); + result.ObjectFileName = new Utf8String(reader.ReadBytesUntil(0, false)); + 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/SectionContribution.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionContribution.cs index 35953c208..83daaae3e 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionContribution.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionContribution.cs @@ -1,125 +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 -{ - /// - /// 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() - { - return 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 - ; - } - - /// - 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); - } -} +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/SectionMap.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionMap.cs index 6c42bb300..bf43a7e56 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionMap.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SectionMap.cs @@ -1,128 +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 -{ - /// - /// 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() - { - return sizeof(ushort) // Attributes - + sizeof(ushort) // Ovl - + sizeof(ushort) // Group - + sizeof(ushort) // Frame - + sizeof(ushort) // SectionName - + sizeof(ushort) // ClassName - + sizeof(uint) // Offset - + sizeof(uint) // SectionLength - ; - } - - /// - 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); - } -} +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/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs index 179173850..35a2efb61 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs @@ -1,172 +1,196 @@ -using System; -using System.Linq; -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 -{ - [Fact] - public void ReadHeader() - { - var file = MsfFile.FromBytes(Properties.Resources.SimpleDllPdb); - var dbiStream = DbiStream.FromReader(file.Streams[DbiStream.StreamIndex].CreateReader()); - - Assert.Equal(1u, dbiStream.Age); - Assert.Equal(DbiAttributes.None, dbiStream.Attributes); - Assert.Equal(MachineType.I386, dbiStream.Machine); - } - - [Fact] - public void ReadModuleNames() - { - var file = MsfFile.FromBytes(Properties.Resources.SimpleDllPdb); - var dbiStream = DbiStream.FromReader(file.Streams[DbiStream.StreamIndex].CreateReader()); - - 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)); - } - - [Fact] - public void ReadSectionContributions() - { - var file = MsfFile.FromBytes(Properties.Resources.SimpleDllPdb); - var dbiStream = DbiStream.FromReader(file.Streams[DbiStream.StreamIndex].CreateReader()); - - 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))); - } - - [Fact] - public void ReadSectionMap() - { - var file = MsfFile.FromBytes(Properties.Resources.SimpleDllPdb); - var dbiStream = DbiStream.FromReader(file.Streams[DbiStream.StreamIndex].CreateReader()); - - 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))); - } - - [Fact] - public void ReadSourceFiles() - { - var file = MsfFile.FromBytes(Properties.Resources.SimpleDllPdb); - var dbiStream = DbiStream.FromReader(file.Streams[DbiStream.StreamIndex].CreateReader()); - - 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); - } - - [Fact] - public void ReadExtraDebugIndices() - { - var file = MsfFile.FromBytes(Properties.Resources.SimpleDllPdb); - var dbiStream = DbiStream.FromReader(file.Streams[DbiStream.StreamIndex].CreateReader()); - Assert.Equal(new ushort[] - { - 0x7, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xB, 0xFFFF, 0xFFFF, 0xFFFF, 0xD, 0xFFFF - }, dbiStream.ExtraStreamIndices); - } -} +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(ByteArrayDataSource.CreateReader(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); + } + + [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); + } +} From d510ec9f207d664096efe2644fa48cdc540ab2d5 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 24 Jun 2022 18:40:47 +0200 Subject: [PATCH 167/184] Add BinaryStreamReader.ReadUtf8String --- .../Metadata/Strings/SerializedStringsStream.cs | 7 +------ .../Metadata/Dbi/ModuleDescriptor.cs | 4 ++-- .../Metadata/Dbi/SerializedDbiStream.cs | 2 +- .../Metadata/Info/SerializedInfoStream.cs | 7 +------ src/AsmResolver/IO/BinaryStreamReader.cs | 12 ++++++++++++ 5 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/AsmResolver.PE/DotNet/Metadata/Strings/SerializedStringsStream.cs b/src/AsmResolver.PE/DotNet/Metadata/Strings/SerializedStringsStream.cs index 78532dfb1..293562f36 100644 --- a/src/AsmResolver.PE/DotNet/Metadata/Strings/SerializedStringsStream.cs +++ b/src/AsmResolver.PE/DotNet/Metadata/Strings/SerializedStringsStream.cs @@ -65,12 +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, false); - - value = rawData.Length != 0 - ? new Utf8String(rawData) - : Utf8String.Empty; - + value = stringsReader.ReadUtf8String(); _cachedStrings[index] = value; } diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/ModuleDescriptor.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/ModuleDescriptor.cs index 5fee61c3e..174ec2dab 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/ModuleDescriptor.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/ModuleDescriptor.cs @@ -151,8 +151,8 @@ public static ModuleDescriptor FromReader(ref BinaryStreamReader reader) reader.ReadUInt32(); result.SourceFileNameIndex = reader.ReadUInt32(); result.PdbFilePathNameIndex = reader.ReadUInt32(); - result.ModuleName = new Utf8String(reader.ReadBytesUntil(0, false)); - result.ObjectFileName = new Utf8String(reader.ReadBytesUntil(0, false)); + result.ModuleName = reader.ReadUtf8String(); + result.ObjectFileName = reader.ReadUtf8String(); reader.Align(4); return result; diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs index 4c6fd2632..172f44a5a 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs @@ -166,7 +166,7 @@ protected override IList GetSourceFiles() { uint nameOffset = reader.ReadUInt32(); var nameReader = stringReaderBuffer.ForkRelative(nameOffset); - files.Add(new Utf8String(nameReader.ReadBytesUntil(0, false))); + files.Add(nameReader.ReadUtf8String()); } result.Add(files); diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Info/SerializedInfoStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Info/SerializedInfoStream.cs index 8ce5c3b21..1aebc173a 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Info/SerializedInfoStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Info/SerializedInfoStream.cs @@ -42,12 +42,7 @@ public SerializedInfoStream(BinaryStreamReader reader) var result = PdbHashTable.FromReader(ref hashTableReader, (key, value) => { var stringReader = stringsReader.ForkRelative(key); - byte[] rawData = stringReader.ReadBytesUntil(0, false); - - var keyString = rawData.Length != 0 - ? new Utf8String(rawData) - : Utf8String.Empty; - + var keyString = stringReader.ReadUtf8String(); return (keyString, (int) value); }); diff --git a/src/AsmResolver/IO/BinaryStreamReader.cs b/src/AsmResolver/IO/BinaryStreamReader.cs index 7a48e29be..d339ceab3 100644 --- a/src/AsmResolver/IO/BinaryStreamReader.cs +++ b/src/AsmResolver/IO/BinaryStreamReader.cs @@ -395,6 +395,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. /// From e4ef4c11a7de2ba25e9e56dcf70528050cecc594 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 24 Jun 2022 18:54:17 +0200 Subject: [PATCH 168/184] Explode DbiStream.BuildNumber into major, minor and newfileformat version properties. --- .../Metadata/Dbi/DbiStream.cs | 28 +++++++++++++++++++ .../Metadata/Dbi/SerializedDbiStream.cs | 4 +++ .../Metadata/Dbi/DbiStreamTest.cs | 3 ++ 3 files changed, 35 insertions(+) diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs index e9180fb4d..247bd21ac 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/DbiStream.cs @@ -32,6 +32,7 @@ public DbiStream() { _typeServerMapStream = new LazyVariable(GetTypeServerMapStream); _ecStream = new LazyVariable(GetECStream); + IsNewVersionFormat = true; } /// @@ -85,6 +86,33 @@ public ushort BuildNumber 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. /// diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs index 172f44a5a..962f02fc7 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Dbi/SerializedDbiStream.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using AsmResolver.IO; using AsmResolver.PE.File.Headers; @@ -28,6 +29,9 @@ public SerializedDbiStream(BinaryStreamReader reader) 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(); diff --git a/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs index 35a2efb61..19e2c5035 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs @@ -36,6 +36,9 @@ public void Header(bool 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] From c7039c55068ccf0cb16781da316dbde50a22dac8 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 25 Jun 2022 16:27:27 +0200 Subject: [PATCH 169/184] BUGFIX: Include pdb hash table and feature codes in size computation of InfoStream. --- .../Metadata/Info/InfoStream.cs | 45 +++++-- .../Metadata/Info/SerializedInfoStream.cs | 2 +- .../Metadata/PdbHashTable.cs | 115 +++++++++++++----- .../Metadata/Dbi/DbiStreamTest.cs | 14 +++ .../Metadata/Info/InfoStreamTest.cs | 48 +++++++- .../Msf/MsfFileTest.cs | 1 - 6 files changed, 181 insertions(+), 44 deletions(-) diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStream.cs index 00fede54c..5dd16b073 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStream.cs @@ -16,6 +16,14 @@ public class InfoStream : SegmentBase /// 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; @@ -115,11 +123,22 @@ public IList Features /// public override uint GetPhysicalSize() { - return sizeof(uint) // Version - + sizeof(uint) // Signature - + sizeof(uint) // Aage - + 16 // UniqueId - ; + 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; } /// @@ -148,12 +167,8 @@ public override void Write(IBinaryStreamWriter writer) writer.WriteBytes(nameBuffer.ToArray()); // Write the hash table. - // 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. StreamIndices.WriteAsPdbHashTable(writer, - str => (ushort) PdbHash.ComputeV1(str), + ComputeStringHash, (key, value) => (stringOffsets[key], (uint) value)); // last NI, safe to put always zero. @@ -163,4 +178,14 @@ public override void Write(IBinaryStreamWriter writer) foreach (var feature in Features) writer.WriteUInt32((uint) feature); } + + 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/SerializedInfoStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Info/SerializedInfoStream.cs index 1aebc173a..f448ce67f 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Info/SerializedInfoStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Info/SerializedInfoStream.cs @@ -46,7 +46,7 @@ public SerializedInfoStream(BinaryStreamReader reader) return (keyString, (int) value); }); - uint lastNi = hashTableReader.ReadUInt32(); // Unused. + hashTableReader.ReadUInt32(); // lastNi (unused). _featureOffset = hashTableReader.Offset; return result; diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/PdbHashTable.cs b/src/AsmResolver.Symbols.Pdb/Metadata/PdbHashTable.cs index 7125fed86..fb58f25be 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/PdbHashTable.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/PdbHashTable.cs @@ -26,8 +26,8 @@ public static class PdbHashTable Func mapper) where TKey : notnull { - uint size = reader.ReadUInt32(); - uint capacity = reader.ReadUInt32(); + uint count = reader.ReadUInt32(); + reader.ReadUInt32(); // Capacity uint presentWordCount = reader.ReadUInt32(); reader.RelativeOffset += presentWordCount * sizeof(uint); @@ -35,8 +35,8 @@ public static class PdbHashTable uint deletedWordCount = reader.ReadUInt32(); reader.RelativeOffset += deletedWordCount * sizeof(uint); - var result = new Dictionary(); - for (int i = 0; i < size; i++) + 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); @@ -47,7 +47,31 @@ public static class PdbHashTable } /// - /// Serializes a dictionary to a PDB hash table. + /// 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. @@ -84,12 +108,12 @@ public static class PdbHashTable writer.WriteUInt32(0); // Write all buckets. - for (int i = 0; i < hashTable.Keys.Length; i++) + for (int i = 0; i < hashTable.Keys!.Length; i++) { if (hashTable.Present.Get(i)) { - writer.WriteUInt32(hashTable.Keys[i]); - writer.WriteUInt32(hashTable.Values[i]); + writer.WriteUInt32(hashTable.Keys![i]); + writer.WriteUInt32(hashTable.Values![i]); } } } @@ -97,56 +121,91 @@ public static class PdbHashTable private static HashTableInfo ToPdbHashTable( this IDictionary dictionary, Func hasher, - Func mapper) + Func? mapper) where TKey : notnull { - // "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 <= dictionary.Count; i++) + 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) { - // Reference implementation allows only 67% of the capacity to be used. - uint maxLoad = capacity * 2 / 3 + 1; - if (i >= maxLoad) - capacity = 2 * maxLoad; + keys = null; + values = null; + } + else + { + keys = new uint[capacity]; + values = new uint[capacity]; } - // Define buckets. - uint[] keys = new uint[capacity]; - uint[] 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 key, uint value) = mapper(item.Key, item.Value); - uint index = hash % capacity; while (present.Get((int) index)) index = (index + 1) % capacity; - keys[index] = key; - values[index] = value; + // 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 new HashTableInfo(capacity, keys, values, present); + return capacity; } private readonly struct HashTableInfo { public readonly uint Capacity; - public readonly uint[] Keys; - public readonly uint[] Values; + 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) + 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/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs index 19e2c5035..889913627 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Dbi/DbiStreamTest.cs @@ -196,4 +196,18 @@ public void ExtraDebugIndices(bool rebuild) 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 index ff78a7b81..03a19f01b 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Info/InfoStreamTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Metadata/Info/InfoStreamTest.cs @@ -10,13 +10,11 @@ namespace AsmResolver.Symbols.Pdb.Tests.Metadata.Info; public class InfoStreamTest { - [Theory] - [InlineData(false)] - [InlineData(true)] - public void ReadWrite(bool rebuild) + 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(); @@ -24,9 +22,28 @@ public void ReadWrite(bool rebuild) infoStream = InfoStream.FromReader(ByteArrayDataSource.CreateReader(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, @@ -35,6 +52,29 @@ public void ReadWrite(bool rebuild) ["/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/Msf/MsfFileTest.cs b/test/AsmResolver.Symbols.Pdb.Tests/Msf/MsfFileTest.cs index e7c589c70..b17cd4b36 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/Msf/MsfFileTest.cs +++ b/test/AsmResolver.Symbols.Pdb.Tests/Msf/MsfFileTest.cs @@ -1,6 +1,5 @@ using System.IO; using System.Linq; -using AsmResolver.IO; using AsmResolver.Symbols.Pdb.Msf; using Xunit; From e8717182094691221d1b65fa1d71900c54077942 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 25 Jun 2022 16:29:48 +0200 Subject: [PATCH 170/184] Use for loop to avoid heap allocated enumerator. --- src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStream.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStream.cs b/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStream.cs index 5dd16b073..eae02174c 100644 --- a/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStream.cs +++ b/src/AsmResolver.Symbols.Pdb/Metadata/Info/InfoStream.cs @@ -175,8 +175,9 @@ public override void Write(IBinaryStreamWriter writer) writer.WriteUInt32(0); // Write feature codes. - foreach (var feature in Features) - writer.WriteUInt32((uint) feature); + var features = Features; + for (int i = 0; i < features.Count; i++) + writer.WriteUInt32((uint) features[i]); } private static uint ComputeStringHash(Utf8String str) From 3aa01a96641aa0621690d95123a3d4eb6231338c Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 8 Jul 2022 10:24:12 +0200 Subject: [PATCH 171/184] Add unusual typeref order test. --- .../TypeRefTokenPreservationTest.cs | 14 ++++++++++++++ .../Properties/Resources.Designer.cs | 7 +++++++ .../Properties/Resources.resx | 3 +++ .../HelloWorld.UnusualNestedTypeRefOrder.exe | Bin 0 -> 4608 bytes 4 files changed, 24 insertions(+) create mode 100644 test/AsmResolver.DotNet.Tests/Resources/HelloWorld.UnusualNestedTypeRefOrder.exe diff --git a/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/TypeRefTokenPreservationTest.cs b/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/TypeRefTokenPreservationTest.cs index ebb5bc237..fa233b1e8 100644 --- a/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/TypeRefTokenPreservationTest.cs +++ b/test/AsmResolver.DotNet.Tests/Builder/TokenPreservation/TypeRefTokenPreservationTest.cs @@ -99,5 +99,19 @@ public void PreserveDuplicatedTypeRefs() references.Select(r => r.MetadataToken).ToHashSet(), newObjectReferences.Select(r => r.MetadataToken).ToHashSet()); } + + [Fact] + public void PreserveNestedTypeRefOrdering() + { + // https://github.com/Washi1337/AsmResolver/issues/329 + + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld_UnusualNestedTypeRefOrder); + var originalTypeRefs = GetMembers(module, TableIndex.TypeRef); + + var newModule = RebuildAndReloadModule(module, MetadataBuilderFlags.PreserveTypeReferenceIndices); + var newTypeRefs = GetMembers(newModule, TableIndex.TypeRef); + + Assert.Equal(originalTypeRefs, newTypeRefs.Take(originalTypeRefs.Count), Comparer); + } } } diff --git a/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs index c2e4010ea..413f1fa55 100644 --- a/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs @@ -164,6 +164,13 @@ public class Resources { } } + public static byte[] HelloWorld_UnusualNestedTypeRefOrder { + get { + object obj = ResourceManager.GetObject("HelloWorld_UnusualNestedTypeRefOrder", 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 5e722a4a9..ecc3273dd 100644 --- a/test/AsmResolver.DotNet.Tests/Properties/Resources.resx +++ b/test/AsmResolver.DotNet.Tests/Properties/Resources.resx @@ -69,6 +69,9 @@ ..\Resources\HelloWorld.SingleFile.v6.WithResources.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\HelloWorld.UnusualNestedTypeRefOrder.exe;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/Resources/HelloWorld.UnusualNestedTypeRefOrder.exe b/test/AsmResolver.DotNet.Tests/Resources/HelloWorld.UnusualNestedTypeRefOrder.exe new file mode 100644 index 0000000000000000000000000000000000000000..c7004037690f31e063ce2515992364ec922b4e0e GIT binary patch literal 4608 zcmeHK-EUk+6+h$HuH#@QRcMtKNGI#G4S`&*V-uypPS*C?iNPOR@7fXRLUZ@-czxUZ zvD~@qcmY)rUs5GNsnkl5`hdhAkX8t_RZHnRqKHaW1yUNp|hSd=WEWKIWza1@zRAWw2O#((Qn)!dI=-9oPqxtT!wh~zL$sTSG!+7^paS7 z{ZMJs)v_LiwaE6RV+TPP%ZidwBam(&^D9N^hgD?_4GkVnO&8{f7R4_5)841wXsvdg zGPFlzi3Y&oj`p($03>=2oq@#AllTqH_|GF6!$qRcfX?4fRO;q`6fWDY8_faG=ep)tvD%J7Kiv-inb~LRHz?C2(M2oLkps&3 zmIK22gXmo?2k)&&c_9?pSGtJKwy^c?mLr4>-{_S5gd?58X}3(h072Dn%J0Psa(7#sMMf$tfZ zH}KEm5~K&|k};XsP1>iXS z67Z|^8sIGb4)E)SUj*#E+=>vKGHpK~Lf>xpc3;ZLrLfxY)M@gy6Gom}p>xXf!t=nZ zG#dsw^c0`|zy|D~*%7QZg4p$y;m2;p_1w5i+6kGZwiOkdI##}w&aqPO@re)?GMsEvMMV{DxsKB5>0luoDo7lFrj^QLFl!F~ zo^zz|1$z{dr1O9g@G5R9(fHw4Gr!&a;K~a}e>3^QPyhNY>Xkwa?2;t_+jdl)cT?Jo!b@5}kG~S4vx4rm zmgh?Ci}B?&K2w})U}E^QZBgFUb>cH)i14weoXKwvj(oPS4xNk(6gg%G26kRrsQ|R8)?Yj>{#088@&aSLt#- zjF(kh?rf?YRvs&-dnyMip2&6yt$MXW#dFigzjA`o)$ePqjvapU=f{3>ZR-#6$-kd` z`}>u&a9u2Yd71)QT* zU<-JmErVVFoKIeB^lI-!LQxI(mtb;#fu^NO;80G(Fs&wf!Z39 zPj$0K6Fag&A-kY;GHEA|GXJ!h*YY?IZB33@zkZW{x0|0uHWh6@*D*hWh0ealQ0uep z&g<=kK1U`tx&eIjx;(wrIuLj_cGk9@l6SR>>rbd9%(+%I9eE zI<0*&l|<&F)=T1AfSnMPr-9hSUEbb4u35b51a5ivybnjG6C0wFCXUmHoyOjLoT`Ui zN!+NLBifn6aV3gpiT>7g8Q*>7waGKvz9*5g`JpnEK_$sZ6*yrPNqs7_R+>L{G9z_t z2UXh(12vUtDxEoVdT4NHaMDhnMiM3hJ(X!h!71%*D&N+}eAkIW9d5+OoX|gIYv0-$ z%Shi2+znKPR#7zT!I+e7V+&P<$4K0~$tNy#85y8bPGy#w)AhRNI_BA9+4Xv6G_epz z4IT5#>SOwIJY(izZLORJ9)Zo21}9SAZop4fEky1XUUD_1Kc?03mQ{(?v4c|MH2B4^ zsJ4_RJ%&>mTQ3A#;o~aG$c8)Z;FW_gZrGky>86a4(OY=aa((nB-zP`gL4d;KXzPSd Z^VjeRZ!-Bm=*o#txSyZ8_dkiizX5<)y`KO8 literal 0 HcmV?d00001 From c73a42762b556785512b878553d298d80dcef699 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 8 Jul 2022 11:16:53 +0200 Subject: [PATCH 172/184] Naive implementation of MetadataTableBuffer.Insert. --- .../DotNetDirectoryBuffer.CodedIndices.cs | 8 +-- .../DotNetDirectoryBuffer.TokenProvider.cs | 32 ++++++++---- .../Builder/DotNetDirectoryFactory.cs | 6 +-- .../Tables/DistinctMetadataTableBuffer.cs | 17 +++++++ .../Metadata/Tables/IMetadataTableBuffer.cs | 2 + .../Tables/UnsortedMetadataTableBuffer.cs | 49 +++++++++++++++++-- .../Builder/PEImageBuildResult.cs | 2 +- 7 files changed, 95 insertions(+), 21 deletions(-) diff --git a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.CodedIndices.cs b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.CodedIndices.cs index 37b5c136b..2aeee551a 100644 --- a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.CodedIndices.cs +++ b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.CodedIndices.cs @@ -26,16 +26,16 @@ private void AddCustomAttribute(MetadataToken ownerToken, CustomAttribute attrib table.Add(attribute, row); } - private uint AddResolutionScope(IResolutionScope? scope) + private uint AddResolutionScope(IResolutionScope? scope, bool allowDuplicates, bool preserveRid) { if (!AssertIsImported(scope)) return 0; var token = scope.MetadataToken.Table switch { - TableIndex.AssemblyRef => GetAssemblyReferenceToken(scope as AssemblyReference), - TableIndex.TypeRef => GetTypeReferenceToken(scope as TypeReference), - TableIndex.ModuleRef => GetModuleReferenceToken(scope as ModuleReference), + TableIndex.AssemblyRef => AddAssemblyReference(scope as AssemblyReference, allowDuplicates, preserveRid), + TableIndex.TypeRef => AddTypeReference(scope as TypeReference, allowDuplicates, preserveRid), + TableIndex.ModuleRef => AddModuleReference(scope as ModuleReference, allowDuplicates, preserveRid), TableIndex.Module => 0, _ => throw new ArgumentOutOfRangeException(nameof(scope)) }; diff --git a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.TokenProvider.cs b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.TokenProvider.cs index 1abba1f59..9ce3f8bab 100644 --- a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.TokenProvider.cs +++ b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.TokenProvider.cs @@ -10,7 +10,10 @@ public partial class DotNetDirectoryBuffer : IMetadataTokenProvider public uint GetUserStringIndex(string value) => Metadata.UserStringsStream.GetStringIndex(value); /// - public MetadataToken GetTypeReferenceToken(TypeReference? type) => AddTypeReference(type, false); + public MetadataToken GetTypeReferenceToken(TypeReference? type) + { + return AddTypeReference(type, false, false); + } /// /// Adds a type reference to the buffer. @@ -21,18 +24,21 @@ public partial class DotNetDirectoryBuffer : IMetadataTokenProvider /// is supposed to be removed and the token of the original should be returned instead. /// /// The newly assigned metadata token. - public MetadataToken AddTypeReference(TypeReference? type, bool allowDuplicates) + public MetadataToken AddTypeReference(TypeReference? type, bool allowDuplicates, bool preserveRid) { if (!AssertIsImported(type)) return MetadataToken.Zero; var table = Metadata.TablesStream.GetDistinctTable(TableIndex.TypeRef); var row = new TypeReferenceRow( - AddResolutionScope(type.Scope), + AddResolutionScope(type.Scope, allowDuplicates, preserveRid), Metadata.StringsStream.GetStringIndex(type.Name), Metadata.StringsStream.GetStringIndex(type.Namespace)); - var token = table.Add(row, allowDuplicates); + var token = preserveRid + ? table.Insert(type.MetadataToken.Rid, row, allowDuplicates) + : table.Add(row, allowDuplicates); + _tokenMapping.Register(type, token); AddCustomAttributes(token, type); return token; @@ -164,7 +170,7 @@ public MetadataToken AddStandAloneSignature(StandAloneSignature? signature, bool /// public MetadataToken GetAssemblyReferenceToken(AssemblyReference? assembly) { - return AddAssemblyReference(assembly, false); + return AddAssemblyReference(assembly, false, false); } /// @@ -176,7 +182,7 @@ public MetadataToken GetAssemblyReferenceToken(AssemblyReference? assembly) /// is supposed to be removed and the token of the original should be returned instead. /// /// The newly assigned metadata token. - public MetadataToken AddAssemblyReference(AssemblyReference? assembly, bool allowDuplicates) + public MetadataToken AddAssemblyReference(AssemblyReference? assembly, bool allowDuplicates, bool preserveRid) { if (assembly is null || !AssertIsImported(assembly)) return MetadataToken.Zero; @@ -193,7 +199,10 @@ public MetadataToken AddAssemblyReference(AssemblyReference? assembly, bool allo Metadata.StringsStream.GetStringIndex(assembly.Culture), Metadata.BlobStream.GetBlobIndex(assembly.HashValue)); - var token = table.Add(row, allowDuplicates); + var token = preserveRid + ? table.Insert(assembly.MetadataToken.Rid, row, allowDuplicates) + : table.Add(row, allowDuplicates); + AddCustomAttributes(token, assembly); return token; } @@ -205,7 +214,7 @@ public MetadataToken AddAssemblyReference(AssemblyReference? assembly, bool allo /// The new metadata token assigned to the module reference. public MetadataToken GetModuleReferenceToken(ModuleReference? reference) { - return AddModuleReference(reference, false); + return AddModuleReference(reference, false, false); } /// @@ -217,7 +226,7 @@ public MetadataToken GetModuleReferenceToken(ModuleReference? reference) /// is supposed to be removed and the token of the original should be returned instead. /// /// The newly assigned metadata token. - public MetadataToken AddModuleReference(ModuleReference? reference, bool allowDuplicates) + public MetadataToken AddModuleReference(ModuleReference? reference, bool allowDuplicates, bool preserveRid) { if (!AssertIsImported(reference)) return MetadataToken.Zero; @@ -225,7 +234,10 @@ public MetadataToken AddModuleReference(ModuleReference? reference, bool allowDu var table = Metadata.TablesStream.GetDistinctTable(TableIndex.ModuleRef); var row = new ModuleReferenceRow(Metadata.StringsStream.GetStringIndex(reference.Name)); - var token = table.Add(row, allowDuplicates); + var token = preserveRid + ? table.Insert(reference.MetadataToken.Rid, row, allowDuplicates) + : table.Add(row, allowDuplicates); + AddCustomAttributes(token, reference); return token; } diff --git a/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs b/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs index 705fda930..7c0619f9f 100644 --- a/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs +++ b/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs @@ -212,19 +212,19 @@ private void ImportBasicTablesIfSpecified(ModuleDefinition module, DotNetDirecto if ((MetadataBuilderFlags & MetadataBuilderFlags.PreserveAssemblyReferenceIndices) != 0) { ImportTables(module, TableIndex.AssemblyRef, - r => buffer.AddAssemblyReference(r, true)); + r => buffer.AddAssemblyReference(r, true, true)); } if ((MetadataBuilderFlags & MetadataBuilderFlags.PreserveModuleReferenceIndices) != 0) { ImportTables(module, TableIndex.ModuleRef, - r => buffer.AddModuleReference(r, true)); + r => buffer.AddModuleReference(r, true, true)); } if ((MetadataBuilderFlags & MetadataBuilderFlags.PreserveTypeReferenceIndices) != 0) { ImportTables(module, TableIndex.TypeRef, - r => buffer.AddTypeReference(r, true)); + r => buffer.AddTypeReference(r, true, true)); } } diff --git a/src/AsmResolver.DotNet/Builder/Metadata/Tables/DistinctMetadataTableBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Tables/DistinctMetadataTableBuffer.cs index 2fba58233..82b16f22f 100644 --- a/src/AsmResolver.DotNet/Builder/Metadata/Tables/DistinctMetadataTableBuffer.cs +++ b/src/AsmResolver.DotNet/Builder/Metadata/Tables/DistinctMetadataTableBuffer.cs @@ -50,6 +50,23 @@ public DistinctMetadataTableBuffer(IMetadataTableBuffer underlyingBuffer) /// public MetadataToken Add(in TRow row) => Add(row, false); + public MetadataToken Insert(uint rid, in TRow row) => Insert(rid, row, false); + + public MetadataToken Insert(uint rid, in TRow row, bool allowDuplicates) + { + if (!_entries.TryGetValue(row, out var token)) + { + token = _underlyingBuffer.Insert(rid, in row); + _entries.Add(row, token); + } + else if (allowDuplicates) + { + token = _underlyingBuffer.Insert(rid, in row); + } + + return token; + } + /// /// Adds a row to the metadata table buffer. /// diff --git a/src/AsmResolver.DotNet/Builder/Metadata/Tables/IMetadataTableBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Tables/IMetadataTableBuffer.cs index bcf1b8bfd..e7ed33df6 100644 --- a/src/AsmResolver.DotNet/Builder/Metadata/Tables/IMetadataTableBuffer.cs +++ b/src/AsmResolver.DotNet/Builder/Metadata/Tables/IMetadataTableBuffer.cs @@ -56,5 +56,7 @@ public interface IMetadataTableBuffer : IMetadataTableBuffer /// The row to add. /// The metadata token that this row was assigned to. MetadataToken Add(in TRow row); + + MetadataToken Insert(uint rid, in TRow row); } } diff --git a/src/AsmResolver.DotNet/Builder/Metadata/Tables/UnsortedMetadataTableBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Tables/UnsortedMetadataTableBuffer.cs index e94a81d44..e3218dc14 100644 --- a/src/AsmResolver.DotNet/Builder/Metadata/Tables/UnsortedMetadataTableBuffer.cs +++ b/src/AsmResolver.DotNet/Builder/Metadata/Tables/UnsortedMetadataTableBuffer.cs @@ -15,7 +15,9 @@ public class UnsortedMetadataTableBuffer : IMetadataTableBuffer where TRow : struct, IMetadataRow { private readonly RefList _entries = new(); + private readonly List _available = new(); private readonly MetadataTable _table; + private uint _currentRid = 0; /// /// Creates a new unsorted metadata table buffer. @@ -33,7 +35,11 @@ public UnsortedMetadataTableBuffer(MetadataTable table) public virtual TRow this[uint rid] { get => _entries[(int) (rid - 1)]; - set => _entries[(int) (rid - 1)] = value; + set + { + _entries[(int) (rid - 1)] = value; + _available[(int) (rid - 1)] = false; + } } /// @@ -42,8 +48,45 @@ public UnsortedMetadataTableBuffer(MetadataTable table) /// public virtual MetadataToken Add(in TRow row) { - _entries.Add(row); - return new MetadataToken(_table.TableIndex, (uint) _entries.Count); + // Move over unavailable slots. + while (_currentRid < _available.Count && !_available[(int) _currentRid]) + _currentRid++; + + // If we moved over all entries, we're adding to the end. + if (_currentRid == _entries.Count) + { + _currentRid++; + } + + return Insert(_currentRid++, row); + } + + /// + public MetadataToken Insert(uint rid, in TRow row) + { + EnsureRowsAllocated(rid); + + var token = new MetadataToken(_table.TableIndex, rid); + + if (!_available[(int) (rid - 1)]) + { + if (EqualityComparer.Default.Equals(row, _entries[(int) (rid - 1)])) + return token; + + throw new InvalidOperationException($"Token 0x{token.ToString()} is already in use."); + } + + this[rid] = row; + return token; + } + + private void EnsureRowsAllocated(uint rid) + { + while (_entries.Count < rid) + { + _entries.Add(default); + _available.Add(true); + } } /// diff --git a/src/AsmResolver.DotNet/Builder/PEImageBuildResult.cs b/src/AsmResolver.DotNet/Builder/PEImageBuildResult.cs index faec68be5..95aef83eb 100644 --- a/src/AsmResolver.DotNet/Builder/PEImageBuildResult.cs +++ b/src/AsmResolver.DotNet/Builder/PEImageBuildResult.cs @@ -34,7 +34,7 @@ public PEImageBuildResult(IPEImage? image, DiagnosticBag diagnosticBag, ITokenMa /// Gets a value indicating whether the image was constructed successfully or not. /// [MemberNotNullWhen(false, nameof(ConstructedImage))] - public bool HasFailed => ConstructedImage is null; + public bool HasFailed => DiagnosticBag.IsFatal; /// /// Gets the bag containing the diagnostics that were collected during the construction of the image. From f60f298184c92afce900cdb25929ccddd78d771f Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 8 Jul 2022 13:05:17 +0200 Subject: [PATCH 173/184] Optimize availability boolean list using bitlists. --- .../DotNetDirectoryBuffer.TokenProvider.cs | 9 + .../Tables/DistinctMetadataTableBuffer.cs | 10 + .../Metadata/Tables/IMetadataTableBuffer.cs | 6 + .../Tables/UnsortedMetadataTableBuffer.cs | 4 +- src/AsmResolver/Collections/BitList.cs | 251 ++++++++++++++++++ .../Collections/BitListTest.cs | 111 ++++++++ 6 files changed, 389 insertions(+), 2 deletions(-) create mode 100644 src/AsmResolver/Collections/BitList.cs create mode 100644 test/AsmResolver.Tests/Collections/BitListTest.cs diff --git a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.TokenProvider.cs b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.TokenProvider.cs index 9ce3f8bab..b3eca662b 100644 --- a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.TokenProvider.cs +++ b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.TokenProvider.cs @@ -23,6 +23,9 @@ public MetadataToken GetTypeReferenceToken(TypeReference? type) /// true if the row is always to be added to the end of the buffer, false if a duplicated row /// is supposed to be removed and the token of the original should be returned instead. /// + /// + /// true if the metadata token of the type should be preserved, false otherwise. + /// /// The newly assigned metadata token. public MetadataToken AddTypeReference(TypeReference? type, bool allowDuplicates, bool preserveRid) { @@ -181,6 +184,9 @@ public MetadataToken GetAssemblyReferenceToken(AssemblyReference? assembly) /// true if the row is always to be added to the end of the buffer, false if a duplicated row /// is supposed to be removed and the token of the original should be returned instead. /// + /// + /// true if the metadata token of the assembly should be preserved, false otherwise. + /// /// The newly assigned metadata token. public MetadataToken AddAssemblyReference(AssemblyReference? assembly, bool allowDuplicates, bool preserveRid) { @@ -225,6 +231,9 @@ public MetadataToken GetModuleReferenceToken(ModuleReference? reference) /// true if the row is always to be added to the end of the buffer, false if a duplicated row /// is supposed to be removed and the token of the original should be returned instead. /// + /// + /// true if the metadata token of the module should be preserved, false otherwise. + /// /// The newly assigned metadata token. public MetadataToken AddModuleReference(ModuleReference? reference, bool allowDuplicates, bool preserveRid) { diff --git a/src/AsmResolver.DotNet/Builder/Metadata/Tables/DistinctMetadataTableBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Tables/DistinctMetadataTableBuffer.cs index 82b16f22f..32a440059 100644 --- a/src/AsmResolver.DotNet/Builder/Metadata/Tables/DistinctMetadataTableBuffer.cs +++ b/src/AsmResolver.DotNet/Builder/Metadata/Tables/DistinctMetadataTableBuffer.cs @@ -50,8 +50,18 @@ public DistinctMetadataTableBuffer(IMetadataTableBuffer underlyingBuffer) /// public MetadataToken Add(in TRow row) => Add(row, false); + /// public MetadataToken Insert(uint rid, in TRow row) => Insert(rid, row, false); + /// + /// Inserts a row into the metadata table at the provided row identifier. + /// + /// The row identifier. + /// The row to add. + /// + /// true if the row is always to be added to the end of the buffer, false if a duplicated row + /// is supposed to be removed and the token of the original should be returned instead. + /// The metadata token that this row was assigned to. public MetadataToken Insert(uint rid, in TRow row, bool allowDuplicates) { if (!_entries.TryGetValue(row, out var token)) diff --git a/src/AsmResolver.DotNet/Builder/Metadata/Tables/IMetadataTableBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Tables/IMetadataTableBuffer.cs index e7ed33df6..5cecd39db 100644 --- a/src/AsmResolver.DotNet/Builder/Metadata/Tables/IMetadataTableBuffer.cs +++ b/src/AsmResolver.DotNet/Builder/Metadata/Tables/IMetadataTableBuffer.cs @@ -57,6 +57,12 @@ public interface IMetadataTableBuffer : IMetadataTableBuffer /// The metadata token that this row was assigned to. MetadataToken Add(in TRow row); + /// + /// Inserts a row into the metadata table at the provided row identifier. + /// + /// The row identifier. + /// The row to add. + /// The metadata token that this row was assigned to. MetadataToken Insert(uint rid, in TRow row); } } diff --git a/src/AsmResolver.DotNet/Builder/Metadata/Tables/UnsortedMetadataTableBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Tables/UnsortedMetadataTableBuffer.cs index e3218dc14..fe754d7ac 100644 --- a/src/AsmResolver.DotNet/Builder/Metadata/Tables/UnsortedMetadataTableBuffer.cs +++ b/src/AsmResolver.DotNet/Builder/Metadata/Tables/UnsortedMetadataTableBuffer.cs @@ -15,9 +15,9 @@ public class UnsortedMetadataTableBuffer : IMetadataTableBuffer where TRow : struct, IMetadataRow { private readonly RefList _entries = new(); - private readonly List _available = new(); + private readonly BitList _available = new(); private readonly MetadataTable _table; - private uint _currentRid = 0; + private uint _currentRid; /// /// Creates a new unsorted metadata table buffer. diff --git a/src/AsmResolver/Collections/BitList.cs b/src/AsmResolver/Collections/BitList.cs new file mode 100644 index 000000000..c4224cd04 --- /dev/null +++ b/src/AsmResolver/Collections/BitList.cs @@ -0,0 +1,251 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace AsmResolver.Collections +{ + /// + /// Represents a bit vector that can be resized dynamically. + /// + public class BitList : IList + { + private const int WordSize = sizeof(int) * 8; + private uint[] _words; + private int _version; + + /// + /// Creates a new bit list. + /// + public BitList() + { + _words = new uint[1]; + } + + /// + /// Creates a new bit list. + /// + /// The initial number of bits that the buffer should at least be able to store. + public BitList(int capacity) + { + _words = new uint[((uint) capacity).Align(WordSize)]; + } + + /// + public int Count + { + get; + private set; + } + + /// + public bool IsReadOnly => false; + + /// + public bool this[int index] + { + get + { + if (index >= Count) + throw new IndexOutOfRangeException(); + + (int wordIndex, int bitIndex) = SplitWordBitIndex(index); + return (_words[wordIndex] >> bitIndex & 1) != 0; + } + set + { + if (index >= Count) + throw new IndexOutOfRangeException(); + + (int wordIndex, int bitIndex) = SplitWordBitIndex(index); + _words[wordIndex] = (_words[wordIndex] & ~(1u << bitIndex)) | (value ? 1u << bitIndex : 0u); + _version++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static (int wordIndex, int bitIndex) SplitWordBitIndex(int index) + { + int wordIndex = Math.DivRem(index, WordSize, out int offset); + return (wordIndex, offset); + } + + /// + public void Add(bool item) + { + EnsureCapacity(Count + 1); + Count++; + this[Count - 1] = item; + _version++; + } + + /// + public void Clear() => Count = 0; + + /// + public bool Contains(bool item) => IndexOf(item) != -1; + + /// + public void CopyTo(bool[] array, int arrayIndex) + { + for (int i = 0; i < Count; i++) + array[arrayIndex + i] = this[i]; + } + + /// + public bool Remove(bool item) + { + int index = IndexOf(item); + if (index == -1) + return false; + + RemoveAt(index); + return true; + } + + /// + public int IndexOf(bool item) + { + for (int i = 0; i < Count; i++) + { + (int wordIndex, int bitIndex) = SplitWordBitIndex(i); + if ((_words[wordIndex] >> bitIndex & 1) != 0 == item) + return i; + } + + return -1; + } + + /// + public void Insert(int index, bool item) + { + if (index > Count) + throw new IndexOutOfRangeException(); + + EnsureCapacity(Count++); + (int wordIndex, int bitIndex) = SplitWordBitIndex(index); + + uint carry = _words[wordIndex] & (1u << (WordSize - 1)); + + // Insert bit into current word. + uint lowerMask = (1u << bitIndex) - 1; + uint upperMask = ~lowerMask; + _words[wordIndex] = (_words[wordIndex] & upperMask) << 1 // Shift left-side of the bit index by one + | (item ? 1u << bitIndex : 0u) // Insert bit. + | (_words[wordIndex] & lowerMask); // Keep right-side of the bit. + + for (int i = wordIndex + 1; i < _words.Length; i++) + { + uint nextCarry = _words[i] & (1u << (WordSize - 1)); + _words[i] = (_words[i] << 1) | (carry >> (WordSize - 1)); + carry = nextCarry; + } + + _version++; + } + + /// + public void RemoveAt(int index) + { + Count--; + (int wordIndex, int bitIndex) = SplitWordBitIndex(index); + + // Note we check both word count and actual bit count. Words in the buffer might contain garbage data for + // every bit index i >= Count. Also, there might be exactly enough words allocated for Count bits, i.e. + // there might not be a "next" word. + uint borrow = wordIndex + 1 < _words.Length && ((uint) index).Align(WordSize) < Count + ? _words[wordIndex + 1] & 1 + : 0; + + uint lowerMask = (1u << bitIndex) - 1; + uint upperMask = ~((1u << (bitIndex + 1)) - 1); + _words[wordIndex] = (_words[wordIndex] & upperMask) >> 1 // Shift left-side of the bit index by one + | (_words[wordIndex] & lowerMask) // Keep right-side of the bit. + | borrow << (WordSize - 1); // Copy first bit of next word into last bit of current. + + for (int i = wordIndex + 1; i < _words.Length; i++) + { + uint nextBorrow = i + 1 < _words.Length && ((uint) index).Align(WordSize) < Count + ? _words[i + 1] & 1 + : 0; + + _words[i] = (_words[i] >> 1) | (borrow << (WordSize - 1)); + borrow = nextBorrow; + } + + _version++; + } + + /// + /// Ensures the provided number of bits can be stored in the bit list. + /// + /// The number of bits to store in the list. + public void EnsureCapacity(int capacity) + { + if (capacity < WordSize * _words.Length) + return; + + int newWordCount = (int) (((uint) capacity).Align(WordSize) / 8); + Array.Resize(ref _words, newWordCount); + } + + /// + /// Returns an enumerator for all bits in the bit vector. + /// + /// The enumerator. + public Enumerator GetEnumerator() => new(this); + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + /// Represents an enumerator that iterates over all bits in a bit list. + /// + public struct Enumerator : IEnumerator + { + private readonly BitList _list; + private readonly int _version; + private int _index = -1; + + /// + /// Creates a new bit enumerator. + /// + /// The list to enumerate. + public Enumerator(BitList list) + { + _version = list._version; + _list = list; + } + + /// + public bool MoveNext() + { + if (_version != _list._version) + throw new InvalidOperationException("Collection was modified."); + + if (_index >= _list.Count) + return false; + + _index++; + return true; + } + + /// + public void Reset() => _index = -1; + + /// + public bool Current => _list[_index]; + + /// + object IEnumerator.Current => Current; + + /// + public void Dispose() + { + } + } + } +} diff --git a/test/AsmResolver.Tests/Collections/BitListTest.cs b/test/AsmResolver.Tests/Collections/BitListTest.cs new file mode 100644 index 000000000..402f8ec2e --- /dev/null +++ b/test/AsmResolver.Tests/Collections/BitListTest.cs @@ -0,0 +1,111 @@ +using System.Linq; +using AsmResolver.Collections; +using Xunit; + +namespace AsmResolver.Tests.Collections +{ + public class BitListTest + { + [Fact] + public void Add() + { + var list = new BitList + { + true, + false, + true, + true, + false, + }; + + Assert.Equal(new[] + { + true, + false, + true, + true, + false + }, list.ToArray()); + } + + [Fact] + public void Insert() + { + var list = new BitList + { + true, + false, + true, + true, + false, + }; + + list.Insert(1, true); + + Assert.Equal(new[] + { + true, + true, + false, + true, + true, + false + }, list.ToArray()); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void InsertIntoLarge(bool parity) + { + var list = new BitList(); + for (int i = 0; i < 100; i++) + list.Add(i % 2 == 0 == parity); + + list.Insert(0, !parity); + + Assert.Equal(101, list.Count); + bool[] expected = Enumerable.Range(0, 101).Select(i => i % 2 == 1 == parity).ToArray(); + Assert.Equal(expected, list.ToArray()); + } + + [Fact] + public void RemoveAt() + { + var list = new BitList + { + true, + false, + true, + true, + false, + }; + + list.RemoveAt(3); + + Assert.Equal(new[] + { + true, + false, + true, + false, + }, list.ToArray()); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void RemoveAtLarge(bool parity) + { + var list = new BitList(); + for (int i = 0; i < 100; i++) + list.Add(i % 2 == 0 == parity); + + list.RemoveAt(0); + + Assert.Equal(99, list.Count); + bool[] expected = Enumerable.Range(0, 99).Select(i => i % 2 == 1 == parity).ToArray(); + Assert.Equal(expected, list.ToArray()); + } + } +} From 55423663d33a607b43da610a71774db7f5da1fd7 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 9 Jul 2022 19:42:40 +0200 Subject: [PATCH 174/184] Add member resolution to older .NET version test. --- .../MetadataResolverTest.cs | 23 ++++++++++++++++++ .../Properties/Resources.Designer.cs | 14 +++++++++++ .../Properties/Resources.resx | 6 +++++ .../Resources/DifferentNetVersion.Library.dll | Bin 0 -> 4096 bytes .../Resources/DifferentNetVersion.MainApp.dll | Bin 0 -> 5120 bytes 5 files changed, 43 insertions(+) create mode 100644 test/AsmResolver.DotNet.Tests/Resources/DifferentNetVersion.Library.dll create mode 100644 test/AsmResolver.DotNet.Tests/Resources/DifferentNetVersion.MainApp.dll diff --git a/test/AsmResolver.DotNet.Tests/MetadataResolverTest.cs b/test/AsmResolver.DotNet.Tests/MetadataResolverTest.cs index 0ac5cbead..0134a7fca 100644 --- a/test/AsmResolver.DotNet.Tests/MetadataResolverTest.cs +++ b/test/AsmResolver.DotNet.Tests/MetadataResolverTest.cs @@ -224,5 +224,28 @@ public void MaliciousExportedTypeLoop() // Attempt to resolve. The test here is that it should not result in an infinite loop / stack overflow. Assert.Null(reference.Resolve()); } + + [Fact] + public void ResolveToOlderNetVersion() + { + // https://github.com/Washi1337/AsmResolver/issues/321 + + var mainApp = ModuleDefinition.FromBytes(Properties.Resources.DifferentNetVersion_MainApp); + var library = ModuleDefinition.FromBytes(Properties.Resources.DifferentNetVersion_Library); + + mainApp.MetadataResolver.AssemblyResolver.AddToCache(library.Assembly!, library.Assembly!); + + var definition = library + .TopLevelTypes.First(t => t.Name == "MyClass") + .Methods.First(m => m.Name == "ThrowMe"); + + var reference = (IMethodDescriptor) mainApp.ManagedEntrypointMethod!.CilMethodBody!.Instructions.First( + i => i.OpCode == CilOpCodes.Callvirt && ((IMethodDescriptor) i.Operand)?.Name == "ThrowMe") + .Operand!; + + var resolved = reference.Resolve(); + Assert.NotNull(resolved); + Assert.Equal(definition, resolved); + } } } diff --git a/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs index 003ecdd06..521727b9c 100644 --- a/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs @@ -191,5 +191,19 @@ public class Resources { return ((byte[])(obj)); } } + + public static byte[] DifferentNetVersion_MainApp { + get { + object obj = ResourceManager.GetObject("DifferentNetVersion_MainApp", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] DifferentNetVersion_Library { + get { + object obj = ResourceManager.GetObject("DifferentNetVersion_Library", resourceCulture); + return ((byte[])(obj)); + } + } } } diff --git a/test/AsmResolver.DotNet.Tests/Properties/Resources.resx b/test/AsmResolver.DotNet.Tests/Properties/Resources.resx index 44c3dbef0..cd83c63b2 100644 --- a/test/AsmResolver.DotNet.Tests/Properties/Resources.resx +++ b/test/AsmResolver.DotNet.Tests/Properties/Resources.resx @@ -81,4 +81,10 @@ ..\Resources\FieldRvaTest.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\DifferentNetVersion.MainApp.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\DifferentNetVersion.Library.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + diff --git a/test/AsmResolver.DotNet.Tests/Resources/DifferentNetVersion.Library.dll b/test/AsmResolver.DotNet.Tests/Resources/DifferentNetVersion.Library.dll new file mode 100644 index 0000000000000000000000000000000000000000..b1e05392b096ad9e44241da225c3419317abf6e9 GIT binary patch literal 4096 zcmeHKONWkJ;<1- z2x>GwqQ-dQKteoukQfeXV&VZ4J&a6Bc4pG+4}2$^Bv7cRHb+Na#K45#BsM#;1u55xWgFKF#z&Y035amU*Rhe~*32^=1c+4z+bt!{g z*ONW=6hr3L-`U{;_+fWE?Muc3A{pHwnFdSyM{{~k5MiWRquA!FN-r9A2%LY z1}qfeH)At;;#x2Z&K8$dwxraD5OG<;$MyQkh{pof8XGeOyrHRwt1_2joS_SN7atl0 z95Q|*U7+b{PE)|Qnht5rTbjQO65$==7g}G=2;B{#$25P5rti^807-pxos4eW05`Lh zI+`dQVH}Ypvu^AoqOIg})O6!$=0v8S?@1nOu{X1Z^pvpe%T|!NsaeXZ_GAn!AuZqv(oSOS$#i3rrrR{_*K}CZ`!#(~ zQ=7DaCh0Obntw^_pVhpDMYtUn*JF@$C8kJwQ6t@e6QrAQn)Eh&NjiYDr1xq41nB@C z#*d_(q!rCq625|8aa3!LYRysFR~pB$8s8yzIITOA)9nP)ls%HRmAEoxf!jy@sfMfW z#N>g%e_~Xjrozy!DoixPNY$*J4KH$P3WK$>nwnBm{Sh~Dxe+N0__ey@s^D(rslblf zO)aNg5~?Mkq{@wI)h@ejy0M1q&ersYp{kYL=A;v~J5Jg`RYgPmgsUg~;IWygss9ks z>{dbO_}+Zuus7`oH9MlwcK_0A!1tz|YJ=&_3KFNPl{=MdAJSYnf07*mi&=?g3~6$V zD<9SwLyB1<+g@|Vkkni2Bd6@RPSj@I-elp2T)R4Fd8{NOeqK;=rl(b)yl6~CiP{=* z%7Gm;ttr<{o@@nB)2^yWx)?lEQFYB(mBq=kXE_{CRA|Hsi-hn zV4~ozqL`*Hh(nAzmB0`E>4?uwseZ0QGjynK4!Cw0qE&->B zt%2trlVe~qv$)@N`6f5q$f>)EiiN}80TnoWL&a_6^1pY}nYD<%rnDsBbaFO}>a6gl zaPwPwhR2qDtWe2yM{UPLEv&FyN^eK8p1+PU=1$WVP|gHk1+P18O0qhCuqgBN;wN{9 zJ$*pbc7KYpTd3WQ3G5>s#7=Un#{ zJeytqfOUM0_?|F!RY#VJ)>#S3xQYXu6+{4Xf@3_<%W4UZv`!+$mH)SR;&t7@d3<7Q zu=cWYY4nR<*COwI24=^dTm6~Z1GngP>gVc*tWAx3#AQ{?Is0e+da+dhyqWR;3_soc N&maB&iT{2D{s4$CfGPk0 literal 0 HcmV?d00001 diff --git a/test/AsmResolver.DotNet.Tests/Resources/DifferentNetVersion.MainApp.dll b/test/AsmResolver.DotNet.Tests/Resources/DifferentNetVersion.MainApp.dll new file mode 100644 index 0000000000000000000000000000000000000000..e484ca0fd42a4675698394dfc1b3c189189fb995 GIT binary patch literal 5120 zcmeHKU2I%e5uUr=b)9YE+9eICLc`6vKOrU8Hcr|m*pAm;yIE>GCf+0kB;vbw&#rHC z|G4L_>#YQ_6sUwmP~m~6R;t7Us^B4&c&H$N#8X?;7k*Ur1$gK~B@lV2kWhs%-<p~CPo^E@3<;=3~`VC?1*;BkfQN1)vG_Up0s}Ehr z>T&xsmFP}wgs2F*FXo*+j9$k#g^z)bE*IMkOxe#pZQui+TMw;#i)cXoZ}kZoix5HY z8OEoG{!vJoUHuBtZjf(3MD(dH_WPoGX1WsX1AV${k`amZ2=tTeAI6lgiQS-7(}`A$ zP=*#LW!n^xUVLm&SDS+MMkpK~f@)|5gRJXrd|hpdXu6A3=rN8f8rznm0==jcRqi0t zKqDNy+JDs!<__ZfK$K()_fqxgU zXJ~r)3)7miCiZRZm@#6Ejf{<+V6zJ30Ip*0&^L&FkNzFTm!r`3T9PHM!wH-u8alU3 ze+7=yKtqq8t5(qe6ZC1UH8kz`bDvl6X(D$Q1rX__AJWTOF9q5r;3hG=uHah=PAdL)6h20*;RWqo z$h@3mxJV3tt?(a#U!<3EuR&%I99}hETSZG7phNWi+;Q;01@#QHF~$B3&;{6a9C)5K zK>YOiWM|^GNVR81sU}SsLL6}W8YQO4-mX@P;AxJ z+%9@Fr_mr_FWn1Rq|X2j(5QkZ6f7%PRq%@nen~+Su$MZ3_mHjd?OMqkaI^gH&O~4s?8}JFmUk03^uh8ECi-4BGtpvB|J-VVeR}|+8 z#t&&PA}`&b{6-pSIH%eHwX-QjhF$J0g5scp@~T?x9TRoHSb^qo^Y4?N-aIJ6*iR zw&QH)l%*7I-Racqs9UgRhAk1zA&rZt{qWM3)>M8D)|?liw0&>qVAX5-p=(AMG@bt_ zHRF3tyVZs}+mytvD&|??nCl9YI~Q3B5kxE68c3akUQVuC1F6ld+4MSF14+CUKeFq# zV@F-1(;WnU&M{jzZI6+7gcL&+yV(?>@S;T#C2V8Cw7qf=7!AiE7|Cy8qJelQ(so*An-@op5IC=k>H_f=t*v^*L3Gv89#AkM0sN?KP3&9fVxbx`d1Ef#I zVjsi*u?XZ;eWas`-Ve){;%d6f2X#W}v0Lz2XroIqMq~Na9YcJ^QSar6o|__y_yG&eJko1e~R_z^bUw7eQA6=i+Leev!ZNulOB~b)|3esYDM{lT`>DVY&U|Bg`|IBI?T zg3wTJ_{fEUTB8GxI5Q$EpjDu;i9Z3|0=grN3c&MAz4p9VFch3oypNQ}sQ z(A`p+Iidk7ME1Bvt5}s_0e%-KJI*A0ju_jK#)+vo@*mZncwd*Wjt?8#h&}IIs{O<7 z>j-`?Xt;&ldT);YR_qD)lJ}{M-Is`si&N(yD*8?5XXk$H#l`rOhJYpQ#?OB>ernxy z^tHIan<(LhD(S+r{06FsiPE{++|kEMx{OS(VLHAiCQ2P4OQ$A_#c|XNs-V=Nz>^cD zcIcgy)~aw#dDKnIjH8zCo;0Ovtc{j**YxbBkZJuCuQplK^-Nc_feH(?**1gt{VeGo z-rtGRLI;nvV_T|LG|V6<4J!pv*p?AjqaVAw`Ikgk(%W{~Lh*(OHcdy0*de83 pI8(A^OJeqLx838znfsH)@!@o1Cev+>dp-VLT|-j;xc&b{;NOBgK(qh= literal 0 HcmV?d00001 From 57b0cb50aaaeb28254404a93ebe4e25e00a74e39 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 9 Jul 2022 19:43:49 +0200 Subject: [PATCH 175/184] BUGFIX: Disable exact assembly version match in signature comparisons during metadata resolution. --- src/AsmResolver.DotNet/DefaultMetadataResolver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AsmResolver.DotNet/DefaultMetadataResolver.cs b/src/AsmResolver.DotNet/DefaultMetadataResolver.cs index da23a4cc8..c60f71c81 100644 --- a/src/AsmResolver.DotNet/DefaultMetadataResolver.cs +++ b/src/AsmResolver.DotNet/DefaultMetadataResolver.cs @@ -14,7 +14,7 @@ public class DefaultMetadataResolver : IMetadataResolver private readonly IDictionary _typeCache; private readonly SignatureComparer _comparer = new() { - AcceptNewerAssemblyVersionNumbers = true + IgnoreAssemblyVersionNumbers = true }; /// From c05209764c4b813deefbd35f278f195b4138313f Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 9 Jul 2022 19:54:53 +0200 Subject: [PATCH 176/184] Fix merge conflict once again. git please git gud. --- .../Properties/Resources.Designer.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs index a9a217cdb..343fda016 100644 --- a/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs @@ -240,14 +240,14 @@ public class Resources { return ((byte[])(obj)); } } - + public static byte[] CallManagedExport_X86 { get { object obj = ResourceManager.GetObject("CallManagedExport_X86", resourceCulture); return ((byte[])(obj)); } } - + public static byte[] CallManagedExport_X64 { get { object obj = ResourceManager.GetObject("CallManagedExport_X64", resourceCulture); @@ -265,6 +265,8 @@ public class Resources { public static byte[] MyLibrary_X64 { get { object obj = ResourceManager.GetObject("MyLibrary_X64", resourceCulture); - + return ((byte[])(obj)); + } + } } } From b43b84afce38dd4a9aeefa847e6a2096f93b59b0 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 9 Jul 2022 20:45:15 +0200 Subject: [PATCH 177/184] Normalize newlines in json test. --- test/AsmResolver.DotNet.Tests/Bundles/BundleFileTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/AsmResolver.DotNet.Tests/Bundles/BundleFileTest.cs b/test/AsmResolver.DotNet.Tests/Bundles/BundleFileTest.cs index cf0bddfb0..edb113ca1 100644 --- a/test/AsmResolver.DotNet.Tests/Bundles/BundleFileTest.cs +++ b/test/AsmResolver.DotNet.Tests/Bundles/BundleFileTest.cs @@ -15,7 +15,7 @@ public void ReadUncompressedStringContents() { var manifest = BundleManifest.FromBytes(Properties.Resources.HelloWorld_SingleFile_V6); var file = manifest.Files.First(f => f.Type == BundleFileType.RuntimeConfigJson); - string contents = Encoding.UTF8.GetString(file.GetData()); + string contents = Encoding.UTF8.GetString(file.GetData()).Replace("\r", ""); Assert.Equal(@"{ ""runtimeOptions"": { @@ -28,7 +28,7 @@ public void ReadUncompressedStringContents() ""System.Reflection.Metadata.MetadataUpdater.IsSupported"": false } } -}", contents); +}".Replace("\r", ""), contents); } [Fact] From 37e8dded4d9d0159927f6005e033c0ba40dcd604 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 9 Jul 2022 20:45:49 +0200 Subject: [PATCH 178/184] Skip native method body test on non-windows platforms. --- .../Code/Native/NativeMethodBodyTest.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs b/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs index 5052f8346..a41eae6e8 100644 --- a/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs +++ b/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using AsmResolver.DotNet.Code.Native; using AsmResolver.DotNet.Signatures; using AsmResolver.PE; @@ -19,6 +20,8 @@ namespace AsmResolver.DotNet.Tests.Code.Native { public class NativeMethodBodyTest : IClassFixture { + private const string NonWindowsPlatform = "Test produces a mixed mode assembly which is not supported on non-Windows platforms."; + private TemporaryDirectoryFixture _fixture; public NativeMethodBodyTest(TemporaryDirectoryFixture fixture) @@ -254,7 +257,7 @@ public void ReadNativeMethodShouldResultInReferenceWithRightContents() Assert.Equal(body.Code, newBuffer); } - [Theory] + [SkippableTheory] [InlineData( true, new byte[] {0xB8, 0x00, 0x00, 0x00, 0x00}, // mov eax, message @@ -267,6 +270,8 @@ public void ReadNativeMethodShouldResultInReferenceWithRightContents() 11u)] public void NativeBodyWithLocalSymbols(bool is32Bit, byte[] movInstruction, uint fixupOffset, AddressFixupType fixupType, uint symbolOffset) { + Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.Windows), NonWindowsPlatform); + // Create native body. var code = new List(movInstruction); code.AddRange(new byte[] From 595424106437fe87bc2af8c1e011655c81f7d0b5 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 9 Jul 2022 20:46:11 +0200 Subject: [PATCH 179/184] List main features of AsmResolver in readme. --- README.md | 62 +++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 31582ebfe..9bf2ec12f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -AsmResolver -=========== +# AsmResolver [![Master branch build status](https://img.shields.io/appveyor/ci/Washi1337/AsmResolver/master.svg)](https://ci.appveyor.com/project/Washi1337/asmresolver/branch/master) [![Nuget feed](https://img.shields.io/nuget/v/AsmResolver.svg)](https://www.nuget.org/packages/AsmResolver/) @@ -12,8 +11,44 @@ AsmResolver is a PE inspection library allowing .NET programmers to read, modify AsmResolver is released under the MIT license. -Binaries --------- +## Features + +AsmResolver has a lot of features. Below a non-exhaustive list: + +- [x] Create, read and write PE files + - [x] Inspect and update PE headers. + - [x] Create, read and write sections. +- [x] Create, read and write various data directories + - [x] Debug Directory (CodeView) + - [x] .NET Directory + - [x] CIL assembler and disassemblers + - [x] Metadata Directory (tables, strings, user-strings, blobs, GUIDs) + - [x] Resources Directory + - [x] Strong Name Signing + - [x] VTable Fixup Directory + - [x] Exception Directory (AMD64) + - [x] Export Directory + - [x] Import Directory + - [x] Base Relocation Directory + - [x] TLS Directory + - [x] Win32 Resources Directory +- [x] Fully mutable object model for .NET modules that is similar to System.Reflection + - [x] Strong type-system with many useful factory methods for quickly constructing new metadata. + - [x] Full metadata importing and cloning (useful for injecting metadata into another assembly) + - [x] .NET Framework 2.0+, .NET Core and .NET 5+ binary file support. + - [x] Infer memory layout of types statically. + - [x] Create, read and write managed resource sets (`.resources` files) + - [x] Create new method bodies containing native code. + - [x] Highly configurable reader and writer options and custom error handling for both. + - [x] Rich support for AppHost and SingleFileHost bundled files. + + +## Documentation + +Check out the [wiki](https://asmresolver.readthedocs.org/) for guides and information on how to use the library. + + +## Binaries Stable builds: @@ -30,13 +65,7 @@ Nightly builds: | development | [![Development branch build status](https://img.shields.io/appveyor/ci/Washi1337/AsmResolver/development.svg)](https://ci.appveyor.com/project/Washi1337/asmresolver/branch/development) -Documentation -------------- -Check out the [wiki](https://asmresolver.readthedocs.org/) for guides and information on how to use the library. - - -Compiling ---------- +## Compiling The solution can be build using the .NET SDK or an IDE that works with the .NET SDK (such as Visual Studio and JetBrains Rider). The main packages target .NET Standard 2.0, and the xUnit test projects target .NET Core 3.1. @@ -51,20 +80,18 @@ To run all tests, simply run: $ dotnet test ``` +## Contributing -Contributing ------------- See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on general workflow and code style. -Found a bug or have questions? ------------------------------- +## Found a bug or have questions? + Please use the [issue tracker](https://github.com/Washi1337/AsmResolver/issues). Try to be as descriptive as possible. You can also join the [Discord](https://discord.gg/Y7DTBkbhJJ) to engage more directly with the community. -Acknowledgements ----------------- +## Acknowledgements AsmResolver started out as a hobby project, but has grown into a community project with various contributors. Without these people, AsmResolver would not have been where it is today! @@ -73,4 +100,3 @@ AsmResolver started out as a hobby project, but has grown into a community proje - Another big thank you to all the people that suggested new features, provided feedback on the API design, have done extensive testing, and/or reported bugs on the [issue board](https://github.com/Washi1337/AsmResolver/issues), by e-mail, or through DMs. If you feel you have been under-represented in these acknowledgements, feel free to contact me. - From e2d65b5c14b5dc90bea80dcdf04b563e45fdc1f1 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 9 Jul 2022 20:46:21 +0200 Subject: [PATCH 180/184] Version bump to v4.11.2 --- AsmResolver.sln | 1 + Directory.Build.props | 2 +- appveyor.yml | 6 +++--- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/AsmResolver.sln b/AsmResolver.sln index 5b61c298f..c8f471ef6 100644 --- a/AsmResolver.sln +++ b/AsmResolver.sln @@ -83,6 +83,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution LICENSE.md = LICENSE.md README.md = README.md Directory.Build.props = Directory.Build.props + appveyor.yml = appveyor.yml EndProjectSection EndProject Global diff --git a/Directory.Build.props b/Directory.Build.props index 134a35eb3..a2c6826c2 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,7 +7,7 @@ https://github.com/Washi1337/AsmResolver git 10 - 4.11.1 + 4.11.2 diff --git a/appveyor.yml b/appveyor.yml index 91e0e257f..64299e25e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,7 +4,7 @@ - master image: Visual Studio 2022 - version: 4.11.0-master-build.{build} + version: 4.11.2-master-build.{build} configuration: Release skip_commits: @@ -23,7 +23,7 @@ deploy: provider: NuGet api_key: - secure: DCeHUu0aAsOjRnoi2DpcuXpj0apD7dxHzglSamP7LGzcZjhIvTBi1ONnjIa7L2zm + secure: L3fXsS7umzD8zwAvTsdGxOg/E6tQ4IR4MfwBAcO8elE7ZwjZ8HO8UPwjiWbp4RMw skip_symbols: false artifact: /.*\.nupkg/ @@ -33,7 +33,7 @@ - development image: Visual Studio 2022 - version: 4.11.0-dev-build.{build} + version: 4.11.2-dev-build.{build} configuration: Release skip_commits: From e8675796a14a81d52668ad2842ed5516763aeb53 Mon Sep 17 00:00:00 2001 From: JPaja Date: Sun, 7 Aug 2022 19:06:59 +0200 Subject: [PATCH 181/184] Fix: Missing analyzers --- .../Analyzers/Definition/CustomAttributeArgumentAnalyzer.cs | 1 + .../Definition/CustomAttributeNamedArgumentAnalyzer.cs | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/AsmResolver.Workspaces.DotNet/Analyzers/Definition/CustomAttributeArgumentAnalyzer.cs b/src/AsmResolver.Workspaces.DotNet/Analyzers/Definition/CustomAttributeArgumentAnalyzer.cs index 4cf2912b4..3a91abd7a 100644 --- a/src/AsmResolver.Workspaces.DotNet/Analyzers/Definition/CustomAttributeArgumentAnalyzer.cs +++ b/src/AsmResolver.Workspaces.DotNet/Analyzers/Definition/CustomAttributeArgumentAnalyzer.cs @@ -13,6 +13,7 @@ protected override void Analyze(AnalysisContext context, CustomAttributeArgument { if (context.HasAnalyzers(typeof(TypeSignature))) { + context.ScheduleForAnalysis(subject.ArgumentType); for (int i = 0; i < subject.Elements.Count; i++) { var element = subject.Elements[i]; diff --git a/src/AsmResolver.Workspaces.DotNet/Analyzers/Definition/CustomAttributeNamedArgumentAnalyzer.cs b/src/AsmResolver.Workspaces.DotNet/Analyzers/Definition/CustomAttributeNamedArgumentAnalyzer.cs index 08e67e221..15c73e38d 100644 --- a/src/AsmResolver.Workspaces.DotNet/Analyzers/Definition/CustomAttributeNamedArgumentAnalyzer.cs +++ b/src/AsmResolver.Workspaces.DotNet/Analyzers/Definition/CustomAttributeNamedArgumentAnalyzer.cs @@ -11,6 +11,11 @@ public class CustomAttributeNamedArgumentAnalyzer : ObjectAnalyzer protected override void Analyze(AnalysisContext context, CustomAttributeNamedArgument subject) { + if (context.HasAnalyzers(typeof(TypeSignature))) + { + context.ScheduleForAnalysis(subject.ArgumentType); + } + if (context.HasAnalyzers(typeof(CustomAttributeArgument))) { context.ScheduleForAnalysis(subject.Argument); From b825b7cc844b9b54654b2ff2db786e35afff5ec4 Mon Sep 17 00:00:00 2001 From: JPaja Date: Sun, 7 Aug 2022 19:34:31 +0200 Subject: [PATCH 182/184] Fix: Generic parameter constraint check --- .../Analyzers/Definition/HasGenericParameterAnalyzer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AsmResolver.Workspaces.DotNet/Analyzers/Definition/HasGenericParameterAnalyzer.cs b/src/AsmResolver.Workspaces.DotNet/Analyzers/Definition/HasGenericParameterAnalyzer.cs index de6f0e03b..f6f276c6b 100644 --- a/src/AsmResolver.Workspaces.DotNet/Analyzers/Definition/HasGenericParameterAnalyzer.cs +++ b/src/AsmResolver.Workspaces.DotNet/Analyzers/Definition/HasGenericParameterAnalyzer.cs @@ -12,8 +12,8 @@ public class HasGenericParameterAnalyzer : ObjectAnalyzer protected override void Analyze(AnalysisContext context, IHasGenericParameters subject) { bool hasGenericParameterAnalyzer = context.HasAnalyzers(typeof(GenericParameter)); - bool hasGenericParameterConstraintAnalyzer = context.HasAnalyzers(typeof(GenericParameter)); - + bool hasGenericParameterConstraintAnalyzer = context.HasAnalyzers(typeof(GenericParameterConstraint)); + for (int i = 0; i < subject.GenericParameters.Count; i++) { var genericParameter = subject.GenericParameters[i]; From d2d3d625f0c9702ba2a610cb91e3ad15b4305ca5 Mon Sep 17 00:00:00 2001 From: JPaja Date: Sun, 7 Aug 2022 22:22:45 +0200 Subject: [PATCH 183/184] Add: GenericInstanceMethodSignatureAnalyzer --- .../GenericInstanceMethodSignatureAnalyzer.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/AsmResolver.Workspaces.DotNet/Analyzers/Signature/GenericInstanceMethodSignatureAnalyzer.cs diff --git a/src/AsmResolver.Workspaces.DotNet/Analyzers/Signature/GenericInstanceMethodSignatureAnalyzer.cs b/src/AsmResolver.Workspaces.DotNet/Analyzers/Signature/GenericInstanceMethodSignatureAnalyzer.cs new file mode 100644 index 000000000..4cde98d24 --- /dev/null +++ b/src/AsmResolver.Workspaces.DotNet/Analyzers/Signature/GenericInstanceMethodSignatureAnalyzer.cs @@ -0,0 +1,23 @@ +using AsmResolver.DotNet.Signatures; +using AsmResolver.DotNet.Signatures.Types; + +namespace AsmResolver.Workspaces.DotNet.Analyzers.Signature +{ + /// + /// Provides a default implementation for an analyzer. + /// + public class GenericInstanceMethodSignatureAnalyzer : ObjectAnalyzer + { + /// + protected override void Analyze(AnalysisContext context, GenericInstanceMethodSignature subject) + { + if (context.HasAnalyzers(typeof(TypeSignature))) + { + for (var i = 0; i < subject.TypeArguments.Count; i++) + { + context.ScheduleForAnalysis(subject.TypeArguments[i]); + } + } + } + } +} From 57d5f681e2d45494ba433cdb012a1f1b8e617b6b Mon Sep 17 00:00:00 2001 From: JPaja Date: Sun, 7 Aug 2022 22:56:12 +0200 Subject: [PATCH 184/184] Fix: GenericInstanceMethodSignatureAnalyzer --- .../Profiles/DotNetTraversalProfile.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AsmResolver.Workspaces.DotNet/Profiles/DotNetTraversalProfile.cs b/src/AsmResolver.Workspaces.DotNet/Profiles/DotNetTraversalProfile.cs index 7d43b3435..6b034cdcf 100644 --- a/src/AsmResolver.Workspaces.DotNet/Profiles/DotNetTraversalProfile.cs +++ b/src/AsmResolver.Workspaces.DotNet/Profiles/DotNetTraversalProfile.cs @@ -49,7 +49,7 @@ public DotNetTraversalProfile() Analyzers.Register(typeof(StandAloneSignature), new StandaloneSignatureAnalyzer()); Analyzers.Register(typeof(InterfaceImplementation), new InterfaceImplementationAnalyzer()); Analyzers.Register(typeof(MethodSpecification), new MethodSpecificationAnalyzer()); - + Analyzers.Register(typeof(GenericInstanceMethodSignature), new GenericInstanceMethodSignatureAnalyzer()); } } }